diff -pruN 1.9.1-1/conf/11-x11-synaptics.fdi 1.9.1-1ubuntu3/conf/11-x11-synaptics.fdi
--- 1.9.1-1/conf/11-x11-synaptics.fdi	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/conf/11-x11-synaptics.fdi	2020-04-09 16:11:16.000000000 +0000
@@ -9,6 +9,7 @@
 <deviceinfo version="0.2">
   <device>
     <match key="info.capabilities" contains="input.touchpad">
+      <match key="info.capabilities" contains_not="input.keyboard">
         <merge key="input.x11_driver" type="string">synaptics</merge>
         <!-- Arbitrary options can be passed to the driver using
              the input.x11_options property since xorg-server-1.5. -->
@@ -35,6 +36,7 @@
         <match key="info.product" contains="Apple|bcm5974">
             <merge key="input.x11_options.SoftButtonAreas" type="string">0 0 0 0 0 0 0 0</merge>
         </match>
+      </match>
     </match>
   </device>
 </deviceinfo>
diff -pruN 1.9.1-1/conf/70-synaptics.conf 1.9.1-1ubuntu3/conf/70-synaptics.conf
--- 1.9.1-1/conf/70-synaptics.conf	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/conf/70-synaptics.conf	2020-04-09 16:11:16.000000000 +0000
@@ -14,7 +14,7 @@ Section "InputClass"
 # This option is recommend on all Linux systems using evdev, but cannot be
 # enabled by default. See the following link for details:
 # http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html
-#       MatchDevicePath "/dev/input/event*"
+      MatchDevicePath "/dev/input/event*"
 EndSection
 
 Section "InputClass"
diff -pruN 1.9.1-1/debian/changelog 1.9.1-1ubuntu3/debian/changelog
--- 1.9.1-1/debian/changelog	2020-04-09 16:11:15.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/changelog	2020-04-09 16:11:16.000000000 +0000
@@ -1,3 +1,23 @@
+xserver-xorg-input-synaptics (1.9.1-1ubuntu3) focal; urgency=medium
+
+  * Revert the previous change but fix the build by restoring files from
+    Debian which were removed due to a merge error
+
+ -- Sebastien Bacher <seb128@ubuntu.com>  Mon, 06 Apr 2020 13:14:41 +0200
+
+xserver-xorg-input-synaptics (1.9.1-1ubuntu2) focal; urgency=medium
+
+  * debian/xserver-xorg-input-synaptics.docs:
+    - don't list non existing files, fixes the build
+
+ -- Sebastien Bacher <seb128@ubuntu.com>  Fri, 03 Apr 2020 21:29:50 +0200
+
+xserver-xorg-input-synaptics (1.9.1-1ubuntu1) cosmic; urgency=medium
+
+  * Resynchronize on Debian
+
+ -- Sebastien Bacher <seb128@ubuntu.com>  Fri, 24 Aug 2018 13:29:09 +0200
+
 xserver-xorg-input-synaptics (1.9.1-1) unstable; urgency=medium
 
   * New upstream release.
@@ -7,6 +27,12 @@ xserver-xorg-input-synaptics (1.9.1-1) u
 
  -- Timo Aaltonen <tjaalton@debian.org>  Thu, 23 Aug 2018 16:18:23 +0300
 
+xserver-xorg-input-synaptics (1.9.0-1ubuntu1) zesty; urgency=medium
+
+  * Merge from Debian.
+
+ -- Timo Aaltonen <tjaalton@debian.org>  Tue, 07 Mar 2017 12:30:57 +0200
+
 xserver-xorg-input-synaptics (1.9.0-1) unstable; urgency=medium
 
   [ Andreas Boll ]
@@ -29,12 +55,98 @@ xserver-xorg-input-synaptics (1.8.3-2) u
 
  -- Emilio Pozuelo Monfort <pochu@debian.org>  Thu, 26 May 2016 18:32:56 +0200
 
+xserver-xorg-input-synaptics (1.8.3-1ubuntu1) yakkety; urgency=medium
+
+  * Merge with Debian, remaining Ubuntu changes:
+  * debian/gbp.conf:
+    - Point at Ubuntu branch.
+  * debian/local/51-synaptics-quirks.conf:
+    - Add quirks for Dell Inspiron, HP Mininote.
+  * debian/local/66-xorg-synaptics.rules:
+    - Udev rules to tag Dell Inspiron, HP Mininote for quirks.
+  * debian/xserver-xorg-input-synaptics.install:
+    - Install quirks
+  * debian/patches/101_resolution_detect_option.patch:
+    - Provide an option to prevent synaptics from communicating the
+      touchpad size to the xserver.
+  * debian/patches/103_enable_cornertapping.patch:
+    - Enable right/middle clicks by tapping in the bottom- and top-right
+      corners.
+  * debian/patches/104_always_enable_tapping.patch:
+    - Enable tapping even when a physical left button is available.
+  * debian/patches/106_always_enable_vert_edge_scroll.patch:
+    - Enable vertical edge tapping by default even if we enable two-finger
+      scrolling as well.
+  * debian/patches/115_evdev_only.patch:
+    - Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback.
+  * debian/patches/118_quell_error_msg.patch:
+    - Don't print error "Unable to find a synaptics device" when used on system
+      that doesn't have a touchpad anyway.
+  * debian/patches/124_syndaemon_events.patch:
+    - Drain spurious events from XRecord connection as a work around for a buggy
+      X server.
+  * debian/patches/128_disable_three_click_action.patch:
+    - Disable three touch click in favor of uTouch gestures.
+  * debian/patches/129_disable_three_touch_tap.patch:
+    - Disable three touch tap in favor of uTouch gestures.
+
+ -- Robert Ancell <robert.ancell@canonical.com>  Wed, 25 May 2016 11:32:52 +1200
+
 xserver-xorg-input-synaptics (1.8.3-1) unstable; urgency=medium
 
   * New upstream release.
 
  -- Julien Cristau <jcristau@debian.org>  Sun, 24 Jan 2016 14:31:57 +0100
 
+xserver-xorg-input-synaptics (1.8.2-1ubuntu3) xenial; urgency=medium
+
+  * Really rebuild against new xserver.
+
+ -- Timo Aaltonen <tjaalton@debian.org>  Thu, 03 Mar 2016 15:36:59 +0200
+
+xserver-xorg-input-synaptics (1.8.2-1ubuntu2) xenial; urgency=medium
+
+  * Rebuild against new xserver ABI.
+
+ -- Timo Aaltonen <tjaalton@debian.org>  Mon, 18 Jan 2016 16:00:22 +0200
+
+xserver-xorg-input-synaptics (1.8.2-1ubuntu1) wily; urgency=medium
+
+  * Merge with Debian, remaining Ubuntu changes:
+  * debian/gbp.conf:
+    - Point at Ubuntu branch.
+  * debian/local/51-synaptics-quirks.conf:
+    - Add quirks for Dell Inspiron, HP Mininote.
+  * debian/local/66-xorg-synaptics.rules:
+    - Udev rules to tag Dell Inspiron, HP Mininote for quirks.
+  * debian/xserver-xorg-input-synaptics.install:
+    - Install quirks
+  * debian/patches/101_resolution_detect_option.patch:
+    - Provide an option to prevent synaptics from communicating the
+      touchpad size to the xserver.
+  * debian/patches/103_enable_cornertapping.patch:
+    - Enable right/middle clicks by tapping in the bottom- and top-right
+      corners.
+  * debian/patches/104_always_enable_tapping.patch:
+    - Enable tapping even when a physical left button is available.
+  * debian/patches/106_always_enable_vert_edge_scroll.patch:
+    - Enable vertical edge tapping by default even if we enable two-finger
+      scrolling as well.
+  * debian/patches/115_evdev_only.patch:
+    - Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback.
+  * debian/patches/118_quell_error_msg.patch:
+    - Don't print error "Unable to find a synaptics device" when used on system
+      that doesn't have a touchpad anyway.
+  * debian/patches/124_syndaemon_events.patch:
+    - Drain spurious events from XRecord connection as a work around for a buggy
+      X server.
+  * debian/patches/128_disable_three_click_action.patch:
+    - Disable three touch click in favor of uTouch gestures.
+  * debian/patches/129_disable_three_touch_tap.patch:
+    - Disable three touch tap in favor of uTouch gestures.
+
+ -- Robert Ancell <robert.ancell@canonical.com>  Tue, 14 Jul 2015 16:30:22 +1200
+
 xserver-xorg-input-synaptics (1.8.2-1) unstable; urgency=medium
 
   * Let uscan verify tarball signatures.
@@ -43,6 +155,16 @@ xserver-xorg-input-synaptics (1.8.2-1) u
 
  -- Julien Cristau <jcristau@debian.org>  Sun, 03 May 2015 10:17:33 +0200
 
+xserver-xorg-input-synaptics (1.8.1-1ubuntu1) utopic; urgency=medium
+
+  * Merge from released sid.
+  * Drop postinst file, only needed for updating from synaptics <= 1.4.1.
+  * Remaining changes:
+    - A bunch of behavioral patches.
+    - Install some behavioral quirks.
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Mon, 22 Sep 2014 09:44:47 +0200
+
 xserver-xorg-input-synaptics (1.8.1-1) unstable; urgency=medium
 
   * New upstream release
@@ -63,6 +185,26 @@ xserver-xorg-input-synaptics (1.8.0-1) u
 
  -- maximilian attems <maks@debian.org>  Sat, 12 Jul 2014 19:20:04 +0200
 
+xserver-xorg-input-synaptics (1.8.0-1~exp2ubuntu3) utopic; urgency=medium
+
+  * Rebuild for new xorg-server abi.
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Wed, 10 Sep 2014 16:29:27 +0200
+
+xserver-xorg-input-synaptics (1.8.0-1~exp2ubuntu2) utopic; urgency=medium
+
+  * Remerge from debian-experimental to pick up libxtst-dev.
+    - Fixes touchpad sometimes not disabling by allowing
+      'syndaemon -R' to use XRecord correctly. (LP: #1323006)
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Tue, 15 Jul 2014 15:07:15 +0200
+
+xserver-xorg-input-synaptics (1.8.0-1~exp2ubuntu1) utopic; urgency=medium
+
+  * Merge from debian-experimental.
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Wed, 02 Jul 2014 16:36:40 +0200
+
 xserver-xorg-input-synaptics (1.8.0-1~exp2) experimental; urgency=medium
 
   * Mark libevdev-dev build dependency [linux-any]. (closes: #749286)
@@ -83,6 +225,27 @@ xserver-xorg-input-synaptics (1.8.0-1~ex
 
  -- maximilian attems <maks@debian.org>  Fri, 23 May 2014 18:27:28 +0200
 
+xserver-xorg-input-synaptics (1.7.99.1-0ubuntu1) UNRELEASED; urgency=medium
+
+  * Merge from unreleased debian-experimental. (LP: #1294515)
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Wed, 26 Mar 2014 10:02:42 +0100
+
+xserver-xorg-input-synaptics (1.7.3-1ubuntu1) trusty; urgency=low
+
+  * Merge from debian-unstable.
+  * Drop long disabled patches from the archive.
+    - 105_correct_multifinger_click.patch
+    - 116_xi2_1.patch
+    - 117_gestures.patch
+    - 119_active_area_touches.patch
+    - 120_active_touches_num_fingers.patch
+    - 121_semi-mt_num_fingers.patch
+    - 123_order_ProcessTouch_for_numFingers.patch  
+  * Fixes xserver 1.15 compatibility. (LP: #1276875)
+
+ -- Robert Hooker <sarvatt@ubuntu.com>  Thu, 06 Feb 2014 11:11:26 -0500
+
 xserver-xorg-input-synaptics (1.7.3-1) unstable; urgency=medium
 
   [ Michele Cane ]
@@ -123,6 +286,26 @@ xserver-xorg-input-synaptics (1.7.1-1) u
 
  -- Julien Cristau <jcristau@debian.org>  Thu, 05 Sep 2013 22:27:28 +0200
 
+xserver-xorg-input-synaptics (1.7.1-0ubuntu2) trusty; urgency=medium
+
+  * Rebuild for xorg 1.15 abi.
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Wed, 05 Feb 2014 09:40:08 +0000
+
+xserver-xorg-input-synaptics (1.7.1-0ubuntu1) saucy; urgency=low
+
+  * Merge from unreleased debian git.
+
+ -- Maarten Lankhorst <maarten.lankhorst@ubuntu.com>  Thu, 25 Jul 2013 15:35:14 +0200
+
+xserver-xorg-input-synaptics (1.6.3-0ubuntu1.1) raring; urgency=low
+
+  * Merge from unreleased debian git.
+  * 130_reset-num_active_touches-on-deviceoff.patch, use-sigsafe-logging.patch:
+    Dropped, upstream.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Fri, 08 Feb 2013 13:40:38 +0200
+
 xserver-xorg-input-synaptics (1.6.2-2) unstable; urgency=low
 
   * Pull latest synaptics-1.6-branch:
@@ -131,12 +314,104 @@ xserver-xorg-input-synaptics (1.6.2-2) u
 
  -- Julien Cristau <jcristau@debian.org>  Mon, 01 Oct 2012 21:55:06 +0200
 
+xserver-xorg-input-synaptics (1.6.2-1ubuntu6) raring; urgency=low
+
+  * use-sigsafe-logging.patch: Upstream commit from master, use signal
+    safe logging with input ABI 18. (LP: #1100586)
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Thu, 17 Jan 2013 10:46:59 +0200
+
+xserver-xorg-input-synaptics (1.6.2-1ubuntu5) quantal; urgency=low
+
+  * Added 130_reset-num_active_touches-on-deviceoff.patch (LP: #956071)
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Thu, 30 Aug 2012 20:13:39 +0300
+
+xserver-xorg-input-synaptics (1.6.2-1ubuntu4) quantal-proposed; urgency=low
+
+  [ Maarten Lankhorst ]
+  * Add -dbg package
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Tue, 14 Aug 2012 19:10:16 +0300
+
+xserver-xorg-input-synaptics (1.6.2-1ubuntu3) quantal-proposed; urgency=low
+
+  * control: Bump the build-depends for new xserver.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Wed, 01 Aug 2012 14:27:06 +0300
+
+xserver-xorg-input-synaptics (1.6.2-1ubuntu2) quantal-proposed; urgency=low
+
+  * Removed patches:
+    - 125_option_rec_revert.patch
+    - 126_ubuntu_xi22.patch
+  * Bump xserver to 1.12 minimum
+
+ -- Maarten Lankhorst <maarten.lankhorst@canonical.com>  Thu, 28 Jun 2012 21:49:46 +0200
+
+xserver-xorg-input-synaptics (1.6.2-1ubuntu1) quantal; urgency=low
+
+  * Merged from Debian unstable
+  * Removed patches that were cherry picked commits and now in 1.6.2:
+    - 201-Avoid-out-of-bounds-access-by-running-num_active_tou.patch
+    - 202-Ignore-pre-existing-touches.patch
+    - 203-Fix-coasting-for-negative-ScrollDelta.patch
+    - 204-Reset-open-slots-array-on-device-disable.patch
+    - 205-Reset-hw-x-y-to-INT_MIN-and-skip-HandleState-until-w.patch
+    - 206-Don-t-allow-for-scroll-distances-of-0-49965.patch
+  * Remaining patches:
+    - 101_resolution_detect_option.patch
+    - 103_enable_cornertapping.patch
+    - 104_always_enable_tapping.patch
+    - 106_always_enable_vert_edge_scroll.patch
+    - 115_evdev_only.patch
+    - 118_quell_error_msg.patch
+    - 124_syndaemon_events.patch
+    - 125_option_rec_revert.patch
+    - 126_ubuntu_xi22.patch
+    - 128_disable_three_click_action.patch
+    - 129_disable_three_touch_tap.patch
+
+ -- Maarten Lankhorst <maarten.lankhorst@canonical.com>  Thu, 14 Jun 2012 09:35:21 +0200
+
 xserver-xorg-input-synaptics (1.6.2-1) unstable; urgency=low
 
   * New upstream release, from the 1.6 branch.
 
  -- Cyril Brulebois <kibi@debian.org>  Wed, 13 Jun 2012 22:22:42 +0200
 
+xserver-xorg-input-synaptics (1.6.1-1ubuntu2) quantal-proposed; urgency=low
+
+  * Enable right button clickpad area by default (LP: #972727)
+    - Upstream has this enabled. We had it disabled for Precise due to the
+      feature landing well after feature freeze. This change puts us back in
+      sync with upstream.
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 07 Jun 2012 10:07:18 -0700
+
+xserver-xorg-input-synaptics (1.6.1-1ubuntu1) quantal; urgency=low
+
+  * Merged from Debian unstable
+  * Refreshed patches for indentation changes
+    - 101_resolution_detect_option.patch
+    - 118_quell_error_msg.patch
+    - 124_syndaemon_events.patch
+    - 125_option_rec_revert.patch
+  * Add temporary patches, for upstream git commits on synaptics-1.6-branch
+  * Fix memory corruption by driver (LP: #941953) FDo #49439
+   - 201-Avoid-out-of-bounds-access-by-running-num_active_tou.patch
+   - 202-Ignore-pre-existing-touches.patch
+  * Fix FDo #49966
+   - 203-Fix-coasting-for-negative-ScrollDelta.patch
+  * Fix jumpy cursor after suspend/resume
+   - 204-Reset-open-slots-array-on-device-disable.patch
+  * Fix another resume bug
+   - 205-Reset-hw-x-y-to-INT_MIN-and-skip-HandleState-until-w.patch
+  * Fix division by 0 or infinite loop with zero scroll distance FDo #49965
+   - 206-Don-t-allow-for-scroll-distances-of-0-49965.patch
+
+ -- Maarten Lankhorst <maarten.lankhorst@canonical.com>  Mon, 21 May 2012 14:14:49 +0200
+
 xserver-xorg-input-synaptics (1.6.1-1) unstable; urgency=low
 
   [ Julien Cristau ]
@@ -154,6 +429,22 @@ xserver-xorg-input-synaptics (1.6.0-1) u
 
  -- Cyril Brulebois <kibi@debian.org>  Mon, 07 May 2012 01:13:08 +0200
 
+xserver-xorg-input-synaptics (1.6.0-0ubuntu1) quantal; urgency=low
+
+  * Update to upstream version 1.6.0
+    - Bug fixes only
+    - Fully fixes touchpad bugs on suspend/resume (LP: #968845)
+  * Drop patches merged upstream
+    - 200_fix_four_tap.patch
+    - 201_fix_touch_count.patch
+    - 202_touch_record_bounds_check.patch
+    - 203_fix_coasting_speed.patch
+    - 204_monotonicise_hw_timestamp.patch
+    - 205_end_touches_on_disable.patch
+  * Refreshed 130_dont_enable_rightbutton_area.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 07 May 2012 12:22:23 -0700
+
 xserver-xorg-input-synaptics (1.5.99.904-2) unstable; urgency=low
 
   * Add a build-dep on libmtdev-dev now that X server 1.12 is in sid,
@@ -173,6 +464,66 @@ xserver-xorg-input-synaptics (1.5.99.902
 
  -- Cyril Brulebois <kibi@debian.org>  Mon, 02 Apr 2012 01:12:26 +0000
 
+xserver-xorg-input-synaptics (1.5.99.902-0ubuntu5.1) precise-proposed; urgency=low
+
+  * debian/patches/204_monotonicise_hw_timestamp.patch:
+    - Cherry-pick patch from upstream to ensure the timestamps used when
+      determining velocities are monotonic.  In certain circumstances the
+      timestamps from hardware events can be less than the timestamps
+      from the preceeding artificially-generated events.
+      + Fixes occasional huge jumps in pointer position.
+      + Fixes two-finger scrolling in GTK+3 windows (such as Nautilus and
+        Evolution) failing some time after login. (LP: #982771)
+  * debian/patches/205_end_touches_on_disable.patch:
+    - Upstream patch to reset touch state on device disable.
+      + Fixes touchpad not working after suspend on some MacBook models
+        (LP: #968845)
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Tue, 24 Apr 2012 14:32:49 +1000
+
+xserver-xorg-input-synaptics (1.5.99.902-0ubuntu5) precise-proposed; urgency=low
+
+  * Fix coasting speed on multitouch touchpads (LP: #930938)
+    - Add patch 203_fix_coasting_speed.patch
+
+ -- Chow Loong Jin <hyperair@debian.org>  Fri, 13 Apr 2012 16:14:04 +0800
+
+xserver-xorg-input-synaptics (1.5.99.902-0ubuntu4) precise; urgency=low
+
+  * Fix crash on Apple trackpads when touching with more than 10 fingers
+    (LP: #974017)
+    - Add temporary patch 202_touch_record_bounds_check.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 09 Apr 2012 11:44:26 -0700
+
+xserver-xorg-input-synaptics (1.5.99.902-0ubuntu3) precise; urgency=low
+
+  * Fix bad default for two touch tap as middle button click (LP: #973783)
+    - Fixed 104_always_enable_tapping.patch
+    - Refreshed 129_disable_three_touch_tap.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Wed, 04 Apr 2012 14:42:34 -0700
+
+xserver-xorg-input-synaptics (1.5.99.902-0ubuntu2) precise; urgency=low
+
+  * Fix three touch taps and clicks on some trackpads (LP: #971783)
+    - Add temporary patch 201_fix_touch_count.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 02 Apr 2012 11:33:58 -0700
+
+xserver-xorg-input-synaptics (1.5.99.902-0ubuntu1) precise; urgency=low
+
+  * New upstream release
+  * Drop patches merged upstream
+    - 200_fix_clickfinger_non-clickpads.patch
+    - 201_fix_clickfinger_clickpad_actions.patch
+  * Revert upstream behavior change: enabling right button area by default
+    - Too late to enable in Precise
+  * Fix erroneous tap actions on four-touch tap (LP: #963327)
+    - Add temporary patch 200_fix_four_tap.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 29 Mar 2012 15:31:40 -0700
+
 xserver-xorg-input-synaptics (1.5.99.901-1) unstable; urgency=low
 
   * New upstream release candidate (1.6 RC1):
@@ -181,6 +532,136 @@ xserver-xorg-input-synaptics (1.5.99.901
 
  -- Cyril Brulebois <kibi@debian.org>  Tue, 20 Mar 2012 21:40:10 +0000
 
+xserver-xorg-input-synaptics (1.5.99.901-0ubuntu2) precise; urgency=low
+
+  * Disable three touch tap in favor of uTouch gestures (LP: #961725)
+    - See
+      https://lists.ubuntu.com/archives/ubuntu-desktop/2012-February/003694.html
+      for details.
+    - Add 129_disable_three_touch_tap.patch
+  * Fix click actions for non-clickpad touchpads (LP: #960108)
+    - Add temporary patch 200_fix_clickfinger_non-clickpads.patch
+  * Fix click action finger count calculation (LP: #961738)
+    - Add temporary patch 201_fix_clickfinger_clickpad_actions.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Wed, 21 Mar 2012 16:00:26 -0700
+
+xserver-xorg-input-synaptics (1.5.99.901-0ubuntu1) precise; urgency=low
+
+  * New upstream release
+    - Includes ClickPad support
+    - Fixes ClickPad click action interference
+    - Re-enables ClickPad support by default (LP: #955404)
+    - Everything else is bug fixes
+  * Remove 129_clickpad.patch, it has been merged upstream
+  * Bump lintian standards to 3.9.3
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 15 Mar 2012 17:58:09 -0700
+
+xserver-xorg-input-synaptics (1.5.99~git20120223-0ubuntu2) precise; urgency=low
+
+  * Disable ClickPad support when there is no physical right button
+    (LP: #941046)
+    - This re-enables click actions, which was enabled by default previously
+    - Users can enable ClickPad support through xorg.conf, xinput, or synclient
+    - See
+      https://lists.ubuntu.com/archives/ubuntu-desktop/2012-February/003751.html
+      for the discussion guiding this change.
+  * Refresh ClickPad support patch with changes from upstream review
+    - No new functionality
+  * Remove bottom area mask for Dell netbook clickpads with two buttons
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Fri, 02 Mar 2012 13:33:26 -0700
+
+xserver-xorg-input-synaptics (1.5.99~git20120223-0ubuntu1) precise; urgency=low
+
+  * Update to latest code in git (0a2fd56)
+    - Only includes bug fixes
+  * Drop temporary patches that have been merged upstream:
+    - 129_tmp_pointer_drift.patch
+    - 130_tmp_touch_count_fix.patch
+  * Revert tap-and-drag locking default change (LP: #934770)
+    - Drop 127_default_drag_lock.patch
+  * Add ClickPad support (LP: #932947)
+    - Add 129_clickpad.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 23 Feb 2012 11:54:37 -0800
+
+xserver-xorg-input-synaptics (1.5.99~git20120220-0ubuntu3) precise; urgency=low
+
+  * Fix crash on multitouch devices when disabled while typing (LP: #931344)
+    - Add temporary patch 130_tmp_touch_count_fix.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Tue, 21 Feb 2012 16:22:17 +0100
+
+xserver-xorg-input-synaptics (1.5.99~git20120220-0ubuntu2) precise; urgency=low
+
+  * Prevent trackpad pointer drift (LP: #921082)
+    - Add temporary patch 129_tmp_pointer_drift.patch from upstream
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 20 Feb 2012 11:55:23 +0100
+
+xserver-xorg-input-synaptics (1.5.99~git20120220-0ubuntu1) precise; urgency=low
+
+  * Update to latest code in git (f9a9065)
+    - Scale single-touch values to multitouch axes (LP: #936856)
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 20 Feb 2012 11:13:10 +0100
+
+xserver-xorg-input-synaptics (1.5.0+git20120210-0ubuntu2) precise; urgency=low
+
+  * Enable tap-and-drag locked drags by default
+  * Disable three-click action by default so three touch gestures work
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 13 Feb 2012 20:51:56 -0800
+
+xserver-xorg-input-synaptics (1.5.0+git20120210-0ubuntu1) precise; urgency=low
+
+  * Update to latest code in git (e6032c3)
+  * Drop 127_multitouch.patch, merged upstream
+  * Drop 126_default_speed.patch, no longer necessary
+  * Refresh 125_option_rec_revert.patch
+  * Add 126_ubuntu_xi22.patch for ubuntu frankenserver support
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Fri, 10 Feb 2012 18:27:53 -0800
+
+xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu2) precise; urgency=low
+
+  * Add some error handling so eventcomm-test doesn't segfault
+    - Modified 127_multitouch.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 19 Jan 2012 17:44:47 -0800
+
+xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1) precise; urgency=low
+
+  * Add multitouch support
+    - Add 127_multitouch.patch
+  * Bump Build-Depends for x11proto-input-dev to 2.1.99.5
+  * Remove obsolete dependencies on utouch-grail and old xserver-xorg-core
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 19 Jan 2012 16:39:31 -0800
+
+xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1~nomt3) precise; urgency=low
+
+  * Fix default speed due to change in motion estimation
+    - Add 126_default_speed.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 12 Jan 2012 03:59:25 -0800
+
+xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1~nomt2) precise; urgency=low
+
+  * Drop patch 122_revert_pressure_finger_default.patch, merged upstream
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 09 Jan 2012 07:03:52 -0800
+
+xserver-xorg-input-synaptics (1.5.0+git20120101-1ubuntu1~nomt1) precise; urgency=low
+
+  * Lower xserver-xorg-dev build dep for Precise's X server
+  * Revert xf86OptionRec upstream changes
+    - This particular API change will not be in Precise's X server
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Mon, 09 Jan 2012 07:00:49 -0800
+
 xserver-xorg-input-synaptics (1.5.0+git20120101-1) experimental; urgency=low
 
   * New upstream snapshot:
@@ -189,6 +670,54 @@ xserver-xorg-input-synaptics (1.5.0+git2
 
  -- Cyril Brulebois <kibi@debian.org>  Sun, 01 Jan 2012 07:54:51 +0100
 
+xserver-xorg-input-synaptics (1.5.0-2ubuntu1~nomt1) UNRELEASED; urgency=low
+
+  * Merge from Debian testing.  Remaining Ubuntu changes:
+    - 101_resolution_detect_option.patch:
+      + Provide an option to prevent synaptics from communicating the
+        touchpad size to the xserver. (LP: 327428)
+    - 103_enable_cornertapping.patch:
+      + Enable right/middle clicks by tapping in the bottom- and top-right
+        corners.
+    - 104_always_enable_tapping.patch:
+      + Enable tapping regardless of the presence of physical buttons.
+    - 106_always_enable_vert_edge_scroll.patch:
+      + Enable vertical edge tapping by default even if we enable two-finger
+        scrolling as well.
+    - 115_evdev_only.patch:
+      + Only bind to /dev/input/event* evdev devices (LP 624985)
+    - 118_quell_error_msg.patch:
+      + Don't print error "Unable to find a synaptics device" when used
+        on system that doesn't have a touchpad anyway. (LP: 716712)
+    - 122_revert_pressure_finger_default.patch:
+      + Fix spurious right click events on some trackpads (LP: 742213)
+    - 124_syndaemon_events.patch:
+      + Drain spurious events from XRecord connection as a work around
+        for a buggy X server. (LP: 754470)
+    - debian/control:
+      + Depend on udev.
+      + Move libxtst-dev to Build-Conflicts so syndaemon does not use XRecord,
+        preventing a wide range of crashes in _CallCallbacks. (LP: 774978)
+    - debian/gbp.conf:
+      + Specify ‘ubuntu’ branch.
+    - debian/local/51-synaptics-quirks.conf:
+      + Split out our synaptics quirks into 51-synaptics-quirks.conf
+    - debian/local/66-xorg-synaptics.rules:
+      + Add the udev rule back for tagging specific machines to be quirked.
+    - debian/xserver-xorg-input-synaptics.postinst.in:
+      + Trigger udev to pick up new udev rules
+  * Temporarily drop multitouch patches for X server 1.11 bringup:
+    + 116_xi2_1.patch
+    + 117_gestures.patch
+    + 119_active_area_touches.patch
+    + 120_active_touches_num_fingers.patch
+    + 121_semi-mt_num_fingers.patch
+    + 123_order_ProcessTouch_for_numFingers.patch
+  * debian/patches/122_revert_pressure_finger_default.patch:
+    + Refresh for new upstream
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Mon, 19 Dec 2011 11:10:25 +1100
+
 xserver-xorg-input-synaptics (1.5.0-2) unstable; urgency=low
 
   * Cherry-pick to fix regression on powerpc (Closes: #649002):
@@ -203,6 +732,69 @@ xserver-xorg-input-synaptics (1.5.0-1) u
 
  -- Cyril Brulebois <kibi@debian.org>  Fri, 11 Nov 2011 17:15:22 +0100
 
+xserver-xorg-input-synaptics (1.4.1-1ubuntu2) oneiric; urgency=low
+
+  * Really install the quirks files, and rename the udev rules file
+    to emphasize that it's about quirks.
+  * postinst: Bump the version check to run udevadm trigger on upgrade.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Thu, 29 Sep 2011 22:16:55 +0300
+
+xserver-xorg-input-synaptics (1.4.1-1ubuntu1) oneiric; urgency=low
+
+  * Merge from Debian unstable, remaining changes:
+    - 101_resolution_detect_option.patch:
+      + Provide an option to prevent synaptics from communicating the
+        touchpad size to the xserver. (LP: 327428)
+    - 103_enable_cornertapping.patch:
+      + Enable right/middle clicks by tapping in the bottom- and top-right
+        corners.
+    - 104_always_enable_tapping.patch:
+      + Enable tapping regardless of the presence of physical buttons.
+    - 106_always_enable_vert_edge_scroll.patch:
+      + Enable vertical edge tapping by default even if we enable two-finger
+        scrolling as well.
+    - 115_evdev_only.patch:
+      + Only bind to /dev/input/event* evdev devices (LP 624985)
+    - 116_xi2_1.patch:
+    - 117_gestures.patch:
+      + multitouch and uTouch gesture support
+    - 118_quell_error_msg.patch:
+      + Don't print error "Unable to find a synaptics device" when used
+        on system that doesn't have a touchpad anyway. (LP: 716712)
+    - 119_active_area_touches.patch:
+    - 120_active_touches_num_fingers.patch:
+    - 121_semi-mt_num_fingers.patch:
+      + Fix handling for SemiMultitouch trackpads with integrated buttons
+        (LP: 736523)
+    - 122_revert_pressure_finger_default.patch:
+      + Fix spurious right click events on some trackpads (LP: 742213)
+    - 123_order_ProcessTouch_for_numFingers.patch:
+      + Fix jumpy cursor on multitouch trackpads (LP: 751525)
+    - 124_syndaemon_events.patch:
+      + Drain spurious events from XRecord connection as a work around
+        for a buggy X server. (LP: 754470)
+    - debian/control:
+      + Add depends on libmtdev and libutouch-grail
+      + Build-depend on multitouch-enabled inputproto.
+      + Depend on multitouch-enabled xserver.
+      + Depend on udev.
+      + Move libxtst-dev to Build-Conflicts so syndaemon does not use XRecord,
+        preventing a wide range of crashes in _CallCallbacks. (LP: 774978)
+    - debian/gbp.conf:
+      + Specify ‘ubuntu’ branch.
+    - debian/local/51-synaptics-quirks.conf:
+      + Split out our synaptics quirks into 51-synaptics-quirks.conf
+    - debian/local/66-xorg-synaptics.rules:
+      + Add the udev rule back for tagging specific machines to be quirked.
+    - debian/xserver-xorg-input-synaptics.postinst.in:
+      + Trigger udev to pick up new udev rules
+  * Drop 114_jumpy_cursor_first_part.patch, fixed upstream.
+  * Update 115_evdev_only.patch to apply.
+  * Update 117_gestures.patch to apply.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Tue, 05 Jul 2011 16:40:31 +0300
+
 xserver-xorg-input-synaptics (1.4.1-1) unstable; urgency=low
 
   * New upstream release:
@@ -246,6 +838,144 @@ xserver-xorg-input-synaptics (1.3.0-2) u
 
  -- Cyril Brulebois <kibi@debian.org>  Sat, 05 Feb 2011 14:40:47 +0100
 
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu15) oneiric; urgency=low
+
+  * Also build-conflict with libxtst-dev as to make sure it's not present in
+    e.g. local debug builds.
+
+ -- Loïc Minier <loic.minier@ubuntu.com>  Fri, 10 Jun 2011 18:01:59 +0200
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu14) oneiric; urgency=low
+
+  * Drop libxtst-dev build dependency so syndaemon does not use XRecord,
+    preventing a wide range of crashes in _CallCallbacks. (LP: #774978)
+
+ -- Robert Hooker <sarvatt@ubuntu.com>  Mon, 23 May 2011 10:13:09 -0400
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu13) oneiric; urgency=low
+
+  * Re-add 116_resolution_detect_option.patch as 101_resolution_detect_option.patch:
+    - This patch was introduced in 1.2.2-2ubuntu7 but got erroneously dropped in the
+      merge for 1.3.99+git20110116.0e27ce3a-0ubuntu1.
+      (LP: #327428)
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Fri, 20 May 2011 11:14:44 -0700
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu12) natty; urgency=low
+
+  * syndaemon: Drain spurious events from XRecord connection as a work around
+    for a buggy X server. This keeps syndaemon from busy looping
+    (LP: #754470)
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Thu, 14 Apr 2011 14:15:33 -0400
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu11) natty; urgency=low
+
+  * Fix jumpy cursor on multitouch trackpads (LP: #751525)
+    - Added 123_order_ProcessTouch_for_numFingers.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Tue, 05 Apr 2011 11:03:12 -0400
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu10) natty; urgency=low
+
+  * Ensure touch mask exists before clearing it (LP: #747126)
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Fri, 01 Apr 2011 08:40:59 -0400
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu9) natty; urgency=low
+
+  * Fix handling for SemiMultitouch trackpads with integrated buttons
+    (LP: #736523)
+    - Added 119_active_area_touches.patch
+    - Added 120_active_touches_num_fingers.patch
+    - Added 121_semi-mt_num_fingers.patch
+  * Fix spurious right click events on some trackpads (LP: #742213)
+    - Added 122_revert_pressure_finger_default.patch
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Wed, 30 Mar 2011 10:54:50 -0400
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu8) natty; urgency=low
+
+  * Disable SemiMultitouch devices, we don't support them yet (LP: #723905)
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Tue, 15 Mar 2011 16:16:21 -0400
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu7) natty; urgency=low
+
+  * Set event window correctly for global gesture event
+    (LP: #728696)
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Fri, 04 Mar 2011 15:40:43 -0500
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu6) natty; urgency=low
+
+  * Fix silly typo in patch name
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Wed, 02 Mar 2011 13:06:17 -0800
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu5) natty; urgency=low
+
+  * Add 118_quell_error_msg.patch:  Don't print error "Unable to find a
+    synaptics device" when used on system that doesn't have a touchpad anyway.
+    (LP: #716712)
+  * Renumber patches 200 and 201 to 116 and 117 so are consistent with the
+    other ubuntu patches.
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Wed, 02 Mar 2011 12:16:32 -0800
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu4) UNRELEASED; urgency=low
+
+  * Add in xi 2.1 support and uTouch gesture support
+    - Added 200_xi2.1.patch
+    - Added 201_gestures.patch
+    - Add depends on libmtdev and libutouch-grail
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Sun, 20 Feb 2011 19:23:11 -0500
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu3) natty; urgency=low
+
+  * debian/control: Bump build-depend on xserver-xorg-dev to really, really
+    build against Xserver 1.10
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Tue, 01 Feb 2011 20:46:57 +1100
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu2) natty; urgency=low
+
+  * Rebuild against Xserver 1.10
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Mon, 31 Jan 2011 18:37:55 -0800
+
+xserver-xorg-input-synaptics (1.3.99+git20110116.0e27ce3a-0ubuntu1) natty; urgency=low
+
+  [ Cyril Brulebois ]
+  * Switch to Architecture: any, adding armhf support (Closes: #604672).
+
+  [ Christopher James Halse Rogers ]
+  * Merge from (unreleased) Debian experimental.  Remaining Ubuntu changes:
+    - Split out our synaptics quirks into 51-synaptics-quirks.conf
+    - Add the udev rule back for tagging specific machines to be quirked
+    - Trigger udev in postinst to pick up udev rules
+    - add Depend: on udev
+    - 103_enable_cornertapping.patch: Enable right/middle clicks by
+      tapping in the bottom- and top-right corners.
+    - 104_always_enable_tapping.patch: Enable tapping regardless of the
+      presence of physical buttons.
+    - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge
+      tapping by default even if we enable two-finger scrolling as well.
+    - 114_jumpy_cursor_first_part.patch: Adds a property to enable filtering
+      jumpy cursor problems on some machines.
+    - Specify ‘ubuntu’ branch in gbp.conf
+    - 115_evdev_only.patch: Only bind to /dev/input/event* evdev devices
+      (LP 624985)
+  * Update to git snapshot to fix build against Xserver 1.10 input API
+  * debian/patches/01-synaptics-dont-grab-if-not-on-current-VT.patch:
+    - Refresh for new upstream.
+  * debian/patches/114_jumpy_cursor_first_part.patch:
+    - Refresh for new upstream.  Drop the multi-touch chunk of the quirk,
+      as that is handled by upstream commit a6ca4d25.
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Tue, 25 Jan 2011 20:01:58 +1100
+
 xserver-xorg-input-synaptics (1.3.0-1) experimental; urgency=low
 
   [ Robert Hooker ]
@@ -274,6 +1004,78 @@ xserver-xorg-input-synaptics (1.2.2-3) e
 
  -- Cyril Brulebois <kibi@debian.org>  Wed, 13 Oct 2010 00:26:45 +0200
 
+xserver-xorg-input-synaptics (1.2.2-2ubuntu7) natty; urgency=low
+
+  * Add 116_resolution_detect_option.patch: Provide an option to prevent
+    synaptics from communicating the touchpad size to the xserver.  This
+    can be used to solve problems where differences in touchpad and screen
+    dimensions cause the mouse pointer to skip pixels when moving.
+    (LP: #327428)
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Wed, 01 Dec 2010 17:03:44 -0800
+
+xserver-xorg-input-synaptics (1.2.2-2ubuntu6) natty; urgency=low
+
+  * Add quirk for Dell Inspiron 6400 to register taps more reliably.
+    The touchpad hardware occasionally sends invalid (1, 5855) coords
+    for taps, which the driver then interprets as a corner tap, which
+    by default get ignored.  Instead of ignoring them, we treat them
+    as left-button clicks.
+    (LP #133060)
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Wed, 01 Dec 2010 12:32:39 -0800
+
+xserver-xorg-input-synaptics (1.2.2-2ubuntu5) maverick; urgency=low
+
+  * 115_evdev_only.patch was added but never applied. (LP: #624985)
+
+ -- Chase Douglas <chase.douglas@ubuntu.com>  Sat, 18 Sep 2010 15:19:21 +0200
+
+xserver-xorg-input-synaptics (1.2.2-2ubuntu4) maverick; urgency=low
+
+  [Chase Douglas]
+  * 115_evdev_only.patch:
+    - Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback
+      (LP: #624985)
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Wed, 15 Sep 2010 16:27:29 -0700
+
+xserver-xorg-input-synaptics (1.2.2-2ubuntu3) maverick; urgency=low
+
+  * Rebuild against new Xserver 1.9 ABI
+
+ -- Christopher James Halse Rogers <raof@ubuntu.com>  Mon, 09 Aug 2010 21:47:56 +1000
+
+xserver-xorg-input-synaptics (1.2.2-2ubuntu2) maverick; urgency=low
+
+  * 51-synaptics-quirks.conf, 66-xorg-synaptics.rules:
+    - Put back quirk for Inspiron 1120 which was probably lost in a merge.
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Fri, 06 Aug 2010 12:26:59 +0200
+
+xserver-xorg-input-synaptics (1.2.2-2ubuntu1) maverick; urgency=low
+
+  [ Robert Hooker ]
+  * Merge from debian unstable, remaining changes:
+    - Split out our synaptics quirks into 51-synaptics-quirks.conf
+    - Add the udev rule back for tagging specific machines to be quirked
+    - 103_enable_cornertapping.patch: Enable right/middle clicks by
+      tapping in the bottom- and top-right corners.
+    - 104_always_enable_tapping.patch: Enable tapping regardless of the
+      presence of physical buttons.
+    - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge
+      tapping by default even if we enable two-finger scrolling as well.
+    - 114_jumpy_cursor_first_part.patch: Adds a property to enable filtering
+      jumpy cursor problems on some machines.
+  * Add a dependency on udev.
+
+  [ Christopher James Halse Rogers ]
+  * debian/gbp.conf:
+    - Make git-buildpackage less annoying by specifying “ubuntu” as our debian
+      branch.
+
+ -- Robert Hooker <sarvatt@ubuntu.com>  Mon, 07 Jun 2010 19:07:59 -0400
+
 xserver-xorg-input-synaptics (1.2.2-2) unstable; urgency=low
 
   * Cherry-pick patch from upstream git to add the xorg.conf.d snippet.
@@ -291,6 +1093,19 @@ xserver-xorg-input-synaptics (1.2.2-2) u
 
  -- Cyril Brulebois <kibi@debian.org>  Sat, 17 Apr 2010 17:48:26 +0200
 
+xserver-xorg-input-synaptics (1.2.2-1ubuntu2) lucid; urgency=low
+
+  * 10-synaptics.conf: Use 'MatchDevicePath "/dev/input/event*"'.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Thu, 01 Apr 2010 14:34:20 +0300
+
+xserver-xorg-input-synaptics (1.2.2-1ubuntu1) lucid; urgency=low
+
+  * Merge from Debian unstable. (LP: #546933)
+  * Rewrite old quirks to use ID_INPUT.tags.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Wed, 31 Mar 2010 00:27:07 +0300
+
 xserver-xorg-input-synaptics (1.2.2-1) unstable; urgency=low
 
   [ Timo Aaltonen ]
@@ -326,6 +1141,22 @@ xserver-xorg-input-synaptics (1.2.1-1) u
 
  -- Cyril Brulebois <kibi@debian.org>  Fri, 08 Jan 2010 00:55:04 +0100
 
+xserver-xorg-input-synaptics (1.2.0-3ubuntu1) lucid; urgency=low
+
+  * Merge from Debian experimental, remaining changes:
+    - 103_enable_cornertapping.patch: Enable right/middle clicks by
+      tapping in the bottom- and top-right corners.
+    - 104_always_enable_tapping.patch: Enable tapping regardless of the
+      presence of physical buttons.
+    - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge 
+      tapping by default even if we enable two-finger scrolling as well.
+  * Disabled until fixed to apply and work:
+    - 114_jumpy_cursor_first_part.patch
+  * Dropped, using udev now:
+    - 115_jumpy_cursor_second_part.patch
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Mon, 07 Dec 2009 22:58:38 +0200
+
 xserver-xorg-input-synaptics (1.2.0-3) experimental; urgency=low
 
   [ Julien Cristau ]
@@ -366,6 +1197,87 @@ xserver-xorg-input-synaptics (1.2.0-1) u
 
  -- Mattia Dongili <malattia@debian.org>  Thu, 19 Nov 2009 21:24:49 +0900
 
+xserver-xorg-input-synaptics (1.1.2-1ubuntu7) karmic; urgency=low
+
+  * debian/patches:
+    - 115_jumpy_cursor_second_part.patch: Set AreaBottomEdge to
+      4100 for Inspiron 1011 and Inspiron 1012. 
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Wed, 14 Oct 2009 16:55:16 +0200
+
+xserver-xorg-input-synaptics (1.1.2-1ubuntu6) karmic; urgency=low
+
+  * debian/patches:
+    - Split 114_jumpy_cursor_threshold.patch into 
+      114_jumpy_cursor_first_part.patch and
+      115_jumpy_cursor_second_part.patch so as to keep options
+      which belong in the fdi file separate from the code.
+    - 115_jumpy_cursor_second_part.patch:
+      + Add quirk for Inspiron 1012.
+    - Remove 114_jumpy_cursor_threshold.patch_old.
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Mon, 05 Oct 2009 12:09:14 +0200
+
+xserver-xorg-input-synaptics (1.1.2-1ubuntu5) karmic; urgency=low
+
+  * Add lpia to the list of Architectures.  LP: #413579.
+
+ -- Steve Langasek <steve.langasek@ubuntu.com>  Mon, 07 Sep 2009 10:44:54 -0700
+
+xserver-xorg-input-synaptics (1.1.2-1ubuntu4) karmic; urgency=low
+
+  * debian/patches:
+    - 112_jumpy_cursor_quirks.patch:
+      + Drop patch.
+    - 114_jumpy_cursor_threshold.patch:
+      + Replace 112_jumpy_cursor_quirks.patch.
+      + Make the threshold an XInput property.
+      + Add quirk for Dell Mini10v and for HP Mini 1000 in the
+        fdi file.
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Wed, 02 Sep 2009 10:44:28 +0200
+
+xserver-xorg-input-synaptics (1.1.2-1ubuntu3) karmic; urgency=low
+
+  * debian/patches:
+    - 112_jumpy_cursor_quirks.patch: Make sure that the quirk
+      affects only the Dell Mini 10v (LP: #405943).
+    - 103_enable_cornertapping.patch: Don't trigger middle click
+      when tapping on the top right corner button (LP: #386017).
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Mon, 17 Aug 2009 10:31:14 +0200
+
+xserver-xorg-input-synaptics (1.1.2-1ubuntu2) karmic; urgency=low
+
+  * debian/patches:
+    - 105_correct_multifinger_click.patch: Drop patch as it breaks
+      the expected behavior for multitouch clicking (LP: #320585).
+    - 111_add_active_area.patch: Backport "Synaptics Area" property
+      from upstream (LP: #402863).
+    - 112_jumpy_cursor_quirks.patch: Add Quirk for Dell Mini 10v 
+      touchpad (LP: #402863).
+    - 113_add_capabilities.patch: Backport read-only "Synaptics 
+      Capabilities" property from upstream.
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Wed, 22 Jul 2009 10:42:11 +0200
+
+xserver-xorg-input-synaptics (1.1.2-1ubuntu1) karmic; urgency=low
+
+  * Merge from Debian unstable, Remaining changes:
+    - Restore the Ubuntu patch series. (LP: #378391)
+     - 103_enable_cornertapping.patch: Enable right/middle clicks by
+       tapping in the bottom- and top-right corners.
+     - 104_always_enable_tapping.patch: Enable tapping regardless of the
+       presence of physical buttons.
+     - 105_correct_multifinger_click.patch: Swap the right/middle clicks
+       generated by multi-finger tapping.
+     - 106_always_enable_vert_edge_scroll.patch: Enable vertical edge 
+       tapping by default even if we enable two-finger scrolling as well.
+     - 110_remove_bound_auto_adjust.patch: Don't auto-adjust to bounds of
+       the hardware scroll area.
+
+ -- Robert Hooker (Sarvatt) <sarvatt@gmail.com>  Sun, 28 Jun 2009 18:13:52 -0400
+
 xserver-xorg-input-synaptics (1.1.2-1) unstable; urgency=low
 
   [ Mattia Dongili ]
@@ -417,6 +1329,52 @@ xserver-xorg-input-synaptics (0.99.3-3)
 
  -- Julien Cristau <jcristau@debian.org>  Mon, 26 Jan 2009 09:08:10 +0100
 
+xfree86-driver-synaptics (0.99.3-2ubuntu4) jaunty; urgency=low
+
+  * debian/patches/110_remove_bound_auto_adjust.patch:
+    - Small change to make sure that the driver does not
+      auto-adjust to the bounds of the hardware scroll area
+      as this causes problem to the touchpads of Acer Aspire
+      One units (LP: #320632).
+
+ -- Alberto Milone <alberto.milone@canonical.com>  Thu, 26 Mar 2009 17:00:52 +0100
+
+xfree86-driver-synaptics (0.99.3-2ubuntu3) jaunty; urgency=low
+
+  * debian/patches/109_override_alps_settings.patch:
+    - Add customised settings for ALPS touchpad (LP: #320632):
+      o Enable vertTwoFingerScroll by default.
+      o Set RightEdge to 900.
+      o Set ClickTime to 0.
+      o Set SingleTapTimeout to 0.
+      o Set MinSpeed to 0.45.
+      o Set MaxSpeed to 0.75.
+  * debian/patches/107_reduce_cursor_speed.patch:
+    - drop and merge with 109_override_alps_settings.patch.
+
+ -- Alberto Milone (tseliot) <albertomilone@alice.it>  Sat, 28 Feb 2009 10:20:08 +0100
+
+xfree86-driver-synaptics (0.99.3-2ubuntu2) jaunty; urgency=low
+
+  [Alberto Milone]
+  * debian/patches/105_always_enable_multifinger_click.patch:
+    - Drop patch
+  * debian/patches/105_correct_multifinger_click.patch:
+    - Fix inverted buttons
+    - Restore ability to drag and drop with physical buttons
+    (LP: #320632)
+
+ -- Alberto Milone (tseliot) <albertomilone@alice.it>  Sat, 31 Jan 2009 18:48:37 +0100
+
+xfree86-driver-synaptics (0.99.3-2ubuntu1) jaunty; urgency=low
+
+  * Merge from Debian experimental.
+  * Revive syndaemon_xinput_props.patch, now refreshed as 108.
+    Thanks to John S. Gruber for noticing it was dropped in haste.
+    (LP: #321355)
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Mon, 26 Jan 2009 09:57:07 +0200
+
 xfree86-driver-synaptics (0.99.3-2) experimental; urgency=low
 
   * Update our copy of xsfbs to get a fixed serverabi rule and generate
@@ -425,6 +1383,35 @@ xfree86-driver-synaptics (0.99.3-2) expe
 
  -- Julien Cristau <jcristau@debian.org>  Mon, 26 Jan 2009 04:43:08 +0100
 
+xfree86-driver-synaptics (0.99.3-1ubuntu2) jaunty; urgency=low
+
+  [ Alberto Milone (tseliot) ]
+  * 103_enable_cornertapping.patch:
+    - Update patch for the new upstream release. (LP: #320632)
+  * 104_always_enable_tapping.patch:
+    - Enable tapping even when a physical left button is available. (LP: #320585)
+  * 105_always_enable_multifinger_click.patch:
+    - Enable multifinger click (where available) even when physical right/middle
+      buttons are available. (LP: #320585)
+  * 106_always_enable_vert_edge_scroll.patch:
+    - Enable scrolling even when doubletap is available. (LP: #320632)
+  * 107_reduce_cursor_speed.patch:
+    - Reduce the cursor speed. (LP: #320639)
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Sat, 24 Jan 2009 20:29:04 +0200
+
+xfree86-driver-synaptics (0.99.3-1ubuntu1) jaunty; urgency=low
+
+  * Merge from Debian experimental.
+  * Drop obsolete patches:
+    -101_no_horizscroll.patch
+    -102_dont_check_abi.diff
+    -104_syndaemon_xinput_props.patch
+    -105_no_bcm5974_shmconfig.patch
+  * Update 103_enable_cornertapping.patch to apply.
+
+ -- Timo Aaltonen <tjaalton@ubuntu.com>  Fri, 23 Jan 2009 14:30:34 +0200
+
 xfree86-driver-synaptics (0.99.3-1) experimental; urgency=low
 
   [ Timo Aaltonen ]
@@ -439,6 +1426,105 @@ xfree86-driver-synaptics (0.99.3-1) expe
 
  -- Julien Cristau <jcristau@debian.org>  Wed, 21 Jan 2009 22:27:10 +0100
 
+xfree86-driver-synaptics (0.15.2-0ubuntu5) intrepid; urgency=low
+
+  * Cherry-picked commit db6e63, "Return correctly on successful property
+    setting"
+  * debian/patches/104_syndaemon_xinput_props.patch:  Add support for XInput
+    device properties to syndaemon.
+  * debian/patches/105_no_bcm5974_shmconfig.patch: Remove the SHMConfig
+    enabling line from the bcm5974 section of the fdi file. (LP: #282004)
+
+ -- William Grant <wgrant@ubuntu.com>  Mon, 13 Oct 2008 21:48:05 +1100
+
+xfree86-driver-synaptics (0.15.2-0ubuntu4) intrepid; urgency=low
+
+  * Cherry-picked commit 355e845, "Adjust to new property API".
+    (LP: #274728)
+  * Refresh patches 101 & 102.
+  * Bump the build-dep on libxi-dev and xserver-xorg-dev.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Mon, 29 Sep 2008 10:23:16 +0300
+
+xfree86-driver-synaptics (0.15.2-0ubuntu3) intrepid; urgency=low
+
+  * Also build for lpia; LP: #275158.
+
+ -- Loic Minier <lool@dooz.org>  Sat, 27 Sep 2008 16:11:42 +0200
+
+xfree86-driver-synaptics (0.15.2-0ubuntu2) intrepid; urgency=low
+
+  [ Alberto Milone ]
+  * 103_enable_cornertapping.patch:
+    - Revert some defaults back to previous values, to fix corner tapping
+      and MaxTapMove. (LP: #271823)
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Tue, 23 Sep 2008 10:46:44 +0300
+
+xfree86-driver-synaptics (0.15.2-0ubuntu1) intrepid; urgency=low
+
+  * New upstream release. (LP: #262276, #268071, #247433, #262305)
+  * Drop 101_ubuntu.diff, superseded by input properties.
+  * Drop 103_fix_off_by_one.diff, applied upstream.
+  * 101_no_horizscroll.patch:
+    - Disable horizontal scrolling by defaulting to FALSE instead of changing
+      the fdi file.
+  * 102_dont_check_abi.diff:
+    - Remove one more check from src/synaptics.c, which was added recently.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Fri, 12 Sep 2008 01:32:57 +0300
+
+xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu5) intrepid; urgency=low
+
+  * Rebuild to fix dependency against xserver-xorg-core.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Wed, 03 Sep 2008 17:45:20 +0300
+
+xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu4) intrepid; urgency=low
+
+  * Run autoreconf at build time; build-depend on automake, libtool and
+    xutils-dev.
+  * Enable 102 again, since the problem with it was that Makefile.in was
+    not regenerated.
+  * Add 103_fix_off_by_one.diff to fix the number of arguments for 
+    properties. Thanks William Grant!
+  * Pull 3d39926875446e from upstream/master: 
+    - Re-enable TapButtons and CornerButtons to work by default.
+    (LP: #262292)
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Sat, 30 Aug 2008 18:20:14 +0300
+
+xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu3) intrepid; urgency=low
+
+  * Disable 102 for now, since the property stuff doesn't seem to be 
+    working right. (LP: #262986)
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Sat, 30 Aug 2008 16:52:51 +0300
+
+xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu2) intrepid; urgency=low
+
+  * 102_dont_check_abi.diff:
+    - Don't check ABI_MAJOR, since we have all that's needed for 
+      properties.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Sat, 30 Aug 2008 13:27:54 +0300
+
+xfree86-driver-synaptics (0.15.0+git20080820-1ubuntu1) intrepid; urgency=low
+
+  * Merge from Debian experimental, remaining changes:
+    - debian/control:
+      + Drop the dummy package
+      + Change the maintainer address
+    - debian/patches:
+      101_ubuntu.diff
+      + Various fixes, split this.
+    - fdi/11-x11-synaptics.fdi
+      + Modify to disable horizontal scrolling like our dexconf used to do.
+  * Pull in support for input properties.
+  * Drop 102_polling.diff, since it's upstream.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Thu, 28 Aug 2008 02:09:47 +0300
+
 xfree86-driver-synaptics (0.15.0+git20080820-1) experimental; urgency=low
 
   * Update to latest upstream git
@@ -478,6 +1564,48 @@ xfree86-driver-synaptics (0.14.7~git2007
 
  -- Julien Cristau <jcristau@debian.org>  Mon, 18 Aug 2008 01:41:39 +0200
 
+xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu4) intrepid; urgency=low
+
+  * Fix the fdi file to disable horizontal scrolling like dexconf used
+    to do.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Mon, 18 Aug 2008 16:27:30 +0300
+
+xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu3) intrepid; urgency=low
+
+  * Enable input-hotplug: install 10-synaptics.fdi in 
+    /usr/share/hal/fdi/policy/20thirdparty.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Fri, 01 Aug 2008 09:58:32 +0300
+
+xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu2) intrepid; urgency=low
+
+  * Rebuild against the new xserver-xorg-dev.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Mon, 07 Jul 2008 17:45:08 +0300
+
+xfree86-driver-synaptics (0.14.7~git20070706-2.1ubuntu1) intrepid; urgency=low
+
+  * Merge from debian unstable, remaining changes:
+    - Rename the source
+    - debian/control:
+      + Drop the dummy package
+      + Change the maintainer address.
+      + Add quilt to build-deps.
+    - Makefile: Add -fno-stack-protector.
+    - debian/patches:
+      100_fix_vt_handling.diff
+      + Something is opening the device even after we've switched away
+        from the VT. The easiest way to handle this is to refuse to open
+        unless we're on the current VT.
+      101_ubuntu.diff
+      + Various fixes, split this.
+      102_polling.diff
+      + Added a patch which increases the polling timeout to save some
+        battery life
+
+ -- Bryce Harrington <bryce@ubuntu.com>  Mon, 09 Jun 2008 22:27:20 -0700
+
 xfree86-driver-synaptics (0.14.7~git20070706-2.1) unstable; urgency=low
 
   * Non-maintainer upload, with Mattia's agreement.
@@ -497,6 +1625,47 @@ xfree86-driver-synaptics (0.14.7~git2007
 
  -- Mattia Dongili <malattia@debian.org>  Tue, 01 Jan 2008 12:38:13 +0900
 
+xfree86-driver-synaptics (0.14.7~git20070706-1ubuntu4) hardy; urgency=low
+
+  * 102_polling.diff:
+    - Added a patch which increases the polling timeout to save some
+      battery life (LP: #184398).
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Wed, 27 Feb 2008 11:58:01 +0200
+
+xfree86-driver-synaptics (0.14.7~git20070706-1ubuntu3) hardy; urgency=low
+
+  * debian/patches/101_ubuntu.diff:
+    - extend to support disabling the touchpad
+    - remove unused bits of the patch
+
+ -- Scott James Remnant <scott@ubuntu.com>  Tue, 22 Jan 2008 13:40:07 +0000
+
+xfree86-driver-synaptics (0.14.7~git20070706-1ubuntu2) hardy; urgency=low
+
+  * Change the source name to match Debian.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Fri, 11 Jan 2008 11:59:09 +0200
+
+xserver-xorg-input-synaptics (0.14.7~git20070706-1ubuntu1) hardy; urgency=low
+
+  * Merge with Debian unstable, remaining changes:
+    - Rename the source
+    - debian/control:
+      + Drop the dummy package
+      + Change the maintainer address.
+      + Add quilt to build-deps.
+    - Makefile: Add -fno-stack-protector.
+    - debian/patches:
+      100_fix_vt_handling.diff
+      + Something is opening the device even after we've switched away
+        from the VT. The easiest way to handle this is to refuse to open
+        unless we're on the current VT.
+      101_ubuntu.diff
+      + Various fixes, split this.
+
+ -- Timo Aaltonen <tepsipakki@ubuntu.com>  Fri, 09 Nov 2007 02:23:16 +0200
+
 xfree86-driver-synaptics (0.14.7~git20070706-1) unstable; urgency=low
 
   * build with xserver-xorg-core 1.4 (Closes: #442314)
@@ -539,6 +1708,81 @@ xfree86-driver-synaptics (0.14.6-2) unst
  
  -- Mattia Dongili <malattia@debian.org>  Wed, 03 Jan 2007 18:38:40 +0100
 
+xserver-xorg-input-synaptics (0.14.6-0ubuntu10) gutsy; urgency=low
+
+  * Bodge around LP: #68370. Something is opening the device even after 
+    we've switched away from the VT. The easiest way to handle this is 
+    to refuse to open unless we're on the current VT.
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Tue, 09 Oct 2007 21:08:23 +0100
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu9) gutsy; urgency=low
+
+  * Actually include the parameter setting code
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Mon, 17 Sep 2007 17:56:53 +0100
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu8) gutsy; urgency=low
+
+  * Fix the build system so it works properly on 64-bit platforms
+  * Add initial support for setting some paramaters via X
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Sun, 16 Sep 2007 18:18:17 +0100
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu7) feisty; urgency=low
+
+  * X and Y size calculations were inverted for edge scrolling
+  * Additionally, the values were scaled incorrectly (LP: #95858)
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Sun, 25 Mar 2007 16:13:42 +0100
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu6) feisty; urgency=low
+
+  * Set defaults based on values the kernel gives us, rather than making stuff
+    up in an utterly miserable manner. This upload brought to you by Milton
+    Nero, a deeply flavoured oat stout. 
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Sat, 24 Mar 2007 15:08:26 +0000
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu5) feisty; urgency=low
+
+  * Attempt to set more reasonable defaults for appletouch devices. Better
+    now, but still not perfect. 
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Sat, 24 Mar 2007 02:44:11 +0000
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu4) feisty; urgency=low
+
+  * debian/control: Update maintainer fields according to debian-
+    maintainer-field spec.
+
+ -- Martin Pitt <martin.pitt@ubuntu.com>  Mon, 12 Mar 2007 14:58:39 +0000
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu3) edgy; urgency=low
+
+  * Add the ugly ALPS hack back
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Mon,  7 Aug 2006 22:55:42 +0100
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu2) edgy; urgency=low
+
+  * Disable Stack Protection for this package for now (module loading fails
+    due to missing symbols)
+
+ -- Rodrigo Parra Novo <rodarvus@ubuntu.com>  Mon, 31 Jul 2006 09:39:16 -0300
+
+xserver-xorg-input-synaptics (0.14.6-0ubuntu1) edgy; urgency=low
+
+  * New Upstream release
+  * New versioned Build-Depends on xserver-xorg-dev (>= 1.1.1)
+  * New versioned Build-Depends on xserver-xorg-core (>= 1.1.1)
+  * Fixed debian/rules to match current Makefile rules (based on Debian
+    xfree86-driver-synaptics package)
+  * Fixed Makefile and manual page, also based on Debian xfree86-driver-synaptics
+  * Dropped Alps patches - unfortunately they don't apply anymore
+
+ -- Rodrigo Parra Novo <rodarvus@ubuntu.com>  Thu, 27 Jul 2006 12:18:18 -0300
+
 xfree86-driver-synaptics (0.14.5-1) unstable; urgency=low
 
   * New upstream release.
@@ -583,6 +1827,76 @@ xfree86-driver-synaptics (0.14.4-1) unst
   * Removed the rediffed patch against Debian's 2.6.8 kernel.
  
  -- Mattia Dongili <malattia@debian.org>  Mon, 07 Nov 2005 22:54:40 +0100
+
+xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu4) dapper; urgency=low
+
+  * New versioned Build-Depends on xserver-xorg-dev (>= 1.1.1)
+  * New versioned Build-Depends on xserver-xorg-core (>= 1.1.1)
+
+ -- Rodrigo Parra Novo <rodarvus@ubuntu.com>  Thu, 27 Jul 2006 11:59:57 -0300
+
+xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu3) dapper; urgency=low
+
+  * Horrible workaround to avoid massive ALPS-related suckage
+
+ -- Matthew Garrett <mjg59@srcf.ucam.org>  Wed, 22 Mar 2006 21:02:26 +0000
+
+xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu2) dapper; urgency=low
+
+  * Build with -fPIC to fix FTBFS
+
+ -- Tollef Fog Heen <tfheen@ubuntu.com>  Wed, 11 Jan 2006 16:20:57 +0100
+
+xserver-xorg-input-synaptics (0.14.3+seriouslythistime-0ubuntu1) dapper; urgency=low
+
+  * Bounce straight back to 0.14.3.
+
+ -- Daniel Stone <daniel.stone@ubuntu.com>  Tue, 25 Oct 2005 19:12:28 +1000
+
+xorg-driver-synaptics (0.14.3+revertedto+0.13.6-0ubuntu3) breezy; urgency=low
+
+  * Change dependency on xserver-xorg to -core (closes: Ubuntu#15902).
+
+ -- Daniel Stone <daniel.stone@ubuntu.com>  Fri, 16 Sep 2005 17:42:34 +1000
+
+xorg-driver-synaptics (0.14.3+revertedto+0.13.6-0ubuntu2) breezy; urgency=low
+
+  * Build client utilities without includes from Xincludes.
+
+ -- Daniel Stone <daniel.stone@ubuntu.com>  Wed,  7 Sep 2005 16:31:51 +1000
+
+xorg-driver-synaptics (0.14.3+revertedto+0.13.6-0ubuntu1) breezy; urgency=low
+
+  * Revert to old upstream version for preview at least, as the new was quite
+    catastrophically broken with ALPS.  Fixed some, broke a lot (closes:
+    Ubuntu#14480).
+  * Backport the confused-finger-count patch.
+  * Hillariously, dpkg-source ignores deleted files, which means that
+    Xincludes stuff keeps getting resurrected.  Change $(X_INCLUDES_ROOT) to
+    /usr in include paths (except for the server DDK) to ensure that we get
+    the installed headers, not the braindead included ones.
+
+ -- Daniel Stone <daniel.stone@ubuntu.com>  Wed,  7 Sep 2005 11:07:06 +1000
+
+xorg-driver-synaptics (0.14.3-1ubuntu2) breezy; urgency=low
+
+  * Move to /usr/lib/xorg for the modular server, and build-depend on
+    xserver-xorg-dev.
+  * Switch dependency to xserver-xorg-core, version it.
+  * Start building synaptics_drv.so instead of .o.
+
+ -- Daniel Stone <daniel.stone@ubuntu.com>  Fri,  2 Sep 2005 12:08:27 +1000
+
+xorg-driver-synaptics (0.14.3-1ubuntu1) breezy; urgency=low
+
+  * Merge with Debian.
+  * New upstream release:
+    + Fixes confused finger count with low pressure on some HPs (closes:
+      Ubuntu#14155).
+  * Remove unnecessary files in Xincludes from x11proto-core-dev and
+    x11proto-input-dev.
+
+ -- Daniel Stone <daniel.stone@ubuntu.com>  Thu,  1 Sep 2005 16:24:27 +1000
  
 xfree86-driver-synaptics (0.14.3-1) unstable; urgency=low
   
diff -pruN 1.9.1-1/debian/control 1.9.1-1ubuntu3/debian/control
--- 1.9.1-1/debian/control	2020-04-09 16:11:15.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/control	2020-04-09 16:11:16.000000000 +0000
@@ -1,7 +1,8 @@
 Source: xserver-xorg-input-synaptics
 Section: x11
 Priority: optional
-Maintainer: Debian X Strike Force <debian-x@lists.debian.org>
+Maintainer: Ubuntu X-SWAT <ubuntu-x@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian X Strike Force <debian-x@lists.debian.org>
 Uploaders: Mattia Dongili <malattia@debian.org>, maximilian attems <maks@debian.org>
 Build-Depends:
  debhelper (>= 10),
diff -pruN 1.9.1-1/debian/gbp.conf 1.9.1-1ubuntu3/debian/gbp.conf
--- 1.9.1-1/debian/gbp.conf	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/gbp.conf	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3 @@
+[DEFAULT]
+debian-branch=ubuntu
+
diff -pruN 1.9.1-1/debian/local/51-synaptics-quirks.conf 1.9.1-1ubuntu3/debian/local/51-synaptics-quirks.conf
--- 1.9.1-1/debian/local/51-synaptics-quirks.conf	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/local/51-synaptics-quirks.conf	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,23 @@
+Section "InputClass"
+	Identifier "Dell Inspiron embedded buttons quirks"
+	MatchTag "inspiron_1011|inspiron_1012"
+	MatchDevicePath "/dev/input/event*"
+	Driver "synaptics"
+	Option "JumpyCursorThreshold" "90"
+EndSection
+
+Section "InputClass"
+	Identifier "Dell Inspiron quirks"
+	MatchTag "inspiron_1120"
+	MatchDevicePath "/dev/input/event*"
+	Driver "synaptics"
+	Option "JumpyCursorThreshold" "250"
+EndSection
+
+Section "InputClass"
+	Identifier "HP Mininote quirks"
+	MatchTag "mininote_1000"
+	MatchDevicePath "/dev/input/event*"
+	Driver "synaptics"
+	Option "JumpyCursorThreshold" "20"
+EndSection
diff -pruN 1.9.1-1/debian/local/66-xorg-synaptics-quirks.rules 1.9.1-1ubuntu3/debian/local/66-xorg-synaptics-quirks.rules
--- 1.9.1-1/debian/local/66-xorg-synaptics-quirks.rules	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/local/66-xorg-synaptics-quirks.rules	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,13 @@
+ACTION!="add|change", GOTO="xorg_synaptics_quirks_end"
+KERNEL!="event*", GOTO="xorg_synaptics_quirks_end"
+
+ENV{ID_INPUT_TOUCHPAD}!="1", GOTO="xorg_synaptics_quirks_end"
+
+# Placeholder for platform specific quirks needing
+# ID_INPUT.tags to be set.
+ATTR{[dmi/id]product_name}=="Inspiron 1011", ENV{ID_INPUT.tags}="inspiron_1011"
+ATTR{[dmi/id]product_name}=="Inspiron 1012", ENV{ID_INPUT.tags}="inspiron_1012"
+ATTR{[dmi/id]product_name}=="Inspiron 1120", ENV{ID_INPUT.tags}="inspiron_1120"
+ATTR{[dmi/id]product_name}=="HP MiniNote 1000", ENV{ID_INPUT.tags}="mininote_1000"
+
+LABEL="xorg_synaptics_quirks_end"
diff -pruN 1.9.1-1/debian/patches/02-do-not-use-synaptics-for-keyboards.patch 1.9.1-1ubuntu3/debian/patches/02-do-not-use-synaptics-for-keyboards.patch
--- 1.9.1-1/debian/patches/02-do-not-use-synaptics-for-keyboards.patch	2020-04-09 16:11:15.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/02-do-not-use-synaptics-for-keyboards.patch	2020-04-09 16:11:16.000000000 +0000
@@ -1,10 +1,10 @@
 do not use the synaptics driver for devices advertising themselves as keyboards
 http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=524130
 
-diff --git a/conf/11-x11-synaptics.fdi b/conf/11-x11-synaptics.fdi
-index a898875..8714a59 100644
---- a/conf/11-x11-synaptics.fdi
-+++ b/conf/11-x11-synaptics.fdi
+Index: xserver-xorg-input-synaptics-1.9.1/conf/11-x11-synaptics.fdi
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/conf/11-x11-synaptics.fdi
++++ xserver-xorg-input-synaptics-1.9.1/conf/11-x11-synaptics.fdi
 @@ -9,6 +9,7 @@
  <deviceinfo version="0.2">
    <device>
@@ -13,11 +13,11 @@ index a898875..8714a59 100644
          <merge key="input.x11_driver" type="string">synaptics</merge>
          <!-- Arbitrary options can be passed to the driver using
               the input.x11_options property since xorg-server-1.5. -->
-@@ -34,6 +35,7 @@
+@@ -35,6 +36,7 @@
          <match key="info.product" contains="Apple|bcm5974">
              <merge key="input.x11_options.SoftButtonAreas" type="string">0 0 0 0 0 0 0 0</merge>
          </match>
 +      </match>
      </match>
- 
-     <match
+   </device>
+ </deviceinfo>
diff -pruN 1.9.1-1/debian/patches/03-bug-return-val.patch 1.9.1-1ubuntu3/debian/patches/03-bug-return-val.patch
--- 1.9.1-1/debian/patches/03-bug-return-val.patch	2020-04-09 16:11:15.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/03-bug-return-val.patch	2020-04-09 16:11:16.000000000 +0000
@@ -1,8 +1,8 @@
-Index: xserver-xorg-input-synaptics.git/src/synaptics.c
+Index: xserver-xorg-input-synaptics-1.9.1/src/synaptics.c
 ===================================================================
---- xserver-xorg-input-synaptics.git.orig/src/synaptics.c	2013-12-10 13:41:57.754590117 +0100
-+++ xserver-xorg-input-synaptics.git/src/synaptics.c	2013-12-10 13:41:57.754590117 +0100
-@@ -2466,7 +2466,9 @@
+--- xserver-xorg-input-synaptics-1.9.1.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.9.1/src/synaptics.c
+@@ -2639,7 +2639,9 @@ clickpad_guess_clickfingers(SynapticsPri
      uint32_t close_point = 0; /* 1 bit for each point close to another one */
      int i, j;
  
diff -pruN 1.9.1-1/debian/patches/101_resolution_detect_option.patch 1.9.1-1ubuntu3/debian/patches/101_resolution_detect_option.patch
--- 1.9.1-1/debian/patches/101_resolution_detect_option.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/101_resolution_detect_option.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,141 @@
+Description: Provide an option to prevent synaptics from communicating the touchpad size to the xserver. 
+Bug-Ubuntu: https://launchpad.net/bugs/327428
+
+Index: xserver-xorg-input-synaptics-1.9.1/include/synaptics-properties.h
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/include/synaptics-properties.h
++++ xserver-xorg-input-synaptics-1.9.1/include/synaptics-properties.h
+@@ -134,6 +134,9 @@
+ #define SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR "Synaptics Pressure Motion Factor"
+ 
+ /* 8 bit (BOOL) */
++#define SYNAPTICS_PROP_RESOLUTION_DETECT "Synaptics Resolution Detect"
++
++/* 8 bit (BOOL) */
+ #define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device"
+ 
+ /* 8 bit (BOOL), 1 value, tap-and-drag */
+Index: xserver-xorg-input-synaptics-1.9.1/man/synaptics.man
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/man/synaptics.man
++++ xserver-xorg-input-synaptics-1.9.1/man/synaptics.man
+@@ -427,6 +427,18 @@ The gesture is enabled by default and ca
+ TapAndDragGesture option to false. Property: "Synaptics Gestures"
+ .
+ .TP
++.BI "Option \*ResolutionDetect\*q \*q" boolean \*q
++Allow or prevent the synaptics driver from reporting the size of the
++touchpad to the X server.  The X server normally uses this information
++to scale movements so that touchpad movement corresponds visually to
++mouse cursor movements on the screen.  However, in some rare cases where
++the touchpad height/width ratio is significantly different from the
++laptop, it can cause the mouse cursor to skip pixels in the X or Y axis.
++This option allows disabling this scaling behavior, which can provide
++smoother mouse movement in such cases.
++Property: "Synaptics Resolution Detect"
++.
++.TP
+ .BI "Option \*qVertResolution\*q \*q" integer \*q
+ Resolution of X coordinates in units/millimeter. The value is used
+ together with HorizResolution to compensate unequal vertical and
+@@ -880,6 +892,10 @@ FLOAT, 2 values, speed, friction.
+ FLOAT, 2 values, min, max.
+ 
+ .TP 7
++.BI "Synaptics Resolution Detect"
++8 bit (BOOL).
++
++.TP 7
+ .BI "Synaptics Grab Event Device"
+ 8 bit (BOOL).
+ 
+Index: xserver-xorg-input-synaptics-1.9.1/src/properties.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/src/properties.c
++++ xserver-xorg-input-synaptics-1.9.1/src/properties.c
+@@ -85,6 +85,7 @@ Atom prop_palm_dim = 0;
+ Atom prop_coastspeed = 0;
+ Atom prop_pressuremotion = 0;
+ Atom prop_pressuremotion_factor = 0;
++Atom prop_resolution_detect = 0;
+ Atom prop_grab = 0;
+ Atom prop_gestures = 0;
+ Atom prop_capabilities = 0;
+@@ -345,6 +346,10 @@ InitDeviceProperties(InputInfoPtr pInfo)
+         InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 2,
+                       fvalues);
+ 
++    prop_resolution_detect =
++        InitAtom(pInfo->dev, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 1,
++                 &para->resolution_detect);
++
+     prop_grab =
+         InitAtom(pInfo->dev, SYNAPTICS_PROP_GRAB, 8, 1,
+                  &para->grab_event_device);
+@@ -747,6 +752,12 @@ SetProperty(DeviceIntPtr dev, Atom prope
+         para->press_motion_min_factor = press[0];
+         para->press_motion_max_factor = press[1];
+     }
++    else if (property == prop_resolution_detect) {
++        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
++            return BadMatch;
++
++        para->resolution_detect = *(BOOL*)prop->data;
++    }
+     else if (property == prop_grab) {
+         if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+             return BadMatch;
+Index: xserver-xorg-input-synaptics-1.9.1/src/synaptics.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.9.1/src/synaptics.c
+@@ -742,6 +742,7 @@ set_default_parameters(InputInfoPtr pInf
+         xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+     pars->press_motion_max_z =
+         xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
++    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
+ 
+     pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+     pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+@@ -1290,7 +1291,7 @@ DeviceInit(DeviceIntPtr dev)
+     }
+ 
+     /* X valuator */
+-    if (priv->minx < priv->maxx) {
++    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
+         min = priv->minx;
+         max = priv->maxx;
+     }
+@@ -1305,7 +1306,7 @@ DeviceInit(DeviceIntPtr dev)
+     xf86InitValuatorDefaults(dev, 0);
+ 
+     /* Y valuator */
+-    if (priv->miny < priv->maxy) {
++    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
+         min = priv->miny;
+         max = priv->maxy;
+     }
+Index: xserver-xorg-input-synaptics-1.9.1/src/synapticsstr.h
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/src/synapticsstr.h
++++ xserver-xorg-input-synaptics-1.9.1/src/synapticsstr.h
+@@ -222,6 +222,7 @@ typedef struct _SynapticsParameters {
+     int press_motion_max_z;     /* finger pressure at which maximum pressure motion factor is applied */
+     double press_motion_min_factor;     /* factor applied on speed when finger pressure is at minimum */
+     double press_motion_max_factor;     /* factor applied on speed when finger pressure is at minimum */
++    Bool resolution_detect;     /* report pad size to xserver? */
+     Bool grab_event_device;     /* grab event device for exclusive use? */
+     Bool tap_and_drag_gesture;  /* Switches the tap-and-drag gesture on/off */
+     unsigned int resolution_horiz;      /* horizontal resolution of touchpad in units/mm */
+Index: xserver-xorg-input-synaptics-1.9.1/tools/synclient.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/tools/synclient.c
++++ xserver-xorg-input-synaptics-1.9.1/tools/synclient.c
+@@ -130,6 +130,7 @@ static struct Parameter params[] = {
+     {"PressureMotionMaxZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	1},
+     {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	0},
+     {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	1},
++    {"ResolutionDetect",      PT_BOOL,   0, 1,     SYNAPTICS_PROP_RESOLUTION_DETECT,	8,      0},
+     {"GrabEventDevice",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_GRAB,	8,	0},
+     {"TapAndDragGesture",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_GESTURES,	8,	0},
+     {"AreaLeftEdge",          PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	0},
diff -pruN 1.9.1-1/debian/patches/103_enable_cornertapping.patch 1.9.1-1ubuntu3/debian/patches/103_enable_cornertapping.patch
--- 1.9.1-1/debian/patches/103_enable_cornertapping.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/103_enable_cornertapping.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,18 @@
+Description: Enable right/middle clicks by tapping in the bottom- and top-right corners.
+Bug-Ubuntu: https://launchpad.net/bugs/320632
+
+Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+@@ -717,8 +717,8 @@ set_default_parameters(InputInfoPtr pInf
+ 
+     pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+     pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+-    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 0);
+-    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 0);
++    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2);
++    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3);
+     pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+     pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+     pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
diff -pruN 1.9.1-1/debian/patches/104_always_enable_tapping.patch 1.9.1-1ubuntu3/debian/patches/104_always_enable_tapping.patch
--- 1.9.1-1/debian/patches/104_always_enable_tapping.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/104_always_enable_tapping.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,22 @@
+Description: Enable tapping even when a physical left button is available.
+Bug-Ubuntu: https://launchpad.net/bugs/320585
+
+Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+@@ -622,10 +622,10 @@ set_default_parameters(InputInfoPtr pInf
+     palmMinWidth = priv->minw + range * (10.0 / 16);
+     emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+ 
+-    /* Enable tap if we don't have a phys left button */
+-    tapButton1 = priv->has_left ? 0 : 1;
+-    tapButton2 = priv->has_left ? 0 : 3;
+-    tapButton3 = priv->has_left ? 0 : 2;
++    /* Enable tap */
++    tapButton1 = 1;
++    tapButton2 = 3;
++    tapButton3 = 2;
+ 
+     /* Enable multifinger-click if only have one physical button,
+        otherwise clickFinger is always button 1. */
diff -pruN 1.9.1-1/debian/patches/106_always_enable_vert_edge_scroll.patch 1.9.1-1ubuntu3/debian/patches/106_always_enable_vert_edge_scroll.patch
--- 1.9.1-1/debian/patches/106_always_enable_vert_edge_scroll.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/106_always_enable_vert_edge_scroll.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,18 @@
+Description: Enable vertical edge tapping by default even if we enable two-finger scrolling as well.
+Bug-Ubuntu: https://launchpad.net/bugs/320632
+
+Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+@@ -633,8 +633,8 @@ set_default_parameters(InputInfoPtr pInf
+     clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+     clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+ 
+-    /* Enable vert edge scroll if we can't detect doubletap */
+-    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
++    /* Enable vert edge scroll */
++    vertEdgeScroll = TRUE;
+     horizEdgeScroll = FALSE;
+ 
+     /* Enable twofinger scroll if we can detect doubletap */
diff -pruN 1.9.1-1/debian/patches/115_evdev_only.patch 1.9.1-1ubuntu3/debian/patches/115_evdev_only.patch
--- 1.9.1-1/debian/patches/115_evdev_only.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/115_evdev_only.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,16 @@
+Description: Prevent non-evdev devices from firing the eventcomm AutoDevProbe callback.
+Bug-Ubuntu: https://launchpad.net/bugs/624985
+
+Index: xserver-xorg-input-synaptics-1.8.2/conf/70-synaptics.conf
+===================================================================
+--- xserver-xorg-input-synaptics-1.8.2.orig/conf/70-synaptics.conf
++++ xserver-xorg-input-synaptics-1.8.2/conf/70-synaptics.conf
+@@ -14,7 +14,7 @@ Section "InputClass"
+ # This option is recommend on all Linux systems using evdev, but cannot be
+ # enabled by default. See the following link for details:
+ # http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html
+-#       MatchDevicePath "/dev/input/event*"
++      MatchDevicePath "/dev/input/event*"
+ EndSection
+ 
+ Section "InputClass"
diff -pruN 1.9.1-1/debian/patches/118_quell_error_msg.patch 1.9.1-1ubuntu3/debian/patches/118_quell_error_msg.patch
--- 1.9.1-1/debian/patches/118_quell_error_msg.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/118_quell_error_msg.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,33 @@
+Description: Don't print error "Unable to find a synaptics device" when used on system that doesn't have a touchpad anyway.
+Bug-Ubuntu: https://launchpad.net/bugs/716712
+
+Index: xserver-xorg-input-synaptics-1.9.1/tools/synclient.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/tools/synclient.c
++++ xserver-xorg-input-synaptics-1.9.1/tools/synclient.c
+@@ -298,9 +298,7 @@ dp_get_device(Display * dpy)
+  unwind:
+     XFree(properties);
+     XFreeDeviceList(info);
+-    if (!dev)
+-        fprintf(stderr, "Unable to find a synaptics device.\n");
+-    else if (error && dev) {
++    if (error && dev) {
+         XCloseDevice(dpy, dev);
+         dev = NULL;
+     }
+Index: xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/tools/syndaemon.c
++++ xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c
+@@ -553,9 +553,7 @@ dp_get_device(Display * dpy)
+  unwind:
+     XFree(properties);
+     XFreeDeviceList(info);
+-    if (!dev)
+-        fprintf(stderr, "Unable to find a synaptics device.\n");
+-    else if (error && dev) {
++    if (error && dev) {
+         XCloseDevice(dpy, dev);
+         dev = NULL;
+     }
diff -pruN 1.9.1-1/debian/patches/124_syndaemon_events.patch 1.9.1-1ubuntu3/debian/patches/124_syndaemon_events.patch
--- 1.9.1-1/debian/patches/124_syndaemon_events.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/124_syndaemon_events.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,43 @@
+From b68ca129acfe56714a79230aa07c2b4b0249fb42 Mon Sep 17 00:00:00 2001
+From: Chase Douglas <chase.douglas@canonical.com>
+Date: Fri, 15 Apr 2011 15:23:17 -0400
+Subject: [PATCH xf86-input-synaptics] Drain XRecord connection of any events after handling replies
+
+If the X server sends an event to the XRecord connection the event
+will never be handled. This will cause the event queue to fill up in
+Xlib and lead to syndaemon running away at 100% cpu usage.
+
+This change drains any events from the connection. It's not a fix for
+the underlying bug in the server or Xlib, but it does paper over the
+issue for now.
+
+https://bugs.launchpad.net/bugs/754470
+http://bugs.freedesktop.org/show_bug.cgi?id=31921
+
+Signed-off-by: Chase Douglas <chase.douglas@canonical.com>
+---
+I'm offering this up as a work around for whatever real bug exists. It
+probably should not be committed upstream though.
+
+ tools/syndaemon.c |    8 ++++++++
+ 1 files changed, 8 insertions(+), 0 deletions(-)
+
+Index: xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.9.1.orig/tools/syndaemon.c
++++ xserver-xorg-input-synaptics-1.9.1/tools/syndaemon.c
+@@ -466,6 +466,14 @@ record_main_loop(Display * display, doub
+                         event.type);
+             }
+ 
++            /* If there are any events left over, they are in error. Drain them
++             * from the connection queue so we don't get stuck. */
++            while (XEventsQueued(dpy_data, QueuedAlready) > 0) {
++                XEvent event;
++                XNextEvent(dpy_data, &event);
++                fprintf(stderr, "bad event received, major opcode %d\n", event.type);
++            }
++
+             if (!ignore_modifier_keys && cbres.key_event) {
+                 disable_event = 1;
+             }
diff -pruN 1.9.1-1/debian/patches/128_disable_three_click_action.patch 1.9.1-1ubuntu3/debian/patches/128_disable_three_click_action.patch
--- 1.9.1-1/debian/patches/128_disable_three_click_action.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/128_disable_three_click_action.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,16 @@
+Description: Disable three touch click in favor of uTouch gestures.
+Bug-Ubuntu: https://launchpad.net/bugs/961725
+
+Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+@@ -631,7 +631,7 @@ set_default_parameters(InputInfoPtr pInf
+        otherwise clickFinger is always button 1. */
+     clickFinger1 = 1;
+     clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+-    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
++    clickFinger3 = 0; /* Disabled by default so three-touch gestures work */
+ 
+     /* Enable vert edge scroll */
+     vertEdgeScroll = TRUE;
diff -pruN 1.9.1-1/debian/patches/129_disable_three_touch_tap.patch 1.9.1-1ubuntu3/debian/patches/129_disable_three_touch_tap.patch
--- 1.9.1-1/debian/patches/129_disable_three_touch_tap.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/129_disable_three_touch_tap.patch	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,16 @@
+Description: Disable three touch tap in favor of uTouch gestures.
+Bug-Ubuntu: https://launchpad.net/bugs/961725
+
+Index: xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+===================================================================
+--- xserver-xorg-input-synaptics-1.8.2.orig/src/synaptics.c
++++ xserver-xorg-input-synaptics-1.8.2/src/synaptics.c
+@@ -625,7 +625,7 @@ set_default_parameters(InputInfoPtr pInf
+     /* Enable tap */
+     tapButton1 = 1;
+     tapButton2 = 3;
+-    tapButton3 = 2;
++    tapButton3 = 0; /* Disabled by default so three-touch gestures work */
+ 
+     /* Enable multifinger-click if only have one physical button,
+        otherwise clickFinger is always button 1. */
diff -pruN 1.9.1-1/debian/patches/series 1.9.1-1ubuntu3/debian/patches/series
--- 1.9.1-1/debian/patches/series	2020-04-09 16:11:15.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/patches/series	2020-04-09 16:11:16.000000000 +0000
@@ -1,2 +1,11 @@
 02-do-not-use-synaptics-for-keyboards.patch
 03-bug-return-val.patch
+101_resolution_detect_option.patch
+103_enable_cornertapping.patch
+104_always_enable_tapping.patch
+106_always_enable_vert_edge_scroll.patch
+115_evdev_only.patch
+118_quell_error_msg.patch
+124_syndaemon_events.patch
+128_disable_three_click_action.patch
+129_disable_three_touch_tap.patch
diff -pruN 1.9.1-1/debian/xserver-xorg-input-synaptics.install 1.9.1-1ubuntu3/debian/xserver-xorg-input-synaptics.install
--- 1.9.1-1/debian/xserver-xorg-input-synaptics.install	2020-04-09 16:11:15.000000000 +0000
+++ 1.9.1-1ubuntu3/debian/xserver-xorg-input-synaptics.install	2020-04-09 16:11:16.000000000 +0000
@@ -2,3 +2,5 @@ usr/lib/xorg/modules/input/*.so
 usr/bin/*
 usr/share/man
 usr/share/X11
+debian/local/51-synaptics-quirks.conf	usr/share/X11/xorg.conf.d
+debian/local/66-xorg-synaptics-quirks.rules	lib/udev/rules.d
diff -pruN 1.9.1-1/include/synaptics-properties.h 1.9.1-1ubuntu3/include/synaptics-properties.h
--- 1.9.1-1/include/synaptics-properties.h	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/include/synaptics-properties.h	2020-04-09 16:11:16.000000000 +0000
@@ -134,6 +134,9 @@
 #define SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR "Synaptics Pressure Motion Factor"
 
 /* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_RESOLUTION_DETECT "Synaptics Resolution Detect"
+
+/* 8 bit (BOOL) */
 #define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device"
 
 /* 8 bit (BOOL), 1 value, tap-and-drag */
diff -pruN 1.9.1-1/man/synaptics.man 1.9.1-1ubuntu3/man/synaptics.man
--- 1.9.1-1/man/synaptics.man	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/man/synaptics.man	2020-04-09 16:11:16.000000000 +0000
@@ -427,6 +427,18 @@ The gesture is enabled by default and ca
 TapAndDragGesture option to false. Property: "Synaptics Gestures"
 .
 .TP
+.BI "Option \*ResolutionDetect\*q \*q" boolean \*q
+Allow or prevent the synaptics driver from reporting the size of the
+touchpad to the X server.  The X server normally uses this information
+to scale movements so that touchpad movement corresponds visually to
+mouse cursor movements on the screen.  However, in some rare cases where
+the touchpad height/width ratio is significantly different from the
+laptop, it can cause the mouse cursor to skip pixels in the X or Y axis.
+This option allows disabling this scaling behavior, which can provide
+smoother mouse movement in such cases.
+Property: "Synaptics Resolution Detect"
+.
+.TP
 .BI "Option \*qVertResolution\*q \*q" integer \*q
 Resolution of X coordinates in units/millimeter. The value is used
 together with HorizResolution to compensate unequal vertical and
@@ -880,6 +892,10 @@ FLOAT, 2 values, speed, friction.
 FLOAT, 2 values, min, max.
 
 .TP 7
+.BI "Synaptics Resolution Detect"
+8 bit (BOOL).
+
+.TP 7
 .BI "Synaptics Grab Event Device"
 8 bit (BOOL).
 
diff -pruN 1.9.1-1/.pc/02-do-not-use-synaptics-for-keyboards.patch/conf/11-x11-synaptics.fdi 1.9.1-1ubuntu3/.pc/02-do-not-use-synaptics-for-keyboards.patch/conf/11-x11-synaptics.fdi
--- 1.9.1-1/.pc/02-do-not-use-synaptics-for-keyboards.patch/conf/11-x11-synaptics.fdi	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/02-do-not-use-synaptics-for-keyboards.patch/conf/11-x11-synaptics.fdi	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- Example fdi file that assigns the synaptics driver to any touchpad in
+     the system.
+
+     DO NOT EDIT THIS FILE, your distribution will likely overwrite
+     it when updating. Copy (and rename) this file into
+     /etc/hal/fdi/policy first before adding options.
+ -->
+<deviceinfo version="0.2">
+  <device>
+    <match key="info.capabilities" contains="input.touchpad">
+        <merge key="input.x11_driver" type="string">synaptics</merge>
+        <!-- Arbitrary options can be passed to the driver using
+             the input.x11_options property since xorg-server-1.5. -->
+        <!-- EXAMPLES:
+	Maximum movement of the finger for detecting a tap
+	<merge key="input.x11_options.MaxTapMove" type="string">2000</merge>
+
+	Enable vertical scrolling when dragging along the right edge
+	<merge key="input.x11_options.VertEdgeScroll" type="string">true</merge>
+
+	Enable vertical scrolling when dragging with two fingers anywhere on the touchpad
+	<merge key="input.x11_options.VertTwoFingerScroll" type="string">true</merge>
+
+	Enable horizontal scrolling when dragging with two fingers anywhere on the touchpad
+	<merge key="input.x11_options.HorizTwoFingerScroll" type="string">true</merge>
+
+	If on, circular scrolling is used
+	<merge key="input.x11_options.CircularScrolling" type="string">true</merge>
+
+	For other possible options, check CONFIGURATION DETAILS in synaptics man page
+        -->
+        <merge key="input.x11_options.SoftButtonAreas" type="string">50% 0 82% 0 0 0 0 0</merge>
+        <merge key="input.x11_options.SecondarySoftButtonAreas" type="string">58% 0 0 8% 42% 58% 0 8%</merge>
+        <match key="info.product" contains="Apple|bcm5974">
+            <merge key="input.x11_options.SoftButtonAreas" type="string">0 0 0 0 0 0 0 0</merge>
+        </match>
+    </match>
+  </device>
+</deviceinfo>
diff -pruN 1.9.1-1/.pc/03-bug-return-val.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/03-bug-return-val.patch/src/synaptics.c
--- 1.9.1-1/.pc/03-bug-return-val.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/03-bug-return-val.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3246 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap if we don't have a phys left button */
+    tapButton1 = priv->has_left ? 0 : 1;
+    tapButton2 = priv->has_left ? 0 : 3;
+    tapButton3 = priv->has_left ? 0 : 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+
+    /* Enable vert edge scroll if we can't detect doubletap */
+    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 0);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 0);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/101_resolution_detect_option.patch/include/synaptics-properties.h 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/include/synaptics-properties.h
--- 1.9.1-1/.pc/101_resolution_detect_option.patch/include/synaptics-properties.h	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/include/synaptics-properties.h	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,161 @@
+/*
+ * Copyright © 2008 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: Peter Hutterer
+ */
+
+#ifndef _SYNAPTICS_PROPERTIES_H_
+#define _SYNAPTICS_PROPERTIES_H_
+
+/**
+ * Properties exported by the synaptics driver. These properties are
+ * recognized by the driver and will change its behavior when modified.
+ * For a description of what each property does, see synaptics.h.
+ */
+
+/* 32 bit, 4 values, left, right, top, bottom */
+#define SYNAPTICS_PROP_EDGES "Synaptics Edges"
+
+/* 32 bit, 3 values, low, high, <deprecated> */
+#define SYNAPTICS_PROP_FINGER "Synaptics Finger"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_TAP_TIME "Synaptics Tap Time"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_TAP_MOVE "Synaptics Tap Move"
+
+/* 32 bit, 3 values, single touch timeout, max tapping time for double
+ * taps, duration of a single click  */
+#define SYNAPTICS_PROP_TAP_DURATIONS "Synaptics Tap Durations"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_CLICKPAD "Synaptics ClickPad"
+
+/* 8 bit (BOOL), <deprecated> */
+#define SYNAPTICS_PROP_TAP_FAST "Synaptics Tap FastTap"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_MIDDLE_TIMEOUT "Synaptics Middle Button Timeout"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_TWOFINGER_PRESSURE "Synaptics Two-Finger Pressure"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_TWOFINGER_WIDTH "Synaptics Two-Finger Width"
+
+/* 32 bit, 2 values, vert, horiz */
+#define SYNAPTICS_PROP_SCROLL_DISTANCE "Synaptics Scrolling Distance"
+
+/* 8 bit (BOOL), 3 values, vertical, horizontal, corner */
+#define SYNAPTICS_PROP_SCROLL_EDGE "Synaptics Edge Scrolling"
+
+/* 8 bit (BOOL), 2 values, vertical, horizontal */
+#define SYNAPTICS_PROP_SCROLL_TWOFINGER "Synaptics Two-Finger Scrolling"
+
+/* FLOAT, 4 values, min, max, accel, <deprecated> */
+#define SYNAPTICS_PROP_SPEED "Synaptics Move Speed"
+
+/* 8 bit (BOOL), 2 values, updown, leftright */
+#define SYNAPTICS_PROP_BUTTONSCROLLING "Synaptics Button Scrolling"
+
+/* 8 bit (BOOL), 2 values, updown, leftright */
+#define SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT "Synaptics Button Scrolling Repeat"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_BUTTONSCROLLING_TIME "Synaptics Button Scrolling Time"
+
+/* 8 bit, valid values (0, 1, 2) */
+#define SYNAPTICS_PROP_OFF "Synaptics Off"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_GUESTMOUSE "Synaptics Guestmouse Off"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_LOCKED_DRAGS "Synaptics Locked Drags"
+
+/* 32 bit */
+#define SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT "Synaptics Locked Drags Timeout"
+
+/* 8 bit, up to MAX_TAP values (see synaptics.h), 0 disables an
+ * element. order: RT, RB, LT, LB, F1, F2, F3 */
+#define SYNAPTICS_PROP_TAP_ACTION "Synaptics Tap Action"
+
+/* 8 bit, up to MAX_CLICK values (see synaptics.h), 0 disables an
+ * element. order: Finger 1, 2, 3 */
+#define SYNAPTICS_PROP_CLICK_ACTION "Synaptics Click Action"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_CIRCULAR_SCROLLING "Synaptics Circular Scrolling"
+
+/* FLOAT */
+#define SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST "Synaptics Circular Scrolling Distance"
+
+/* 8 bit, valid values 0..8 (inclusive)
+ * order: any edge, top, top + right, right, right + bottom, bottom, bottom +
+ * left, left, left  + top */
+#define SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER "Synaptics Circular Scrolling Trigger"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_CIRCULAR_PAD "Synaptics Circular Pad"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_PALM_DETECT "Synaptics Palm Detection"
+
+/* 32 bit, 2 values, width, z */
+#define SYNAPTICS_PROP_PALM_DIMENSIONS "Synaptics Palm Dimensions"
+
+/* FLOAT, 2 values, speed, friction */
+#define SYNAPTICS_PROP_COASTING_SPEED "Synaptics Coasting Speed"
+
+/* CARD32, 2 values, min, max */
+#define SYNAPTICS_PROP_PRESSURE_MOTION "Synaptics Pressure Motion"
+
+/* FLOAT, 2 values, min, max */
+#define SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR "Synaptics Pressure Motion Factor"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device"
+
+/* 8 bit (BOOL), 1 value, tap-and-drag */
+#define SYNAPTICS_PROP_GESTURES "Synaptics Gestures"
+
+/* 8 bit (BOOL), 7 values (read-only), has_left, has_middle, has_right,
+ * has_double, has_triple, has_pressure, has_width */
+#define SYNAPTICS_PROP_CAPABILITIES "Synaptics Capabilities"
+
+/* 32 bit unsigned, 2 values, vertical, horizontal in units/millimeter */
+#define SYNAPTICS_PROP_RESOLUTION "Synaptics Pad Resolution"
+
+/* 32 bit, 4 values, left, right, top, bottom */
+#define SYNAPTICS_PROP_AREA "Synaptics Area"
+
+/* 32 bit, 4 values, left, right, top, buttom */
+#define SYNAPTICS_PROP_SOFTBUTTON_AREAS "Synaptics Soft Button Areas"
+
+/* 32 bit, 4 values, left, right, top, buttom */
+#define SYNAPTICS_PROP_SECONDARY_SOFTBUTTON_AREAS "Synaptics Secondary Soft Button Areas"
+
+/* 32 Bit Integer, 2 values, horizontal hysteresis, vertical hysteresis */
+#define SYNAPTICS_PROP_NOISE_CANCELLATION "Synaptics Noise Cancellation"
+
+#endif                          /* _SYNAPTICS_PROPERTIES_H_ */
diff -pruN 1.9.1-1/.pc/101_resolution_detect_option.patch/man/synaptics.man 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/man/synaptics.man
--- 1.9.1-1/.pc/101_resolution_detect_option.patch/man/synaptics.man	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/man/synaptics.man	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,985 @@
+.\" shorthand for double quote that works everywhere.
+.ds q \N'34'
+.TH SYNAPTICS __drivermansuffix__ __vendorversion__
+.SH NAME
+synaptics \- touchpad input driver
+.SH SYNOPSIS
+.nf
+.B "Section \*qInputDevice\*q"
+.BI "  Identifier \*q" devname \*q
+.B  "  Driver \*qsynaptics\*q"
+.BI "  Option \*qDevice\*q   \*q" devpath \*q
+.BI "  Option \*qPath\*q     \*q" path \*q
+\ \ ...
+.B EndSection
+.fi
+.SH DESCRIPTION
+.B synaptics
+is an __xservername__ input driver for touchpads.
+Even though touchpads can be handled by the normal evdev or mouse drivers,
+this driver allows more advanced features of the
+touchpad to become available. Some benefits would be:
+.IP \(bu 4
+Movement with adjustable, non-linear acceleration and speed.
+.IP \(bu 4
+Button events through short touching of the touchpad.
+.IP \(bu 4
+Double-Button events through double short touching of the touchpad.
+.IP \(bu 4
+Dragging through short touching and holding down the finger on the
+touchpad (tap-and-drag gesture).
+.IP \(bu 4
+Middle and right button events on the upper and lower corner of the
+touchpad.
+.IP \(bu 4
+Vertical scrolling (button four and five events) through moving the
+finger on the right side of the touchpad.
+.IP \(bu 4
+The up/down button sends button four/five events.
+.IP \(bu 4
+Horizontal scrolling (button six and seven events) through moving the
+finger on the lower side of the touchpad.
+.IP \(bu 4
+The multi-buttons send button four/five events for vertical scrolling
+and button six/seven events for horizontal scrolling.
+.IP \(bu 4
+Adjustable finger detection.
+.IP \(bu 4
+Multifinger taps: two finger for right button and three finger for
+middle button events.
+.
+(Needs hardware support.
+.
+Not all models implement this feature.)
+.IP \(bu 4
+Pressure-dependent motion speed.
+.LP
+Note that depending on the touchpad firmware, some of these features
+might be available even without using the synaptics driver. Note also
+that some functions are not available on all touchpad models, because
+they need support from the touchpad hardware/firmware. (Multifinger
+taps for example.)
+.PP
+The name "synaptics" is historical and the driver still provides the
+synaptics protocol parsing code. Under Linux however, the hardware-specifics
+are handled by the kernel and this driver will work for any touchpad that
+has a working kernel driver. If your device is recognized as \*qPS/2
+Mouse\*q or similar, the kernel driver does not support your device and this
+driver will only provide limited functionality.
+
+.SH CONFIGURATION OPTIONS
+Please refer to __xconfigfile__(__filemansuffix__) for general configuration
+details and for options that can be used with all input drivers.  This
+section only covers configuration details specific to this driver.
+.PP
+The following driver
+.B Options
+are supported:
+.TP 7
+.BI "Option \*qDevice\*q \*q" string \*q
+This option specifies the device file in your \*q/dev\*q directory which will
+be used to access the physical device. Normally you should use something like
+\*q/dev/input/eventX\*q, where X is some integer.
+.TP 7
+.BI "Option \*qProtocol\*q \*q" string \*q
+Specifies which kernel driver will be used by this driver. This is the list of
+supported drivers and their default use scenarios.
+.TS
+l l.
+auto-dev	automatic, default (recommend)
+event	Linux 2.6 kernel events
+psaux	raw device access (Linux 2.4)
+psm	FreeBSD psm driver
+.TE
+.TP 7
+.BI "Option \*qLeftEdge\*q \*q" integer \*q
+X coordinate for left edge. Property: "Synaptics Edges"
+.TP 7
+.BI "Option \*qRightEdge\*q \*q" integer \*q
+X coordinate for right edge. Property: "Synaptics Edges"
+.TP 7
+.BI "Option \*qTopEdge\*q \*q" integer \*q
+Y coordinate for top edge. Property: "Synaptics Edges"
+.TP 7
+.BI "Option \*qBottomEdge\*q \*q" integer \*q
+Y coordinate for bottom edge. Property: "Synaptics Edges"
+.TP 7
+.BI "Option \*qFingerLow\*q \*q" integer \*q
+When finger pressure drops below this value, the driver counts it as a
+release. Property: "Synaptics Finger"
+.TP 7
+.BI "Option \*qFingerHigh\*q \*q" integer \*q
+When finger pressure goes above this value, the driver counts it as a
+touch. Property: "Synaptics Finger"
+.TP 7
+.BI "Option \*qMaxTapTime\*q \*q" integer \*q
+Maximum time (in milliseconds) for detecting a tap. Property: "Synaptics Tap
+Durations"
+.TP 7
+.BI "Option \*qMaxTapMove\*q \*q" integer \*q
+Maximum movement of the finger for detecting a tap. Property: "Synaptics Tap
+Move"
+.TP 7
+.BI "Option \*qMaxDoubleTapTime\*q \*q" integer \*q
+Maximum time (in milliseconds) for detecting a double tap. Property:
+"Synaptics Tap Durations"
+.TP 7
+.BI "Option \*qClickTime\*q \*q" integer \*q
+The duration of the mouse click generated by tapping. Property: "Synaptics Tap
+Durations"
+.TP 7
+.BI "Option \*qClickPad\*q \*q" boolean \*q
+Whether the device is a click pad. See
+.B ClickPad support
+for more details. Property: "Synaptics ClickPad"
+.TP 7
+.BI "Option \*qVertEdgeScroll\*q \*q" boolean \*q
+Enable vertical scrolling when dragging along the right edge. Property:
+"Synaptics Edge Scrolling"
+.TP 7
+.BI "Option \*qHorizEdgeScroll\*q \*q" boolean \*q
+Enable horizontal scrolling when dragging along the bottom edge. Property:
+"Synaptics Edge Scrolling"
+.TP 7
+.BI "Option \*qCornerCoasting\*q \*q" boolean \*q
+Enable edge scrolling to continue while the finger stays in an edge corner.
+Property: "Synaptics Edge Scrolling"
+.TP 7
+.BI "Option \*qVertTwoFingerScroll\*q \*q" boolean \*q
+Enable vertical scrolling when dragging with two fingers anywhere on
+the touchpad. Property: "Synaptics Two-Finger Scrolling"
+.TP 7
+.BI "Option \*qHorizTwoFingerScroll\*q \*q" boolean \*q
+Enable horizontal scrolling when dragging with two fingers anywhere on
+the touchpad. Property: "Synaptics Two-Finger Scrolling"
+.TP 7
+.BI "Option \*qVertScrollDelta\*q \*q" integer \*q
+Move distance of the finger for a scroll event. Property: "Synaptics Scrolling
+Distance"
+.TP 7
+.BI "Option \*qHorizScrollDelta\*q \*q" integer \*q
+Move distance of the finger for a scroll event. Property: "Synaptics Scrolling
+Distance"
+.TP
+.BI "Option \*qMinSpeed\*q \*q" float \*q
+Minimum speed factor. Property: "Synaptics Move Speed"
+.TP
+.BI "Option \*qMaxSpeed\*q \*q" float \*q
+Maximum speed factor. Property: "Synaptics Move Speed"
+.TP
+.BI "Option \*qAccelFactor\*q \*q" float \*q
+Acceleration factor for normal pointer movements. Property: "Synaptics Move
+Speed"
+.TP
+.BI "Option \*qPressureMotionMinZ\*q \*q" integer \*q
+Finger pressure at which minimum pressure motion factor is applied. Property:
+"Synaptics Pressure Motion"
+.TP
+.BI "Option \*qPressureMotionMaxZ\*q \*q" integer \*q
+Finger pressure at which maximum pressure motion factor is applied.  Property:
+"Synaptics Pressure Motion"
+.TP
+.BI "Option \*qPressureMotionMinFactor\*q \*q" integer \*q
+Lowest setting for pressure motion factor. Property: "Synaptics Pressure
+Motion Factor"
+.TP
+.BI "Option \*qPressureMotionMaxFactor\*q \*q" integer \*q
+Greatest setting for pressure motion factor. Property: "Synaptics Pressure
+Motion Factor"
+.TP
+.BI "Option \*qHorizHysteresis\*q \*q" integer \*q
+The minimum horizontal HW distance required to generate motion events. Can be
+specified as a percentage. Increase if noise motion is a problem for you. Zero
+is disabled.
+Default: 0.5 percent of the diagonal or (in case of evdev) the appropriate
+"fuzz" as advertised by the device.
+.TP
+.BI "Option \*qVertHysteresis\*q \*q" integer \*q
+The minimum vertical HW distance required to generate motion events. See
+\fBHorizHysteresis\fR.
+.TP
+.BI "Option \*qUpDownScrolling\*q \*q" boolean \*q
+If on, the up/down buttons generate button 4/5 events.
+.
+If off, the up button generates a double click and the down button
+generates a button 2 event. This option is only available for touchpads with
+physical scroll buttons.
+Property: "Synaptics Button Scrolling"
+.TP
+.BI "Option \*qLeftRightScrolling\*q \*q" boolean \*q
+If on, the left/right buttons generate button 6/7 events.
+.
+If off, the left/right buttons both generate button 2 events.
+This option is only available for touchpads with physical scroll buttons.
+Property: "Synaptics Button Scrolling"
+.TP
+.BI "Option \*qUpDownScrollRepeat\*q \*q" boolean \*q
+If on, and the up/down buttons are used for scrolling
+(\fBUpDownScrolling\fR), these buttons will send auto-repeating 4/5 events,
+with the delay between repeats determined by \fBScrollButtonRepeat\fR.
+This option is only available for touchpads with physical scroll buttons.
+Property: "Synaptics Button Scrolling Repeat"
+.TP
+.BI "Option \*qLeftRightScrollRepeat\*q \*q" boolean \*q
+If on, and the left/right buttons are used for scrolling
+(\fBLeftRightScrolling\fR), these buttons will send auto-repeating 6/7 events,
+with the delay between repeats determined by \fBScrollButtonRepeat\fR.
+This option is only available for touchpads with physical scroll buttons.
+Property: "Synaptics Button Scrolling Repeat"
+.TP
+.BI "Option \*qScrollButtonRepeat\*q \*q" integer \*q
+The number of milliseconds between repeats of button events 4-7 from the
+up/down/left/right scroll buttons.
+This option is only available for touchpads with physical scroll buttons.
+Property: "Synaptics Button Scrolling Time"
+.TP
+.BI "Option \*qEmulateMidButtonTime\*q \*q" integer \*q
+Maximum time (in milliseconds) for middle button emulation. Property:
+"Synaptics Middle Button Timeout"
+.TP
+.BI "Option \*qEmulateTwoFingerMinZ\*q \*q" integer \*q
+For touchpads not capable of detecting multiple fingers but are capable
+of detecting finger pressure and width, this sets the
+Z pressure threshold.  When both Z pressure and W width thresholds
+are crossed, a two finger press will be emulated. This defaults
+to a value that disables emulation on touchpads with real two-finger detection
+and defaults to a value that enables emulation on remaining touchpads that
+support pressure and width support.
+Property: "Synaptics Two-Finger Pressure"
+.TP
+.BI "Option \*qEmulateTwoFingerMinW\*q \*q" integer \*q
+For touchpads not capable of detecting multiple fingers but are
+capable of detecting finger width and pressure, this sets the
+W width threshold.  When both W width and Z pressure thresholds
+are crossed, a two finger press will be emulated. This feature works best
+with (\fBPalmDetect\fR) off. Property: "Synaptics Two-Finger Width"
+.TP
+.BI "Option \*qTouchpadOff\*q \*q" integer \*q
+Switch off the touchpad.
+.
+Valid values are:
+.TS
+l l.
+0	Touchpad is enabled
+1	Touchpad is switched off (physical clicks still work)
+2	Only tapping and scrolling is switched off
+.TE
+When the touchpad is switched off, button events caused by a physical
+button press are still interpreted. On a ClickPad, this includes
+software-emulated middle and right buttons as defined by
+the SoftButtonAreas setting.
+.TP
+Property: "Synaptics Off"
+.TP
+.BI "Option \*qLockedDrags\*q \*q" boolean \*q
+If off, a tap-and-drag gesture ends when you release the finger.
+.
+If on, the gesture is active until you tap a second time, or until
+LockedDragTimeout expires. Property: "Synaptics Locked Drags"
+.TP
+.BI "Option \*qLockedDragTimeout\*q \*q" integer \*q
+This parameter specifies how long it takes (in milliseconds) for the
+LockedDrags mode to be automatically turned off after the finger is
+released from the touchpad. Property: "Synaptics Locked Drags Timeout"
+.TP
+.BI "Option \*qRTCornerButton\*q \*q" integer \*q
+.
+Which mouse button is reported on a right top corner tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qRBCornerButton\*q \*q" integer \*q
+Which mouse button is reported on a right bottom corner tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qLTCornerButton\*q \*q" integer \*q
+Which mouse button is reported on a left top corner tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qLBCornerButton\*q \*q" integer \*q
+Which mouse button is reported on a left bottom corner tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qTapButton1\*q \*q" integer \*q
+Which mouse button is reported on a non-corner one-finger tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qTapButton2\*q \*q" integer \*q
+Which mouse button is reported on a non-corner two-finger tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qTapButton3\*q \*q" integer \*q
+Which mouse button is reported on a non-corner three-finger tap.
+.
+Set to 0 to disable. Property: "Synaptics Tap Action"
+.TP
+.BI "Option \*qClickFinger1\*q \*q" integer \*q
+Which mouse button is reported when left-clicking with one finger.
+.
+Set to 0 to disable. Property: "Synaptics Click Action"
+.TP
+.BI "Option \*qClickFinger2\*q \*q" integer \*q
+Which mouse button is reported when left-clicking with two fingers.
+.
+Set to 0 to disable. Property: "Synaptics Click Action"
+.TP
+.BI "Option \*qClickFinger3\*q \*q" integer \*q
+Which mouse button is reported when left-clicking with three fingers.
+.
+Set to 0 to disable. Property: "Synaptics Click Action"
+.TP
+.BI "Option \*qCircularScrolling\*q \*q" boolean \*q
+If on, circular scrolling is used. Property: "Synaptics Circular Scrolling"
+.TP
+.BI "Option \*qCircScrollDelta\*q \*q" float \*q
+Move angle (radians) of finger to generate a scroll event. Property: "Synaptics
+Circular Scrolling Distance"
+.TP
+.BI "Option \*qCircScrollTrigger\*q \*q" integer \*q
+Trigger region on the touchpad to start circular scrolling
+.TS
+l l.
+0	All Edges
+1	Top Edge
+2	Top Right Corner
+3	Right Edge
+4	Bottom Right Corner
+5	Bottom Edge
+6	Bottom Left Corner
+7	Left Edge
+8	Top Left Corner
+.TE
+Property: "Synaptics Circular Scrolling Trigger"
+.TP
+.BI "Option \*qCircularPad\*q \*q" boolean \*q
+.
+Instead of being a rectangle, the edge is the ellipse enclosed by the
+Left/Right/Top/BottomEdge parameters.
+.
+For circular touchpads. Property: "Synaptics Circular Pad"
+.TP
+.BI "Option \*qPalmDetect\*q \*q" boolean \*q
+If palm detection should be enabled.
+.
+Note that this also requires hardware/firmware support from the
+touchpad. Property: "Synaptics Palm Detection"
+.TP
+.BI "Option \*qPalmMinWidth\*q \*q" integer \*q
+Minimum finger width at which touch is considered a palm. Property: "Synaptics
+Palm Dimensions"
+.TP
+.BI "Option \*qPalmMinZ\*q \*q" integer \*q
+Minimum finger pressure at which touch is considered a palm. Property:
+"Synaptics Palm Dimensions"
+.TP
+.BI "Option \*qCoastingSpeed\*q \*q" float \*q
+Your finger needs to produce this many scrolls per second in order to start
+coasting.  The default is 20 which should prevent you from starting coasting
+unintentionally.
+.
+0 disables coasting. Property: "Synaptics Coasting Speed"
+.TP
+.BI "Option \*qCoastingFriction\*q \*q" float \*q
+Number of scrolls/second² to decrease the coasting speed.  Default
+is 50.
+Property: "Synaptics Coasting Speed"
+.TP
+.BI "Option \*qSingleTapTimeout\*q \*q" integer \*q
+Timeout after a tap to recognize it as a single tap. Property: "Synaptics Tap
+Durations"
+.TP
+.BI "Option \*qGrabEventDevice\*q \*q" boolean \*q
+If GrabEventDevice is true, the driver will grab the event device for
+exclusive use when using the linux 2.6 event protocol.
+.
+When using other protocols, this option has no effect.
+.
+Grabbing the event device means that no other user space or kernel
+space program sees the touchpad events. 
+.
+This is desirable if the X config file includes /dev/input/mice as an
+input device, but is undesirable if you want to monitor the device
+from user space.
+.
+When changing this parameter with the synclient program, the change
+will not take effect until the synaptics driver is disabled and
+reenabled. 
+.
+This can be achieved by switching to a text console and then switching
+back to X.
+.
+.
+.TP
+.BI "Option \*qTapAndDragGesture\*q \*q" boolean \*q
+Switch on/off the tap-and-drag gesture.
+.
+This gesture is an alternative way of dragging.
+.
+It is performed by tapping (touching and releasing the finger), then
+touching again and moving the finger on the touchpad.
+.
+The gesture is enabled by default and can be disabled by setting the
+TapAndDragGesture option to false. Property: "Synaptics Gestures"
+.
+.TP
+.BI "Option \*qVertResolution\*q \*q" integer \*q
+Resolution of X coordinates in units/millimeter. The value is used
+together with HorizResolution to compensate unequal vertical and
+horizontal sensitivity. Setting VertResolution and HorizResolution
+equal values means no compensation. Default value is read from
+the touchpad or set to 1 if value could not be read.
+Property: "Synaptics Pad Resolution"
+.
+.TP
+.BI "Option \*qHorizResolution\*q \*q" integer \*q
+Resolution of Y coordinates in units/millimeter. The value is used
+together with VertResolution to compensate unequal vertical and
+horizontal sensitivity. Setting VertResolution and HorizResolution
+equal values means no compensation. Default value is read from
+the touchpad or set to 1 if value could not be read.
+Property: "Synaptics Pad Resolution"
+.
+.TP
+.BI "Option \*qAreaLeftEdge\*q \*q" integer \*q
+Ignore movements, scrolling and tapping which start left of this edge.
+.
+The option is disabled by default and can be enabled by setting the
+AreaLeftEdge option to any integer value other than zero. If supported by the
+server (version 1.9 and later), the edge may be specified in percent of
+the total width of the touchpad. Property: "Synaptics Area"
+.
+.TP
+.BI "Option \*qAreaRightEdge\*q \*q" integer \*q
+Ignore movements, scrolling and tapping which start right of this edge.
+.
+The option is disabled by default and can be enabled by setting the
+AreaRightEdge option to any integer value other than zero. If supported by the
+server (version 1.9 and later), the edge may be specified in percent of
+the total width of the touchpad. Property: "Synaptics Area"
+.
+.TP
+.BI "Option \*qAreaTopEdge\*q \*q" integer \*q
+Ignore movements, scrolling and tapping which start above this edge.
+.
+The option is disabled by default and can be enabled by setting the
+AreaTopEdge option to any integer value other than zero. If supported by the
+server (version 1.9 and later), the edge may be specified in percent of
+the total height of the touchpad. Property: "Synaptics Area"
+.
+.TP
+.BI "Option \*qAreaBottomEdge\*q \*q" integer \*q
+Ignore movements, scrolling and tapping which start below this edge.
+.
+The option is disabled by default and can be enabled by setting the
+AreaBottomEdge option to any integer value other than zero. If supported by the
+server (version 1.9 and later), the edge may be specified in percent of
+the total height of the touchpad. Property: "Synaptics Area"
+.
+.TP
+.BI "Option \*qSoftButtonAreas\*q \*q" "RBL RBR RBT RBB MBL MBR MBT MBB" \*q
+This option is only available on ClickPad devices. 
+Enable soft button click area support on ClickPad devices. 
+The first four parameters are the left, right, top, bottom edge of the right
+button, respectively, the second four parameters are the left, right, top,
+bottom edge of the middle button, respectively. Any of the values may be
+given as percentage of the touchpad width or height, whichever applies.
+If any edge is set to 0 (not 0%), the button is assumed to extend to
+infinity in the given direction. Setting all values to 0 (not 0%) disables
+soft button areas. Button areas may not overlap, however it is permitted for two
+buttons to share an edge value.
+Property: "Synaptics Soft Button Areas"
+.
+.TP
+.BI "Option \*qHasSecondarySoftButtons\*q \*q" boolean \*q
+This option is only available on ClickPad devices.
+Enable the secondary software button area support. The exact area must be
+set in option \*qSecondarySoftButtonAreas\*q.  See
+.B ClickPad support
+for more details.
+.
+.TP
+.BI "Option \*qSecondarySoftButtonAreas\*q \*q" "RBL RBR RBT RBB MBL MBR MBT MBB" \*q
+This option is only available on ClickPad devices and only if
+.B Option \*qHasSecondarySoftButtons\*q
+is enabled.
+Define the secondary soft button click areas on ClickPad devices (usually on
+top of the device).
+For the allowed values for this option, see
+.B Option \*qSoftButtonAreas\*q.
+Primary and secondary soft button areas must not overlap each other. If they do,
+the behavior of the driver is undefined.
+Property: "Synaptics Secondary Soft Button Areas". This property is only
+initialized if 
+.B Option \*qHasSecondarySoftButtons\*q 
+is enabled and this option is set in the __xconfigfile__(__filemansuffix__).
+.
+
+.SH CONFIGURATION DETAILS
+.SS Area handling
+The LeftEdge, RightEdge, TopEdge and BottomEdge parameters are used to
+define the edge and corner areas of the touchpad.
+.
+The parameters split the touchpad area in 9 pieces, like this:
+.LP
+.TS
+l|l|lsls
+---
+|c|cw(5P)|c|l
+---
+|c|c|c|l
+|c|c|c|l
+|c|c|c|l
+---
+|c|c|c|l
+---
+|lsl|ll.
+	LeftEdge	RightEdge
+			Physical top edge
+1	2	3
+			TopEdge
+
+4	5	6
+
+			BottomEdge
+7	8	9
+			Physical bottom edge
+Physical left edge		Physical right edge
+.TE
+.LP
+Coordinates to the left of LeftEdge are part of the left edge (areas
+1, 4 and 7), coordinates to the left of LeftEdge and above TopEdge
+(area 1) are part of the upper left corner, etc.
+.PP
+A good way to find appropriate edge parameters is to use evtest(1) on the
+device to see the x/y coordinates corresponding to different positions on
+the touchpad.
+.PP
+The perceived physical edges may be adjusted with the AreaLeftEdge,
+AreaRightEdge, AreaTopEdge, and AreaBottomEdge options. If these values are
+set to something other than the physical edges, input that starts in the
+space between the area edge and the respective physical edge is ignored.
+Note that this reduces the available space on the touchpad to start motions
+in.
+.SS Tapping
+A tap event happens when the finger is touched and released in a time
+interval shorter than MaxTapTime, and the touch and release
+coordinates are less than MaxTapMove units apart.
+.
+A "touch" event happens when the Z value goes above FingerHigh, and an
+"untouch" event happens when the Z value goes below FingerLow.
+.
+.LP
+The MaxDoubleTapTime parameter has the same function as the MaxTapTime
+parameter, but for the second, third, etc tap in a tap sequence.
+.
+If you can't perform double clicks fast enough (for example, xmms
+depends on fast double clicks), try reducing this parameter.
+.
+If you can't get word selection to work in xterm (ie button down,
+button up, button down, move mouse), try increasing this parameter.
+.
+.LP
+The ClickTime parameter controls the delay between the button down and
+button up X events generated in response to a tap event.
+.
+A too long value can cause undesirable autorepeat in scroll bars and a
+too small value means that visual feedback from the gui application
+you are interacting with is harder to see.
+.
+.SS Acceleration
+The MinSpeed, MaxSpeed and AccelFactor parameters control the pointer
+motion speed.
+.
+The speed value defines the scaling between touchpad coordinates and
+screen coordinates.
+.
+When moving the finger very slowly, the MinSpeed value is used, when
+moving very fast the MaxSpeed value is used.
+.
+When moving the finger at moderate speed, you get a pointer motion
+speed somewhere between MinSpeed and MaxSpeed.
+.
+If you don't want any acceleration, set MinSpeed and MaxSpeed to the
+same value.
+.
+.LP
+The MinSpeed, MaxSpeed and AccelFactor parameters don't have any
+effect on scrolling speed.
+.
+Scrolling speed is determined solely from the VertScrollDelta and
+HorizScrollDelta parameters.
+.
+To invert the direction of vertical or horizontal scrolling, set
+VertScrollDelta or HorizScrollDelta to a negative value.
+.
+.LP
+Acceleration is mostly handled outside the driver, thus the driver will
+translate MinSpeed into constant deceleration and adapt MaxSpeed at
+startup time. This ensures you can user the other acceleration profiles, albeit
+without pressure motion. However the numbers at runtime will likely be different
+from any options you may have set.
+
+.SS Pressure motion
+When pressure motion is activated, the cursor motion speed depends
+on the pressure exerted on the touchpad (the more pressure exerted on
+the touchpad, the faster the pointer).
+.
+More precisely the speed is first calculated according to MinSpeed,
+MaxSpeed and AccelFactor, and then is multiplied by a sensitivity
+factor.
+.
+.LP
+The sensitivity factor can be adjusted using the PressureMotion
+parameters.
+.
+If the pressure is below PressureMotionMinZ, PressureMotionMinFactor
+is used, and if the pressure is greater than PressureMotionMaxZ,
+PressureMotionMaxFactor is used.
+.
+For a pressure value between PressureMotionMinZ and
+PressureMotionMaxZ, the factor is increased linearly.
+.
+.SS Middle button emulation
+Since most synaptics touchpad models don't have a button that
+corresponds to the middle button on a mouse, the driver can emulate
+middle mouse button events.
+.
+If you press both the left and right mouse buttons at almost the same
+time (no more than EmulateMidButtonTime milliseconds apart) the driver
+generates a middle mouse button event.
+.
+.SS Circular scrolling
+Circular scrolling acts like a scrolling wheel on the touchpad.
+.
+Scrolling is engaged when a drag starts in the given CircScrollTrigger
+region, which can be all edges, a particular side, or a particular
+corner.
+.
+Once scrolling is engaged, moving your finger in clockwise circles
+around the center of the touchpad will generate scroll down events and
+counter clockwise motion will generate scroll up events.
+.
+Lifting your finger will disengage circular scrolling.
+.
+Use tight circles near the center of the pad for fast scrolling and
+large circles for better control.
+.
+When used together with vertical scrolling, hitting the upper or lower
+right corner will seamlessly switch over from vertical to circular
+scrolling.
+
+.SS Coasting
+Coasting is enabled by setting the CoastingSpeed parameter to a
+non-zero value.
+.
+Coasting comes in two flavors: conventional (finger off) coasting, and
+corner (finger on) coasting.
+.LP
+Conventional coasting is enabled when coasting is enabled,
+and CornerCoasting is set to false.
+.
+When conventional coasting is enabled, horizontal/vertical scrolling
+can continue after the finger is released from the lower/right edge of
+the touchpad.
+.
+The driver computes the scrolling speed corresponding to the finger
+speed immediately before the finger leaves the touchpad.
+.
+If this scrolling speed is larger than the CoastingSpeed parameter
+(measured in scroll events per second), the scrolling will continue
+with the same speed in the same direction until the finger touches the
+touchpad again.
+.
+.LP
+Corner coasting is enabled when coasting is enabled, and
+CornerCoasting is set to true.
+.
+When corner coasting is enabled, edge scrolling can continue as long
+as the finger stays in a corner.
+.
+Coasting begins when the finger enters the corner, and continues until
+the finger leaves the corner.
+.
+CornerCoasting takes precedence over the seamless switch from edge
+scrolling to circular scrolling.  That is, if CornerCoasting is
+active, scrolling will stop, and circular scrolling will not start,
+when the finger leaves the corner.
+
+.SS Noise cancellation
+The synaptics has a built-in noise cancellation based on hysteresis. This means
+that incoming coordinates actually shift a box of predefined dimensions such
+that it covers the incoming coordinate, and only the boxes own center is used
+as input. Obviously, the smaller the box the better, but the likelyhood of
+noise motion coming through also increases.
+
+.SS ClickPad support
+A click pad device has button(s) integrated into the touchpad surface. The
+user must press downward on the touchpad in order to generated a button
+press. ClickPad support is enabled if the option
+.B ClickPad
+is set or the property is set at runtime. On some platforms, this option
+will be set automatically if the kernel detects a matching device. On Linux,
+the device must have the INPUT_PROP_BUTTONPAD property set.
+.LP
+ClickPads do not support middle mouse button emulation. If enabling ClickPad
+support at runime, the user must also set the middle mouse button timeout to
+0. If auto-detected, middle mouse button emulation is disabled by the
+driver.
+.LP
+ClickPads provide software emulated buttons through 
+.B Option \*qSoftButtonAreas\*q.
+These buttons enable areas on the touchpad to perform as right or middle
+mouse button. When the user performs a click within a defined soft button
+area, a right or middle click is performed.
+.LP
+Some laptops, most notably the Lenovo T440, T540 and x240 series, provide a
+pointing stick without physical buttons. On those laptops, the top of the
+touchpad acts as software-emulated button area. This area can be enabled
+with
+.B Option \*qHasSecondarySoftButtons\*q
+and configured
+with
+.B Option \*qSecondarySoftButtonAreas\*q.
+On some platforms, this option
+will be set automatically if the kernel detects a matching device. On Linux,
+the device must have the INPUT_PROP_TOPBUTTONPAD property set.
+
+.SH "DEVICE PROPERTIES"
+Synaptics 1.0 and higher support input device properties if the driver is
+running on X server 1.6 or higher. The synclient tool
+shipped with synaptics version 1.1 uses input device properties by default.
+.
+Properties supported:
+.TP 7
+.BI "Synaptics Edges"
+32 bit, 4 values, left, right, top, bottom.
+
+.TP 7
+.BI "Synaptics Finger"
+32 bit, 3 values, low, high, press.
+
+.TP 7
+.BI "Synaptics Tap Time"
+32 bit.
+
+.TP 7
+.BI "Synaptics Tap Move"
+32 bit.
+
+.TP 7
+.BI "Synaptics Tap Durations"
+32 bit, 3 values, single touch timeout, max tapping time for double taps,
+duration of a single click.
+
+.TP 7
+.BI "Synaptics ClickPad"
+8 bit (Bool).
+
+.TP 7
+.BI "Synaptics Middle Button Timeout"
+32 bit.
+
+.TP 7
+.BI "Synaptics Two-Finger Pressure"
+32 bit.
+
+.TP 7
+.BI "Synaptics Two-Finger Width"
+32 bit.
+
+.TP 7
+.BI "Synaptics Scrolling Distance"
+32 bit, 2 values, vert, horiz.
+
+.TP 7
+.BI "Synaptics Edge Scrolling"
+8 bit (BOOL), 3 values, vertical, horizontal, corner.
+
+.TP 7
+.BI "Synaptics Two-Finger Scrolling"
+8 bit (BOOL), 2 values, vertical, horizontal.
+
+.TP 7
+.BI "Synaptics Move Speed"
+FLOAT, 4 values, min, max, accel, <deprecated>
+
+.TP 7
+.BI "Synaptics Button Scrolling"
+8 bit (BOOL), 2 values, updown, leftright.
+
+.TP 7
+.BI "Synaptics Button Scrolling Repeat"
+8 bit (BOOL), 2 values, updown, leftright.
+
+.TP 7
+.BI "Synaptics Button Scrolling Time"
+32 bit.
+
+.TP 7
+.BI "Synaptics Off"
+8 bit, valid values (0, 1, 2).
+
+.TP 7
+.BI "Synaptics Locked Drags"
+8 bit (BOOL).
+
+.TP 7
+.BI "Synaptics Locked Drags Timeout"
+32 bit.
+
+.TP 7
+.BI "Synaptics Tap Action"
+8 bit, up to MAX_TAP values (see synaptics.h), 0 disables an element. order:
+RT, RB, LT, LB, F1, F2, F3.
+
+.TP 7
+.BI "Synaptics Click Action"
+8 bit, up to MAX_CLICK values (see synaptics.h), 0 disables an element.
+order: Finger 1, 2, 3.
+
+.TP 7
+.BI "Synaptics Circular Scrolling"
+8 bit (BOOL).
+
+.TP 7
+.BI "Synaptics Circular Scrolling Distance"
+FLOAT.
+
+.TP 7
+.BI "Synaptics Circular Scrolling Trigger"
+8 bit, valid values 0..8 (inclusive) order: any edge, top, top + right,
+right, right + bottom, bottom, bottom + left, left, left  + top.
+
+.TP 7
+.BI "Synaptics Circular Pad"
+8 bit (BOOL).
+
+.TP 7
+.BI "Synaptics Palm Detection"
+8 bit (BOOL).
+
+.TP 7
+.BI "Synaptics Palm Dimensions"
+32 bit, 2 values, width, z.
+
+.TP 7
+.BI "Synaptics Coasting Speed"
+FLOAT, 2 values, speed, friction.
+
+.TP 7
+.BI "Synaptics Pressure Motion"
+32 bit, 2 values, min, max.
+
+.TP 7
+.BI "Synaptics Pressure Motion Factor"
+FLOAT, 2 values, min, max.
+
+.TP 7
+.BI "Synaptics Grab Event Device"
+8 bit (BOOL).
+
+.TP 7
+.BI "Synaptics Gestures"
+8 bit (BOOL), 1 value, tap-and-drag.
+
+.TP 7
+.BI "Synaptics Area"
+The AreaLeftEdge, AreaRightEdge, AreaTopEdge and AreaBottomEdge parameters are used to
+define the edges of the active area of the touchpad. All movements, scrolling and tapping
+which take place outside of this area will be ignored. This property is disabled by
+default.
+
+32 bit, 4 values, left, right, top, bottom. 0 disables an element.
+
+.TP 7
+.BI "Synaptics Soft Button Areas"
+This property is only available on ClickPad devices.
+The Right and middle soft button areas are used to support right and middle
+click actions on a ClickPad device. Providing 0 for all values of a given button
+disables the button area.
+
+32 bit, 8 values, RBL, RBR, RBT, RBB, MBL, MBR, MBT, MBB.
+
+.TP 7
+.BI "Synaptics Capabilities"
+This read-only property expresses the physical capability of the touchpad,
+most notably whether the touchpad hardware supports multi-finger tapping and
+scrolling.
+
+8 bit (BOOL), 7 values (read-only), has left button, has middle button, has
+right button, two-finger detection, three-finger detection, pressure detection, and finger/palm width detection.
+
+.TP 7
+.BI "Synaptics Pad Resolution"
+32 bit unsigned, 2 values (read-only), vertical, horizontal in units/millimeter.
+
+.SH "NOTES"
+Configuration through
+.I InputClass
+sections is recommended in X servers 1.8 and later. See xorg.conf.d(5) for
+more details. An example xorg.conf.d snippet is provided in
+.I ${sourcecode}/conf/70-synaptics.conf
+.LP
+Configuration through hal fdi files is recommended in X servers 1.5, 1.6 and
+1.7. An example hal policy file is provided in
+.I ${sourcecode}/conf/11-x11-synaptics.fdi
+.LP
+If either of
+.BI "Protocol \*q" auto-dev \*q
+(default) or
+.BI "Protocol \*q" event \*q
+is used, the driver initializes defaults based on the capabilities reported by
+the kernel driver. Acceleration, edges and resolution are based on the dimensions
+reported by the kernel. If the kernel reports multi-finger detection, two-finger
+vertical scrolling is enabled, horizontal two-finger scrolling is disabled and
+edge scrolling is disabled. If no multi-finger capabilities are reported,
+edge scrolling is enabled for both horizontal and vertical scrolling.
+Tapping is disabled by default for touchpads with one or more physical buttons.
+To enable it you need to map tap actions to buttons. See the "TapButton1",
+"TapButton2" and "TapButton3" options.
+.LP
+Button mapping for physical buttons is handled in the server.
+If the device is switched to left-handed (an in-server mapping of physical
+buttons 1, 2, 3 to the logical buttons 3, 2, 1, respectively), both physical
+and TapButtons are affected. To counteract this, the TapButtons need to be set
+up in reverse order (TapButton1=3, TapButton2=1).
+
+.SH "REMOVED OPTIONS"
+The following options are no longer part of the driver configuration:
+.TP
+.BI "Option \*qRepeater\*q \*q" string \*q
+.TP
+.BI "Option \*qHistorySize\*q \*q" integer \*q
+.TP
+.BI "Option \*qSpecialScrollAreaRight\*q \*q" boolean \*q
+.TP
+.BI "Option \*qGuestMouseOff\*q \*q" boolean \*q
+.TP
+.BI "Option \*qSHMConfig\*q \*q" boolean \*q
+.TP
+.BI "Option \*qFingerPress\*q \*q" integer \*q
+.TP
+.BI "Option \*qTrackstickSpeed\*q \*q" float \*q
+.TP
+.BI "Option \*qEdgeMotionMinZ\*q \*q" integer \*q
+.TP
+.BI "Option \*qEdgeMotionMaxZ\*q \*q" integer \*q
+.TP
+.BI "Option \*qEdgeMotionMinSpeed\*q \*q" integer \*q
+.TP
+.BI "Option \*qEdgeMotionMaxSpeed\*q \*q" integer \*q
+.TP
+.BI "Option \*qEdgeMotionUseAlways\*q \*q" boolean \*q
+.TP
+
+.SH "AUTHORS"
+.LP
+Peter Osterlund <petero2@telia.com> and many others.
+.SH "SEE ALSO"
+.LP
+__xservername__(__appmansuffix__), __xconfigfile__(__filemansuffix__), Xserver(__appmansuffix__), X(__miscmansuffix__), synclient(__appmansuffix__), syndaemon(__appmansuffix__)
diff -pruN 1.9.1-1/.pc/101_resolution_detect_option.patch/src/properties.c 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/src/properties.c
--- 1.9.1-1/.pc/101_resolution_detect_option.patch/src/properties.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/src/properties.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,832 @@
+/*
+ * Copyright © 2008-2012 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: Peter Hutterer
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include "xf86Module.h"
+
+#include <X11/Xatom.h>
+#include <xf86.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+#ifndef XATOM_FLOAT
+#define XATOM_FLOAT "FLOAT"
+#endif
+
+#ifndef XI_PROP_PRODUCT_ID
+#define XI_PROP_PRODUCT_ID "Device Product ID"
+#endif
+
+#ifndef XI_PROP_DEVICE_NODE
+#define XI_PROP_DEVICE_NODE "Device Node"
+#endif
+
+static Atom float_type;
+
+Atom prop_edges = 0;
+Atom prop_finger = 0;
+Atom prop_tap_time = 0;
+Atom prop_tap_move = 0;
+Atom prop_tap_durations = 0;
+Atom prop_clickpad = 0;
+Atom prop_middle_timeout = 0;
+Atom prop_twofinger_pressure = 0;
+Atom prop_twofinger_width = 0;
+Atom prop_scrolldist = 0;
+Atom prop_scrolledge = 0;
+Atom prop_scrolltwofinger = 0;
+Atom prop_speed = 0;
+Atom prop_edgemotion_pressure = 0;
+Atom prop_edgemotion_speed = 0;
+Atom prop_edgemotion_always = 0;
+Atom prop_buttonscroll = 0;
+Atom prop_buttonscroll_repeat = 0;
+Atom prop_buttonscroll_time = 0;
+Atom prop_off = 0;
+Atom prop_lockdrags = 0;
+Atom prop_lockdrags_time = 0;
+Atom prop_tapaction = 0;
+Atom prop_clickaction = 0;
+Atom prop_circscroll = 0;
+Atom prop_circscroll_dist = 0;
+Atom prop_circscroll_trigger = 0;
+Atom prop_circpad = 0;
+Atom prop_palm = 0;
+Atom prop_palm_dim = 0;
+Atom prop_coastspeed = 0;
+Atom prop_pressuremotion = 0;
+Atom prop_pressuremotion_factor = 0;
+Atom prop_grab = 0;
+Atom prop_gestures = 0;
+Atom prop_capabilities = 0;
+Atom prop_resolution = 0;
+Atom prop_area = 0;
+Atom prop_softbutton_areas = 0;
+Atom prop_secondary_softbutton_areas = 0;
+Atom prop_noise_cancellation = 0;
+Atom prop_product_id = 0;
+Atom prop_device_node = 0;
+
+static Atom
+InitTypedAtom(DeviceIntPtr dev, char *name, Atom type, int format, int nvalues,
+              int *values)
+{
+    int i;
+    Atom atom;
+    uint8_t val_8[9];           /* we never have more than 9 values in an atom */
+    uint16_t val_16[9];
+    uint32_t val_32[9];
+    pointer converted;
+
+    for (i = 0; i < nvalues; i++) {
+        switch (format) {
+        case 8:
+            val_8[i] = values[i];
+            break;
+        case 16:
+            val_16[i] = values[i];
+            break;
+        case 32:
+            val_32[i] = values[i];
+            break;
+        }
+    }
+
+    switch (format) {
+    case 8:
+        converted = val_8;
+        break;
+    case 16:
+        converted = val_16;
+        break;
+    case 32:
+    default:
+        converted = val_32;
+        break;
+    }
+
+    atom = MakeAtom(name, strlen(name), TRUE);
+    XIChangeDeviceProperty(dev, atom, type, format, PropModeReplace, nvalues,
+                           converted, FALSE);
+    XISetDevicePropertyDeletable(dev, atom, FALSE);
+    return atom;
+}
+
+static Atom
+InitAtom(DeviceIntPtr dev, char *name, int format, int nvalues, int *values)
+{
+    return InitTypedAtom(dev, name, XA_INTEGER, format, nvalues, values);
+}
+
+static Atom
+InitFloatAtom(DeviceIntPtr dev, char *name, int nvalues, float *values)
+{
+    Atom atom;
+
+    atom = MakeAtom(name, strlen(name), TRUE);
+    XIChangeDeviceProperty(dev, atom, float_type, 32, PropModeReplace,
+                           nvalues, values, FALSE);
+    XISetDevicePropertyDeletable(dev, atom, FALSE);
+    return atom;
+}
+
+static void
+InitSoftButtonProperty(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    SynapticsParameters *para = &priv->synpara;
+    int values[8];
+
+    values[0] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][LEFT];
+    values[1] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][RIGHT];
+    values[2] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][TOP];
+    values[3] = para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA][BOTTOM];
+    values[4] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][LEFT];
+    values[5] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][RIGHT];
+    values[6] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][TOP];
+    values[7] = para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA][BOTTOM];
+    prop_softbutton_areas =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 8, values);
+
+    if (!para->has_secondary_buttons)
+        return;
+
+    values[0] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][LEFT];
+    values[1] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][RIGHT];
+    values[2] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][TOP];
+    values[3] = para->softbutton_areas[TOP_RIGHT_BUTTON_AREA][BOTTOM];
+    values[4] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][LEFT];
+    values[5] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][RIGHT];
+    values[6] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][TOP];
+    values[7] = para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA][BOTTOM];
+
+    if (values[0] || values[1] || values[2] || values[4] ||
+        values[5] || values[6] || values[7])
+        prop_secondary_softbutton_areas =
+            InitAtom(pInfo->dev, SYNAPTICS_PROP_SECONDARY_SOFTBUTTON_AREAS, 32, 8, values);
+}
+
+void
+InitDeviceProperties(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    SynapticsParameters *para = &priv->synpara;
+    int values[9];              /* we never have more than 9 values in an atom */
+    float fvalues[4];           /* never have more than 4 float values */
+
+    float_type = XIGetKnownProperty(XATOM_FLOAT);
+    if (!float_type) {
+        float_type = MakeAtom(XATOM_FLOAT, strlen(XATOM_FLOAT), TRUE);
+        if (!float_type) {
+            xf86IDrvMsg(pInfo, X_ERROR, "Failed to init float atom. "
+                        "Disabling property support.\n");
+            return;
+        }
+    }
+
+    values[0] = para->left_edge;
+    values[1] = para->right_edge;
+    values[2] = para->top_edge;
+    values[3] = para->bottom_edge;
+
+    prop_edges = InitAtom(pInfo->dev, SYNAPTICS_PROP_EDGES, 32, 4, values);
+
+    values[0] = para->finger_low;
+    values[1] = para->finger_high;
+    values[2] = 0;
+
+    prop_finger = InitAtom(pInfo->dev, SYNAPTICS_PROP_FINGER, 32, 3, values);
+    prop_tap_time =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_TIME, 32, 1, &para->tap_time);
+    prop_tap_move =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_MOVE, 32, 1, &para->tap_move);
+
+    values[0] = para->single_tap_timeout;
+    values[1] = para->tap_time_2;
+    values[2] = para->click_time;
+
+    prop_tap_durations =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_DURATIONS, 32, 3, values);
+    prop_clickpad =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_CLICKPAD, 8, 1, &para->clickpad);
+    prop_middle_timeout =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_MIDDLE_TIMEOUT, 32, 1,
+                 &para->emulate_mid_button_time);
+    prop_twofinger_pressure =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_TWOFINGER_PRESSURE, 32, 1,
+                 &para->emulate_twofinger_z);
+    prop_twofinger_width =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_TWOFINGER_WIDTH, 32, 1,
+                 &para->emulate_twofinger_w);
+
+    values[0] = para->scroll_dist_vert;
+    values[1] = para->scroll_dist_horiz;
+    prop_scrolldist =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 2, values);
+
+    values[0] = para->scroll_edge_vert;
+    values[1] = para->scroll_edge_horiz;
+    values[2] = para->scroll_edge_corner;
+    prop_scrolledge =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_SCROLL_EDGE, 8, 3, values);
+    values[0] = para->scroll_twofinger_vert;
+    values[1] = para->scroll_twofinger_horiz;
+    prop_scrolltwofinger =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 2, values);
+
+    fvalues[0] = para->min_speed;
+    fvalues[1] = para->max_speed;
+    fvalues[2] = para->accl;
+    fvalues[3] = 0;
+    prop_speed = InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_SPEED, 4, fvalues);
+
+    if (priv->has_scrollbuttons) {
+        values[0] = para->updown_button_scrolling;
+        values[1] = para->leftright_button_scrolling;
+        prop_buttonscroll =
+            InitAtom(pInfo->dev, SYNAPTICS_PROP_BUTTONSCROLLING, 8, 2, values);
+
+        values[0] = para->updown_button_repeat;
+        values[1] = para->leftright_button_repeat;
+        prop_buttonscroll_repeat =
+            InitAtom(pInfo->dev, SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT, 8, 2,
+                     values);
+        prop_buttonscroll_time =
+            InitAtom(pInfo->dev, SYNAPTICS_PROP_BUTTONSCROLLING_TIME, 32, 1,
+                     &para->scroll_button_repeat);
+    }
+
+    prop_off =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_OFF, 8, 1, &para->touchpad_off);
+    prop_lockdrags =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_LOCKED_DRAGS, 8, 1,
+                 &para->locked_drags);
+    prop_lockdrags_time =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT, 32, 1,
+                 &para->locked_drag_time);
+
+    memcpy(values, para->tap_action, MAX_TAP * sizeof(int));
+    prop_tapaction =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_TAP_ACTION, 8, MAX_TAP, values);
+
+    memcpy(values, para->click_action, MAX_CLICK * sizeof(int));
+    prop_clickaction =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_CLICK_ACTION, 8, MAX_CLICK, values);
+
+    prop_circscroll =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING, 8, 1,
+                 &para->circular_scrolling);
+
+    fvalues[0] = para->scroll_dist_circ;
+    prop_circscroll_dist =
+        InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST, 1,
+                      fvalues);
+
+    prop_circscroll_trigger =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER, 8, 1,
+                 &para->circular_trigger);
+    prop_circpad =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_CIRCULAR_PAD, 8, 1,
+                 &para->circular_pad);
+    prop_palm =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_PALM_DETECT, 8, 1,
+                 &para->palm_detect);
+
+    values[0] = para->palm_min_width;
+    values[1] = para->palm_min_z;
+
+    prop_palm_dim =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 2, values);
+
+    fvalues[0] = para->coasting_speed;
+    fvalues[1] = para->coasting_friction;
+    prop_coastspeed =
+        InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_COASTING_SPEED, 2, fvalues);
+
+    values[0] = para->press_motion_min_z;
+    values[1] = para->press_motion_max_z;
+    prop_pressuremotion =
+        InitTypedAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION, XA_CARDINAL,
+                      32, 2, values);
+
+    fvalues[0] = para->press_motion_min_factor;
+    fvalues[1] = para->press_motion_max_factor;
+
+    prop_pressuremotion_factor =
+        InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 2,
+                      fvalues);
+
+    prop_grab =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_GRAB, 8, 1,
+                 &para->grab_event_device);
+
+    values[0] = para->tap_and_drag_gesture;
+    prop_gestures = InitAtom(pInfo->dev, SYNAPTICS_PROP_GESTURES, 8, 1, values);
+
+    values[0] = priv->has_left;
+    values[1] = priv->has_middle;
+    values[2] = priv->has_right;
+    values[3] = priv->has_double;
+    values[4] = priv->has_triple;
+    values[5] = priv->has_pressure;
+    values[6] = priv->has_width;
+    prop_capabilities =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_CAPABILITIES, 8, 7, values);
+
+    values[0] = para->resolution_vert;
+    values[1] = para->resolution_horiz;
+    prop_resolution =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_RESOLUTION, 32, 2, values);
+
+    values[0] = para->area_left_edge;
+    values[1] = para->area_right_edge;
+    values[2] = para->area_top_edge;
+    values[3] = para->area_bottom_edge;
+    prop_area = InitAtom(pInfo->dev, SYNAPTICS_PROP_AREA, 32, 4, values);
+
+    if (para->clickpad)
+        InitSoftButtonProperty(pInfo);
+
+    values[0] = para->hyst_x;
+    values[1] = para->hyst_y;
+    prop_noise_cancellation = InitAtom(pInfo->dev,
+                                       SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 2,
+                                       values);
+
+    /* only init product_id property if we actually know them */
+    if (priv->id_vendor || priv->id_product) {
+        values[0] = priv->id_vendor;
+        values[1] = priv->id_product;
+        prop_product_id =
+            InitAtom(pInfo->dev, XI_PROP_PRODUCT_ID, 32, 2, values);
+    }
+
+    if (priv->device) {
+        prop_device_node =
+            MakeAtom(XI_PROP_DEVICE_NODE, strlen(XI_PROP_DEVICE_NODE), TRUE);
+        XIChangeDeviceProperty(pInfo->dev, prop_device_node, XA_STRING, 8,
+                               PropModeReplace, strlen(priv->device),
+                               (pointer) priv->device, FALSE);
+        XISetDevicePropertyDeletable(pInfo->dev, prop_device_node, FALSE);
+    }
+
+}
+
+int
+SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+            BOOL checkonly)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    SynapticsParameters *para = &priv->synpara;
+    SynapticsParameters tmp;
+
+    /* If checkonly is set, no parameters may be changed. So just let the code
+     * change temporary variables and forget about it. */
+    if (checkonly) {
+        tmp = *para;
+        para = &tmp;
+    }
+
+    if (property == prop_edges) {
+        INT32 *edges;
+
+        if (prop->size != 4 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        edges = (INT32 *) prop->data;
+        if (edges[0] > edges[1] || edges[2] > edges[3])
+            return BadValue;
+
+        para->left_edge = edges[0];
+        para->right_edge = edges[1];
+        para->top_edge = edges[2];
+        para->bottom_edge = edges[3];
+
+    }
+    else if (property == prop_finger) {
+        INT32 *finger;
+
+        if (prop->size != 3 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        finger = (INT32 *) prop->data;
+        if (finger[0] > finger[1])
+            return BadValue;
+
+        para->finger_low = finger[0];
+        para->finger_high = finger[1];
+    }
+    else if (property == prop_tap_time) {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->tap_time = *(INT32 *) prop->data;
+
+    }
+    else if (property == prop_tap_move) {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->tap_move = *(INT32 *) prop->data;
+    }
+    else if (property == prop_tap_durations) {
+        INT32 *timeouts;
+
+        if (prop->size != 3 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        timeouts = (INT32 *) prop->data;
+
+        para->single_tap_timeout = timeouts[0];
+        para->tap_time_2 = timeouts[1];
+        para->click_time = timeouts[2];
+    }
+    else if (property == prop_clickpad) {
+        BOOL value;
+
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        value = *(BOOL *) prop->data;
+        if (!para->clickpad && value && !prop_softbutton_areas)
+            InitSoftButtonProperty(pInfo);
+        else if (para->clickpad && !value && prop_softbutton_areas) {
+            XIDeleteDeviceProperty(dev, prop_softbutton_areas, FALSE);
+            prop_softbutton_areas = 0;
+        }
+
+        para->clickpad = *(BOOL *) prop->data;
+    }
+    else if (property == prop_middle_timeout) {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->emulate_mid_button_time = *(INT32 *) prop->data;
+    }
+    else if (property == prop_twofinger_pressure) {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->emulate_twofinger_z = *(INT32 *) prop->data;
+    }
+    else if (property == prop_twofinger_width) {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->emulate_twofinger_w = *(INT32 *) prop->data;
+    }
+    else if (property == prop_scrolldist) {
+        INT32 *dist;
+
+        if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        dist = (INT32 *) prop->data;
+        if (dist[0] == 0 || dist[1] == 0)
+            return BadValue;
+
+        if (para->scroll_dist_vert != dist[0]) {
+            para->scroll_dist_vert = dist[0];
+            SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                              para->scroll_dist_vert, 0);
+        }
+        if (para->scroll_dist_horiz != dist[1]) {
+            para->scroll_dist_horiz = dist[1];
+            SetScrollValuator(dev, priv->scroll_axis_horiz,
+                              SCROLL_TYPE_HORIZONTAL, para->scroll_dist_horiz,
+                              0);
+        }
+    }
+    else if (property == prop_scrolledge) {
+        CARD8 *edge;
+
+        if (prop->size != 3 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        edge = (BOOL *) prop->data;
+        para->scroll_edge_vert = edge[0];
+        para->scroll_edge_horiz = edge[1];
+        para->scroll_edge_corner = edge[2];
+    }
+    else if (property == prop_scrolltwofinger) {
+        CARD8 *twofinger;
+
+        if (prop->size != 2 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        twofinger = (BOOL *) prop->data;
+        para->scroll_twofinger_vert = twofinger[0];
+        para->scroll_twofinger_horiz = twofinger[1];
+    }
+    else if (property == prop_speed) {
+        float *speed;
+
+        if (prop->size != 4 || prop->format != 32 || prop->type != float_type)
+            return BadMatch;
+
+        speed = (float *) prop->data;
+        para->min_speed = speed[0];
+        para->max_speed = speed[1];
+        para->accl = speed[2];
+    }
+    else if (property == prop_buttonscroll) {
+        BOOL *scroll;
+
+        if (!priv->has_scrollbuttons)
+            return BadMatch;
+
+        if (prop->size != 2 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        scroll = (BOOL *) prop->data;
+        para->updown_button_scrolling = scroll[0];
+        para->leftright_button_scrolling = scroll[1];
+
+    }
+    else if (property == prop_buttonscroll_repeat) {
+        BOOL *repeat;
+
+        if (!priv->has_scrollbuttons)
+            return BadMatch;
+
+        if (prop->size != 2 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        repeat = (BOOL *) prop->data;
+        para->updown_button_repeat = repeat[0];
+        para->leftright_button_repeat = repeat[1];
+    }
+    else if (property == prop_buttonscroll_time) {
+        if (!priv->has_scrollbuttons)
+            return BadMatch;
+
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->scroll_button_repeat = *(INT32 *) prop->data;
+
+    }
+    else if (property == prop_off) {
+        CARD8 off;
+
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        off = *(CARD8 *) prop->data;
+
+        if (off > 2)
+            return BadValue;
+
+        para->touchpad_off = off;
+    }
+    else if (property == prop_gestures) {
+        BOOL *gestures;
+
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        gestures = (BOOL *) prop->data;
+        para->tap_and_drag_gesture = gestures[0];
+    }
+    else if (property == prop_lockdrags) {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->locked_drags = *(BOOL *) prop->data;
+    }
+    else if (property == prop_lockdrags_time) {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->locked_drag_time = *(INT32 *) prop->data;
+    }
+    else if (property == prop_tapaction) {
+        int i;
+        CARD8 *action;
+
+        if (prop->size > MAX_TAP || prop->format != 8 ||
+            prop->type != XA_INTEGER)
+            return BadMatch;
+
+        action = (CARD8 *) prop->data;
+
+        for (i = 0; i < MAX_TAP; i++)
+            para->tap_action[i] = action[i];
+    }
+    else if (property == prop_clickaction) {
+        int i;
+        CARD8 *action;
+
+        if (prop->size > MAX_CLICK || prop->format != 8 ||
+            prop->type != XA_INTEGER)
+            return BadMatch;
+
+        action = (CARD8 *) prop->data;
+
+        for (i = 0; i < MAX_CLICK; i++)
+            para->click_action[i] = action[i];
+    }
+    else if (property == prop_circscroll) {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->circular_scrolling = *(BOOL *) prop->data;
+
+    }
+    else if (property == prop_circscroll_dist) {
+        float circdist;
+
+        if (prop->size != 1 || prop->format != 32 || prop->type != float_type)
+            return BadMatch;
+
+        circdist = *(float *) prop->data;
+        if (circdist == 0)
+            return BadValue;
+
+        para->scroll_dist_circ = circdist;
+    }
+    else if (property == prop_circscroll_trigger) {
+        int trigger;
+
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        trigger = *(CARD8 *) prop->data;
+        if (trigger > 8)
+            return BadValue;
+
+        para->circular_trigger = trigger;
+
+    }
+    else if (property == prop_circpad) {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->circular_pad = *(BOOL *) prop->data;
+    }
+    else if (property == prop_palm) {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->palm_detect = *(BOOL *) prop->data;
+    }
+    else if (property == prop_palm_dim) {
+        INT32 *dim;
+
+        if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        dim = (INT32 *) prop->data;
+
+        para->palm_min_width = dim[0];
+        para->palm_min_z = dim[1];
+    }
+    else if (property == prop_coastspeed) {
+        float *coast_speeds;
+
+        if (prop->size != 2 || prop->format != 32 || prop->type != float_type)
+            return BadMatch;
+
+        coast_speeds = (float *) prop->data;
+        para->coasting_speed = coast_speeds[0];
+        para->coasting_friction = coast_speeds[1];
+    }
+    else if (property == prop_pressuremotion) {
+        CARD32 *press;
+
+        if (prop->size != 2 || prop->format != 32 || prop->type != XA_CARDINAL)
+            return BadMatch;
+
+        press = (CARD32 *) prop->data;
+        if (press[0] > press[1])
+            return BadValue;
+
+        para->press_motion_min_z = press[0];
+        para->press_motion_max_z = press[1];
+    }
+    else if (property == prop_pressuremotion_factor) {
+        float *press;
+
+        if (prop->size != 2 || prop->format != 32 || prop->type != float_type)
+            return BadMatch;
+
+        press = (float *) prop->data;
+        if (press[0] > press[1])
+            return BadValue;
+
+        para->press_motion_min_factor = press[0];
+        para->press_motion_max_factor = press[1];
+    }
+    else if (property == prop_grab) {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->grab_event_device = *(BOOL *) prop->data;
+    }
+    else if (property == prop_capabilities) {
+        /* read-only */
+        return BadValue;
+    }
+    else if (property == prop_resolution) {
+        /* read-only */
+        return BadValue;
+    }
+    else if (property == prop_area) {
+        INT32 *area;
+
+        if (prop->size != 4 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        area = (INT32 *) prop->data;
+        if ((((area[0] != 0) && (area[1] != 0)) && (area[0] > area[1])) ||
+            (((area[2] != 0) && (area[3] != 0)) && (area[2] > area[3])))
+            return BadValue;
+
+        para->area_left_edge = area[0];
+        para->area_right_edge = area[1];
+        para->area_top_edge = area[2];
+        para->area_bottom_edge = area[3];
+    }
+    else if (property == prop_softbutton_areas) {
+        int *areas;
+
+        if (prop->size != 8 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        areas = (int *) prop->data;
+        if (!SynapticsIsSoftButtonAreasValid(areas))
+            return BadValue;
+
+        memcpy(para->softbutton_areas[BOTTOM_RIGHT_BUTTON_AREA], areas, 4 * sizeof(int));
+        memcpy(para->softbutton_areas[BOTTOM_MIDDLE_BUTTON_AREA], areas + 4, 4 * sizeof(int));
+    }
+    else if (property == prop_secondary_softbutton_areas) {
+        int *areas;
+
+        if (prop->size != 8 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        areas = (int *) prop->data;
+        if (!SynapticsIsSoftButtonAreasValid(areas))
+            return BadValue;
+
+        memcpy(para->softbutton_areas[TOP_RIGHT_BUTTON_AREA], areas, 4 * sizeof(int));
+        memcpy(para->softbutton_areas[TOP_MIDDLE_BUTTON_AREA], areas + 4, 4 * sizeof(int));
+    }
+    else if (property == prop_noise_cancellation) {
+        INT32 *hyst;
+
+        if (prop->size != 2 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        hyst = (INT32 *) prop->data;
+        if (hyst[0] < 0 || hyst[1] < 0)
+            return BadValue;
+        para->hyst_x = hyst[0];
+        para->hyst_y = hyst[1];
+    }
+    else if (property == prop_product_id || property == prop_device_node)
+        return BadValue;        /* read-only */
+    else { /* unknown property */
+        if (strcmp(SYNAPTICS_PROP_SOFTBUTTON_AREAS, NameForAtom(property)) == 0)
+        {
+            prop_softbutton_areas = property;
+            if (SetProperty(dev, property, prop, checkonly) != Success)
+                prop_softbutton_areas = 0;
+            else if (!checkonly)
+                XISetDevicePropertyDeletable(dev, property, FALSE);
+        }
+    }
+
+    return Success;
+}
diff -pruN 1.9.1-1/.pc/101_resolution_detect_option.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/src/synaptics.c
--- 1.9.1-1/.pc/101_resolution_detect_option.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3248 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap if we don't have a phys left button */
+    tapButton1 = priv->has_left ? 0 : 1;
+    tapButton2 = priv->has_left ? 0 : 3;
+    tapButton3 = priv->has_left ? 0 : 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+
+    /* Enable vert edge scroll if we can't detect doubletap */
+    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 0);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 0);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+#ifdef BUG_RETURN_VAL
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/101_resolution_detect_option.patch/src/synapticsstr.h 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/src/synapticsstr.h
--- 1.9.1-1/.pc/101_resolution_detect_option.patch/src/synapticsstr.h	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/src/synapticsstr.h	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,334 @@
+/*
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef	_SYNAPTICSSTR_H_
+#define _SYNAPTICSSTR_H_
+
+#include "synproto.h"
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 18
+#define LogMessageVerbSigSafe xf86MsgVerb
+#endif
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) > 19
+#define NO_DRIVER_SCALING 1
+#elif GET_ABI_MAJOR(ABI_XINPUT_VERSION) == 19 && GET_ABI_MINOR(ABI_XINPUT_VERSION) >= 2
+/* as of 19.2, the server takes device resolution into account when scaling
+   relative events from abs device, so we must not scale in synaptics. */
+#define NO_DRIVER_SCALING 1
+#endif
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 23
+#define HAVE_THREADED_INPUT 1
+#endif
+
+#ifdef DBG
+#undef DBG
+#endif
+
+#ifdef DEBUG
+#define DBG(verb, ...) \
+    xf86MsgVerb(X_INFO, verb, __VA_ARGS__)
+#else
+#define DBG(verb, msg, ...)     /* */
+#endif
+
+/******************************************************************************
+ *		Definitions
+ *					structs, typedefs, #defines, enums
+ *****************************************************************************/
+#define SYNAPTICS_MOVE_HISTORY	5
+#define SYNAPTICS_MAX_TOUCHES	10
+#define SYN_MAX_BUTTONS 12      /* Max number of mouse buttons */
+
+/* Minimum and maximum values for scroll_button_repeat */
+#define SBR_MIN 10
+#define SBR_MAX 1000
+
+enum OffState {
+    TOUCHPAD_ON = 0,
+    TOUCHPAD_OFF = 1,
+    TOUCHPAD_TAP_OFF = 2,
+};
+
+enum TapEvent {
+    RT_TAP = 0,                 /* Right top corner */
+    RB_TAP,                     /* Right bottom corner */
+    LT_TAP,                     /* Left top corner */
+    LB_TAP,                     /* Left bottom corner */
+    F1_TAP,                     /* Non-corner tap, one finger */
+    F2_TAP,                     /* Non-corner tap, two fingers */
+    F3_TAP,                     /* Non-corner tap, three fingers */
+    MAX_TAP
+};
+
+enum ClickFingerEvent {
+    F1_CLICK1 = 0,              /* Click left, one finger */
+    F2_CLICK1,                  /* Click left, two fingers */
+    F3_CLICK1,                  /* Click left, three fingers */
+    MAX_CLICK
+};
+
+
+typedef struct _SynapticsMoveHist {
+    int x, y;
+    CARD32 millis;
+} SynapticsMoveHistRec;
+
+typedef struct _SynapticsTouchAxis {
+    const char *label;
+    int min;
+    int max;
+    int res;
+} SynapticsTouchAxisRec;
+
+enum FingerState {              /* Note! The order matters. Compared with < operator. */
+    FS_BLOCKED = -1,
+    FS_UNTOUCHED = 0,           /* this is 0 so it's the initialized value. */
+    FS_TOUCHED = 1,
+    FS_PRESSED = 2,
+};
+
+enum MovingState {
+    MS_FALSE,
+    MS_TOUCHPAD_RELATIVE,
+};
+
+enum MidButtonEmulation {
+    MBE_OFF,                    /* No button pressed */
+    MBE_LEFT,                   /* Left button pressed, waiting for right button or timeout */
+    MBE_RIGHT,                  /* Right button pressed, waiting for left button or timeout */
+    MBE_MID,                    /* Left and right buttons pressed, waiting for both buttons
+                                   to be released */
+    MBE_TIMEOUT,                /* Waiting for both buttons to be released. */
+    MBE_LEFT_CLICK,             /* Emulate left button click. */
+    MBE_RIGHT_CLICK,            /* Emulate right button click. */
+};
+
+/* See docs/tapndrag.dia for a state machine diagram */
+enum TapState {
+    TS_START,                   /* No tap/drag in progress */
+    TS_1,                       /* After first touch */
+    TS_MOVE,                    /* Pointer movement enabled */
+    TS_2A,                      /* After first release */
+    TS_2B,                      /* After second/third/... release */
+    TS_SINGLETAP,               /* After timeout after first release */
+    TS_3,                       /* After second touch */
+    TS_DRAG,                    /* Pointer drag enabled */
+    TS_4,                       /* After release when "locked drags" enabled */
+    TS_5,                       /* After touch when "locked drags" enabled */
+    TS_CLICKPAD_MOVE,           /* After left button press on a clickpad */
+};
+
+enum TapButtonState {
+    TBS_BUTTON_UP,              /* "Virtual tap button" is up */
+    TBS_BUTTON_DOWN,            /* "Virtual tap button" is down */
+};
+
+enum TouchpadModel {
+    MODEL_UNKNOWN = 0,
+    MODEL_SYNAPTICS,
+    MODEL_ALPS,
+    MODEL_APPLETOUCH,
+    MODEL_ELANTECH,
+    MODEL_UNIBODY_MACBOOK
+};
+
+enum SoftButtonAreas {
+    NO_BUTTON_AREA = -1,
+    BOTTOM_BUTTON_AREA = 0,
+    BOTTOM_RIGHT_BUTTON_AREA = 0,
+    BOTTOM_MIDDLE_BUTTON_AREA = 1,
+    TOP_BUTTON_AREA = 2,
+    TOP_RIGHT_BUTTON_AREA = 2,
+    TOP_MIDDLE_BUTTON_AREA = 3
+};
+
+enum SoftButtonAreaEdges {
+    LEFT = 0,
+    RIGHT = 1,
+    TOP = 2,
+    BOTTOM = 3
+};
+
+typedef struct _SynapticsParameters {
+    /* Parameter data */
+    int left_edge, right_edge, top_edge, bottom_edge;   /* edge coordinates absolute */
+    int finger_low, finger_high, finger_press;  /* finger detection values in Z-values */
+    int tap_time;
+    int tap_move;               /* max. tapping time and movement in packets and coord. */
+    int single_tap_timeout;     /* timeout to recognize a single tap */
+    int tap_time_2;             /* max. tapping time for double taps */
+    int click_time;             /* The duration of a single click */
+    Bool clickpad;              /* Device is a has integrated buttons */
+    Bool has_secondary_buttons; /* Device has a top soft-button area */
+    int clickpad_ignore_motion_time; /* Ignore motion for X ms after a click */
+    int emulate_mid_button_time;        /* Max time between left and right button presses to
+                                           emulate a middle button press. */
+    int emulate_twofinger_z;    /* pressure threshold to emulate two finger touch (for Alps) */
+    int emulate_twofinger_w;    /* Finger width threshold to emulate two finger touch */
+    int scroll_dist_vert;       /* Scrolling distance in absolute coordinates */
+    int scroll_dist_horiz;      /* Scrolling distance in absolute coordinates */
+    Bool scroll_edge_vert;      /* Enable/disable vertical scrolling on right edge */
+    Bool scroll_edge_horiz;     /* Enable/disable horizontal scrolling on left edge */
+    Bool scroll_edge_corner;    /* Enable/disable continuous edge scrolling when in the corner */
+    Bool scroll_twofinger_vert; /* Enable/disable vertical two-finger scrolling */
+    Bool scroll_twofinger_horiz;        /* Enable/disable horizontal two-finger scrolling */
+    double min_speed, max_speed, accl;  /* movement parameters */
+
+    Bool updown_button_scrolling;       /* Up/Down-Button scrolling or middle/double-click */
+    Bool leftright_button_scrolling;    /* Left/right-button scrolling, or two lots of middle button */
+    Bool updown_button_repeat;  /* If up/down button being used to scroll, auto-repeat? */
+    Bool leftright_button_repeat;       /* If left/right button being used to scroll, auto-repeat? */
+    int scroll_button_repeat;   /* time, in milliseconds, between scroll events being
+                                 * sent when holding down scroll buttons */
+    int touchpad_off;           /* Switches the touchpad off
+                                 * 0 : Not off
+                                 * 1 : Off
+                                 * 2 : Only tapping and scrolling off
+                                 */
+    Bool locked_drags;          /* Enable locked drags */
+    int locked_drag_time;       /* timeout for locked drags */
+    int tap_action[MAX_TAP];    /* Button to report on tap events */
+    int click_action[MAX_CLICK];        /* Button to report on click with fingers */
+    Bool circular_scrolling;    /* Enable circular scrolling */
+    double scroll_dist_circ;    /* Scrolling angle radians */
+    int circular_trigger;       /* Trigger area for circular scrolling */
+    Bool circular_pad;          /* Edge has an oval or circular shape */
+    Bool palm_detect;           /* Enable Palm Detection */
+    int palm_min_width;         /* Palm detection width */
+    int palm_min_z;             /* Palm detection depth */
+    double coasting_speed;      /* Coasting threshold scrolling speed in scrolls/s */
+    double coasting_friction;   /* Number of scrolls per second per second to change coasting speed */
+    int press_motion_min_z;     /* finger pressure at which minimum pressure motion factor is applied */
+    int press_motion_max_z;     /* finger pressure at which maximum pressure motion factor is applied */
+    double press_motion_min_factor;     /* factor applied on speed when finger pressure is at minimum */
+    double press_motion_max_factor;     /* factor applied on speed when finger pressure is at minimum */
+    Bool grab_event_device;     /* grab event device for exclusive use? */
+    Bool tap_and_drag_gesture;  /* Switches the tap-and-drag gesture on/off */
+    unsigned int resolution_horiz;      /* horizontal resolution of touchpad in units/mm */
+    unsigned int resolution_vert;       /* vertical resolution of touchpad in units/mm */
+    int area_left_edge, area_right_edge, area_top_edge, area_bottom_edge;       /* area coordinates absolute */
+    int softbutton_areas[4][4]; /* soft button area coordinates, 0 => right, 1 => middle , 2 => secondary right, 3 => secondary middle button */
+    int hyst_x, hyst_y;         /* x and y width of hysteresis box */
+
+    int maxDeltaMM;               /* maximum delta movement (vector length) in mm */
+} SynapticsParameters;
+
+struct _SynapticsPrivateRec {
+    SynapticsParameters synpara;        /* Default parameter settings, read from
+                                           the X config file */
+    struct SynapticsProtocolOperations *proto_ops;
+    void *proto_data;           /* protocol-specific data */
+
+    struct SynapticsHwState *hwState;
+
+    const char *device;         /* device node */
+    CARD32 timer_time;          /* when timer last fired */
+    OsTimerPtr timer;           /* for up/down-button repeat, tap processing, etc */
+
+    struct CommData comm;
+
+    struct SynapticsHwState *local_hw_state;    /* used in place of local hw state variables */
+
+    SynapticsMoveHistRec move_hist[SYNAPTICS_MOVE_HISTORY];     /* movement history */
+    int hist_index;             /* Last added entry in move_hist[] */
+    int hyst_center_x;          /* center x of hysteresis */
+    int hyst_center_y;          /* center y of hysteresis */
+    struct {
+        int last_x;             /* last x-scroll position */
+        int last_y;             /* last y-scroll position */
+        double delta_x;         /* accumulated horiz scroll delta */
+        double delta_y;         /* accumulated vert scroll delta */
+        double last_a;          /* last angle-scroll position */
+        CARD32 last_millis;     /* time last scroll event posted */
+        double coast_speed_x;   /* Horizontal coasting speed in scrolls/s */
+        double coast_speed_y;   /* Vertical coasting speed in scrolls/s */
+        double coast_delta_x;   /* Accumulated horizontal coast delta */
+        double coast_delta_y;   /* Accumulated vertical coast delta */
+        int packets_this_scroll;        /* Events received for this scroll */
+    } scroll;
+    int count_packet_finger;    /* packet counter with finger on the touchpad */
+    int button_delay_millis;    /* button delay for 3rd button emulation */
+    Bool prev_up;               /* Previous up button value, for double click emulation */
+    enum FingerState finger_state;      /* previous finger state */
+    CARD32 last_motion_millis;  /* time of the last motion */
+    enum SoftButtonAreas last_button_area;    /* Last button area we were in */
+    int clickpad_click_millis;  /* Time of last clickpad click */
+
+    enum TapState tap_state;    /* State of tap processing */
+    int tap_max_fingers;        /* Max number of fingers seen since entering start state */
+    int tap_button;             /* Which button started the tap processing */
+    enum TapButtonState tap_button_state;       /* Current tap action */
+    SynapticsMoveHistRec touch_on;      /* data when the touchpad is touched/released */
+
+    enum MovingState moving_state;      /* previous moving state */
+    Bool vert_scroll_edge_on;   /* Keeps track of currently active scroll modes */
+    Bool horiz_scroll_edge_on;  /* Keeps track of currently active scroll modes */
+    Bool vert_scroll_twofinger_on;      /* Keeps track of currently active scroll modes */
+    Bool horiz_scroll_twofinger_on;     /* Keeps track of currently active scroll modes */
+    Bool circ_scroll_on;        /* Keeps track of currently active scroll modes */
+    Bool circ_scroll_vert;      /* True: Generate vertical scroll events
+                                   False: Generate horizontal events */
+    enum MidButtonEmulation mid_emu_state;      /* emulated 3rd button */
+    int repeatButtons;          /* buttons for repeat */
+    int nextRepeat;             /* Time when to trigger next auto repeat event */
+    int lastButtons;            /* last state of the buttons */
+    int prev_z;                 /* previous z value, for palm detection */
+    int prevFingers;            /* previous numFingers, for transition detection */
+    int avg_width;              /* weighted average of previous fingerWidth values */
+#ifndef NO_DRIVER_SCALING
+    double horiz_coeff;         /* normalization factor for x coordintes */
+    double vert_coeff;          /* normalization factor for y coordintes */
+#endif
+
+    int minx, maxx, miny, maxy; /* min/max dimensions as detected */
+    int minp, maxp, minw, maxw; /* min/max pressure and finger width as detected */
+    int resx, resy;             /* resolution of coordinates as detected in units/mm */
+    Bool has_left;              /* left button detected for this device */
+    Bool has_right;             /* right button detected for this device */
+    Bool has_middle;            /* middle button detected for this device */
+    Bool has_double;            /* double click detected for this device */
+    Bool has_triple;            /* triple click detected for this device */
+    Bool has_pressure;          /* device reports pressure */
+    Bool has_width;             /* device reports finger width */
+    Bool has_scrollbuttons;     /* device has physical scrollbuttons */
+    Bool has_semi_mt;           /* device is only semi-multitouch capable */
+    Bool has_mt_palm_detect;    /* device reports per finger width and pressure */
+
+    enum TouchpadModel model;   /* The detected model */
+    unsigned short id_vendor;   /* vendor id */
+    unsigned short id_product;  /* product id */
+
+    int scroll_axis_horiz;      /* Horizontal smooth-scrolling axis */
+    int scroll_axis_vert;       /* Vertical smooth-scrolling axis */
+    ValuatorMask *scroll_events_mask;   /* ValuatorMask for smooth-scrolling */
+
+    Bool has_touch;             /* Device has multitouch capabilities */
+    int max_touches;            /* Number of touches supported */
+    int num_mt_axes;            /* Number of multitouch axes other than X, Y */
+    SynapticsTouchAxisRec *touch_axes;  /* Touch axis information other than X, Y */
+    int num_slots;              /* Number of touch slots allocated */
+    int *open_slots;            /* Array of currently open touch slots */
+    int num_active_touches;     /* Number of active touches on device */
+};
+
+#endif                          /* _SYNAPTICSSTR_H_ */
diff -pruN 1.9.1-1/.pc/101_resolution_detect_option.patch/tools/synclient.c 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/tools/synclient.c
--- 1.9.1-1/.pc/101_resolution_detect_option.patch/tools/synclient.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/101_resolution_detect_option.patch/tools/synclient.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,523 @@
+/*
+ * Copyright © 2002-2005,2007 Peter Osterlund
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Peter Osterlund (petero2@telia.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stddef.h>
+#include <math.h>
+#include <limits.h>
+
+#include <X11/Xdefs.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XI.h>
+#include <X11/extensions/XInput.h>
+#include "synaptics-properties.h"
+
+#ifndef XATOM_FLOAT
+#define XATOM_FLOAT "FLOAT"
+#endif
+
+#define SYN_MAX_BUTTONS 12
+#define SBR_MIN 10
+#define SBR_MAX 1000
+
+union flong {                   /* Xlibs 64-bit property handling madness */
+    long l;
+    float f;
+};
+
+enum ParaType {
+    PT_INT,
+    PT_BOOL,
+    PT_DOUBLE
+};
+
+struct Parameter {
+    char *name;                 /* Name of parameter */
+    enum ParaType type;         /* Type of parameter */
+    double min_val;             /* Minimum allowed value */
+    double max_val;             /* Maximum allowed value */
+    char *prop_name;            /* Property name */
+    int prop_format;            /* Property format (0 for floats) */
+    int prop_offset;            /* Offset inside property */
+};
+
+static struct Parameter params[] = {
+    {"LeftEdge",              PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	0},
+    {"RightEdge",             PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	1},
+    {"TopEdge",               PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	2},
+    {"BottomEdge",            PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	3},
+    {"FingerLow",             PT_INT,    0, 255,   SYNAPTICS_PROP_FINGER,	32,	0},
+    {"FingerHigh",            PT_INT,    0, 255,   SYNAPTICS_PROP_FINGER,	32,	1},
+    {"MaxTapTime",            PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_TIME,	32,	0},
+    {"MaxTapMove",            PT_INT,    0, 2000,  SYNAPTICS_PROP_TAP_MOVE,	32,	0},
+    {"MaxDoubleTapTime",      PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	1},
+    {"SingleTapTimeout",      PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	0},
+    {"ClickTime",             PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	2},
+    {"EmulateMidButtonTime",  PT_INT,    0, 1000,  SYNAPTICS_PROP_MIDDLE_TIMEOUT,32,	0},
+    {"EmulateTwoFingerMinZ",  PT_INT,    0, 1000,  SYNAPTICS_PROP_TWOFINGER_PRESSURE,	32,	0},
+    {"EmulateTwoFingerMinW",  PT_INT,    0, 15,    SYNAPTICS_PROP_TWOFINGER_WIDTH,	32,	0},
+    {"VertScrollDelta",       PT_INT,    -1000, 1000,  SYNAPTICS_PROP_SCROLL_DISTANCE,	32,	0},
+    {"HorizScrollDelta",      PT_INT,    -1000, 1000,  SYNAPTICS_PROP_SCROLL_DISTANCE,	32,	1},
+    {"VertEdgeScroll",        PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	0},
+    {"HorizEdgeScroll",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	1},
+    {"CornerCoasting",        PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	2},
+    {"VertTwoFingerScroll",   PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_TWOFINGER,	8,	0},
+    {"HorizTwoFingerScroll",  PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_TWOFINGER,	8,	1},
+    {"MinSpeed",              PT_DOUBLE, 0, 255.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	0},
+    {"MaxSpeed",              PT_DOUBLE, 0, 255.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	1},
+    {"AccelFactor",           PT_DOUBLE, 0, 1.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	2},
+    {"UpDownScrolling",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING,  8,	0},
+    {"LeftRightScrolling",    PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING,  8,	1},
+    {"UpDownScrollRepeat",    PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT,   8,	0},
+    {"LeftRightScrollRepeat", PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT,   8,	1},
+    {"ScrollButtonRepeat",    PT_INT,    SBR_MIN , SBR_MAX, SYNAPTICS_PROP_BUTTONSCROLLING_TIME, 32,	0},
+    {"TouchpadOff",           PT_INT,    0, 2,     SYNAPTICS_PROP_OFF,		8,	0},
+    {"LockedDrags",           PT_BOOL,   0, 1,     SYNAPTICS_PROP_LOCKED_DRAGS,	8,	0},
+    {"LockedDragTimeout",     PT_INT,    0, 30000, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT,	32,	0},
+    {"RTCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	0},
+    {"RBCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	1},
+    {"LTCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	2},
+    {"LBCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	3},
+    {"TapButton1",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	4},
+    {"TapButton2",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	5},
+    {"TapButton3",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	6},
+    {"ClickFinger1",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	0},
+    {"ClickFinger2",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	1},
+    {"ClickFinger3",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	2},
+    {"CircularScrolling",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_CIRCULAR_SCROLLING,	8,	0},
+    {"CircScrollDelta",       PT_DOUBLE, .01, 3,   SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST,	0 /* float */,	0},
+    {"CircScrollTrigger",     PT_INT,    0, 8,     SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER,	8,	0},
+    {"CircularPad",           PT_BOOL,   0, 1,     SYNAPTICS_PROP_CIRCULAR_PAD,	8,	0},
+    {"PalmDetect",            PT_BOOL,   0, 1,     SYNAPTICS_PROP_PALM_DETECT,	8,	0},
+    {"PalmMinWidth",          PT_INT,    0, 15,    SYNAPTICS_PROP_PALM_DIMENSIONS,	32,	0},
+    {"PalmMinZ",              PT_INT,    0, 255,   SYNAPTICS_PROP_PALM_DIMENSIONS,	32,	1},
+    {"CoastingSpeed",         PT_DOUBLE, 0, 255,    SYNAPTICS_PROP_COASTING_SPEED,	0 /* float*/,	0},
+    {"CoastingFriction",      PT_DOUBLE, 0, 255,   SYNAPTICS_PROP_COASTING_SPEED,	0 /* float*/,	1},
+    {"PressureMotionMinZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	0},
+    {"PressureMotionMaxZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	1},
+    {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	0},
+    {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	1},
+    {"GrabEventDevice",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_GRAB,	8,	0},
+    {"TapAndDragGesture",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_GESTURES,	8,	0},
+    {"AreaLeftEdge",          PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	0},
+    {"AreaRightEdge",         PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	1},
+    {"AreaTopEdge",           PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	2},
+    {"AreaBottomEdge",        PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	3},
+    {"HorizHysteresis",       PT_INT,    0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32,	0},
+    {"VertHysteresis",        PT_INT,    0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32,	1},
+    {"ClickPad",              PT_BOOL,   0, 1,     SYNAPTICS_PROP_CLICKPAD,	8,	0},
+    {"RightButtonAreaLeft",   PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	0},
+    {"RightButtonAreaRight",  PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	1},
+    {"RightButtonAreaTop",    PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	2},
+    {"RightButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	3},
+    {"MiddleButtonAreaLeft",  PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	4},
+    {"MiddleButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	5},
+    {"MiddleButtonAreaTop",   PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	6},
+    {"MiddleButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	7},
+    { NULL, 0, 0, 0, 0 }
+};
+
+static double
+parse_cmd(char *cmd, struct Parameter **par)
+{
+    char *eqp = strchr(cmd, '=');
+
+    *par = NULL;
+
+    if (eqp) {
+        int j;
+        int found = 0;
+
+        *eqp = 0;
+        for (j = 0; params[j].name; j++) {
+            if (strcasecmp(cmd, params[j].name) == 0) {
+                found = 1;
+                break;
+            }
+        }
+        if (found) {
+            double val = atof(&eqp[1]);
+
+            *par = &params[j];
+
+            if (val < (*par)->min_val)
+                val = (*par)->min_val;
+            if (val > (*par)->max_val)
+                val = (*par)->max_val;
+
+            return val;
+        }
+        else {
+            printf("Unknown parameter %s\n", cmd);
+        }
+    }
+    else {
+        printf("Invalid command: %s\n", cmd);
+    }
+
+    return 0;
+}
+
+/** Init display connection or NULL on error */
+static Display *
+dp_init()
+{
+    Display *dpy = NULL;
+    XExtensionVersion *v = NULL;
+    Atom touchpad_type = 0;
+    Atom synaptics_property = 0;
+    int error = 0;
+
+    dpy = XOpenDisplay(NULL);
+    if (!dpy) {
+        fprintf(stderr, "Failed to connect to X Server.\n");
+        error = 1;
+        goto unwind;
+    }
+
+    v = XGetExtensionVersion(dpy, INAME);
+    if (!v->present ||
+        (v->major_version * 1000 + v->minor_version) <
+        (XI_Add_DeviceProperties_Major * 1000 +
+         XI_Add_DeviceProperties_Minor)) {
+        fprintf(stderr, "X server supports X Input %d.%d. I need %d.%d.\n",
+                v->major_version, v->minor_version,
+                XI_Add_DeviceProperties_Major, XI_Add_DeviceProperties_Minor);
+        error = 1;
+        goto unwind;
+    }
+
+    /* We know synaptics sets XI_TOUCHPAD for all the devices. */
+    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
+    if (!touchpad_type) {
+        fprintf(stderr, "XI_TOUCHPAD not initialised.\n");
+        error = 1;
+        goto unwind;
+    }
+
+    synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True);
+    if (!synaptics_property) {
+        fprintf(stderr, "Couldn't find synaptics properties. No synaptics "
+                "driver loaded?\n");
+        error = 1;
+        goto unwind;
+    }
+
+ unwind:
+    XFree(v);
+    if (error && dpy) {
+        XCloseDisplay(dpy);
+        dpy = NULL;
+    }
+    return dpy;
+}
+
+static XDevice *
+dp_get_device(Display * dpy)
+{
+    XDevice *dev = NULL;
+    XDeviceInfo *info = NULL;
+    int ndevices = 0;
+    Atom touchpad_type = 0;
+    Atom synaptics_property = 0;
+    Atom *properties = NULL;
+    int nprops = 0;
+    int error = 0;
+
+    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
+    synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True);
+    info = XListInputDevices(dpy, &ndevices);
+
+    while (ndevices--) {
+        if (info[ndevices].type == touchpad_type) {
+            dev = XOpenDevice(dpy, info[ndevices].id);
+            if (!dev) {
+                fprintf(stderr, "Failed to open device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            properties = XListDeviceProperties(dpy, dev, &nprops);
+            if (!properties || !nprops) {
+                fprintf(stderr, "No properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            while (nprops--) {
+                if (properties[nprops] == synaptics_property)
+                    break;
+            }
+            if (!nprops) {
+                fprintf(stderr, "No synaptics properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            break;              /* Yay, device is suitable */
+        }
+    }
+
+ unwind:
+    XFree(properties);
+    XFreeDeviceList(info);
+    if (!dev)
+        fprintf(stderr, "Unable to find a synaptics device.\n");
+    else if (error && dev) {
+        XCloseDevice(dpy, dev);
+        dev = NULL;
+    }
+    return dev;
+}
+
+static void
+dp_set_variables(Display * dpy, XDevice * dev, int argc, char *argv[],
+                 int first_cmd)
+{
+    int i;
+    double val;
+    struct Parameter *par;
+    Atom prop, type, float_type;
+    int format;
+    unsigned char *data;
+    unsigned long nitems, bytes_after;
+
+    union flong *f;
+    long *n;
+    char *b;
+
+    float_type = XInternAtom(dpy, XATOM_FLOAT, True);
+    if (!float_type)
+        fprintf(stderr, "Float properties not available.\n");
+
+    for (i = first_cmd; i < argc; i++) {
+        val = parse_cmd(argv[i], &par);
+        if (!par)
+            continue;
+
+        prop = XInternAtom(dpy, par->prop_name, True);
+        if (!prop) {
+            fprintf(stderr, "Property for '%s' not available. Skipping.\n",
+                    par->name);
+            continue;
+
+        }
+
+        XGetDeviceProperty(dpy, dev, prop, 0, 1000, False, AnyPropertyType,
+                           &type, &format, &nitems, &bytes_after, &data);
+
+        if (type == None) {
+            fprintf(stderr, "Property for '%s' not available. Skipping.\n",
+                    par->name);
+            continue;
+        }
+
+        switch (par->prop_format) {
+        case 8:
+            if (format != par->prop_format || type != XA_INTEGER) {
+                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+            b = (char *) data;
+            b[par->prop_offset] = rint(val);
+            break;
+        case 32:
+            if (format != par->prop_format ||
+                (type != XA_INTEGER && type != XA_CARDINAL)) {
+                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+            n = (long *) data;
+            n[par->prop_offset] = rint(val);
+            break;
+        case 0:                /* float */
+            if (!float_type)
+                continue;
+            if (format != 32 || type != float_type) {
+                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+            f = (union flong *) data;
+            f[par->prop_offset].f = val;
+            break;
+        }
+
+        XChangeDeviceProperty(dpy, dev, prop, type, format,
+                              PropModeReplace, data, nitems);
+        XFlush(dpy);
+    }
+}
+
+/* FIXME: horribly inefficient. */
+static void
+dp_show_settings(Display * dpy, XDevice * dev)
+{
+    int j;
+    Atom a, type, float_type;
+    int format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data;
+    int len;
+
+    union flong *f;
+    long *i;
+    char *b;
+
+    float_type = XInternAtom(dpy, XATOM_FLOAT, True);
+    if (!float_type)
+        fprintf(stderr, "Float properties not available.\n");
+
+    printf("Parameter settings:\n");
+    for (j = 0; params[j].name; j++) {
+        struct Parameter *par = &params[j];
+
+        a = XInternAtom(dpy, par->prop_name, True);
+        if (!a)
+            continue;
+
+        len =
+            1 +
+            ((par->prop_offset * (par->prop_format ? par->prop_format : 32) /
+              8)) / 4;
+
+        XGetDeviceProperty(dpy, dev, a, 0, len, False,
+                           AnyPropertyType, &type, &format,
+                           &nitems, &bytes_after, &data);
+        if (type == None)
+            continue;
+
+        switch (par->prop_format) {
+        case 8:
+            if (format != par->prop_format || type != XA_INTEGER) {
+                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+
+            b = (char *) data;
+            printf("    %-23s = %d\n", par->name, b[par->prop_offset]);
+            break;
+        case 32:
+            if (format != par->prop_format ||
+                (type != XA_INTEGER && type != XA_CARDINAL)) {
+                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+
+            i = (long *) data;
+            printf("    %-23s = %ld\n", par->name, i[par->prop_offset]);
+            break;
+        case 0:                /* Float */
+            if (!float_type)
+                continue;
+            if (format != 32 || type != float_type) {
+                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+
+            f = (union flong *) data;
+            printf("    %-23s = %g\n", par->name, f[par->prop_offset].f);
+            break;
+        }
+
+        XFree(data);
+    }
+}
+
+static void
+usage(void)
+{
+    fprintf(stderr, "Usage: synclient [-h] [-l] [-V] [-?] [var1=value1 [var2=value2] ...]\n");
+    fprintf(stderr, "  -l List current user settings\n");
+    fprintf(stderr, "  -V Print synclient version string and exit\n");
+    fprintf(stderr, "  -? Show this help message\n");
+    fprintf(stderr, "  var=value  Set user parameter 'var' to 'value'.\n");
+    exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int c;
+    int dump_settings = 0;
+    int first_cmd;
+
+    Display *dpy;
+    XDevice *dev;
+
+    if (argc == 1)
+        dump_settings = 1;
+
+    /* Parse command line parameters */
+    while ((c = getopt(argc, argv, "lV?")) != -1) {
+        switch (c) {
+        case 'l':
+            dump_settings = 1;
+            break;
+        case 'V':
+            printf("%s\n", VERSION);
+            exit(0);
+        case '?':
+        default:
+            usage();
+        }
+    }
+
+    first_cmd = optind;
+    if (!dump_settings && first_cmd == argc)
+        usage();
+
+    dpy = dp_init();
+    if (!dpy || !(dev = dp_get_device(dpy)))
+        return 1;
+
+    dp_set_variables(dpy, dev, argc, argv, first_cmd);
+    if (dump_settings)
+        dp_show_settings(dpy, dev);
+
+    XCloseDevice(dpy, dev);
+    XCloseDisplay(dpy);
+
+    return 0;
+}
diff -pruN 1.9.1-1/.pc/103_enable_cornertapping.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/103_enable_cornertapping.patch/src/synaptics.c
--- 1.9.1-1/.pc/103_enable_cornertapping.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/103_enable_cornertapping.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3249 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap if we don't have a phys left button */
+    tapButton1 = priv->has_left ? 0 : 1;
+    tapButton2 = priv->has_left ? 0 : 3;
+    tapButton3 = priv->has_left ? 0 : 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+
+    /* Enable vert edge scroll if we can't detect doubletap */
+    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 0);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 0);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+#ifdef BUG_RETURN_VAL
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/104_always_enable_tapping.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/104_always_enable_tapping.patch/src/synaptics.c
--- 1.9.1-1/.pc/104_always_enable_tapping.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/104_always_enable_tapping.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3249 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap if we don't have a phys left button */
+    tapButton1 = priv->has_left ? 0 : 1;
+    tapButton2 = priv->has_left ? 0 : 3;
+    tapButton3 = priv->has_left ? 0 : 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+
+    /* Enable vert edge scroll if we can't detect doubletap */
+    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+#ifdef BUG_RETURN_VAL
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/106_always_enable_vert_edge_scroll.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/106_always_enable_vert_edge_scroll.patch/src/synaptics.c
--- 1.9.1-1/.pc/106_always_enable_vert_edge_scroll.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/106_always_enable_vert_edge_scroll.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3249 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap */
+    tapButton1 = 1;
+    tapButton2 = 3;
+    tapButton3 = 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+
+    /* Enable vert edge scroll if we can't detect doubletap */
+    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+#ifdef BUG_RETURN_VAL
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/115_evdev_only.patch/conf/70-synaptics.conf 1.9.1-1ubuntu3/.pc/115_evdev_only.patch/conf/70-synaptics.conf
--- 1.9.1-1/.pc/115_evdev_only.patch/conf/70-synaptics.conf	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/115_evdev_only.patch/conf/70-synaptics.conf	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,46 @@
+# Example xorg.conf.d snippet that assigns the touchpad driver
+# to all touchpads. See xorg.conf.d(5) for more information on
+# InputClass.
+# DO NOT EDIT THIS FILE, your distribution will likely overwrite
+# it when updating. Copy (and rename) this file into
+# /etc/X11/xorg.conf.d first.
+# Additional options may be added in the form of
+#   Option "OptionName" "value"
+#
+Section "InputClass"
+        Identifier "touchpad catchall"
+        Driver "synaptics"
+        MatchIsTouchpad "on"
+# This option is recommend on all Linux systems using evdev, but cannot be
+# enabled by default. See the following link for details:
+# http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html
+#       MatchDevicePath "/dev/input/event*"
+EndSection
+
+Section "InputClass"
+        Identifier "touchpad ignore duplicates"
+        MatchIsTouchpad "on"
+        MatchOS "Linux"
+        MatchDevicePath "/dev/input/mouse*"
+        Option "Ignore" "on"
+EndSection
+
+# This option enables the bottom right corner to be a right button on clickpads
+# and the right and middle top areas to be right / middle buttons on clickpads
+# with a top button area.
+# This option is only interpreted by clickpads.
+Section "InputClass"
+        Identifier "Default clickpad buttons"
+        MatchDriver "synaptics"
+        Option "SoftButtonAreas" "50% 0 82% 0 0 0 0 0"
+        Option "SecondarySoftButtonAreas" "58% 0 0 15% 42% 58% 0 15%"
+EndSection
+
+# This option disables software buttons on Apple touchpads.
+# This option is only interpreted by clickpads.
+Section "InputClass"
+        Identifier "Disable clickpad buttons on Apple touchpads"
+        MatchProduct "Apple|bcm5974"
+        MatchDriver "synaptics"
+        Option "SoftButtonAreas" "0 0 0 0 0 0 0 0"
+EndSection
diff -pruN 1.9.1-1/.pc/118_quell_error_msg.patch/tools/synclient.c 1.9.1-1ubuntu3/.pc/118_quell_error_msg.patch/tools/synclient.c
--- 1.9.1-1/.pc/118_quell_error_msg.patch/tools/synclient.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/118_quell_error_msg.patch/tools/synclient.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,524 @@
+/*
+ * Copyright © 2002-2005,2007 Peter Osterlund
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Peter Osterlund (petero2@telia.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stddef.h>
+#include <math.h>
+#include <limits.h>
+
+#include <X11/Xdefs.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XI.h>
+#include <X11/extensions/XInput.h>
+#include "synaptics-properties.h"
+
+#ifndef XATOM_FLOAT
+#define XATOM_FLOAT "FLOAT"
+#endif
+
+#define SYN_MAX_BUTTONS 12
+#define SBR_MIN 10
+#define SBR_MAX 1000
+
+union flong {                   /* Xlibs 64-bit property handling madness */
+    long l;
+    float f;
+};
+
+enum ParaType {
+    PT_INT,
+    PT_BOOL,
+    PT_DOUBLE
+};
+
+struct Parameter {
+    char *name;                 /* Name of parameter */
+    enum ParaType type;         /* Type of parameter */
+    double min_val;             /* Minimum allowed value */
+    double max_val;             /* Maximum allowed value */
+    char *prop_name;            /* Property name */
+    int prop_format;            /* Property format (0 for floats) */
+    int prop_offset;            /* Offset inside property */
+};
+
+static struct Parameter params[] = {
+    {"LeftEdge",              PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	0},
+    {"RightEdge",             PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	1},
+    {"TopEdge",               PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	2},
+    {"BottomEdge",            PT_INT,    0, 10000, SYNAPTICS_PROP_EDGES,	32,	3},
+    {"FingerLow",             PT_INT,    0, 255,   SYNAPTICS_PROP_FINGER,	32,	0},
+    {"FingerHigh",            PT_INT,    0, 255,   SYNAPTICS_PROP_FINGER,	32,	1},
+    {"MaxTapTime",            PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_TIME,	32,	0},
+    {"MaxTapMove",            PT_INT,    0, 2000,  SYNAPTICS_PROP_TAP_MOVE,	32,	0},
+    {"MaxDoubleTapTime",      PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	1},
+    {"SingleTapTimeout",      PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	0},
+    {"ClickTime",             PT_INT,    0, 1000,  SYNAPTICS_PROP_TAP_DURATIONS,32,	2},
+    {"EmulateMidButtonTime",  PT_INT,    0, 1000,  SYNAPTICS_PROP_MIDDLE_TIMEOUT,32,	0},
+    {"EmulateTwoFingerMinZ",  PT_INT,    0, 1000,  SYNAPTICS_PROP_TWOFINGER_PRESSURE,	32,	0},
+    {"EmulateTwoFingerMinW",  PT_INT,    0, 15,    SYNAPTICS_PROP_TWOFINGER_WIDTH,	32,	0},
+    {"VertScrollDelta",       PT_INT,    -1000, 1000,  SYNAPTICS_PROP_SCROLL_DISTANCE,	32,	0},
+    {"HorizScrollDelta",      PT_INT,    -1000, 1000,  SYNAPTICS_PROP_SCROLL_DISTANCE,	32,	1},
+    {"VertEdgeScroll",        PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	0},
+    {"HorizEdgeScroll",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	1},
+    {"CornerCoasting",        PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_EDGE,	8,	2},
+    {"VertTwoFingerScroll",   PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_TWOFINGER,	8,	0},
+    {"HorizTwoFingerScroll",  PT_BOOL,   0, 1,     SYNAPTICS_PROP_SCROLL_TWOFINGER,	8,	1},
+    {"MinSpeed",              PT_DOUBLE, 0, 255.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	0},
+    {"MaxSpeed",              PT_DOUBLE, 0, 255.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	1},
+    {"AccelFactor",           PT_DOUBLE, 0, 1.0,   SYNAPTICS_PROP_SPEED,	0, /*float */	2},
+    {"UpDownScrolling",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING,  8,	0},
+    {"LeftRightScrolling",    PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING,  8,	1},
+    {"UpDownScrollRepeat",    PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT,   8,	0},
+    {"LeftRightScrollRepeat", PT_BOOL,   0, 1,     SYNAPTICS_PROP_BUTTONSCROLLING_REPEAT,   8,	1},
+    {"ScrollButtonRepeat",    PT_INT,    SBR_MIN , SBR_MAX, SYNAPTICS_PROP_BUTTONSCROLLING_TIME, 32,	0},
+    {"TouchpadOff",           PT_INT,    0, 2,     SYNAPTICS_PROP_OFF,		8,	0},
+    {"LockedDrags",           PT_BOOL,   0, 1,     SYNAPTICS_PROP_LOCKED_DRAGS,	8,	0},
+    {"LockedDragTimeout",     PT_INT,    0, 30000, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT,	32,	0},
+    {"RTCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	0},
+    {"RBCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	1},
+    {"LTCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	2},
+    {"LBCornerButton",        PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	3},
+    {"TapButton1",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	4},
+    {"TapButton2",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	5},
+    {"TapButton3",            PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION,	8,	6},
+    {"ClickFinger1",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	0},
+    {"ClickFinger2",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	1},
+    {"ClickFinger3",          PT_INT,    0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION,	8,	2},
+    {"CircularScrolling",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_CIRCULAR_SCROLLING,	8,	0},
+    {"CircScrollDelta",       PT_DOUBLE, .01, 3,   SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST,	0 /* float */,	0},
+    {"CircScrollTrigger",     PT_INT,    0, 8,     SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER,	8,	0},
+    {"CircularPad",           PT_BOOL,   0, 1,     SYNAPTICS_PROP_CIRCULAR_PAD,	8,	0},
+    {"PalmDetect",            PT_BOOL,   0, 1,     SYNAPTICS_PROP_PALM_DETECT,	8,	0},
+    {"PalmMinWidth",          PT_INT,    0, 15,    SYNAPTICS_PROP_PALM_DIMENSIONS,	32,	0},
+    {"PalmMinZ",              PT_INT,    0, 255,   SYNAPTICS_PROP_PALM_DIMENSIONS,	32,	1},
+    {"CoastingSpeed",         PT_DOUBLE, 0, 255,    SYNAPTICS_PROP_COASTING_SPEED,	0 /* float*/,	0},
+    {"CoastingFriction",      PT_DOUBLE, 0, 255,   SYNAPTICS_PROP_COASTING_SPEED,	0 /* float*/,	1},
+    {"PressureMotionMinZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	0},
+    {"PressureMotionMaxZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	1},
+    {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	0},
+    {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	1},
+    {"ResolutionDetect",      PT_BOOL,   0, 1,     SYNAPTICS_PROP_RESOLUTION_DETECT,	8,      0},
+    {"GrabEventDevice",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_GRAB,	8,	0},
+    {"TapAndDragGesture",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_GESTURES,	8,	0},
+    {"AreaLeftEdge",          PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	0},
+    {"AreaRightEdge",         PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	1},
+    {"AreaTopEdge",           PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	2},
+    {"AreaBottomEdge",        PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	3},
+    {"HorizHysteresis",       PT_INT,    0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32,	0},
+    {"VertHysteresis",        PT_INT,    0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32,	1},
+    {"ClickPad",              PT_BOOL,   0, 1,     SYNAPTICS_PROP_CLICKPAD,	8,	0},
+    {"RightButtonAreaLeft",   PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	0},
+    {"RightButtonAreaRight",  PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	1},
+    {"RightButtonAreaTop",    PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	2},
+    {"RightButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	3},
+    {"MiddleButtonAreaLeft",  PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	4},
+    {"MiddleButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	5},
+    {"MiddleButtonAreaTop",   PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	6},
+    {"MiddleButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS,	32,	7},
+    { NULL, 0, 0, 0, 0 }
+};
+
+static double
+parse_cmd(char *cmd, struct Parameter **par)
+{
+    char *eqp = strchr(cmd, '=');
+
+    *par = NULL;
+
+    if (eqp) {
+        int j;
+        int found = 0;
+
+        *eqp = 0;
+        for (j = 0; params[j].name; j++) {
+            if (strcasecmp(cmd, params[j].name) == 0) {
+                found = 1;
+                break;
+            }
+        }
+        if (found) {
+            double val = atof(&eqp[1]);
+
+            *par = &params[j];
+
+            if (val < (*par)->min_val)
+                val = (*par)->min_val;
+            if (val > (*par)->max_val)
+                val = (*par)->max_val;
+
+            return val;
+        }
+        else {
+            printf("Unknown parameter %s\n", cmd);
+        }
+    }
+    else {
+        printf("Invalid command: %s\n", cmd);
+    }
+
+    return 0;
+}
+
+/** Init display connection or NULL on error */
+static Display *
+dp_init()
+{
+    Display *dpy = NULL;
+    XExtensionVersion *v = NULL;
+    Atom touchpad_type = 0;
+    Atom synaptics_property = 0;
+    int error = 0;
+
+    dpy = XOpenDisplay(NULL);
+    if (!dpy) {
+        fprintf(stderr, "Failed to connect to X Server.\n");
+        error = 1;
+        goto unwind;
+    }
+
+    v = XGetExtensionVersion(dpy, INAME);
+    if (!v->present ||
+        (v->major_version * 1000 + v->minor_version) <
+        (XI_Add_DeviceProperties_Major * 1000 +
+         XI_Add_DeviceProperties_Minor)) {
+        fprintf(stderr, "X server supports X Input %d.%d. I need %d.%d.\n",
+                v->major_version, v->minor_version,
+                XI_Add_DeviceProperties_Major, XI_Add_DeviceProperties_Minor);
+        error = 1;
+        goto unwind;
+    }
+
+    /* We know synaptics sets XI_TOUCHPAD for all the devices. */
+    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
+    if (!touchpad_type) {
+        fprintf(stderr, "XI_TOUCHPAD not initialised.\n");
+        error = 1;
+        goto unwind;
+    }
+
+    synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True);
+    if (!synaptics_property) {
+        fprintf(stderr, "Couldn't find synaptics properties. No synaptics "
+                "driver loaded?\n");
+        error = 1;
+        goto unwind;
+    }
+
+ unwind:
+    XFree(v);
+    if (error && dpy) {
+        XCloseDisplay(dpy);
+        dpy = NULL;
+    }
+    return dpy;
+}
+
+static XDevice *
+dp_get_device(Display * dpy)
+{
+    XDevice *dev = NULL;
+    XDeviceInfo *info = NULL;
+    int ndevices = 0;
+    Atom touchpad_type = 0;
+    Atom synaptics_property = 0;
+    Atom *properties = NULL;
+    int nprops = 0;
+    int error = 0;
+
+    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
+    synaptics_property = XInternAtom(dpy, SYNAPTICS_PROP_EDGES, True);
+    info = XListInputDevices(dpy, &ndevices);
+
+    while (ndevices--) {
+        if (info[ndevices].type == touchpad_type) {
+            dev = XOpenDevice(dpy, info[ndevices].id);
+            if (!dev) {
+                fprintf(stderr, "Failed to open device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            properties = XListDeviceProperties(dpy, dev, &nprops);
+            if (!properties || !nprops) {
+                fprintf(stderr, "No properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            while (nprops--) {
+                if (properties[nprops] == synaptics_property)
+                    break;
+            }
+            if (!nprops) {
+                fprintf(stderr, "No synaptics properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            break;              /* Yay, device is suitable */
+        }
+    }
+
+ unwind:
+    XFree(properties);
+    XFreeDeviceList(info);
+    if (!dev)
+        fprintf(stderr, "Unable to find a synaptics device.\n");
+    else if (error && dev) {
+        XCloseDevice(dpy, dev);
+        dev = NULL;
+    }
+    return dev;
+}
+
+static void
+dp_set_variables(Display * dpy, XDevice * dev, int argc, char *argv[],
+                 int first_cmd)
+{
+    int i;
+    double val;
+    struct Parameter *par;
+    Atom prop, type, float_type;
+    int format;
+    unsigned char *data;
+    unsigned long nitems, bytes_after;
+
+    union flong *f;
+    long *n;
+    char *b;
+
+    float_type = XInternAtom(dpy, XATOM_FLOAT, True);
+    if (!float_type)
+        fprintf(stderr, "Float properties not available.\n");
+
+    for (i = first_cmd; i < argc; i++) {
+        val = parse_cmd(argv[i], &par);
+        if (!par)
+            continue;
+
+        prop = XInternAtom(dpy, par->prop_name, True);
+        if (!prop) {
+            fprintf(stderr, "Property for '%s' not available. Skipping.\n",
+                    par->name);
+            continue;
+
+        }
+
+        XGetDeviceProperty(dpy, dev, prop, 0, 1000, False, AnyPropertyType,
+                           &type, &format, &nitems, &bytes_after, &data);
+
+        if (type == None) {
+            fprintf(stderr, "Property for '%s' not available. Skipping.\n",
+                    par->name);
+            continue;
+        }
+
+        switch (par->prop_format) {
+        case 8:
+            if (format != par->prop_format || type != XA_INTEGER) {
+                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+            b = (char *) data;
+            b[par->prop_offset] = rint(val);
+            break;
+        case 32:
+            if (format != par->prop_format ||
+                (type != XA_INTEGER && type != XA_CARDINAL)) {
+                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+            n = (long *) data;
+            n[par->prop_offset] = rint(val);
+            break;
+        case 0:                /* float */
+            if (!float_type)
+                continue;
+            if (format != 32 || type != float_type) {
+                fprintf(stderr, "   %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+            f = (union flong *) data;
+            f[par->prop_offset].f = val;
+            break;
+        }
+
+        XChangeDeviceProperty(dpy, dev, prop, type, format,
+                              PropModeReplace, data, nitems);
+        XFlush(dpy);
+    }
+}
+
+/* FIXME: horribly inefficient. */
+static void
+dp_show_settings(Display * dpy, XDevice * dev)
+{
+    int j;
+    Atom a, type, float_type;
+    int format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data;
+    int len;
+
+    union flong *f;
+    long *i;
+    char *b;
+
+    float_type = XInternAtom(dpy, XATOM_FLOAT, True);
+    if (!float_type)
+        fprintf(stderr, "Float properties not available.\n");
+
+    printf("Parameter settings:\n");
+    for (j = 0; params[j].name; j++) {
+        struct Parameter *par = &params[j];
+
+        a = XInternAtom(dpy, par->prop_name, True);
+        if (!a)
+            continue;
+
+        len =
+            1 +
+            ((par->prop_offset * (par->prop_format ? par->prop_format : 32) /
+              8)) / 4;
+
+        XGetDeviceProperty(dpy, dev, a, 0, len, False,
+                           AnyPropertyType, &type, &format,
+                           &nitems, &bytes_after, &data);
+        if (type == None)
+            continue;
+
+        switch (par->prop_format) {
+        case 8:
+            if (format != par->prop_format || type != XA_INTEGER) {
+                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+
+            b = (char *) data;
+            printf("    %-23s = %d\n", par->name, b[par->prop_offset]);
+            break;
+        case 32:
+            if (format != par->prop_format ||
+                (type != XA_INTEGER && type != XA_CARDINAL)) {
+                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+
+            i = (long *) data;
+            printf("    %-23s = %ld\n", par->name, i[par->prop_offset]);
+            break;
+        case 0:                /* Float */
+            if (!float_type)
+                continue;
+            if (format != 32 || type != float_type) {
+                fprintf(stderr, "    %-23s = format mismatch (%d)\n",
+                        par->name, format);
+                break;
+            }
+
+            f = (union flong *) data;
+            printf("    %-23s = %g\n", par->name, f[par->prop_offset].f);
+            break;
+        }
+
+        XFree(data);
+    }
+}
+
+static void
+usage(void)
+{
+    fprintf(stderr, "Usage: synclient [-h] [-l] [-V] [-?] [var1=value1 [var2=value2] ...]\n");
+    fprintf(stderr, "  -l List current user settings\n");
+    fprintf(stderr, "  -V Print synclient version string and exit\n");
+    fprintf(stderr, "  -? Show this help message\n");
+    fprintf(stderr, "  var=value  Set user parameter 'var' to 'value'.\n");
+    exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+    int c;
+    int dump_settings = 0;
+    int first_cmd;
+
+    Display *dpy;
+    XDevice *dev;
+
+    if (argc == 1)
+        dump_settings = 1;
+
+    /* Parse command line parameters */
+    while ((c = getopt(argc, argv, "lV?")) != -1) {
+        switch (c) {
+        case 'l':
+            dump_settings = 1;
+            break;
+        case 'V':
+            printf("%s\n", VERSION);
+            exit(0);
+        case '?':
+        default:
+            usage();
+        }
+    }
+
+    first_cmd = optind;
+    if (!dump_settings && first_cmd == argc)
+        usage();
+
+    dpy = dp_init();
+    if (!dpy || !(dev = dp_get_device(dpy)))
+        return 1;
+
+    dp_set_variables(dpy, dev, argc, argv, first_cmd);
+    if (dump_settings)
+        dp_show_settings(dpy, dev);
+
+    XCloseDevice(dpy, dev);
+    XCloseDisplay(dpy);
+
+    return 0;
+}
diff -pruN 1.9.1-1/.pc/118_quell_error_msg.patch/tools/syndaemon.c 1.9.1-1ubuntu3/.pc/118_quell_error_msg.patch/tools/syndaemon.c
--- 1.9.1-1/.pc/118_quell_error_msg.patch/tools/syndaemon.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/118_quell_error_msg.patch/tools/syndaemon.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,676 @@
+/*
+ * Copyright © 2003-2004 Peter Osterlund
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Peter Osterlund (petero2@telia.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XInput.h>
+#ifdef HAVE_X11_EXTENSIONS_RECORD_H
+#include <X11/Xproto.h>
+#include <X11/extensions/record.h>
+#endif                          /* HAVE_X11_EXTENSIONS_RECORD_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include "synaptics-properties.h"
+
+enum KeyboardActivity {
+    ActivityNew,
+    ActivityNone,
+    ActivityReset
+};
+
+enum TouchpadState {
+    TouchpadOn = 0,
+    TouchpadOff = 1,
+    TappingOff = 2
+};
+
+static Bool pad_disabled
+    /* internal flag, this does not correspond to device state */ ;
+static int ignore_modifier_combos;
+static int ignore_modifier_keys;
+static int background;
+static const char *pid_file;
+static Display *display;
+static XDevice *dev;
+static Atom touchpad_off_prop;
+static enum TouchpadState previous_state;
+static enum TouchpadState disable_state = TouchpadOff;
+static int verbose;
+
+#define KEYMAP_SIZE 32
+static unsigned char keyboard_mask[KEYMAP_SIZE];
+
+static void
+usage(void)
+{
+    fprintf(stderr,
+            "Usage: syndaemon [-i idle-time] [-m poll-delay] [-d] [-t] [-k]\n");
+    fprintf(stderr,
+            "  -i How many seconds to wait after the last key press before\n");
+    fprintf(stderr, "     enabling the touchpad. (default is 2.0s)\n");
+    fprintf(stderr, "  -m How many milli-seconds to wait until next poll.\n");
+    fprintf(stderr, "     (default is 200ms)\n");
+    fprintf(stderr, "  -d Start as a daemon, i.e. in the background.\n");
+    fprintf(stderr, "  -p Create a pid file with the specified name.\n");
+    fprintf(stderr,
+            "  -t Only disable tapping and scrolling, not mouse movements.\n");
+    fprintf(stderr,
+            "  -k Ignore modifier keys when monitoring keyboard activity.\n");
+    fprintf(stderr, "  -K Like -k but also ignore Modifier+Key combos.\n");
+    fprintf(stderr, "  -R Use the XRecord extension.\n");
+    fprintf(stderr, "  -v Print diagnostic messages.\n");
+    fprintf(stderr, "  -? Show this help message.\n");
+    exit(1);
+}
+
+static void
+store_current_touchpad_state(void)
+{
+    Atom real_type;
+    int real_format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data;
+
+    if ((XGetDeviceProperty(display, dev, touchpad_off_prop, 0, 1, False,
+                            XA_INTEGER, &real_type, &real_format, &nitems,
+                            &bytes_after, &data) == Success) &&
+        (real_type != None)) {
+        previous_state = data[0];
+    }
+}
+
+/**
+ * Toggle touchpad enabled/disabled state, decided by value.
+ */
+static void
+toggle_touchpad(Bool enable)
+{
+    unsigned char data;
+
+    if (pad_disabled && enable) {
+        data = previous_state;
+        pad_disabled = False;
+        if (verbose)
+            printf("Enable\n");
+    }
+    else if (!pad_disabled && !enable &&
+             previous_state != disable_state && previous_state != TouchpadOff) {
+        store_current_touchpad_state();
+        pad_disabled = True;
+        data = disable_state;
+        if (verbose)
+            printf("Disable\n");
+    }
+    else
+        return;
+
+    /* This potentially overwrites a different client's setting, but ... */
+    XChangeDeviceProperty(display, dev, touchpad_off_prop, XA_INTEGER, 8,
+                          PropModeReplace, &data, 1);
+    XFlush(display);
+}
+
+static void
+signal_handler(int signum)
+{
+    toggle_touchpad(True);
+
+    if (pid_file)
+        unlink(pid_file);
+    kill(getpid(), signum);
+}
+
+static void
+install_signal_handler(void)
+{
+    static int signals[] = {
+        SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
+        SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE,
+        SIGALRM, SIGTERM,
+#ifdef SIGPWR
+        SIGPWR
+#endif
+    };
+    int i;
+    struct sigaction act;
+    sigset_t set;
+
+    sigemptyset(&set);
+    act.sa_handler = signal_handler;
+    act.sa_mask = set;
+#ifdef SA_RESETHAND
+    act.sa_flags = SA_RESETHAND;
+#else
+    act.sa_flags = 0;
+#endif
+
+    for (i = 0; i < sizeof(signals) / sizeof(int); i++) {
+        if (sigaction(signals[i], &act, NULL) == -1) {
+            perror("sigaction");
+            exit(2);
+        }
+    }
+}
+
+static enum KeyboardActivity
+keyboard_activity(Display * display)
+{
+    static unsigned char old_key_state[KEYMAP_SIZE];
+    unsigned char key_state[KEYMAP_SIZE];
+    int i;
+    int ret = ActivityNone;
+
+    XQueryKeymap(display, (char *) key_state);
+
+    for (i = 0; i < KEYMAP_SIZE; i++) {
+        if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) {
+            ret = ActivityNew;
+            break;
+        }
+    }
+    if (ignore_modifier_combos) {
+        for (i = 0; i < KEYMAP_SIZE; i++) {
+            if (key_state[i] & ~keyboard_mask[i]) {
+                if (old_key_state[i] & ~keyboard_mask[i])
+                    ret = ActivityNone;
+                else
+                    ret = ActivityReset;
+                break;
+            }
+        }
+    }
+    for (i = 0; i < KEYMAP_SIZE; i++)
+        old_key_state[i] = key_state[i];
+    return ret;
+}
+
+static double
+get_time(void)
+{
+    struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec + tv.tv_usec / 1000000.0;
+}
+
+static void
+main_loop(Display * display, double idle_time, int poll_delay)
+{
+    double last_activity = 0.0;
+    double current_time;
+
+    keyboard_activity(display);
+
+    for (;;) {
+        current_time = get_time();
+        switch (keyboard_activity(display)) {
+            case ActivityNew:
+                last_activity = current_time;
+                break;
+            case ActivityNone:
+                /* NOP */;
+                break;
+            case ActivityReset:
+                last_activity = 0.0;
+                break;
+        }
+
+        /* If system times goes backwards, touchpad can get locked. Make
+         * sure our last activity wasn't in the future and reset if it was. */
+        if (last_activity > current_time)
+            last_activity = current_time - idle_time - 1;
+
+        if (current_time > last_activity + idle_time) { /* Enable touchpad */
+            toggle_touchpad(True);
+        }
+        else {                  /* Disable touchpad */
+            toggle_touchpad(False);
+        }
+
+        usleep(poll_delay);
+    }
+}
+
+static void
+clear_bit(unsigned char *ptr, int bit)
+{
+    int byte_num = bit / 8;
+    int bit_num = bit % 8;
+
+    ptr[byte_num] &= ~(1 << bit_num);
+}
+
+static void
+setup_keyboard_mask(Display * display, int ignore_modifier_keys)
+{
+    XModifierKeymap *modifiers;
+    int i;
+
+    for (i = 0; i < KEYMAP_SIZE; i++)
+        keyboard_mask[i] = 0xff;
+
+    if (ignore_modifier_keys) {
+        modifiers = XGetModifierMapping(display);
+        for (i = 0; i < 8 * modifiers->max_keypermod; i++) {
+            KeyCode kc = modifiers->modifiermap[i];
+
+            if (kc != 0)
+                clear_bit(keyboard_mask, kc);
+        }
+        XFreeModifiermap(modifiers);
+    }
+}
+
+/* ---- the following code is for using the xrecord extension ----- */
+#ifdef HAVE_X11_EXTENSIONS_RECORD_H
+
+#define MAX_MODIFIERS 16
+
+/* used for exchanging information with the callback function */
+struct xrecord_callback_results {
+    XModifierKeymap *modifiers;
+    Bool key_event;
+    Bool non_modifier_event;
+    KeyCode pressed_modifiers[MAX_MODIFIERS];
+};
+
+/* test if the xrecord extension is found */
+Bool
+check_xrecord(Display * display)
+{
+
+    Bool found;
+    Status status;
+    int major_opcode, minor_opcode, first_error;
+    int version[2];
+
+    found = XQueryExtension(display,
+                            "RECORD",
+                            &major_opcode, &minor_opcode, &first_error);
+
+    status = XRecordQueryVersion(display, version, version + 1);
+    if (verbose && status) {
+        printf("X RECORD extension version %d.%d\n", version[0], version[1]);
+    }
+    return found;
+}
+
+/* called by XRecordProcessReplies() */
+void
+xrecord_callback(XPointer closure, XRecordInterceptData * recorded_data)
+{
+
+    struct xrecord_callback_results *cbres;
+    xEvent *xev;
+    int nxev;
+
+    cbres = (struct xrecord_callback_results *) closure;
+
+    if (recorded_data->category != XRecordFromServer) {
+        XRecordFreeData(recorded_data);
+        return;
+    }
+
+    nxev = recorded_data->data_len / 8;
+    xev = (xEvent *) recorded_data->data;
+    while (nxev--) {
+
+        if ((xev->u.u.type == KeyPress) || (xev->u.u.type == KeyRelease)) {
+            int i;
+            int is_modifier = 0;
+
+            cbres->key_event = 1;       /* remember, a key was pressed or released. */
+
+            /* test if it was a modifier */
+            for (i = 0; i < 8 * cbres->modifiers->max_keypermod; i++) {
+                KeyCode kc = cbres->modifiers->modifiermap[i];
+
+                if (kc == xev->u.u.detail) {
+                    is_modifier = 1;    /* yes, it is a modifier. */
+                    break;
+                }
+            }
+
+            if (is_modifier) {
+                if (xev->u.u.type == KeyPress) {
+                    for (i = 0; i < MAX_MODIFIERS; ++i)
+                        if (!cbres->pressed_modifiers[i]) {
+                            cbres->pressed_modifiers[i] = xev->u.u.detail;
+                            break;
+                        }
+                }
+                else {          /* KeyRelease */
+                    for (i = 0; i < MAX_MODIFIERS; ++i)
+                        if (cbres->pressed_modifiers[i] == xev->u.u.detail)
+                            cbres->pressed_modifiers[i] = 0;
+                }
+
+            }
+            else {
+                /* remember, a non-modifier was pressed. */
+                cbres->non_modifier_event = 1;
+            }
+        }
+
+        xev++;
+    }
+
+    XRecordFreeData(recorded_data);     /* cleanup */
+}
+
+static int
+is_modifier_pressed(const struct xrecord_callback_results *cbres)
+{
+    int i;
+
+    for (i = 0; i < MAX_MODIFIERS; ++i)
+        if (cbres->pressed_modifiers[i])
+            return 1;
+
+    return 0;
+}
+
+void
+record_main_loop(Display * display, double idle_time)
+{
+
+    struct xrecord_callback_results cbres;
+    XRecordContext context;
+    XRecordClientSpec cspec = XRecordAllClients;
+    Display *dpy_data;
+    XRecordRange *range;
+    int i;
+
+    dpy_data = XOpenDisplay(NULL);      /* we need an additional data connection. */
+    range = XRecordAllocRange();
+
+    range->device_events.first = KeyPress;
+    range->device_events.last = KeyRelease;
+
+    context = XRecordCreateContext(dpy_data, 0, &cspec, 1, &range, 1);
+
+    XRecordEnableContextAsync(dpy_data, context, xrecord_callback,
+                              (XPointer) & cbres);
+
+    cbres.modifiers = XGetModifierMapping(display);
+    /* clear list of modifiers */
+    for (i = 0; i < MAX_MODIFIERS; ++i)
+        cbres.pressed_modifiers[i] = 0;
+
+    while (1) {
+
+        int fd = ConnectionNumber(dpy_data);
+        fd_set read_fds;
+        int ret;
+        int disable_event = 0;
+        int modifier_event = 0;
+        struct timeval timeout;
+
+        FD_ZERO(&read_fds);
+        FD_SET(fd, &read_fds);
+
+        ret = select(fd + 1 /* =(max descriptor in read_fds) + 1 */ ,
+                     &read_fds, NULL, NULL,
+                     pad_disabled ? &timeout : NULL
+                     /* timeout only required for enabling */ );
+
+        if (FD_ISSET(fd, &read_fds)) {
+
+            cbres.key_event = 0;
+            cbres.non_modifier_event = 0;
+
+            XRecordProcessReplies(dpy_data);
+
+            /* If there are any events left over, they are in error. Drain them
+             * from the connection queue so we don't get stuck. */
+            while (XEventsQueued(dpy_data, QueuedAlready) > 0) {
+                XEvent event;
+
+                XNextEvent(dpy_data, &event);
+                fprintf(stderr, "bad event received, major opcode %d\n",
+                        event.type);
+            }
+
+            if (!ignore_modifier_keys && cbres.key_event) {
+                disable_event = 1;
+            }
+
+            if (cbres.non_modifier_event) {
+                if (ignore_modifier_combos && is_modifier_pressed(&cbres)) {
+                    modifier_event = 1;
+                } else {
+                    disable_event = 1;
+                }
+            } else if (ignore_modifier_keys) {
+                modifier_event = 1;
+            }
+        }
+
+        if (disable_event) {
+            /* adjust the enable_time */
+            timeout.tv_sec = (int) idle_time;
+            timeout.tv_usec = (idle_time - (double) timeout.tv_sec) * 1.e6;
+
+            toggle_touchpad(False);
+        }
+
+        if (modifier_event && pad_disabled) {
+            toggle_touchpad(True);
+        }
+
+        if (ret == 0 && pad_disabled) { /* timeout => enable event */
+            toggle_touchpad(True);
+        }
+    }                           /* end while(1) */
+
+    XFreeModifiermap(cbres.modifiers);
+}
+#endif                          /* HAVE_X11_EXTENSIONS_RECORD_H */
+
+static XDevice *
+dp_get_device(Display * dpy)
+{
+    XDevice *dev = NULL;
+    XDeviceInfo *info = NULL;
+    int ndevices = 0;
+    Atom touchpad_type = 0;
+    Atom *properties = NULL;
+    int nprops = 0;
+    int error = 0;
+
+    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
+    touchpad_off_prop = XInternAtom(dpy, SYNAPTICS_PROP_OFF, True);
+    info = XListInputDevices(dpy, &ndevices);
+
+    while (ndevices--) {
+        if (info[ndevices].type == touchpad_type) {
+            dev = XOpenDevice(dpy, info[ndevices].id);
+            if (!dev) {
+                fprintf(stderr, "Failed to open device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            properties = XListDeviceProperties(dpy, dev, &nprops);
+            if (!properties || !nprops) {
+                fprintf(stderr, "No properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            while (nprops--) {
+                if (properties[nprops] == touchpad_off_prop)
+                    break;
+            }
+            if (nprops < 0) {
+                fprintf(stderr, "No synaptics properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            break;              /* Yay, device is suitable */
+        }
+    }
+
+ unwind:
+    XFree(properties);
+    XFreeDeviceList(info);
+    if (!dev)
+        fprintf(stderr, "Unable to find a synaptics device.\n");
+    else if (error && dev) {
+        XCloseDevice(dpy, dev);
+        dev = NULL;
+    }
+    return dev;
+}
+
+int
+main(int argc, char *argv[])
+{
+    double idle_time = 2.0;
+    int poll_delay = 200000;    /* 200 ms */
+    int c;
+    int use_xrecord = 0;
+
+    /* Parse command line parameters */
+    while ((c = getopt(argc, argv, "i:m:dtp:kKR?v")) != EOF) {
+        switch (c) {
+        case 'i':
+            idle_time = atof(optarg);
+            break;
+        case 'm':
+            poll_delay = atoi(optarg) * 1000;
+            break;
+        case 'd':
+            background = 1;
+            break;
+        case 't':
+            disable_state = TappingOff;
+            break;
+        case 'p':
+            pid_file = optarg;
+            break;
+        case 'k':
+            ignore_modifier_keys = 1;
+            break;
+        case 'K':
+            ignore_modifier_combos = 1;
+            ignore_modifier_keys = 1;
+            break;
+        case 'R':
+            use_xrecord = 1;
+            break;
+        case 'v':
+            verbose = 1;
+            break;
+        case '?':
+        default:
+            usage();
+            break;
+        }
+    }
+    if (idle_time <= 0.0)
+        usage();
+
+    /* Open a connection to the X server */
+    display = XOpenDisplay(NULL);
+    if (!display) {
+        fprintf(stderr, "Can't open display.\n");
+        exit(2);
+    }
+
+    if (!(dev = dp_get_device(display)))
+        exit(2);
+
+    /* Install a signal handler to restore synaptics parameters on exit */
+    install_signal_handler();
+
+    if (background) {
+        pid_t pid;
+
+        if ((pid = fork()) < 0) {
+            perror("fork");
+            exit(3);
+        }
+        else if (pid != 0)
+            exit(0);
+
+        /* Child (daemon) is running here */
+        setsid();               /* Become session leader */
+        chdir("/");             /* In case the file system gets unmounted */
+        umask(0);               /* We don't want any surprises */
+        if (pid_file) {
+            FILE *fd = fopen(pid_file, "w");
+
+            if (!fd) {
+                perror("Can't create pid file");
+                exit(3);
+            }
+            fprintf(fd, "%d\n", getpid());
+            fclose(fd);
+        }
+    }
+
+    pad_disabled = False;
+    store_current_touchpad_state();
+
+#ifdef HAVE_X11_EXTENSIONS_RECORD_H
+    if (use_xrecord) {
+        if (check_xrecord(display))
+            record_main_loop(display, idle_time);
+        else {
+            fprintf(stderr, "Use of XRecord requested, but failed to "
+                    " initialize.\n");
+            exit(4);
+        }
+    }
+    else
+#endif                          /* HAVE_X11_EXTENSIONS_RECORD_H */
+    {
+        setup_keyboard_mask(display, ignore_modifier_keys);
+
+        /* Run the main loop */
+        main_loop(display, idle_time, poll_delay);
+    }
+    return 0;
+}
+
+/* vim: set noexpandtab tabstop=8 shiftwidth=4: */
diff -pruN 1.9.1-1/.pc/124_syndaemon_events.patch/tools/syndaemon.c 1.9.1-1ubuntu3/.pc/124_syndaemon_events.patch/tools/syndaemon.c
--- 1.9.1-1/.pc/124_syndaemon_events.patch/tools/syndaemon.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/124_syndaemon_events.patch/tools/syndaemon.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,674 @@
+/*
+ * Copyright © 2003-2004 Peter Osterlund
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Peter Osterlund (petero2@telia.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XInput.h>
+#ifdef HAVE_X11_EXTENSIONS_RECORD_H
+#include <X11/Xproto.h>
+#include <X11/extensions/record.h>
+#endif                          /* HAVE_X11_EXTENSIONS_RECORD_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include "synaptics-properties.h"
+
+enum KeyboardActivity {
+    ActivityNew,
+    ActivityNone,
+    ActivityReset
+};
+
+enum TouchpadState {
+    TouchpadOn = 0,
+    TouchpadOff = 1,
+    TappingOff = 2
+};
+
+static Bool pad_disabled
+    /* internal flag, this does not correspond to device state */ ;
+static int ignore_modifier_combos;
+static int ignore_modifier_keys;
+static int background;
+static const char *pid_file;
+static Display *display;
+static XDevice *dev;
+static Atom touchpad_off_prop;
+static enum TouchpadState previous_state;
+static enum TouchpadState disable_state = TouchpadOff;
+static int verbose;
+
+#define KEYMAP_SIZE 32
+static unsigned char keyboard_mask[KEYMAP_SIZE];
+
+static void
+usage(void)
+{
+    fprintf(stderr,
+            "Usage: syndaemon [-i idle-time] [-m poll-delay] [-d] [-t] [-k]\n");
+    fprintf(stderr,
+            "  -i How many seconds to wait after the last key press before\n");
+    fprintf(stderr, "     enabling the touchpad. (default is 2.0s)\n");
+    fprintf(stderr, "  -m How many milli-seconds to wait until next poll.\n");
+    fprintf(stderr, "     (default is 200ms)\n");
+    fprintf(stderr, "  -d Start as a daemon, i.e. in the background.\n");
+    fprintf(stderr, "  -p Create a pid file with the specified name.\n");
+    fprintf(stderr,
+            "  -t Only disable tapping and scrolling, not mouse movements.\n");
+    fprintf(stderr,
+            "  -k Ignore modifier keys when monitoring keyboard activity.\n");
+    fprintf(stderr, "  -K Like -k but also ignore Modifier+Key combos.\n");
+    fprintf(stderr, "  -R Use the XRecord extension.\n");
+    fprintf(stderr, "  -v Print diagnostic messages.\n");
+    fprintf(stderr, "  -? Show this help message.\n");
+    exit(1);
+}
+
+static void
+store_current_touchpad_state(void)
+{
+    Atom real_type;
+    int real_format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data;
+
+    if ((XGetDeviceProperty(display, dev, touchpad_off_prop, 0, 1, False,
+                            XA_INTEGER, &real_type, &real_format, &nitems,
+                            &bytes_after, &data) == Success) &&
+        (real_type != None)) {
+        previous_state = data[0];
+    }
+}
+
+/**
+ * Toggle touchpad enabled/disabled state, decided by value.
+ */
+static void
+toggle_touchpad(Bool enable)
+{
+    unsigned char data;
+
+    if (pad_disabled && enable) {
+        data = previous_state;
+        pad_disabled = False;
+        if (verbose)
+            printf("Enable\n");
+    }
+    else if (!pad_disabled && !enable &&
+             previous_state != disable_state && previous_state != TouchpadOff) {
+        store_current_touchpad_state();
+        pad_disabled = True;
+        data = disable_state;
+        if (verbose)
+            printf("Disable\n");
+    }
+    else
+        return;
+
+    /* This potentially overwrites a different client's setting, but ... */
+    XChangeDeviceProperty(display, dev, touchpad_off_prop, XA_INTEGER, 8,
+                          PropModeReplace, &data, 1);
+    XFlush(display);
+}
+
+static void
+signal_handler(int signum)
+{
+    toggle_touchpad(True);
+
+    if (pid_file)
+        unlink(pid_file);
+    kill(getpid(), signum);
+}
+
+static void
+install_signal_handler(void)
+{
+    static int signals[] = {
+        SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
+        SIGBUS, SIGFPE, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE,
+        SIGALRM, SIGTERM,
+#ifdef SIGPWR
+        SIGPWR
+#endif
+    };
+    int i;
+    struct sigaction act;
+    sigset_t set;
+
+    sigemptyset(&set);
+    act.sa_handler = signal_handler;
+    act.sa_mask = set;
+#ifdef SA_RESETHAND
+    act.sa_flags = SA_RESETHAND;
+#else
+    act.sa_flags = 0;
+#endif
+
+    for (i = 0; i < sizeof(signals) / sizeof(int); i++) {
+        if (sigaction(signals[i], &act, NULL) == -1) {
+            perror("sigaction");
+            exit(2);
+        }
+    }
+}
+
+static enum KeyboardActivity
+keyboard_activity(Display * display)
+{
+    static unsigned char old_key_state[KEYMAP_SIZE];
+    unsigned char key_state[KEYMAP_SIZE];
+    int i;
+    int ret = ActivityNone;
+
+    XQueryKeymap(display, (char *) key_state);
+
+    for (i = 0; i < KEYMAP_SIZE; i++) {
+        if ((key_state[i] & ~old_key_state[i]) & keyboard_mask[i]) {
+            ret = ActivityNew;
+            break;
+        }
+    }
+    if (ignore_modifier_combos) {
+        for (i = 0; i < KEYMAP_SIZE; i++) {
+            if (key_state[i] & ~keyboard_mask[i]) {
+                if (old_key_state[i] & ~keyboard_mask[i])
+                    ret = ActivityNone;
+                else
+                    ret = ActivityReset;
+                break;
+            }
+        }
+    }
+    for (i = 0; i < KEYMAP_SIZE; i++)
+        old_key_state[i] = key_state[i];
+    return ret;
+}
+
+static double
+get_time(void)
+{
+    struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec + tv.tv_usec / 1000000.0;
+}
+
+static void
+main_loop(Display * display, double idle_time, int poll_delay)
+{
+    double last_activity = 0.0;
+    double current_time;
+
+    keyboard_activity(display);
+
+    for (;;) {
+        current_time = get_time();
+        switch (keyboard_activity(display)) {
+            case ActivityNew:
+                last_activity = current_time;
+                break;
+            case ActivityNone:
+                /* NOP */;
+                break;
+            case ActivityReset:
+                last_activity = 0.0;
+                break;
+        }
+
+        /* If system times goes backwards, touchpad can get locked. Make
+         * sure our last activity wasn't in the future and reset if it was. */
+        if (last_activity > current_time)
+            last_activity = current_time - idle_time - 1;
+
+        if (current_time > last_activity + idle_time) { /* Enable touchpad */
+            toggle_touchpad(True);
+        }
+        else {                  /* Disable touchpad */
+            toggle_touchpad(False);
+        }
+
+        usleep(poll_delay);
+    }
+}
+
+static void
+clear_bit(unsigned char *ptr, int bit)
+{
+    int byte_num = bit / 8;
+    int bit_num = bit % 8;
+
+    ptr[byte_num] &= ~(1 << bit_num);
+}
+
+static void
+setup_keyboard_mask(Display * display, int ignore_modifier_keys)
+{
+    XModifierKeymap *modifiers;
+    int i;
+
+    for (i = 0; i < KEYMAP_SIZE; i++)
+        keyboard_mask[i] = 0xff;
+
+    if (ignore_modifier_keys) {
+        modifiers = XGetModifierMapping(display);
+        for (i = 0; i < 8 * modifiers->max_keypermod; i++) {
+            KeyCode kc = modifiers->modifiermap[i];
+
+            if (kc != 0)
+                clear_bit(keyboard_mask, kc);
+        }
+        XFreeModifiermap(modifiers);
+    }
+}
+
+/* ---- the following code is for using the xrecord extension ----- */
+#ifdef HAVE_X11_EXTENSIONS_RECORD_H
+
+#define MAX_MODIFIERS 16
+
+/* used for exchanging information with the callback function */
+struct xrecord_callback_results {
+    XModifierKeymap *modifiers;
+    Bool key_event;
+    Bool non_modifier_event;
+    KeyCode pressed_modifiers[MAX_MODIFIERS];
+};
+
+/* test if the xrecord extension is found */
+Bool
+check_xrecord(Display * display)
+{
+
+    Bool found;
+    Status status;
+    int major_opcode, minor_opcode, first_error;
+    int version[2];
+
+    found = XQueryExtension(display,
+                            "RECORD",
+                            &major_opcode, &minor_opcode, &first_error);
+
+    status = XRecordQueryVersion(display, version, version + 1);
+    if (verbose && status) {
+        printf("X RECORD extension version %d.%d\n", version[0], version[1]);
+    }
+    return found;
+}
+
+/* called by XRecordProcessReplies() */
+void
+xrecord_callback(XPointer closure, XRecordInterceptData * recorded_data)
+{
+
+    struct xrecord_callback_results *cbres;
+    xEvent *xev;
+    int nxev;
+
+    cbres = (struct xrecord_callback_results *) closure;
+
+    if (recorded_data->category != XRecordFromServer) {
+        XRecordFreeData(recorded_data);
+        return;
+    }
+
+    nxev = recorded_data->data_len / 8;
+    xev = (xEvent *) recorded_data->data;
+    while (nxev--) {
+
+        if ((xev->u.u.type == KeyPress) || (xev->u.u.type == KeyRelease)) {
+            int i;
+            int is_modifier = 0;
+
+            cbres->key_event = 1;       /* remember, a key was pressed or released. */
+
+            /* test if it was a modifier */
+            for (i = 0; i < 8 * cbres->modifiers->max_keypermod; i++) {
+                KeyCode kc = cbres->modifiers->modifiermap[i];
+
+                if (kc == xev->u.u.detail) {
+                    is_modifier = 1;    /* yes, it is a modifier. */
+                    break;
+                }
+            }
+
+            if (is_modifier) {
+                if (xev->u.u.type == KeyPress) {
+                    for (i = 0; i < MAX_MODIFIERS; ++i)
+                        if (!cbres->pressed_modifiers[i]) {
+                            cbres->pressed_modifiers[i] = xev->u.u.detail;
+                            break;
+                        }
+                }
+                else {          /* KeyRelease */
+                    for (i = 0; i < MAX_MODIFIERS; ++i)
+                        if (cbres->pressed_modifiers[i] == xev->u.u.detail)
+                            cbres->pressed_modifiers[i] = 0;
+                }
+
+            }
+            else {
+                /* remember, a non-modifier was pressed. */
+                cbres->non_modifier_event = 1;
+            }
+        }
+
+        xev++;
+    }
+
+    XRecordFreeData(recorded_data);     /* cleanup */
+}
+
+static int
+is_modifier_pressed(const struct xrecord_callback_results *cbres)
+{
+    int i;
+
+    for (i = 0; i < MAX_MODIFIERS; ++i)
+        if (cbres->pressed_modifiers[i])
+            return 1;
+
+    return 0;
+}
+
+void
+record_main_loop(Display * display, double idle_time)
+{
+
+    struct xrecord_callback_results cbres;
+    XRecordContext context;
+    XRecordClientSpec cspec = XRecordAllClients;
+    Display *dpy_data;
+    XRecordRange *range;
+    int i;
+
+    dpy_data = XOpenDisplay(NULL);      /* we need an additional data connection. */
+    range = XRecordAllocRange();
+
+    range->device_events.first = KeyPress;
+    range->device_events.last = KeyRelease;
+
+    context = XRecordCreateContext(dpy_data, 0, &cspec, 1, &range, 1);
+
+    XRecordEnableContextAsync(dpy_data, context, xrecord_callback,
+                              (XPointer) & cbres);
+
+    cbres.modifiers = XGetModifierMapping(display);
+    /* clear list of modifiers */
+    for (i = 0; i < MAX_MODIFIERS; ++i)
+        cbres.pressed_modifiers[i] = 0;
+
+    while (1) {
+
+        int fd = ConnectionNumber(dpy_data);
+        fd_set read_fds;
+        int ret;
+        int disable_event = 0;
+        int modifier_event = 0;
+        struct timeval timeout;
+
+        FD_ZERO(&read_fds);
+        FD_SET(fd, &read_fds);
+
+        ret = select(fd + 1 /* =(max descriptor in read_fds) + 1 */ ,
+                     &read_fds, NULL, NULL,
+                     pad_disabled ? &timeout : NULL
+                     /* timeout only required for enabling */ );
+
+        if (FD_ISSET(fd, &read_fds)) {
+
+            cbres.key_event = 0;
+            cbres.non_modifier_event = 0;
+
+            XRecordProcessReplies(dpy_data);
+
+            /* If there are any events left over, they are in error. Drain them
+             * from the connection queue so we don't get stuck. */
+            while (XEventsQueued(dpy_data, QueuedAlready) > 0) {
+                XEvent event;
+
+                XNextEvent(dpy_data, &event);
+                fprintf(stderr, "bad event received, major opcode %d\n",
+                        event.type);
+            }
+
+            if (!ignore_modifier_keys && cbres.key_event) {
+                disable_event = 1;
+            }
+
+            if (cbres.non_modifier_event) {
+                if (ignore_modifier_combos && is_modifier_pressed(&cbres)) {
+                    modifier_event = 1;
+                } else {
+                    disable_event = 1;
+                }
+            } else if (ignore_modifier_keys) {
+                modifier_event = 1;
+            }
+        }
+
+        if (disable_event) {
+            /* adjust the enable_time */
+            timeout.tv_sec = (int) idle_time;
+            timeout.tv_usec = (idle_time - (double) timeout.tv_sec) * 1.e6;
+
+            toggle_touchpad(False);
+        }
+
+        if (modifier_event && pad_disabled) {
+            toggle_touchpad(True);
+        }
+
+        if (ret == 0 && pad_disabled) { /* timeout => enable event */
+            toggle_touchpad(True);
+        }
+    }                           /* end while(1) */
+
+    XFreeModifiermap(cbres.modifiers);
+}
+#endif                          /* HAVE_X11_EXTENSIONS_RECORD_H */
+
+static XDevice *
+dp_get_device(Display * dpy)
+{
+    XDevice *dev = NULL;
+    XDeviceInfo *info = NULL;
+    int ndevices = 0;
+    Atom touchpad_type = 0;
+    Atom *properties = NULL;
+    int nprops = 0;
+    int error = 0;
+
+    touchpad_type = XInternAtom(dpy, XI_TOUCHPAD, True);
+    touchpad_off_prop = XInternAtom(dpy, SYNAPTICS_PROP_OFF, True);
+    info = XListInputDevices(dpy, &ndevices);
+
+    while (ndevices--) {
+        if (info[ndevices].type == touchpad_type) {
+            dev = XOpenDevice(dpy, info[ndevices].id);
+            if (!dev) {
+                fprintf(stderr, "Failed to open device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            properties = XListDeviceProperties(dpy, dev, &nprops);
+            if (!properties || !nprops) {
+                fprintf(stderr, "No properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            while (nprops--) {
+                if (properties[nprops] == touchpad_off_prop)
+                    break;
+            }
+            if (nprops < 0) {
+                fprintf(stderr, "No synaptics properties on device '%s'.\n",
+                        info[ndevices].name);
+                error = 1;
+                goto unwind;
+            }
+
+            break;              /* Yay, device is suitable */
+        }
+    }
+
+ unwind:
+    XFree(properties);
+    XFreeDeviceList(info);
+    if (error && dev) {
+        XCloseDevice(dpy, dev);
+        dev = NULL;
+    }
+    return dev;
+}
+
+int
+main(int argc, char *argv[])
+{
+    double idle_time = 2.0;
+    int poll_delay = 200000;    /* 200 ms */
+    int c;
+    int use_xrecord = 0;
+
+    /* Parse command line parameters */
+    while ((c = getopt(argc, argv, "i:m:dtp:kKR?v")) != EOF) {
+        switch (c) {
+        case 'i':
+            idle_time = atof(optarg);
+            break;
+        case 'm':
+            poll_delay = atoi(optarg) * 1000;
+            break;
+        case 'd':
+            background = 1;
+            break;
+        case 't':
+            disable_state = TappingOff;
+            break;
+        case 'p':
+            pid_file = optarg;
+            break;
+        case 'k':
+            ignore_modifier_keys = 1;
+            break;
+        case 'K':
+            ignore_modifier_combos = 1;
+            ignore_modifier_keys = 1;
+            break;
+        case 'R':
+            use_xrecord = 1;
+            break;
+        case 'v':
+            verbose = 1;
+            break;
+        case '?':
+        default:
+            usage();
+            break;
+        }
+    }
+    if (idle_time <= 0.0)
+        usage();
+
+    /* Open a connection to the X server */
+    display = XOpenDisplay(NULL);
+    if (!display) {
+        fprintf(stderr, "Can't open display.\n");
+        exit(2);
+    }
+
+    if (!(dev = dp_get_device(display)))
+        exit(2);
+
+    /* Install a signal handler to restore synaptics parameters on exit */
+    install_signal_handler();
+
+    if (background) {
+        pid_t pid;
+
+        if ((pid = fork()) < 0) {
+            perror("fork");
+            exit(3);
+        }
+        else if (pid != 0)
+            exit(0);
+
+        /* Child (daemon) is running here */
+        setsid();               /* Become session leader */
+        chdir("/");             /* In case the file system gets unmounted */
+        umask(0);               /* We don't want any surprises */
+        if (pid_file) {
+            FILE *fd = fopen(pid_file, "w");
+
+            if (!fd) {
+                perror("Can't create pid file");
+                exit(3);
+            }
+            fprintf(fd, "%d\n", getpid());
+            fclose(fd);
+        }
+    }
+
+    pad_disabled = False;
+    store_current_touchpad_state();
+
+#ifdef HAVE_X11_EXTENSIONS_RECORD_H
+    if (use_xrecord) {
+        if (check_xrecord(display))
+            record_main_loop(display, idle_time);
+        else {
+            fprintf(stderr, "Use of XRecord requested, but failed to "
+                    " initialize.\n");
+            exit(4);
+        }
+    }
+    else
+#endif                          /* HAVE_X11_EXTENSIONS_RECORD_H */
+    {
+        setup_keyboard_mask(display, ignore_modifier_keys);
+
+        /* Run the main loop */
+        main_loop(display, idle_time, poll_delay);
+    }
+    return 0;
+}
+
+/* vim: set noexpandtab tabstop=8 shiftwidth=4: */
diff -pruN 1.9.1-1/.pc/128_disable_three_click_action.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/128_disable_three_click_action.patch/src/synaptics.c
--- 1.9.1-1/.pc/128_disable_three_click_action.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/128_disable_three_click_action.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3249 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap */
+    tapButton1 = 1;
+    tapButton2 = 3;
+    tapButton3 = 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+
+    /* Enable vert edge scroll */
+    vertEdgeScroll = TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+#ifdef BUG_RETURN_VAL
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/129_disable_three_touch_tap.patch/src/synaptics.c 1.9.1-1ubuntu3/.pc/129_disable_three_touch_tap.patch/src/synaptics.c
--- 1.9.1-1/.pc/129_disable_three_touch_tap.patch/src/synaptics.c	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/129_disable_three_touch_tap.patch/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,3249 @@
+/*
+ * Copyright  1999 Henry Davies
+ * Copyright  2001 Stefan Gmeiner
+ * Copyright  2002 S. Lehner
+ * Copyright  2002 Peter Osterlund
+ * Copyright  2002 Linuxcare Inc. David Kennedy
+ * Copyright  2003 Hartwig Felger
+ * Copyright  2003 Jrg Bsner
+ * Copyright  2003 Fred Hucht
+ * Copyright  2004 Alexei Gilchrist
+ * Copyright  2004 Matthias Ihmig
+ * Copyright  2006 Stefan Bethge
+ * Copyright  2006 Christian Thaeter
+ * Copyright  2007 Joseph P. Skudlarek
+ * Copyright  2008 Fedor P. Goncharov
+ * Copyright  2008-2012 Red Hat, Inc.
+ * Copyright  2011 The Chromium OS Authors
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ *      Joseph P. Skudlarek <Jskud@Jskud.com>
+ *      Christian Thaeter <chth@gmx.net>
+ *      Stefan Bethge <stefan.bethge@web.de>
+ *      Matthias Ihmig <m.ihmig@gmx.net>
+ *      Alexei Gilchrist <alexei@physics.uq.edu.au>
+ *      Jrg Bsner <ich@joerg-boesner.de>
+ *      Hartwig Felger <hgfelger@hgfelger.de>
+ *      Peter Osterlund <petero2@telia.com>
+ *      S. Lehner <sam_x@bluemail.ch>
+ *      Stefan Gmeiner <riddlebox@freesurf.ch>
+ *      Henry Davies <hdavies@ameritech.net> for the
+ *      Linuxcare Inc. David Kennedy <dkennedy@linuxcare.com>
+ *      Fred Hucht <fred@thp.Uni-Duisburg.de>
+ *      Fedor P. Goncharov <fedgo@gorodok.net>
+ *      Simon Thum <simon.thum@gmx.de>
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <xorg-server.h>
+#include <unistd.h>
+#include <misc.h>
+#include <xf86.h>
+#include <math.h>
+#include <stdio.h>
+#include <xf86_OSproc.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/XI2.h>
+#include <xserver-properties.h>
+#include <ptrveloc.h>
+
+#include "synapticsstr.h"
+#include "synaptics-properties.h"
+
+enum EdgeType {
+    NO_EDGE = 0,
+    BOTTOM_EDGE = 1,
+    TOP_EDGE = 2,
+    LEFT_EDGE = 4,
+    RIGHT_EDGE = 8,
+    LEFT_BOTTOM_EDGE = BOTTOM_EDGE | LEFT_EDGE,
+    RIGHT_BOTTOM_EDGE = BOTTOM_EDGE | RIGHT_EDGE,
+    RIGHT_TOP_EDGE = TOP_EDGE | RIGHT_EDGE,
+    LEFT_TOP_EDGE = TOP_EDGE | LEFT_EDGE
+};
+
+/*
+ * We expect to be receiving a steady 80 packets/sec (which gives 40
+ * reports/sec with more than one finger on the pad, as Advanced Gesture Mode
+ * requires two PS/2 packets per report).  Instead of a random scattering of
+ * magic 13 and 20ms numbers scattered throughout the driver, introduce
+ * POLL_MS as 14ms, which is slightly less than 80Hz.  13ms is closer to
+ * 80Hz, but if the kernel event reporting was even slightly delayed,
+ * we would produce synthetic motion followed immediately by genuine
+ * motion, so use 14.
+ *
+ * We use this to call back at a constant rate to at least produce the
+ * illusion of smooth motion.  It works a lot better than you'd expect.
+*/
+#define POLL_MS 14
+
+#define MAX(a, b) (((a)>(b))?(a):(b))
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define TIME_DIFF(a, b) ((int)((a)-(b)))
+
+#define SQR(x) ((x) * (x))
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define INPUT_BUFFER_SIZE 200
+
+/*****************************************************************************
+ * Forward declaration
+ ****************************************************************************/
+static int SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static void SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags);
+static Bool DeviceControl(DeviceIntPtr, int);
+static void ReadInput(InputInfoPtr);
+static int HandleState(InputInfoPtr, struct SynapticsHwState *, CARD32 now,
+                       Bool from_timer);
+static int ControlProc(InputInfoPtr, xDeviceCtl *);
+static int SwitchMode(ClientPtr, DeviceIntPtr, int);
+static int DeviceInit(DeviceIntPtr);
+static int DeviceOn(DeviceIntPtr);
+static int DeviceOff(DeviceIntPtr);
+static int DeviceClose(DeviceIntPtr);
+static Bool QueryHardware(InputInfoPtr);
+static void ReadDevDimensions(InputInfoPtr);
+#ifndef NO_DRIVER_SCALING
+static void ScaleCoordinates(SynapticsPrivate * priv,
+                             struct SynapticsHwState *hw);
+static void CalculateScalingCoeffs(SynapticsPrivate * priv);
+#endif
+static void SanitizeDimensions(InputInfoPtr pInfo);
+
+void InitDeviceProperties(InputInfoPtr pInfo);
+int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
+                BOOL checkonly);
+
+const static struct {
+    const char *name;
+    struct SynapticsProtocolOperations *proto_ops;
+} protocols[] = {
+#ifdef BUILD_EVENTCOMM
+    { "event", &event_proto_operations },
+#endif
+#ifdef BUILD_PSMCOMM
+    { "psm", &psm_proto_operations },
+#endif
+#ifdef BUILD_PS2COMM
+    { "psaux", &psaux_proto_operations },
+    { "alps", &alps_proto_operations },
+#endif
+    { NULL, NULL }
+};
+
+InputDriverRec SYNAPTICS = {
+    1,
+    "synaptics",
+    NULL,
+    SynapticsPreInit,
+    SynapticsUnInit,
+    NULL,
+    NULL,
+#ifdef XI86_DRV_CAP_SERVER_FD
+    XI86_DRV_CAP_SERVER_FD
+#endif
+};
+
+static XF86ModuleVersionInfo VersionRec = {
+    "synaptics",
+    MODULEVENDORSTRING,
+    MODINFOSTRING1,
+    MODINFOSTRING2,
+    XORG_VERSION_CURRENT,
+    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCHLEVEL,
+    ABI_CLASS_XINPUT,
+    ABI_XINPUT_VERSION,
+    MOD_CLASS_XINPUT,
+    {0, 0, 0, 0}
+};
+
+static pointer
+SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
+{
+    xf86AddInputDriver(&SYNAPTICS, module, 0);
+    return module;
+}
+
+_X_EXPORT XF86ModuleData synapticsModuleData = {
+    &VersionRec,
+    &SetupProc,
+    NULL
+};
+
+/*****************************************************************************
+ *	Function Definitions
+ ****************************************************************************/
+static inline void
+SynapticsCloseFd(InputInfoPtr pInfo)
+{
+    if (pInfo->fd > -1 && !(pInfo->flags & XI86_SERVER_FD)) {
+        xf86CloseSerial(pInfo->fd);
+        pInfo->fd = -1;
+    }
+}
+
+/**
+ * Fill in default dimensions for backends that cannot query the hardware.
+ * Eventually, we want the edges to be 1900/5400 for x, 1900/4000 for y.
+ * These values are based so that calculate_edge_widths() will give us the
+ * right values.
+ *
+ * The default values 1900, etc. come from the dawn of time, when men where
+ * men, or possibly apes.
+ */
+static void
+SanitizeDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->minx >= priv->maxx) {
+        priv->minx = 1615;
+        priv->maxx = 5685;
+        priv->resx = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid x-axis range.  defaulting to %d - %d\n",
+                    priv->minx, priv->maxx);
+    }
+
+    if (priv->miny >= priv->maxy) {
+        priv->miny = 1729;
+        priv->maxy = 4171;
+        priv->resy = 0;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid y-axis range.  defaulting to %d - %d\n",
+                    priv->miny, priv->maxy);
+    }
+
+    if (priv->minp >= priv->maxp) {
+        priv->minp = 0;
+        priv->maxp = 255;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid pressure range.  defaulting to %d - %d\n",
+                    priv->minp, priv->maxp);
+    }
+
+    if (priv->minw >= priv->maxw) {
+        priv->minw = 0;
+        priv->maxw = 15;
+
+        xf86IDrvMsg(pInfo, X_PROBED,
+                    "invalid finger width range.  defaulting to %d - %d\n",
+                    priv->minw, priv->maxw);
+    }
+}
+
+static Bool
+SetDeviceAndProtocol(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    char *proto, *device;
+    int i;
+
+    proto = xf86SetStrOption(pInfo->options, "Protocol", NULL);
+    device = xf86SetStrOption(pInfo->options, "Device", NULL);
+
+    /* If proto is auto-dev, unset and let the code do the rest */
+    if (proto && !strcmp(proto, "auto-dev")) {
+        free(proto);
+        proto = NULL;
+    }
+
+    for (i = 0; protocols[i].name; i++) {
+        if ((!device || !proto) &&
+            protocols[i].proto_ops->AutoDevProbe &&
+            protocols[i].proto_ops->AutoDevProbe(pInfo, device))
+            break;
+        else if (proto && !strcmp(proto, protocols[i].name))
+            break;
+    }
+    free(proto);
+    free(device);
+
+    priv->proto_ops = protocols[i].proto_ops;
+
+    return (priv->proto_ops != NULL);
+}
+
+static void
+calculate_edge_widths(SynapticsPrivate * priv, int *l, int *r, int *t, int *b)
+{
+    int width, height;
+    int ewidth, eheight;        /* edge width/height */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+
+    if (priv->model == MODEL_SYNAPTICS) {
+        ewidth = width * .07;
+        eheight = height * .07;
+    }
+    else if (priv->model == MODEL_ALPS) {
+        ewidth = width * .15;
+        eheight = height * .15;
+    }
+    else if (priv->model == MODEL_APPLETOUCH ||
+             priv->model == MODEL_UNIBODY_MACBOOK) {
+        ewidth = width * .085;
+        eheight = height * .085;
+    }
+    else {
+        ewidth = width * .04;
+        eheight = height * .054;
+    }
+
+    *l = priv->minx + ewidth;
+    *r = priv->maxx - ewidth;
+    *t = priv->miny + eheight;
+    *b = priv->maxy - eheight;
+}
+
+static void
+calculate_tap_hysteresis(SynapticsPrivate * priv, int range,
+                         int *fingerLow, int *fingerHigh)
+{
+    switch (priv->model) {
+    case MODEL_ELANTECH:
+        /* All Elantech touchpads don't need the Z filtering to get the
+         * number of fingers correctly. See Documentation/elantech.txt
+         * in the kernel.
+         */
+        *fingerLow = priv->minp + 1;
+        *fingerHigh = priv->minp + 1;
+        break;
+    case MODEL_UNIBODY_MACBOOK:
+        *fingerLow = 70;
+        *fingerHigh = 75;
+        break;
+    default:
+        *fingerLow = priv->minp + range * (25.0 / 256);
+        *fingerHigh = priv->minp + range * (30.0 / 256);
+        break;
+    }
+}
+
+/* Area options support both percent values and absolute values. This is
+ * awkward. The xf86Set* calls will print to the log, but they'll
+ * also print an error if we request a percent value but only have an
+ * int. So - check first for percent, then call xf86Set* again to get
+ * the log message.
+ */
+static int
+set_percent_option(pointer options, const char *optname,
+                   const int range, const int offset, const int default_value)
+{
+    int result;
+    double percent = xf86CheckPercentOption(options, optname, -1);
+
+    if (percent >= 0.0) {
+        percent = xf86SetPercentOption(options, optname, -1);
+        result = percent / 100.0 * range + offset;
+    } else
+        result = xf86SetIntOption(options, optname, default_value);
+
+    return result;
+}
+
+Bool
+SynapticsIsSoftButtonAreasValid(int *values)
+{
+    Bool right_disabled = FALSE;
+    Bool middle_disabled = FALSE;
+
+    enum {
+        /* right button left, right, top, bottom */
+        RBL = 0,
+        RBR = 1,
+        RBT = 2,
+        RBB = 3,
+        /* middle button left, right, top, bottom */
+        MBL = 4,
+        MBR = 5,
+        MBT = 6,
+        MBB = 7,
+    };
+
+    /* Check right button area */
+    if ((((values[RBL] != 0) && (values[RBR] != 0)) && (values[RBL] > values[RBR])) ||
+        (((values[RBT] != 0) && (values[RBB] != 0)) && (values[RBT] > values[RBB])))
+        return FALSE;
+
+    /* Check middle button area */
+    if ((((values[MBL] != 0) && (values[MBR] != 0)) && (values[MBL] > values[MBR])) ||
+        (((values[MBT] != 0) && (values[MBB] != 0)) && (values[MBT] > values[MBB])))
+        return FALSE;
+
+    if (values[RBL] == 0 && values[RBR] == 0 && values[RBT] == 0 && values[RBB] == 0)
+        right_disabled = TRUE;
+
+    if (values[MBL] == 0 && values[MBR] == 0 && values[MBT] == 0 && values[MBB] == 0)
+        middle_disabled = TRUE;
+
+    if (!right_disabled &&
+        ((values[RBL] && values[RBL] == values[RBR]) ||
+         (values[RBT] && values[RBT] == values[RBB])))
+        return FALSE;
+
+    if (!middle_disabled &&
+        ((values[MBL] && values[MBL] == values[MBR]) ||
+         (values[MBT] && values[MBT] == values[MBB])))
+        return FALSE;
+
+    /* Check for overlapping button areas */
+    if (!right_disabled && !middle_disabled) {
+        int right_left = values[RBL] ? values[RBL] : INT_MIN;
+        int right_right = values[RBR] ? values[RBR] : INT_MAX;
+        int right_top = values[RBT] ? values[RBT] : INT_MIN;
+        int right_bottom = values[RBB] ? values[RBB] : INT_MAX;
+        int middle_left = values[MBL] ? values[MBL] : INT_MIN;
+        int middle_right = values[MBR] ? values[MBR] : INT_MAX;
+        int middle_top = values[MBT] ? values[MBT] : INT_MIN;
+        int middle_bottom = values[MBB] ? values[MBB] : INT_MAX;
+
+        /* If areas overlap in the Y axis */
+        if ((right_bottom <= middle_bottom && right_bottom >= middle_top) ||
+            (right_top <= middle_bottom && right_top >= middle_top)) {
+            /* Check for overlapping left edges */
+            if ((right_left < middle_left && right_right > middle_left) ||
+                (middle_left < right_left && middle_right > right_left))
+                return FALSE;
+
+            /* Check for overlapping right edges */
+            if ((right_right > middle_right && right_left < middle_right) ||
+                (middle_right > right_right && middle_left < right_right))
+                return FALSE;
+        }
+
+        /* If areas overlap in the X axis */
+        if ((right_left >= middle_left && right_left <= middle_right) ||
+            (right_right >= middle_left && right_right <= middle_right)) {
+            /* Check for overlapping top edges */
+            if ((right_top < middle_top && right_bottom > middle_top) ||
+                (middle_top < right_top && middle_bottom > right_top))
+                return FALSE;
+
+            /* Check for overlapping bottom edges */
+            if ((right_bottom > middle_bottom && right_top < middle_bottom) ||
+                (middle_bottom > right_bottom && middle_top < right_bottom))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+set_softbutton_areas_option(InputInfoPtr pInfo, char *option_name, int offset)
+{
+    SynapticsPrivate *priv = pInfo->private;
+    SynapticsParameters *pars = &priv->synpara;
+    int values[8];
+    int in_percent = 0;         /* bitmask for which ones are in % */
+    char *option_string;
+    char *next_num;
+    char *end_str;
+    int i;
+    int width, height;
+
+    if (!pars->clickpad)
+        return;
+
+    option_string = xf86SetStrOption(pInfo->options, option_name, NULL);
+    if (!option_string)
+        return;
+
+    next_num = option_string;
+
+    for (i = 0; i < 8 && *next_num != '\0'; i++) {
+        long int value = strtol(next_num, &end_str, 0);
+
+        if (value > INT_MAX || value < -INT_MAX)
+            goto fail;
+
+        values[i] = value;
+
+        if (next_num != end_str) {
+            if (*end_str == '%') {
+                in_percent |= 1 << i;
+                end_str++;
+            }
+            next_num = end_str;
+        }
+        else
+            goto fail;
+    }
+
+    if (i < 8 || *next_num != '\0')
+        goto fail;
+
+    width = priv->maxx - priv->minx;
+    height = priv->maxy - priv->miny;
+
+    for (i = 0; in_percent && i < 8; i++) {
+        int base, size;
+
+        if ((in_percent & (1 << i)) == 0 || values[i] == 0)
+            continue;
+
+        size = ((i % 4) < 2) ? width : height;
+        base = ((i % 4) < 2) ? priv->minx : priv->miny;
+        values[i] = base + size * values[i] / 100.0;
+    }
+
+    if (!SynapticsIsSoftButtonAreasValid(values))
+        goto fail;
+
+    memcpy(pars->softbutton_areas[offset], values, 4 * sizeof(int));
+    memcpy(pars->softbutton_areas[offset + 1], values + 4, 4 * sizeof(int));
+
+    free(option_string);
+
+    return;
+
+ fail:
+    xf86IDrvMsg(pInfo, X_ERROR,
+                "invalid %s value '%s', keeping defaults\n",
+                option_name, option_string);
+    free(option_string);
+}
+
+static void
+set_primary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SoftButtonAreas", BOTTOM_BUTTON_AREA);
+}
+
+static void
+set_secondary_softbutton_areas_option(InputInfoPtr pInfo)
+{
+    set_softbutton_areas_option(pInfo, "SecondarySoftButtonAreas", TOP_BUTTON_AREA);
+}
+
+static void
+set_default_parameters(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = pInfo->private;    /* read-only */
+    pointer opts = pInfo->options;      /* read-only */
+    SynapticsParameters *pars = &priv->synpara; /* modified */
+
+    int horizScrollDelta, vertScrollDelta;      /* pixels */
+    int tapMove;                /* pixels */
+    int l, r, t, b;             /* left, right, top, bottom */
+    double accelFactor;         /* 1/pixels */
+    int fingerLow, fingerHigh;  /* pressure */
+    int emulateTwoFingerMinZ;   /* pressure */
+    int emulateTwoFingerMinW;   /* width */
+    int pressureMotionMinZ, pressureMotionMaxZ; /* pressure */
+    int palmMinWidth, palmMinZ; /* pressure */
+    int tapButton1, tapButton2, tapButton3;
+    int clickFinger1, clickFinger2, clickFinger3;
+    Bool vertEdgeScroll, horizEdgeScroll;
+    Bool vertTwoFingerScroll, horizTwoFingerScroll;
+    int horizResolution = 1;
+    int vertResolution = 1;
+    int width, height, diag, range;
+    int horizHyst, vertHyst;
+    int middle_button_timeout;
+    int grab_event_device = 0;
+    const char *source;
+
+    /* The synaptics specs specify typical edge widths of 4% on x, and 5.4% on
+     * y (page 7) [Synaptics TouchPad Interfacing Guide, 510-000080 - A
+     * Second Edition, http://www.synaptics.com/support/dev_support.cfm, 8 Sep
+     * 2008]. We use 7% for both instead for synaptics devices, and 15% for
+     * ALPS models.
+     * http://bugs.freedesktop.org/show_bug.cgi?id=21214
+     *
+     * If the range was autodetected, apply these edge widths to all four
+     * sides.
+     */
+
+    width = abs(priv->maxx - priv->minx);
+    height = abs(priv->maxy - priv->miny);
+    diag = sqrt(width * width + height * height);
+
+    calculate_edge_widths(priv, &l, &r, &t, &b);
+
+    /* Again, based on typical x/y range and defaults */
+    horizScrollDelta = diag * .020;
+    vertScrollDelta = diag * .020;
+    tapMove = diag * .044;
+    accelFactor = 200.0 / diag; /* trial-and-error */
+
+    /* hysteresis, assume >= 0 is a detected value (e.g. evdev fuzz) */
+    horizHyst = pars->hyst_x >= 0 ? pars->hyst_x : diag * 0.005;
+    vertHyst = pars->hyst_y >= 0 ? pars->hyst_y : diag * 0.005;
+
+    range = priv->maxp - priv->minp + 1;
+
+    calculate_tap_hysteresis(priv, range, &fingerLow, &fingerHigh);
+
+    /* scaling based on defaults and a pressure of 256 */
+    emulateTwoFingerMinZ = priv->minp + range * (282.0 / 256);
+    pressureMotionMinZ = priv->minp + range * (30.0 / 256);
+    pressureMotionMaxZ = priv->minp + range * (160.0 / 256);
+    palmMinZ = priv->minp + range * (200.0 / 256);
+
+    range = priv->maxw - priv->minw + 1;
+
+    /* scaling based on defaults below and a tool width of 16 */
+    palmMinWidth = priv->minw + range * (10.0 / 16);
+    emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
+
+    /* Enable tap */
+    tapButton1 = 1;
+    tapButton2 = 3;
+    tapButton3 = 2;
+
+    /* Enable multifinger-click if only have one physical button,
+       otherwise clickFinger is always button 1. */
+    clickFinger1 = 1;
+    clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
+    clickFinger3 = 0; /* Disabled by default so three-touch gestures work */
+
+    /* Enable vert edge scroll */
+    vertEdgeScroll = TRUE;
+    horizEdgeScroll = FALSE;
+
+    /* Enable twofinger scroll if we can detect doubletap */
+    vertTwoFingerScroll = priv->has_double ? TRUE : FALSE;
+    horizTwoFingerScroll = FALSE;
+
+    /* Use resolution reported by hardware if available */
+    if ((priv->resx > 0) && (priv->resy > 0)) {
+        horizResolution = priv->resx;
+        vertResolution = priv->resy;
+    }
+
+    /* set the parameters */
+    pars->left_edge = xf86SetIntOption(opts, "LeftEdge", l);
+    pars->right_edge = xf86SetIntOption(opts, "RightEdge", r);
+    pars->top_edge = xf86SetIntOption(opts, "TopEdge", t);
+    pars->bottom_edge = xf86SetIntOption(opts, "BottomEdge", b);
+
+    pars->area_top_edge =
+        set_percent_option(opts, "AreaTopEdge", height, priv->miny, 0);
+    pars->area_bottom_edge =
+        set_percent_option(opts, "AreaBottomEdge", height, priv->miny, 0);
+    pars->area_left_edge =
+        set_percent_option(opts, "AreaLeftEdge", width, priv->minx, 0);
+    pars->area_right_edge =
+        set_percent_option(opts, "AreaRightEdge", width, priv->minx, 0);
+
+    pars->hyst_x =
+        set_percent_option(opts, "HorizHysteresis", width, 0, horizHyst);
+    pars->hyst_y =
+        set_percent_option(opts, "VertHysteresis", height, 0, vertHyst);
+
+    pars->finger_low = xf86SetIntOption(opts, "FingerLow", fingerLow);
+    pars->finger_high = xf86SetIntOption(opts, "FingerHigh", fingerHigh);
+    pars->tap_time = xf86SetIntOption(opts, "MaxTapTime", 180);
+    pars->tap_move = xf86SetIntOption(opts, "MaxTapMove", tapMove);
+    pars->tap_time_2 = xf86SetIntOption(opts, "MaxDoubleTapTime", 180);
+    pars->click_time = xf86SetIntOption(opts, "ClickTime", 100);
+    pars->clickpad = xf86SetBoolOption(opts, "ClickPad", pars->clickpad);       /* Probed */
+    if (pars->clickpad)
+        pars->has_secondary_buttons = xf86SetBoolOption(opts,
+                                                        "HasSecondarySoftButtons",
+                                                        pars->has_secondary_buttons);
+    pars->clickpad_ignore_motion_time = 100; /* ms */
+    /* middle mouse button emulation on a clickpad? nah, you're joking */
+    middle_button_timeout = pars->clickpad ? 0 : 75;
+    pars->emulate_mid_button_time =
+        xf86SetIntOption(opts, "EmulateMidButtonTime", middle_button_timeout);
+    pars->emulate_twofinger_z =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinZ", emulateTwoFingerMinZ);
+    pars->emulate_twofinger_w =
+        xf86SetIntOption(opts, "EmulateTwoFingerMinW", emulateTwoFingerMinW);
+    pars->scroll_dist_vert =
+        xf86SetIntOption(opts, "VertScrollDelta", vertScrollDelta);
+    pars->scroll_dist_horiz =
+        xf86SetIntOption(opts, "HorizScrollDelta", horizScrollDelta);
+    pars->scroll_edge_vert =
+        xf86SetBoolOption(opts, "VertEdgeScroll", vertEdgeScroll);
+    pars->scroll_edge_horiz =
+        xf86SetBoolOption(opts, "HorizEdgeScroll", horizEdgeScroll);
+    pars->scroll_edge_corner = xf86SetBoolOption(opts, "CornerCoasting", FALSE);
+    pars->scroll_twofinger_vert =
+        xf86SetBoolOption(opts, "VertTwoFingerScroll", vertTwoFingerScroll);
+    pars->scroll_twofinger_horiz =
+        xf86SetBoolOption(opts, "HorizTwoFingerScroll", horizTwoFingerScroll);
+    pars->touchpad_off = xf86SetIntOption(opts, "TouchpadOff", TOUCHPAD_ON);
+
+    if (priv->has_scrollbuttons) {
+        pars->updown_button_scrolling =
+            xf86SetBoolOption(opts, "UpDownScrolling", TRUE);
+        pars->leftright_button_scrolling =
+            xf86SetBoolOption(opts, "LeftRightScrolling", TRUE);
+        pars->updown_button_repeat =
+            xf86SetBoolOption(opts, "UpDownScrollRepeat", TRUE);
+        pars->leftright_button_repeat =
+            xf86SetBoolOption(opts, "LeftRightScrollRepeat", TRUE);
+    }
+    pars->scroll_button_repeat =
+        xf86SetIntOption(opts, "ScrollButtonRepeat", 100);
+
+    pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
+    pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3);
+    pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
+    pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
+    pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
+    pars->tap_action[F2_TAP] = xf86SetIntOption(opts, "TapButton2", tapButton2);
+    pars->tap_action[F3_TAP] = xf86SetIntOption(opts, "TapButton3", tapButton3);
+    pars->click_action[F1_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger1", clickFinger1);
+    pars->click_action[F2_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger2", clickFinger2);
+    pars->click_action[F3_CLICK1] =
+        xf86SetIntOption(opts, "ClickFinger3", clickFinger3);
+    pars->circular_scrolling =
+        xf86SetBoolOption(opts, "CircularScrolling", FALSE);
+    pars->circular_trigger = xf86SetIntOption(opts, "CircScrollTrigger", 0);
+    pars->circular_pad = xf86SetBoolOption(opts, "CircularPad", FALSE);
+    pars->palm_detect = xf86SetBoolOption(opts, "PalmDetect", FALSE);
+    pars->palm_min_width = xf86SetIntOption(opts, "PalmMinWidth", palmMinWidth);
+    pars->palm_min_z = xf86SetIntOption(opts, "PalmMinZ", palmMinZ);
+    pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180);
+    pars->press_motion_min_z =
+        xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
+    pars->press_motion_max_z =
+        xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
+
+    pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
+    pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
+    pars->accl = xf86SetRealOption(opts, "AccelFactor", accelFactor);
+    pars->scroll_dist_circ = xf86SetRealOption(opts, "CircScrollDelta", 0.1);
+    pars->coasting_speed = xf86SetRealOption(opts, "CoastingSpeed", 20.0);
+    pars->coasting_friction = xf86SetRealOption(opts, "CoastingFriction", 50);
+    pars->press_motion_min_factor =
+        xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
+    pars->press_motion_max_factor =
+        xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
+
+    /* Only grab the device by default if it's not coming from a config
+       backend. This way we avoid the device being added twice and sending
+       duplicate events.
+      */
+    source = xf86CheckStrOption(opts, "_source", NULL);
+    if (source == NULL || strncmp(source, "server/", 7) != 0)
+        grab_event_device = TRUE;
+    pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", grab_event_device);
+
+    pars->tap_and_drag_gesture =
+        xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
+    pars->resolution_horiz =
+        xf86SetIntOption(opts, "HorizResolution", horizResolution);
+    pars->resolution_vert =
+        xf86SetIntOption(opts, "VertResolution", vertResolution);
+    if (pars->resolution_horiz <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid X resolution, using 1 instead.\n");
+        pars->resolution_horiz = 1;
+    }
+    if (pars->resolution_vert <= 0) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Invalid Y resolution, using 1 instead.\n");
+        pars->resolution_vert = 1;
+    }
+
+    /* Touchpad sampling rate is too low to detect all movements.
+       A user may lift one finger and put another one down within the same
+       EV_SYN or even between samplings so the driver doesn't notice at all.
+
+       We limit the movement to 20 mm within one event, that is more than
+       recordings showed is needed (17mm on a T440).
+      */
+    if (pars->resolution_horiz > 1 &&
+        pars->resolution_vert > 1)
+        pars->maxDeltaMM = 20;
+    else {
+        /* on devices without resolution set the vector length to 0.25 of
+           the touchpad diagonal */
+        pars->maxDeltaMM = diag * 0.25;
+    }
+
+
+    /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
+    if (pars->top_edge > pars->bottom_edge) {
+        int tmp = pars->top_edge;
+
+        pars->top_edge = pars->bottom_edge;
+        pars->bottom_edge = tmp;
+        xf86IDrvMsg(pInfo, X_WARNING,
+                    "TopEdge is bigger than BottomEdge. Fixing.\n");
+    }
+
+    set_primary_softbutton_areas_option(pInfo);
+    if (pars->has_secondary_buttons)
+        set_secondary_softbutton_areas_option(pInfo);
+}
+
+static double
+SynapticsAccelerationProfile(DeviceIntPtr dev,
+                             DeviceVelocityPtr vel,
+                             double velocity, double thr, double acc)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    double accelfct;
+
+    /*
+     * synaptics accel was originally base on device coordinate based
+     * velocity, which we recover this way so para->accl retains its scale.
+     */
+    velocity /= vel->const_acceleration;
+
+    /* speed up linear with finger velocity */
+    accelfct = velocity * para->accl;
+
+    /* clip acceleration factor */
+    if (accelfct > para->max_speed * acc)
+        accelfct = para->max_speed * acc;
+    else if (accelfct < para->min_speed)
+        accelfct = para->min_speed;
+
+    /* modify speed according to pressure */
+    if (priv->moving_state == MS_TOUCHPAD_RELATIVE) {
+        int minZ = para->press_motion_min_z;
+        int maxZ = para->press_motion_max_z;
+        double minFctr = para->press_motion_min_factor;
+        double maxFctr = para->press_motion_max_factor;
+
+        if (priv->hwState->z <= minZ) {
+            accelfct *= minFctr;
+        }
+        else if (priv->hwState->z >= maxZ) {
+            accelfct *= maxFctr;
+        }
+        else {
+            accelfct *=
+                minFctr + (priv->hwState->z - minZ) * (maxFctr -
+                                                       minFctr) / (maxZ - minZ);
+        }
+    }
+
+    return accelfct;
+}
+
+static int
+SynapticsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv;
+
+    /* allocate memory for SynapticsPrivateRec */
+    priv = calloc(1, sizeof(SynapticsPrivate));
+    if (!priv)
+        return BadAlloc;
+
+    pInfo->type_name = XI_TOUCHPAD;
+    pInfo->device_control = DeviceControl;
+    pInfo->read_input = ReadInput;
+    pInfo->control_proc = ControlProc;
+    pInfo->switch_mode = SwitchMode;
+    pInfo->private = priv;
+
+    /* allocate now so we don't allocate in the signal handler */
+    priv->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+    if (!priv->timer) {
+        free(priv);
+        return BadAlloc;
+    }
+
+    /* may change pInfo->options */
+    if (!SetDeviceAndProtocol(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Synaptics driver unable to detect protocol\n");
+        goto SetupProc_fail;
+    }
+
+    priv->device = xf86FindOptionValue(pInfo->options, "Device");
+
+    /* open the touchpad device */
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_ERROR, "Synaptics driver unable to open device\n");
+        goto SetupProc_fail;
+    }
+    xf86ErrorFVerb(6, "port opened successfully\n");
+
+    /* initialize variables */
+    priv->repeatButtons = 0;
+    priv->nextRepeat = 0;
+    priv->count_packet_finger = 0;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->touch_on.millis = 0;
+    priv->synpara.hyst_x = -1;
+    priv->synpara.hyst_y = -1;
+
+    /* read hardware dimensions */
+    ReadDevDimensions(pInfo);
+
+    set_default_parameters(pInfo);
+
+#ifndef NO_DRIVER_SCALING
+    CalculateScalingCoeffs(priv);
+#endif
+
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+
+    if (!QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "Unable to query/initialize Synaptics hardware.\n");
+        goto SetupProc_fail;
+    }
+
+    xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+
+    return Success;
+
+ SetupProc_fail:
+    SynapticsCloseFd(pInfo);
+
+    if (priv->comm.buffer)
+        XisbFree(priv->comm.buffer);
+    free(priv->proto_data);
+    free(priv->timer);
+    free(priv);
+    pInfo->private = NULL;
+    return BadAlloc;
+}
+
+/*
+ *  Uninitialize the device.
+ */
+static void
+SynapticsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+    SynapticsPrivate *priv = ((SynapticsPrivate *) pInfo->private);
+
+    if (priv && priv->timer)
+        free(priv->timer);
+    if (priv && priv->proto_data)
+        free(priv->proto_data);
+    if (priv && priv->scroll_events_mask)
+        valuator_mask_free(&priv->scroll_events_mask);
+    if (priv && priv->open_slots)
+        free(priv->open_slots);
+    free(pInfo->private);
+    pInfo->private = NULL;
+    xf86DeleteInput(pInfo, 0);
+}
+
+/*
+ *  Alter the control parameters for the mouse. Note that all special
+ *  protocol values are handled by dix.
+ */
+static void
+SynapticsCtrl(DeviceIntPtr device, PtrCtrl * ctrl)
+{
+}
+
+static int
+DeviceControl(DeviceIntPtr dev, int mode)
+{
+    Bool RetValue;
+
+    switch (mode) {
+    case DEVICE_INIT:
+        RetValue = DeviceInit(dev);
+        break;
+    case DEVICE_ON:
+        RetValue = DeviceOn(dev);
+        break;
+    case DEVICE_OFF:
+        RetValue = DeviceOff(dev);
+        break;
+    case DEVICE_CLOSE:
+        RetValue = DeviceClose(dev);
+        break;
+    default:
+        RetValue = BadValue;
+    }
+
+    return RetValue;
+}
+
+static int
+DeviceOn(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    DBG(3, "Synaptics DeviceOn called\n");
+
+    pInfo->fd = xf86OpenSerial(pInfo->options);
+    if (pInfo->fd == -1) {
+        xf86IDrvMsg(pInfo, X_WARNING, "cannot open input device\n");
+        return !Success;
+    }
+
+    if (priv->proto_ops->DeviceOnHook &&
+        !priv->proto_ops->DeviceOnHook(pInfo, &priv->synpara))
+         goto error;
+
+    priv->comm.buffer = XisbNew(pInfo->fd, INPUT_BUFFER_SIZE);
+    if (!priv->comm.buffer)
+        goto error;
+
+    xf86FlushInput(pInfo->fd);
+
+    /* reinit the pad */
+    if (!QueryHardware(pInfo))
+        goto error;
+
+    xf86AddEnabledDevice(pInfo);
+    dev->public.on = TRUE;
+
+    return Success;
+
+error:
+    if (priv->comm.buffer) {
+        XisbFree(priv->comm.buffer);
+        priv->comm.buffer = NULL;
+    }
+    SynapticsCloseFd(pInfo);
+    return !Success;
+}
+
+static void
+SynapticsReset(SynapticsPrivate * priv)
+{
+    int i;
+
+    SynapticsResetHwState(priv->hwState);
+    SynapticsResetHwState(priv->local_hw_state);
+    SynapticsResetHwState(priv->comm.hwState);
+
+    memset(priv->move_hist, 0, sizeof(priv->move_hist));
+    priv->hyst_center_x = 0;
+    priv->hyst_center_y = 0;
+    memset(&priv->scroll, 0, sizeof(priv->scroll));
+    priv->count_packet_finger = 0;
+    priv->finger_state = FS_UNTOUCHED;
+    priv->last_motion_millis = 0;
+    priv->clickpad_click_millis = 0;
+    priv->last_button_area = NO_BUTTON_AREA;
+    priv->tap_state = TS_START;
+    priv->tap_button = 0;
+    priv->tap_button_state = TBS_BUTTON_UP;
+    priv->moving_state = MS_FALSE;
+    priv->vert_scroll_edge_on = FALSE;
+    priv->horiz_scroll_edge_on = FALSE;
+    priv->vert_scroll_twofinger_on = FALSE;
+    priv->horiz_scroll_twofinger_on = FALSE;
+    priv->circ_scroll_on = FALSE;
+    priv->circ_scroll_vert = FALSE;
+    priv->mid_emu_state = MBE_OFF;
+    priv->nextRepeat = 0;
+    priv->lastButtons = 0;
+    priv->prev_z = 0;
+    priv->prevFingers = 0;
+    priv->num_active_touches = 0;
+
+    for (i = 0; i < priv->num_slots; i++)
+        priv->open_slots[i] = -1;
+}
+
+static int
+DeviceOff(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Bool rc = Success;
+
+    DBG(3, "Synaptics DeviceOff called\n");
+
+    if (pInfo->fd != -1) {
+        TimerCancel(priv->timer);
+        xf86RemoveEnabledDevice(pInfo);
+        SynapticsReset(priv);
+
+        if (priv->proto_ops->DeviceOffHook &&
+            !priv->proto_ops->DeviceOffHook(pInfo))
+            rc = !Success;
+        if (priv->comm.buffer) {
+            XisbFree(priv->comm.buffer);
+            priv->comm.buffer = NULL;
+        }
+        SynapticsCloseFd(pInfo);
+    }
+    dev->public.on = FALSE;
+    return rc;
+}
+
+static int
+DeviceClose(DeviceIntPtr dev)
+{
+    Bool RetValue;
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    RetValue = DeviceOff(dev);
+    TimerFree(priv->timer);
+    priv->timer = NULL;
+    free(priv->touch_axes);
+    priv->touch_axes = NULL;
+    SynapticsHwStateFree(&priv->hwState);
+    SynapticsHwStateFree(&priv->local_hw_state);
+    SynapticsHwStateFree(&priv->comm.hwState);
+    return RetValue;
+}
+
+static void
+InitAxesLabels(Atom *labels, int nlabels, const SynapticsPrivate * priv)
+{
+    int i;
+
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 4:
+        labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+    case 3:
+        labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+    case 2:
+        labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+    case 1:
+        labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+        break;
+    }
+
+    for (i = 0; i < priv->num_mt_axes; i++) {
+        SynapticsTouchAxisRec *axis = &priv->touch_axes[i];
+        int axnum = nlabels - priv->num_mt_axes + i;
+
+        labels[axnum] = XIGetKnownProperty(axis->label);
+    }
+}
+
+static void
+InitButtonLabels(Atom *labels, int nlabels)
+{
+    memset(labels, 0, nlabels * sizeof(Atom));
+    switch (nlabels) {
+    default:
+    case 7:
+        labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+    case 6:
+        labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+    case 5:
+        labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+    case 4:
+        labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+    case 3:
+        labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+    case 2:
+        labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+    case 1:
+        labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+        break;
+    }
+}
+
+static void
+DeviceInitTouch(DeviceIntPtr dev, Atom *axes_labels)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    if (!priv->has_touch)
+        return;
+
+    priv->num_slots =
+        priv->max_touches ? priv->max_touches : SYNAPTICS_MAX_TOUCHES;
+
+    priv->open_slots = malloc(priv->num_slots * sizeof(int));
+    if (!priv->open_slots) {
+        xf86IDrvMsg(pInfo, X_ERROR,
+                    "failed to allocate open touch slots array\n");
+        priv->has_touch = 0;
+        priv->num_slots = 0;
+    }
+}
+
+static int
+DeviceInit(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    Atom float_type, prop;
+    float tmpf;
+    unsigned char map[SYN_MAX_BUTTONS + 1];
+    int i;
+    int min, max;
+    int num_axes = 2;
+    Atom btn_labels[SYN_MAX_BUTTONS] = { 0 };
+    Atom *axes_labels;
+    DeviceVelocityPtr pVel;
+
+    num_axes += 2;
+
+    num_axes += priv->num_mt_axes;
+
+    axes_labels = calloc(num_axes, sizeof(Atom));
+    if (!axes_labels) {
+        xf86IDrvMsg(pInfo, X_ERROR, "failed to allocate axis labels\n");
+        return !Success;
+    }
+
+    InitAxesLabels(axes_labels, num_axes, priv);
+    InitButtonLabels(btn_labels, SYN_MAX_BUTTONS);
+
+    DBG(3, "Synaptics DeviceInit called\n");
+
+    for (i = 0; i <= SYN_MAX_BUTTONS; i++)
+        map[i] = i;
+
+    dev->public.on = FALSE;
+
+    InitPointerDeviceStruct((DevicePtr) dev, map,
+                            SYN_MAX_BUTTONS,
+                            btn_labels,
+                            SynapticsCtrl,
+                            GetMotionHistorySize(), num_axes, axes_labels);
+
+    /*
+     * setup dix acceleration to match legacy synaptics settings, and
+     * etablish a device-specific profile to do stuff like pressure-related
+     * acceleration.
+     */
+    if (NULL != (pVel = GetDevicePredictableAccelData(dev))) {
+        SetDeviceSpecificAccelerationProfile(pVel,
+                                             SynapticsAccelerationProfile);
+
+        /* float property type */
+        float_type = XIGetKnownProperty(XATOM_FLOAT);
+
+        /* translate MinAcc to constant deceleration.
+         * May be overridden in xf86InitValuatorDefaults */
+        tmpf = 1.0 / priv->synpara.min_speed;
+
+        xf86IDrvMsg(pInfo, X_CONFIG,
+                    "(accel) MinSpeed is now constant deceleration " "%.1f\n",
+                    tmpf);
+        prop = XIGetKnownProperty(ACCEL_PROP_CONSTANT_DECELERATION);
+        XIChangeDeviceProperty(dev, prop, float_type, 32,
+                               PropModeReplace, 1, &tmpf, FALSE);
+
+        /* adjust accordingly */
+        priv->synpara.max_speed /= priv->synpara.min_speed;
+        priv->synpara.min_speed = 1.0;
+
+        /* synaptics seems to report 80 packet/s, but dix scales for
+         * 100 packet/s by default. */
+        pVel->corr_mul = 12.5f; /*1000[ms]/80[/s] = 12.5 */
+
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) MaxSpeed is now %.2f\n",
+                    priv->synpara.max_speed);
+        xf86IDrvMsg(pInfo, X_CONFIG, "(accel) AccelFactor is now %.3f\n",
+                    priv->synpara.accl);
+
+        prop = XIGetKnownProperty(ACCEL_PROP_PROFILE_NUMBER);
+        i = AccelProfileDeviceSpecific;
+        XIChangeDeviceProperty(dev, prop, XA_INTEGER, 32,
+                               PropModeReplace, 1, &i, FALSE);
+    }
+
+    /* X valuator */
+    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
+        min = priv->minx;
+        max = priv->maxx;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 0, axes_labels[0], min, max,
+			       priv->resx * 1000, 0, priv->resx * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 0);
+
+    /* Y valuator */
+    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
+        min = priv->miny;
+        max = priv->maxy;
+    }
+    else {
+        min = 0;
+        max = -1;
+    }
+
+    xf86InitValuatorAxisStruct(dev, 1, axes_labels[1], min, max,
+			       priv->resy * 1000, 0, priv->resy * 1000,
+			       Relative);
+    xf86InitValuatorDefaults(dev, 1);
+
+    xf86InitValuatorAxisStruct(dev, 2, axes_labels[2], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_horiz = 2;
+    xf86InitValuatorAxisStruct(dev, 3, axes_labels[3], 0, -1, 0, 0, 0,
+                               Relative);
+    priv->scroll_axis_vert = 3;
+    priv->scroll_events_mask = valuator_mask_new(MAX_VALUATORS);
+    if (!priv->scroll_events_mask) {
+        free(axes_labels);
+        return !Success;
+    }
+
+    SetScrollValuator(dev, priv->scroll_axis_horiz, SCROLL_TYPE_HORIZONTAL,
+                      priv->synpara.scroll_dist_horiz, 0);
+    SetScrollValuator(dev, priv->scroll_axis_vert, SCROLL_TYPE_VERTICAL,
+                      priv->synpara.scroll_dist_vert, 0);
+
+    DeviceInitTouch(dev, axes_labels);
+
+    free(axes_labels);
+
+    priv->hwState = SynapticsHwStateAlloc(priv);
+    if (!priv->hwState)
+        goto fail;
+
+    priv->local_hw_state = SynapticsHwStateAlloc(priv);
+    if (!priv->local_hw_state)
+        goto fail;
+
+    priv->comm.hwState = SynapticsHwStateAlloc(priv);
+
+    InitDeviceProperties(pInfo);
+    XIRegisterPropertyHandler(pInfo->dev, SetProperty, NULL, NULL);
+
+    SynapticsReset(priv);
+
+    return Success;
+
+ fail:
+    free(priv->local_hw_state);
+    free(priv->hwState);
+    free(priv->open_slots);
+    return !Success;
+}
+
+/*
+ * Convert from absolute X/Y coordinates to a coordinate system where
+ * -1 corresponds to the left/upper edge and +1 corresponds to the
+ * right/lower edge.
+ */
+static void
+relative_coords(SynapticsPrivate * priv, int x, int y,
+                double *relX, double *relY)
+{
+    int minX = priv->synpara.left_edge;
+    int maxX = priv->synpara.right_edge;
+    int minY = priv->synpara.top_edge;
+    int maxY = priv->synpara.bottom_edge;
+    double xCenter = (minX + maxX) / 2.0;
+    double yCenter = (minY + maxY) / 2.0;
+
+    if ((maxX - xCenter > 0) && (maxY - yCenter > 0)) {
+        *relX = (x - xCenter) / (maxX - xCenter);
+        *relY = (y - yCenter) / (maxY - yCenter);
+    }
+    else {
+        *relX = 0;
+        *relY = 0;
+    }
+}
+
+/* return angle of point relative to center */
+static double
+angle(SynapticsPrivate * priv, int x, int y)
+{
+    double xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2.0;
+    double yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2.0;
+
+    return atan2(-(y - yCenter), x - xCenter);
+}
+
+/* return angle difference */
+static double
+diffa(double a1, double a2)
+{
+    double da = fmod(a2 - a1, 2 * M_PI);
+
+    if (da < 0)
+        da += 2 * M_PI;
+    if (da > M_PI)
+        da -= 2 * M_PI;
+    return da;
+}
+
+static enum EdgeType
+circular_edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = 0;
+    double relX, relY, relR;
+
+    relative_coords(priv, x, y, &relX, &relY);
+    relR = SQR(relX) + SQR(relY);
+
+    if (relR > 1) {
+        /* we are outside the ellipse enclosed by the edge parameters */
+        if (relX > M_SQRT1_2)
+            edge |= RIGHT_EDGE;
+        else if (relX < -M_SQRT1_2)
+            edge |= LEFT_EDGE;
+
+        if (relY < -M_SQRT1_2)
+            edge |= TOP_EDGE;
+        else if (relY > M_SQRT1_2)
+            edge |= BOTTOM_EDGE;
+    }
+
+    return edge;
+}
+
+static enum EdgeType
+edge_detection(SynapticsPrivate * priv, int x, int y)
+{
+    enum EdgeType edge = NO_EDGE;
+
+    if (priv->synpara.circular_pad)
+        return circular_edge_detection(priv, x, y);
+
+    if (x > priv->synpara.right_edge)
+        edge |= RIGHT_EDGE;
+    else if (x < priv->synpara.left_edge)
+        edge |= LEFT_EDGE;
+
+    if (y < priv->synpara.top_edge)
+        edge |= TOP_EDGE;
+    else if (y > priv->synpara.bottom_edge)
+        edge |= BOTTOM_EDGE;
+
+    return edge;
+}
+
+/* Checks whether coordinates are in the Synaptics Area
+ * or not. If no Synaptics Area is defined (i.e. if
+ * priv->synpara.area_{left|right|top|bottom}_edge are
+ * all set to zero), the function returns TRUE.
+ */
+static Bool
+is_inside_active_area(SynapticsPrivate * priv, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    /* If a finger is down, then it must have started inside the active_area,
+       allow the motion to complete using the entire area */
+    if (priv->finger_state >= FS_TOUCHED)
+        return TRUE;
+
+    if ((priv->synpara.area_left_edge != 0) &&
+        (x < priv->synpara.area_left_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_right_edge != 0) &&
+             (x > priv->synpara.area_right_edge))
+        inside_area = FALSE;
+
+    if ((priv->synpara.area_top_edge != 0) && (y < priv->synpara.area_top_edge))
+        inside_area = FALSE;
+    else if ((priv->synpara.area_bottom_edge != 0) &&
+             (y > priv->synpara.area_bottom_edge))
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_button_area(SynapticsParameters * para, int which, int x, int y)
+{
+    Bool inside_area = TRUE;
+
+    if (para->softbutton_areas[which][LEFT] == 0 &&
+        para->softbutton_areas[which][RIGHT] == 0 &&
+        para->softbutton_areas[which][TOP] == 0 &&
+        para->softbutton_areas[which][BOTTOM] == 0)
+        return FALSE;
+
+    if (para->softbutton_areas[which][LEFT] &&
+        x < para->softbutton_areas[which][LEFT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][RIGHT] &&
+             x > para->softbutton_areas[which][RIGHT])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][TOP] &&
+             y < para->softbutton_areas[which][TOP])
+        inside_area = FALSE;
+    else if (para->softbutton_areas[which][BOTTOM] &&
+             y > para->softbutton_areas[which][BOTTOM])
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static Bool
+is_inside_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, BOTTOM_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_rightbutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_RIGHT_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_sec_middlebutton_area(SynapticsParameters * para, int x, int y)
+{
+    return is_inside_button_area(para, TOP_MIDDLE_BUTTON_AREA, x, y);
+}
+
+static Bool
+is_inside_top_or_bottom_button_area(SynapticsParameters * para, int offset,
+                                    int x, int y)
+{
+    Bool inside_area = TRUE;
+    Bool right_valid, middle_valid;
+    int top, bottom;
+
+    /* We don't have a left button area, so we only check the y axis */
+    right_valid = para->softbutton_areas[offset][TOP] ||
+                  para->softbutton_areas[offset][BOTTOM];
+    middle_valid = para->softbutton_areas[offset + 1][TOP] ||
+                   para->softbutton_areas[offset + 1][BOTTOM];
+
+    if (!right_valid && !middle_valid)
+        return FALSE;
+
+    /* Check both buttons are horizontally aligned */
+    if (right_valid && middle_valid && (
+            para->softbutton_areas[offset][TOP] !=
+                para->softbutton_areas[offset + 1][TOP] ||
+            para->softbutton_areas[offset][BOTTOM] !=
+                para->softbutton_areas[offset + 1][BOTTOM]))
+        return FALSE;
+
+    if (right_valid) {
+        top    = para->softbutton_areas[offset][TOP];
+        bottom = para->softbutton_areas[offset][BOTTOM];
+    }
+    else {
+        top    = para->softbutton_areas[offset + 1][TOP];
+        bottom = para->softbutton_areas[offset + 1][BOTTOM];
+    }
+
+    if (top && y < top)
+        inside_area = FALSE;
+    else if (bottom && y > bottom)
+        inside_area = FALSE;
+
+    return inside_area;
+}
+
+static enum SoftButtonAreas
+current_button_area(SynapticsParameters * para, int x, int y)
+{
+    if (is_inside_top_or_bottom_button_area(para, BOTTOM_BUTTON_AREA, x, y))
+        return BOTTOM_BUTTON_AREA;
+    else if (is_inside_top_or_bottom_button_area(para, TOP_BUTTON_AREA, x, y))
+        return TOP_BUTTON_AREA;
+    else
+        return NO_BUTTON_AREA;
+}
+
+static CARD32
+timerFunc(OsTimerPtr timer, CARD32 now, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay;
+#if !HAVE_THREADED_INPUT
+    int sigstate = xf86BlockSIGIO();
+#else
+    input_lock();
+#endif
+
+    priv->hwState->millis += now - priv->timer_time;
+    SynapticsCopyHwState(hw, priv->hwState);
+    SynapticsResetTouchHwState(hw, FALSE);
+    delay = HandleState(pInfo, hw, hw->millis, TRUE);
+
+    priv->timer_time = now;
+    priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+
+#if !HAVE_THREADED_INPUT
+    xf86UnblockSIGIO(sigstate);
+#else
+    input_unlock();
+#endif
+
+    return 0;
+}
+
+static int
+clamp(int val, int min, int max)
+{
+    if (val < min)
+        return min;
+    else if (val < max)
+        return val;
+    else
+        return max;
+}
+
+static Bool
+SynapticsGetHwState(InputInfoPtr pInfo, SynapticsPrivate * priv,
+                    struct SynapticsHwState *hw)
+{
+    return priv->proto_ops->ReadHwState(pInfo, &priv->comm, hw);
+}
+
+/*
+ *  called for each full received packet from the touchpad
+ */
+static void
+ReadInput(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    struct SynapticsHwState *hw = priv->local_hw_state;
+    int delay = 0;
+    Bool newDelay = FALSE;
+
+    SynapticsResetTouchHwState(hw, FALSE);
+
+    while (SynapticsGetHwState(pInfo, priv, hw)) {
+        /* Semi-mt device touch slots do not track touches. When there is a
+         * change in the number of touches, we must disregard the temporary
+         * motion changes. */
+        if (priv->has_semi_mt && hw->numFingers != priv->hwState->numFingers) {
+            hw->cumulative_dx = priv->hwState->cumulative_dx;
+            hw->cumulative_dy = priv->hwState->cumulative_dy;
+        }
+
+        /* timer may cause actual events to lag behind (#48777) */
+        if (priv->hwState->millis > hw->millis)
+            hw->millis = priv->hwState->millis;
+
+        SynapticsCopyHwState(priv->hwState, hw);
+        delay = HandleState(pInfo, hw, hw->millis, FALSE);
+        newDelay = TRUE;
+    }
+
+    if (newDelay) {
+        priv->timer_time = GetTimeInMillis();
+        priv->timer = TimerSet(priv->timer, 0, delay, timerFunc, pInfo);
+    }
+}
+
+static int
+HandleMidButtonEmulation(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                         CARD32 now, int *delay)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool done = FALSE;
+    int timeleft;
+    int mid = 0;
+
+    if (para->emulate_mid_button_time <= 0)
+        return mid;
+
+    while (!done) {
+        switch (priv->mid_emu_state) {
+        case MBE_LEFT_CLICK:
+        case MBE_RIGHT_CLICK:
+        case MBE_OFF:
+            priv->button_delay_millis = now;
+            if (hw->left) {
+                priv->mid_emu_state = MBE_LEFT;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_RIGHT;
+            }
+            else {
+                done = TRUE;
+            }
+            break;
+        case MBE_LEFT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->left) {
+                priv->mid_emu_state = MBE_LEFT_CLICK;
+                done = TRUE;
+            }
+            else if ((!hw->left) || (timeleft <= 0)) {
+                hw->left = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->right) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->left = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_RIGHT:
+            timeleft =
+                TIME_DIFF(priv->button_delay_millis +
+                          para->emulate_mid_button_time, now);
+            if (timeleft > 0)
+                *delay = MIN(*delay, timeleft);
+
+            /* timeout, but within the same ReadInput cycle! */
+            if ((timeleft <= 0) && !hw->right) {
+                priv->mid_emu_state = MBE_RIGHT_CLICK;
+                done = TRUE;
+            }
+            else if (!hw->right || (timeleft <= 0)) {
+                hw->right = TRUE;
+                priv->mid_emu_state = MBE_TIMEOUT;
+                done = TRUE;
+            }
+            else if (hw->left) {
+                priv->mid_emu_state = MBE_MID;
+            }
+            else {
+                hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_MID:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                mid = TRUE;
+                hw->left = hw->right = FALSE;
+                done = TRUE;
+            }
+            break;
+        case MBE_TIMEOUT:
+            if (!hw->left && !hw->right) {
+                priv->mid_emu_state = MBE_OFF;
+            }
+            else {
+                done = TRUE;
+            }
+        }
+    }
+    return mid;
+}
+
+static enum FingerState
+SynapticsDetectFinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger;
+
+    /* finger detection thru pressure and threshold */
+    if (hw->z < para->finger_low)
+        return FS_UNTOUCHED;
+
+    if (priv->finger_state == FS_BLOCKED)
+        return FS_BLOCKED;
+
+    if (hw->z > para->finger_high && priv->finger_state == FS_UNTOUCHED)
+        finger = FS_TOUCHED;
+    else
+        finger = priv->finger_state;
+
+    if (!para->palm_detect)
+        return finger;
+
+    /* palm detection */
+
+    if ((hw->z > para->palm_min_z) && (hw->fingerWidth > para->palm_min_width))
+        return FS_BLOCKED;
+
+    if (priv->has_mt_palm_detect)
+        return finger;
+
+    if (hw->x == 0 || priv->finger_state == FS_UNTOUCHED)
+        priv->avg_width = 0;
+    else
+        priv->avg_width += (hw->fingerWidth - priv->avg_width + 1) / 2;
+
+    if (finger != FS_UNTOUCHED && priv->finger_state == FS_UNTOUCHED) {
+        int safe_width = MAX(hw->fingerWidth, priv->avg_width);
+
+        if (hw->numFingers > 1 ||       /* more than one finger -> not a palm */
+            ((safe_width < 6) && (priv->prev_z < para->finger_high)) || /* thin finger, distinct touch -> not a palm */
+            ((safe_width < 7) && (priv->prev_z < para->finger_high / 2))) {     /* thin finger, distinct touch -> not a palm */
+            /* leave finger value as is */
+        }
+        else if (hw->z > priv->prev_z + 1)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->z < priv->prev_z - 5)      /* z not stable, may be a palm */
+            finger = FS_UNTOUCHED;
+        else if (hw->fingerWidth > para->palm_min_width)        /* finger width too large -> probably palm */
+            finger = FS_UNTOUCHED;
+    }
+    priv->prev_z = hw->z;
+
+    return finger;
+}
+
+static void
+SelectTapButton(SynapticsPrivate * priv, enum EdgeType edge)
+{
+    enum TapEvent tap;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF) {
+        priv->tap_button = 0;
+        return;
+    }
+
+    switch (priv->tap_max_fingers) {
+    case 1:
+        switch (edge) {
+        case RIGHT_TOP_EDGE:
+            DBG(7, "right top edge\n");
+            tap = RT_TAP;
+            break;
+        case RIGHT_BOTTOM_EDGE:
+            DBG(7, "right bottom edge\n");
+            tap = RB_TAP;
+            break;
+        case LEFT_TOP_EDGE:
+            DBG(7, "left top edge\n");
+            tap = LT_TAP;
+            break;
+        case LEFT_BOTTOM_EDGE:
+            DBG(7, "left bottom edge\n");
+            tap = LB_TAP;
+            break;
+        default:
+            DBG(7, "no edge\n");
+            tap = F1_TAP;
+            break;
+        }
+        break;
+    case 2:
+        DBG(7, "two finger tap\n");
+        tap = F2_TAP;
+        break;
+    case 3:
+        DBG(7, "three finger tap\n");
+        tap = F3_TAP;
+        break;
+    default:
+        priv->tap_button = 0;
+        return;
+    }
+
+    priv->tap_button = priv->synpara.tap_action[tap];
+    priv->tap_button = clamp(priv->tap_button, 0, SYN_MAX_BUTTONS);
+}
+
+static void
+SetTapState(SynapticsPrivate * priv, enum TapState tap_state, CARD32 millis)
+{
+    DBG(3, "SetTapState - %d -> %d (millis:%u)\n", priv->tap_state, tap_state,
+        millis);
+    switch (tap_state) {
+    case TS_START:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        priv->tap_max_fingers = 0;
+        break;
+    case TS_1:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2A:
+	priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_2B:
+        priv->tap_button_state = TBS_BUTTON_UP;
+        break;
+    case TS_3:
+        priv->tap_button_state = TBS_BUTTON_DOWN;
+        break;
+    case TS_SINGLETAP:
+	priv->tap_button_state = TBS_BUTTON_DOWN;
+        priv->touch_on.millis = millis;
+        break;
+    default:
+        break;
+    }
+    priv->tap_state = tap_state;
+}
+
+static void
+SetMovingState(SynapticsPrivate * priv, enum MovingState moving_state,
+               CARD32 millis)
+{
+    DBG(7, "SetMovingState - %d -> %d center at %d/%d (millis:%u)\n",
+        priv->moving_state, moving_state, priv->hwState->x, priv->hwState->y,
+        millis);
+
+    priv->moving_state = moving_state;
+}
+
+static int
+GetTimeOut(SynapticsPrivate * priv)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    switch (priv->tap_state) {
+    case TS_1:
+    case TS_3:
+    case TS_5:
+        return para->tap_time;
+    case TS_SINGLETAP:
+        return para->click_time;
+    case TS_2A:
+        return para->single_tap_timeout;
+    case TS_2B:
+        return para->tap_time_2;
+    case TS_4:
+        return para->locked_drag_time;
+    default:
+        return -1;              /* No timeout */
+    }
+}
+
+static int
+HandleTapProcessing(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                    CARD32 now, enum FingerState finger,
+                    Bool inside_active_area)
+{
+    SynapticsParameters *para = &priv->synpara;
+    Bool touch, release, is_timeout, move, press;
+    int timeleft, timeout;
+    enum EdgeType edge;
+    int delay = 1000000000;
+
+    if (para->touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED)
+        return delay;
+
+    touch = finger >= FS_TOUCHED && priv->finger_state == FS_UNTOUCHED;
+    release = finger == FS_UNTOUCHED && priv->finger_state >= FS_TOUCHED;
+    move = (finger >= FS_TOUCHED &&
+            (priv->tap_max_fingers <=
+             ((priv->horiz_scroll_twofinger_on ||
+               priv->vert_scroll_twofinger_on) ? 2 : 1)) &&
+            (priv->prevFingers == hw->numFingers &&
+             ((abs(hw->x - priv->touch_on.x) >= para->tap_move) ||
+              (abs(hw->y - priv->touch_on.y) >= para->tap_move))));
+    press = (hw->left || hw->right || hw->middle);
+
+    if (touch) {
+        priv->touch_on.x = hw->x;
+        priv->touch_on.y = hw->y;
+        priv->touch_on.millis = now;
+    }
+    else if (release) {
+        priv->touch_on.millis = now;
+    }
+    if (hw->z > para->finger_high)
+        if (priv->tap_max_fingers < hw->numFingers)
+            priv->tap_max_fingers = hw->numFingers;
+    timeout = GetTimeOut(priv);
+    timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+    is_timeout = timeleft <= 0;
+
+ restart:
+    switch (priv->tap_state) {
+    case TS_START:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        break;
+    case TS_1:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move) {
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (finger == FS_TOUCHED) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+            }
+            SetTapState(priv, TS_MOVE, now);
+            goto restart;
+        }
+        else if (release) {
+            edge = edge_detection(priv, priv->touch_on.x, priv->touch_on.y);
+            SelectTapButton(priv, edge);
+            /* Disable taps outside of the active area */
+            if (!inside_active_area) {
+                priv->tap_button = 0;
+            }
+            SetTapState(priv, TS_2A, now);
+        }
+        break;
+    case TS_MOVE:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_2A:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_2B:
+        if (touch)
+            SetTapState(priv, TS_3, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_SINGLETAP, now);
+        break;
+    case TS_SINGLETAP:
+        if (touch)
+            SetTapState(priv, TS_1, now);
+        else if (is_timeout)
+            SetTapState(priv, TS_START, now);
+        break;
+    case TS_3:
+        if (move) {
+            if (para->tap_and_drag_gesture) {
+                SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (is_timeout) {
+            if (para->tap_and_drag_gesture) {
+                if (finger == FS_TOUCHED) {
+                    SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+                }
+                SetTapState(priv, TS_DRAG, now);
+            }
+            else {
+                SetTapState(priv, TS_1, now);
+            }
+            goto restart;
+        }
+        else if (release) {
+            SetTapState(priv, TS_2B, now);
+        }
+        break;
+    case TS_DRAG:
+        if (para->clickpad && press) {
+            SetTapState(priv, TS_CLICKPAD_MOVE, now);
+            goto restart;
+        }
+        if (move)
+            SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            if (para->locked_drags) {
+                SetTapState(priv, TS_4, now);
+            }
+            else {
+                SetTapState(priv, TS_START, now);
+            }
+        }
+        break;
+    case TS_4:
+        if (is_timeout) {
+            SetTapState(priv, TS_START, now);
+            goto restart;
+        }
+        if (touch)
+            SetTapState(priv, TS_5, now);
+        break;
+    case TS_5:
+        if (is_timeout || move) {
+            SetTapState(priv, TS_DRAG, now);
+            goto restart;
+        }
+        else if (release) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_START, now);
+        }
+        break;
+    case TS_CLICKPAD_MOVE:
+        /* Disable scrolling once a button is pressed on a clickpad */
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+
+        /* Assume one touch is only for holding the clickpad button down */
+        if (hw->numFingers > 1)
+            hw->numFingers--;
+        SetMovingState(priv, MS_TOUCHPAD_RELATIVE, now);
+        if (!press) {
+            SetMovingState(priv, MS_FALSE, now);
+            SetTapState(priv, TS_MOVE, now);
+            priv->count_packet_finger = 0;
+        }
+        break;
+    }
+
+    timeout = GetTimeOut(priv);
+    if (timeout >= 0) {
+        timeleft = TIME_DIFF(priv->touch_on.millis + timeout, now);
+        delay = clamp(timeleft, 1, delay);
+    }
+    return delay;
+}
+
+#define HIST(a) (priv->move_hist[((priv->hist_index - (a) + SYNAPTICS_MOVE_HISTORY) % SYNAPTICS_MOVE_HISTORY)])
+#define HIST_DELTA(a, b, e) ((HIST((a)).e) - (HIST((b)).e))
+
+static void
+store_history(SynapticsPrivate * priv, int x, int y, CARD32 millis)
+{
+    int idx = (priv->hist_index + 1) % SYNAPTICS_MOVE_HISTORY;
+
+    priv->move_hist[idx].x = x;
+    priv->move_hist[idx].y = y;
+    priv->move_hist[idx].millis = millis;
+    priv->hist_index = idx;
+    if (priv->count_packet_finger < SYNAPTICS_MOVE_HISTORY)
+        priv->count_packet_finger++;
+}
+
+/*
+ * Estimate the slope for the data sequence [x3, x2, x1, x0] by using
+ * linear regression to fit a line to the data and use the slope of the
+ * line.
+ */
+static double
+estimate_delta(double x0, double x1, double x2, double x3)
+{
+    return x0 * 0.3 + x1 * 0.1 - x2 * 0.1 - x3 * 0.3;
+}
+
+/**
+ * Applies hysteresis. center is shifted such that it is in range with
+ * in by the margin again. The new center is returned.
+ * @param in the current value
+ * @param center the current center
+ * @param margin the margin to center in which no change is applied
+ * @return the new center (which might coincide with the previous)
+ */
+static int
+hysteresis(int in, int center, int margin)
+{
+    int diff = in - center;
+
+    if (abs(diff) <= margin) {
+        diff = 0;
+    }
+    else if (diff > margin) {
+        diff -= margin;
+    }
+    else if (diff < -margin) {
+        diff += margin;
+    }
+    return center + diff;
+}
+
+static void
+get_delta(SynapticsPrivate *priv, const struct SynapticsHwState *hw,
+          enum EdgeType edge, double *dx, double *dy)
+{
+    *dx = hw->x - HIST(0).x;
+    *dy = hw->y - HIST(0).y;
+}
+
+/* Vector length, but not sqrt'ed, we only need it for comparison */
+static inline double
+vlenpow2(double x, double y)
+{
+    return x * x + y * y;
+}
+
+/**
+ * Compute relative motion ('deltas') including edge motion.
+ */
+static int
+ComputeDeltas(SynapticsPrivate * priv, const struct SynapticsHwState *hw,
+              enum EdgeType edge, int *dxP, int *dyP, Bool inside_area)
+{
+    enum MovingState moving_state;
+    double dx, dy;
+    double vlen;
+    int delay = 1000000000;
+
+    dx = dy = 0;
+
+    moving_state = priv->moving_state;
+    if (moving_state == MS_FALSE) {
+        switch (priv->tap_state) {
+        case TS_MOVE:
+        case TS_DRAG:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        case TS_1:
+        case TS_3:
+        case TS_5:
+            moving_state = MS_TOUCHPAD_RELATIVE;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (!inside_area || !moving_state || priv->finger_state == FS_BLOCKED ||
+        priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on || priv->prevFingers != hw->numFingers ||
+        (moving_state == MS_TOUCHPAD_RELATIVE && hw->numFingers != 1)) {
+        /* reset packet counter. */
+        priv->count_packet_finger = 0;
+        goto out;
+    }
+
+    /* To create the illusion of fluid motion, call back at roughly the report
+     * rate, even in the absence of new hardware events; see comment above
+     * POLL_MS declaration. */
+    delay = MIN(delay, POLL_MS);
+
+    if (priv->count_packet_finger <= 1)
+        goto out;               /* skip the lot */
+
+    if (moving_state == MS_TOUCHPAD_RELATIVE)
+        get_delta(priv, hw, edge, &dx, &dy);
+
+ out:
+    priv->prevFingers = hw->numFingers;
+
+    vlen = vlenpow2(dx/priv->synpara.resolution_horiz,
+                    dy/priv->synpara.resolution_vert);
+
+    if (vlen > priv->synpara.maxDeltaMM * priv->synpara.maxDeltaMM) {
+        dx = 0;
+        dy = 0;
+    }
+
+    *dxP = dx;
+    *dyP = dy;
+
+    return delay;
+}
+
+static double
+estimate_delta_circ(SynapticsPrivate * priv)
+{
+    double a1 = angle(priv, HIST(3).x, HIST(3).y);
+    double a2 = angle(priv, HIST(2).x, HIST(2).y);
+    double a3 = angle(priv, HIST(1).x, HIST(1).y);
+    double a4 = angle(priv, HIST(0).x, HIST(0).y);
+    double d1 = diffa(a2, a1);
+    double d2 = d1 + diffa(a3, a2);
+    double d3 = d2 + diffa(a4, a3);
+
+    return estimate_delta(d3, d2, d1, 0);
+}
+
+/* vert and horiz are to know which direction to start coasting
+ * circ is true if the user had been circular scrolling.
+ */
+static void
+start_coasting(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+               Bool vert, Bool horiz, Bool circ)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->scroll.coast_delta_y = 0.0;
+    priv->scroll.coast_delta_x = 0.0;
+
+    if ((priv->scroll.packets_this_scroll > 3) && (para->coasting_speed > 0.0)) {
+        double pkt_time = HIST_DELTA(0, 3, millis) / 1000.0;
+
+        if (vert && !circ) {
+            double dy =
+                estimate_delta(HIST(0).y, HIST(1).y, HIST(2).y, HIST(3).y);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dy / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_y = scrolls_per_sec;
+                    priv->scroll.coast_delta_y = (hw->y - priv->scroll.last_y);
+                }
+            }
+        }
+        if (horiz && !circ) {
+            double dx =
+                estimate_delta(HIST(0).x, HIST(1).x, HIST(2).x, HIST(3).x);
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (dx / abs(para->scroll_dist_vert)) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    priv->scroll.coast_speed_x = scrolls_per_sec;
+                    priv->scroll.coast_delta_x = (hw->x - priv->scroll.last_x);
+                }
+            }
+        }
+        if (circ) {
+            double da = estimate_delta_circ(priv);
+
+            if (pkt_time > 0) {
+                double scrolls_per_sec = (da / para->scroll_dist_circ) / pkt_time;
+
+                if (fabs(scrolls_per_sec) >= para->coasting_speed) {
+                    if (vert) {
+                        priv->scroll.coast_speed_y = scrolls_per_sec;
+                        priv->scroll.coast_delta_y =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                    else if (horiz) {
+                        priv->scroll.coast_speed_x = scrolls_per_sec;
+                        priv->scroll.coast_delta_x =
+                            diffa(priv->scroll.last_a,
+                                  angle(priv, hw->x, hw->y));
+                    }
+                }
+            }
+        }
+    }
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static void
+stop_coasting(SynapticsPrivate * priv)
+{
+    priv->scroll.coast_speed_x = 0;
+    priv->scroll.coast_speed_y = 0;
+    priv->scroll.packets_this_scroll = 0;
+}
+
+static int
+HandleScrolling(SynapticsPrivate * priv, struct SynapticsHwState *hw,
+                enum EdgeType edge, Bool finger)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int delay = 1000000000;
+
+    if (priv->synpara.touchpad_off == TOUCHPAD_TAP_OFF ||
+        priv->synpara.touchpad_off == TOUCHPAD_OFF ||
+        priv->finger_state == FS_BLOCKED) {
+        stop_coasting(priv);
+        priv->circ_scroll_on = FALSE;
+        priv->vert_scroll_edge_on = FALSE;
+        priv->horiz_scroll_edge_on = FALSE;
+        priv->vert_scroll_twofinger_on = FALSE;
+        priv->horiz_scroll_twofinger_on = FALSE;
+        return delay;
+    }
+
+    /* scroll detection */
+    if (finger && priv->finger_state == FS_UNTOUCHED) {
+        stop_coasting(priv);
+        priv->scroll.delta_y = 0;
+        priv->scroll.delta_x = 0;
+        if (para->circular_scrolling) {
+            if ((para->circular_trigger == 0 && edge) ||
+                (para->circular_trigger == 1 && edge & TOP_EDGE) ||
+                (para->circular_trigger == 2 && edge & TOP_EDGE &&
+                 edge & RIGHT_EDGE) || (para->circular_trigger == 3 &&
+                                        edge & RIGHT_EDGE) ||
+                (para->circular_trigger == 4 && edge & RIGHT_EDGE &&
+                 edge & BOTTOM_EDGE) || (para->circular_trigger == 5 &&
+                                         edge & BOTTOM_EDGE) ||
+                (para->circular_trigger == 6 && edge & BOTTOM_EDGE &&
+                 edge & LEFT_EDGE) || (para->circular_trigger == 7 &&
+                                       edge & LEFT_EDGE) ||
+                (para->circular_trigger == 8 && edge & LEFT_EDGE &&
+                 edge & TOP_EDGE)) {
+                priv->circ_scroll_on = TRUE;
+                priv->circ_scroll_vert = TRUE;
+                priv->scroll.last_a = angle(priv, hw->x, hw->y);
+                DBG(7, "circular scroll detected on edge\n");
+            }
+        }
+    }
+    if (!priv->circ_scroll_on) {
+        if (finger) {
+            if (hw->numFingers == 2) {
+                if (!priv->vert_scroll_twofinger_on &&
+                    (para->scroll_twofinger_vert) &&
+                    (para->scroll_dist_vert != 0)) {
+                    stop_coasting(priv);
+                    priv->vert_scroll_twofinger_on = TRUE;
+                    priv->vert_scroll_edge_on = FALSE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert two-finger scroll detected\n");
+                }
+                if (!priv->horiz_scroll_twofinger_on &&
+                    (para->scroll_twofinger_horiz) &&
+                    (para->scroll_dist_horiz != 0)) {
+                    stop_coasting(priv);
+                    priv->horiz_scroll_twofinger_on = TRUE;
+                    priv->horiz_scroll_edge_on = FALSE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz two-finger scroll detected\n");
+                }
+            }
+        }
+        if (finger && priv->finger_state == FS_UNTOUCHED) {
+            if (!priv->vert_scroll_twofinger_on &&
+                !priv->horiz_scroll_twofinger_on) {
+                if ((para->scroll_edge_vert) && (para->scroll_dist_vert != 0) &&
+                    (edge & RIGHT_EDGE)) {
+                    priv->vert_scroll_edge_on = TRUE;
+                    priv->scroll.last_y = hw->y;
+                    DBG(7, "vert edge scroll detected on right edge\n");
+                }
+                if ((para->scroll_edge_horiz) && (para->scroll_dist_horiz != 0)
+                    && (edge & BOTTOM_EDGE)) {
+                    priv->horiz_scroll_edge_on = TRUE;
+                    priv->scroll.last_x = hw->x;
+                    DBG(7, "horiz edge scroll detected on bottom edge\n");
+                }
+            }
+        }
+    }
+    {
+        Bool oldv = priv->vert_scroll_twofinger_on || priv->vert_scroll_edge_on
+            || (priv->circ_scroll_on && priv->circ_scroll_vert);
+
+        Bool oldh = priv->horiz_scroll_twofinger_on ||
+            priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                           !priv->circ_scroll_vert);
+
+        Bool oldc = priv->circ_scroll_on;
+
+        if (priv->circ_scroll_on && !finger) {
+            /* circular scroll locks in until finger is raised */
+            DBG(7, "cicular scroll off\n");
+            priv->circ_scroll_on = FALSE;
+        }
+
+        if (!finger || hw->numFingers != 2) {
+            if (priv->vert_scroll_twofinger_on) {
+                DBG(7, "vert two-finger scroll off\n");
+                priv->vert_scroll_twofinger_on = FALSE;
+            }
+            if (priv->horiz_scroll_twofinger_on) {
+                DBG(7, "horiz two-finger scroll off\n");
+                priv->horiz_scroll_twofinger_on = FALSE;
+            }
+        }
+
+        if (priv->vert_scroll_edge_on && (!(edge & RIGHT_EDGE) || !finger)) {
+            DBG(7, "vert edge scroll off\n");
+            priv->vert_scroll_edge_on = FALSE;
+        }
+        if (priv->horiz_scroll_edge_on && (!(edge & BOTTOM_EDGE) || !finger)) {
+            DBG(7, "horiz edge scroll off\n");
+            priv->horiz_scroll_edge_on = FALSE;
+        }
+        /* If we were corner edge scrolling (coasting),
+         * but no longer in corner or raised a finger, then stop coasting. */
+        if (para->scroll_edge_corner &&
+            (priv->scroll.coast_speed_x || priv->scroll.coast_speed_y)) {
+            Bool is_in_corner = ((edge & RIGHT_EDGE) &&
+                                 (edge & (TOP_EDGE | BOTTOM_EDGE))) ||
+                ((edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE)));
+            if (!is_in_corner || !finger) {
+                DBG(7, "corner edge scroll off\n");
+                stop_coasting(priv);
+            }
+        }
+        /* if we were scrolling, but couldn't corner edge scroll,
+         * and are no longer scrolling, then start coasting */
+        oldv = oldv && !(priv->vert_scroll_twofinger_on ||
+                         priv->vert_scroll_edge_on || (priv->circ_scroll_on &&
+                                                       priv->circ_scroll_vert));
+
+        oldh = oldh && !(priv->horiz_scroll_twofinger_on ||
+                         priv->horiz_scroll_edge_on || (priv->circ_scroll_on &&
+                                                        !priv->
+                                                        circ_scroll_vert));
+
+        oldc = oldc && !priv->circ_scroll_on;
+
+        if ((oldv || oldh) && !para->scroll_edge_corner) {
+            start_coasting(priv, hw, oldv, oldh, oldc);
+        }
+    }
+
+    /* if hitting a corner (top right or bottom right) while vertical
+     * scrolling is active, consider starting corner edge scrolling or
+     * switching over to circular scrolling smoothly */
+    if (priv->vert_scroll_edge_on && !priv->horiz_scroll_edge_on &&
+        (edge & RIGHT_EDGE) && (edge & (TOP_EDGE | BOTTOM_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_y == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, TRUE, FALSE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->vert_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = TRUE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+    /* Same treatment for horizontal scrolling */
+    if (priv->horiz_scroll_edge_on && !priv->vert_scroll_edge_on &&
+        (edge & BOTTOM_EDGE) && (edge & (LEFT_EDGE | RIGHT_EDGE))) {
+        if (para->scroll_edge_corner) {
+            if (priv->scroll.coast_speed_x == 0) {
+                /* FYI: We can generate multiple start_coasting requests if
+                 * we're in the corner, but we were moving so slowly when we
+                 * got here that we didn't actually start coasting. */
+                DBG(7, "corner edge scroll on\n");
+                start_coasting(priv, hw, FALSE, TRUE, FALSE);
+            }
+        }
+        else if (para->circular_scrolling) {
+            priv->horiz_scroll_edge_on = FALSE;
+            priv->circ_scroll_on = TRUE;
+            priv->circ_scroll_vert = FALSE;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+            DBG(7, "switching to circular scrolling\n");
+        }
+    }
+
+    if (priv->vert_scroll_edge_on || priv->horiz_scroll_edge_on ||
+        priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on ||
+        priv->circ_scroll_on) {
+        priv->scroll.packets_this_scroll++;
+    }
+
+    if (priv->vert_scroll_edge_on || priv->vert_scroll_twofinger_on) {
+        /* + = down, - = up */
+        if (para->scroll_dist_vert != 0 && hw->y != priv->scroll.last_y) {
+            priv->scroll.delta_y += (hw->y - priv->scroll.last_y);
+            priv->scroll.last_y = hw->y;
+        }
+    }
+    if (priv->horiz_scroll_edge_on || priv->horiz_scroll_twofinger_on) {
+        /* + = right, - = left */
+        if (para->scroll_dist_horiz != 0 && hw->x != priv->scroll.last_x) {
+            priv->scroll.delta_x += (hw->x - priv->scroll.last_x);
+            priv->scroll.last_x = hw->x;
+        }
+    }
+    if (priv->circ_scroll_on) {
+        /* + = counter clockwise, - = clockwise */
+        double delta = para->scroll_dist_circ;
+        double diff = diffa(priv->scroll.last_a, angle(priv, hw->x, hw->y));
+
+        if (delta >= 0.005 && diff != 0.0) {
+            if (priv->circ_scroll_vert)
+                priv->scroll.delta_y -= diff / delta * para->scroll_dist_vert;
+            else
+                priv->scroll.delta_x -= diff / delta * para->scroll_dist_horiz;
+            priv->scroll.last_a = angle(priv, hw->x, hw->y);
+        }
+    }
+
+    if (priv->scroll.coast_speed_y) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddy = para->coasting_friction * dtime;
+
+        priv->scroll.delta_y += priv->scroll.coast_speed_y * dtime * abs(para->scroll_dist_vert);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_y) < ddy) {
+            priv->scroll.coast_speed_y = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_y +=
+                (priv->scroll.coast_speed_y < 0 ? ddy : -ddy);
+        }
+    }
+
+    if (priv->scroll.coast_speed_x) {
+        double dtime = (hw->millis - priv->scroll.last_millis) / 1000.0;
+        double ddx = para->coasting_friction * dtime;
+        priv->scroll.delta_x += priv->scroll.coast_speed_x * dtime * abs(para->scroll_dist_horiz);
+        delay = MIN(delay, POLL_MS);
+        if (abs(priv->scroll.coast_speed_x) < ddx) {
+            priv->scroll.coast_speed_x = 0;
+            priv->scroll.packets_this_scroll = 0;
+        }
+        else {
+            priv->scroll.coast_speed_x +=
+                (priv->scroll.coast_speed_x < 0 ? ddx : -ddx);
+        }
+    }
+
+    return delay;
+}
+
+/**
+ * Check if any 2+ fingers are close enough together to assume this is a
+ * ClickFinger action.
+ */
+static int
+clickpad_guess_clickfingers(SynapticsPrivate * priv,
+                            struct SynapticsHwState *hw)
+{
+    int nfingers = 0;
+    uint32_t close_point = 0; /* 1 bit for each point close to another one */
+    int i, j;
+
+#ifdef BUG_RETURN_VAL
+    BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
+
+    for (i = 0; i < hw->num_mt_mask - 1; i++) {
+        ValuatorMask *f1;
+
+        if (hw->slot_state[i] == SLOTSTATE_EMPTY ||
+            hw->slot_state[i] == SLOTSTATE_CLOSE)
+            continue;
+
+        f1 = hw->mt_mask[i];
+
+        for (j = i + 1; j < hw->num_mt_mask; j++) {
+            ValuatorMask *f2;
+            double x1, x2, y1, y2;
+
+            if (hw->slot_state[j] == SLOTSTATE_EMPTY ||
+                hw->slot_state[j] == SLOTSTATE_CLOSE)
+                continue;
+
+            f2 = hw->mt_mask[j];
+
+            x1 = valuator_mask_get_double(f1, 0);
+            y1 = valuator_mask_get_double(f1, 1);
+
+            x2 = valuator_mask_get_double(f2, 0);
+            y2 = valuator_mask_get_double(f2, 1);
+
+            /* FIXME: fingers closer together than 30% of touchpad width, but
+             * really, this should be dependent on the touchpad size. Also,
+             * you'll need to find a touchpad that doesn't lie about it's
+             * size. Good luck. */
+            if (abs(x1 - x2) < (priv->maxx - priv->minx) * .3 &&
+                abs(y1 - y2) < (priv->maxy - priv->miny) * .3) {
+                close_point |= (1 << j);
+                close_point |= (1 << i);
+            }
+        }
+    }
+
+    while (close_point > 0) {
+        nfingers += close_point & 0x1;
+        close_point >>= 1;
+    }
+
+    /* Some trackpads touchpad only track two touchpoints but announce
+     * BTN_TOOL_TRIPLETAP (which sets hw->numFingers to 3), when this happens
+     * the user likely intents to do a 3 finger click, so handle it as such.
+     */
+    if (hw->numFingers >= 3 && hw->num_mt_mask < 3)
+        nfingers = 3;
+
+    return nfingers;
+}
+
+static void
+handle_clickfinger(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    SynapticsParameters *para = &priv->synpara;
+    int action = 0;
+    int nfingers = hw->numFingers;
+
+    /* if this is a clickpad, clickfinger handling is:
+     * one finger down: no action, this is a normal click
+     * two fingers down: F2_CLICK
+     * three fingers down: F3_CLICK
+     */
+
+    if (para->clickpad)
+        nfingers = clickpad_guess_clickfingers(priv, hw);
+
+    switch (nfingers) {
+    case 1:
+        action = para->click_action[F1_CLICK1];
+        break;
+    case 2:
+        action = para->click_action[F2_CLICK1];
+        break;
+    case 3:
+        action = para->click_action[F3_CLICK1];
+        break;
+    }
+    switch (action) {
+    case 1:
+        hw->left = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 2:
+        hw->left = 0;
+        hw->middle = 1 | BTN_EMULATED_FLAG;
+        break;
+    case 3:
+        hw->left = 0;
+        hw->right = 1 | BTN_EMULATED_FLAG;
+        break;
+    }
+}
+
+/* Adjust the hardware state according to the extra buttons (if the touchpad
+ * has any and not many touchpads do these days). These buttons are up/down
+ * tilt buttons and/or left/right buttons that then map into a specific
+ * function (or scrolling into).
+ */
+static Bool
+adjust_state_from_scrollbuttons(const InputInfoPtr pInfo,
+                                struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    Bool double_click = FALSE;
+
+    if (!para->updown_button_scrolling) {
+        if (hw->down) {         /* map down button to middle button */
+            hw->middle = TRUE;
+        }
+
+        if (hw->up) {           /* up button generates double click */
+            if (!priv->prev_up)
+                double_click = TRUE;
+        }
+        priv->prev_up = hw->up;
+
+        /* reset up/down button events */
+        hw->up = hw->down = FALSE;
+    }
+
+    /* Left/right button scrolling, or middle clicks */
+    if (!para->leftright_button_scrolling) {
+        if (hw->multi[2] || hw->multi[3])
+            hw->middle = TRUE;
+
+        /* reset left/right button events */
+        hw->multi[2] = hw->multi[3] = FALSE;
+    }
+
+    return double_click;
+}
+
+static void
+update_hw_button_state(const InputInfoPtr pInfo, struct SynapticsHwState *hw,
+                       CARD32 now, int *delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+
+    /* Treat the first two multi buttons as up/down for now. */
+    hw->up |= hw->multi[0];
+    hw->down |= hw->multi[1];
+
+    /* 3rd button emulation */
+    hw->middle |= HandleMidButtonEmulation(priv, hw, now, delay);
+
+    /* If this is a clickpad and the user clicks in a soft button area, press
+     * the soft button instead. */
+    if (para->clickpad) {
+        /* hw->left is down, but no other buttons were already down */
+        if (!(priv->lastButtons & 7) && hw->left && !hw->right && !hw->middle) {
+            /* If the finger down event is delayed, the x and y
+             * coordinates are stale so we delay processing the click */
+            if (hw->z < para->finger_low) {
+                hw->left = 0;
+                goto out;
+            }
+            if (is_inside_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_sec_rightbutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->right = 1;
+            }
+            else if (is_inside_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            else if (is_inside_sec_middlebutton_area(para, hw->x, hw->y)) {
+                hw->left = 0;
+                hw->middle = 1;
+            }
+            priv->clickpad_click_millis = now;
+        }
+        else if (hw->left) {
+            hw->left   = (priv->lastButtons & 1) ? 1 : 0;
+            hw->middle = (priv->lastButtons & 2) ? 1 : 0;
+            hw->right  = (priv->lastButtons & 4) ? 1 : 0;
+        }
+    }
+
+    /* Fingers emulate other buttons. ClickFinger can only be
+       triggered on transition, when left is pressed
+     */
+    if (hw->left && !(priv->lastButtons & 7) && hw->numFingers >= 1)
+        handle_clickfinger(priv, hw);
+
+out:
+    /* Two finger emulation */
+    if (hw->numFingers == 1 && hw->z >= para->emulate_twofinger_z &&
+        hw->fingerWidth >= para->emulate_twofinger_w) {
+        hw->numFingers = 2;
+    }
+}
+
+static void
+post_button_click(const InputInfoPtr pInfo, const int button)
+{
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, TRUE, 0, 0);
+    xf86PostButtonEvent(pInfo->dev, FALSE, button, FALSE, 0, 0);
+}
+
+static void
+post_scroll_events(const InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+
+    valuator_mask_zero(priv->scroll_events_mask);
+
+    if (priv->scroll.delta_y != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_vert, priv->scroll.delta_y);
+        priv->scroll.delta_y = 0;
+    }
+    if (priv->scroll.delta_x != 0.0) {
+        valuator_mask_set_double(priv->scroll_events_mask,
+                                 priv->scroll_axis_horiz, priv->scroll.delta_x);
+        priv->scroll.delta_x = 0;
+    }
+    if (valuator_mask_num_valuators(priv->scroll_events_mask))
+        xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_events_mask);
+}
+
+static inline int
+repeat_scrollbuttons(const InputInfoPtr pInfo,
+                     const struct SynapticsHwState *hw,
+                     int buttons, CARD32 now, int delay)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    int repeat_delay, timeleft;
+    int rep_buttons = 0;
+
+    if (para->updown_button_repeat)
+        rep_buttons |= (1 << (4 - 1)) | (1 << (5 - 1));
+    if (para->leftright_button_repeat)
+        rep_buttons |= (1 << (6 - 1)) | (1 << (7 - 1));
+
+    /* Handle auto repeat buttons */
+    repeat_delay = clamp(para->scroll_button_repeat, SBR_MIN, SBR_MAX);
+    if (((hw->up || hw->down) && para->updown_button_repeat &&
+         para->updown_button_scrolling) ||
+        ((hw->multi[2] || hw->multi[3]) && para->leftright_button_repeat &&
+         para->leftright_button_scrolling)) {
+        priv->repeatButtons = buttons & rep_buttons;
+        if (!priv->nextRepeat) {
+            priv->nextRepeat = now + repeat_delay * 2;
+        }
+    }
+    else {
+        priv->repeatButtons = 0;
+        priv->nextRepeat = 0;
+    }
+
+    if (priv->repeatButtons) {
+        timeleft = TIME_DIFF(priv->nextRepeat, now);
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+        if (timeleft <= 0) {
+            int change, id;
+
+            change = priv->repeatButtons;
+            while (change) {
+                id = ffs(change);
+                change &= ~(1 << (id - 1));
+                if (id == 4)
+                    priv->scroll.delta_y -= para->scroll_dist_vert;
+                else if (id == 5)
+                    priv->scroll.delta_y += para->scroll_dist_vert;
+                else if (id == 6)
+                    priv->scroll.delta_x -= para->scroll_dist_horiz;
+                else if (id == 7)
+                    priv->scroll.delta_x += para->scroll_dist_horiz;
+            }
+
+            priv->nextRepeat = now + repeat_delay;
+            delay = MIN(delay, repeat_delay);
+        }
+    }
+
+    return delay;
+}
+
+/* Update the open slots and number of active touches */
+static void
+UpdateTouchState(InputInfoPtr pInfo, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+    int i;
+
+    for (i = 0; i < hw->num_mt_mask; i++) {
+        if (hw->slot_state[i] == SLOTSTATE_OPEN) {
+            priv->open_slots[priv->num_active_touches] = i;
+            priv->num_active_touches++;
+            BUG_WARN(priv->num_active_touches > priv->num_slots);
+        }
+        else if (hw->slot_state[i] == SLOTSTATE_CLOSE) {
+            Bool found = FALSE;
+            int j;
+
+            for (j = 0; j < priv->num_active_touches - 1; j++) {
+                if (priv->open_slots[j] == i)
+                    found = TRUE;
+
+                if (found)
+                    priv->open_slots[j] = priv->open_slots[j + 1];
+            }
+
+            BUG_WARN(priv->num_active_touches == 0);
+            if (priv->num_active_touches > 0)
+                priv->num_active_touches--;
+        }
+    }
+
+    SynapticsResetTouchHwState(hw, FALSE);
+}
+
+static void
+filter_jitter(SynapticsPrivate * priv, int *x, int *y)
+{
+    SynapticsParameters *para = &priv->synpara;
+
+    priv->hyst_center_x = hysteresis(*x, priv->hyst_center_x, para->hyst_x);
+    priv->hyst_center_y = hysteresis(*y, priv->hyst_center_y, para->hyst_y);
+    *x = priv->hyst_center_x;
+    *y = priv->hyst_center_y;
+}
+
+static void
+reset_hw_state(struct SynapticsHwState *hw)
+{
+    hw->x = 0;
+    hw->y = 0;
+    hw->z = 0;
+    hw->numFingers = 0;
+    hw->fingerWidth = 0;
+}
+
+/*
+ * React on changes in the hardware state. This function is called every time
+ * the hardware state changes. The return value is used to specify how many
+ * milliseconds to wait before calling the function again if no state change
+ * occurs.
+ *
+ * from_timer denotes if HandleState was triggered from a timer (e.g. to
+ * generate fake motion events, or for the tap-to-click state machine), rather
+ * than from having received a motion event.
+ */
+static int
+HandleState(InputInfoPtr pInfo, struct SynapticsHwState *hw, CARD32 now,
+            Bool from_timer)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (pInfo->private);
+    SynapticsParameters *para = &priv->synpara;
+    enum FingerState finger = FS_UNTOUCHED;
+    int dx = 0, dy = 0, buttons, id;
+    enum EdgeType edge = NO_EDGE;
+    int change;
+    int double_click = FALSE;
+    int delay = 1000000000;
+    int timeleft;
+    Bool inside_active_area;
+    Bool using_cumulative_coords = FALSE;
+    Bool ignore_motion;
+
+    /* We need both and x/y, the driver can't handle just one of the two
+     * yet. But since it's possible to hit a phys button on non-clickpads
+     * without ever getting motion data first, we must continue with 0/0 for
+     * that case. */
+    if (hw->x == INT_MIN || hw->y == INT_MAX) {
+        if (para->clickpad)
+            return delay;
+        else if (hw->left || hw->right || hw->middle) {
+            hw->x = (hw->x == INT_MIN) ? 0 : hw->x;
+            hw->y = (hw->y == INT_MIN) ? 0 : hw->y;
+        }
+    }
+
+    /* If a physical button is pressed on a clickpad or a two-finger scrolling
+     * is ongoing, use cumulative relative touch movements for motion */
+    if (para->clickpad &&
+        ((priv->lastButtons & 7) ||
+        (priv->vert_scroll_twofinger_on || priv->horiz_scroll_twofinger_on)) &&
+        priv->last_button_area != TOP_BUTTON_AREA) {
+        hw->x = hw->cumulative_dx;
+        hw->y = hw->cumulative_dy;
+        using_cumulative_coords = TRUE;
+    }
+
+    /* apply hysteresis before doing anything serious. This cancels
+     * out a lot of noise which might surface in strange phenomena
+     * like flicker in scrolling or noise motion. */
+    filter_jitter(priv, &hw->x, &hw->y);
+
+    inside_active_area = is_inside_active_area(priv, hw->x, hw->y);
+
+    /* Ignore motion *starting* inside softbuttonareas */
+    if (priv->finger_state < FS_TOUCHED)
+        priv->last_button_area = current_button_area(para, hw->x, hw->y);
+    /* If we already have a finger down, clear last_button_area if it goes
+       outside of the softbuttonareas */
+    else if (priv->last_button_area != NO_BUTTON_AREA &&
+             current_button_area(para, hw->x, hw->y) == NO_BUTTON_AREA)
+        priv->last_button_area = NO_BUTTON_AREA;
+
+    ignore_motion = para->touchpad_off == TOUCHPAD_OFF ||
+        (!using_cumulative_coords && priv->last_button_area != NO_BUTTON_AREA);
+
+    /* these two just update hw->left, right, etc. */
+    update_hw_button_state(pInfo, hw, now, &delay);
+    if (priv->has_scrollbuttons)
+        double_click = adjust_state_from_scrollbuttons(pInfo, hw);
+
+    /* Ignore motion the first X ms after a clickpad click */
+    if (priv->clickpad_click_millis) {
+        if(TIME_DIFF(priv->clickpad_click_millis +
+                     para->clickpad_ignore_motion_time, now) > 0)
+            ignore_motion = TRUE;
+        else
+            priv->clickpad_click_millis = 0;
+    }
+
+    /* now we know that these _coordinates_ aren't in the area.
+       invalid are: x, y, z, numFingers, fingerWidth
+       valid are: millis, left/right/middle/up/down/etc.
+     */
+    if (!inside_active_area)
+        reset_hw_state(hw);
+
+    /* no edge or finger detection outside of area */
+    if (inside_active_area) {
+        edge = edge_detection(priv, hw->x, hw->y);
+        if (!from_timer)
+            finger = SynapticsDetectFinger(priv, hw);
+        else
+            finger = priv->finger_state;
+    }
+
+    /* tap and drag detection. Needs to be performed even if the finger is in
+     * the dead area to reset the state. */
+    timeleft = HandleTapProcessing(priv, hw, now, finger, inside_active_area);
+    if (timeleft > 0)
+        delay = MIN(delay, timeleft);
+
+    if (inside_active_area) {
+        /* Don't bother about scrolling in the dead area of the touchpad. */
+        timeleft = HandleScrolling(priv, hw, edge, (finger >= FS_TOUCHED));
+        if (timeleft > 0)
+            delay = MIN(delay, timeleft);
+
+        /*
+         * Compensate for unequal x/y resolution. This needs to be done after
+         * calculations that require unadjusted coordinates, for example edge
+         * detection.
+         */
+#ifndef NO_DRIVER_SCALING
+        ScaleCoordinates(priv, hw);
+#endif
+    }
+
+    dx = dy = 0;
+
+    timeleft = ComputeDeltas(priv, hw, edge, &dx, &dy, inside_active_area);
+    delay = MIN(delay, timeleft);
+
+    buttons = ((hw->left ? 0x01 : 0) |
+               (hw->middle ? 0x02 : 0) |
+               (hw->right ? 0x04 : 0) |
+               (hw->up ? 0x08 : 0) |
+               (hw->down ? 0x10 : 0) |
+               (hw->multi[2] ? 0x20 : 0) | (hw->multi[3] ? 0x40 : 0));
+
+    if (priv->tap_button > 0 && priv->tap_button_state == TBS_BUTTON_DOWN)
+        buttons |= 1 << (priv->tap_button - 1);
+
+    /* Post events */
+    if (finger >= FS_TOUCHED && (dx || dy) && !ignore_motion)
+        xf86PostMotionEvent(pInfo->dev, 0, 0, 2, dx, dy);
+
+    if (priv->mid_emu_state == MBE_LEFT_CLICK) {
+        post_button_click(pInfo, 1);
+        priv->mid_emu_state = MBE_OFF;
+    }
+    else if (priv->mid_emu_state == MBE_RIGHT_CLICK) {
+        post_button_click(pInfo, 3);
+        priv->mid_emu_state = MBE_OFF;
+    }
+
+    change = buttons ^ priv->lastButtons;
+    while (change) {
+        id = ffs(change);       /* number of first set bit 1..32 is returned */
+        change &= ~(1 << (id - 1));
+        xf86PostButtonEvent(pInfo->dev, FALSE, id, (buttons & (1 << (id - 1))),
+                            0, 0);
+    }
+
+    if (priv->has_scrollbuttons)
+        delay = repeat_scrollbuttons(pInfo, hw, buttons, now, delay);
+
+    /* Process scroll events only if coordinates are
+     * in the Synaptics Area
+     */
+    if (inside_active_area &&
+        (priv->scroll.delta_x != 0.0 || priv->scroll.delta_y != 0.0)) {
+        post_scroll_events(pInfo);
+        priv->scroll.last_millis = hw->millis;
+    }
+
+    if (double_click) {
+        post_button_click(pInfo, 1);
+        post_button_click(pInfo, 1);
+    }
+
+    UpdateTouchState(pInfo, hw);
+
+    /* Save old values of some state variables */
+    priv->finger_state = finger;
+    priv->lastButtons = buttons;
+
+    /* generate a history of the absolute positions */
+    if (inside_active_area)
+        store_history(priv, hw->x, hw->y, hw->millis);
+
+    return delay;
+}
+
+static int
+ControlProc(InputInfoPtr pInfo, xDeviceCtl * control)
+{
+    DBG(3, "Control Proc called\n");
+    return Success;
+}
+
+static int
+SwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
+{
+    DBG(3, "SwitchMode called\n");
+
+    return XI_BadMode;
+}
+
+static void
+ReadDevDimensions(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    if (priv->proto_ops->ReadDevDimensions)
+        priv->proto_ops->ReadDevDimensions(pInfo);
+
+    SanitizeDimensions(pInfo);
+}
+
+static Bool
+QueryHardware(InputInfoPtr pInfo)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) pInfo->private;
+
+    priv->comm.protoBufTail = 0;
+
+    if (!priv->proto_ops->QueryHardware(pInfo)) {
+        xf86IDrvMsg(pInfo, X_PROBED, "no supported touchpad found\n");
+        if (priv->proto_ops->DeviceOffHook)
+            priv->proto_ops->DeviceOffHook(pInfo);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#ifndef NO_DRIVER_SCALING
+static void
+ScaleCoordinates(SynapticsPrivate * priv, struct SynapticsHwState *hw)
+{
+    int xCenter = (priv->synpara.left_edge + priv->synpara.right_edge) / 2;
+    int yCenter = (priv->synpara.top_edge + priv->synpara.bottom_edge) / 2;
+
+    hw->x = (hw->x - xCenter) * priv->horiz_coeff + xCenter;
+    hw->y = (hw->y - yCenter) * priv->vert_coeff + yCenter;
+}
+
+void
+CalculateScalingCoeffs(SynapticsPrivate * priv)
+{
+    int vertRes = priv->synpara.resolution_vert;
+    int horizRes = priv->synpara.resolution_horiz;
+
+    if ((horizRes > vertRes) && (horizRes > 0)) {
+        priv->horiz_coeff = vertRes / (double) horizRes;
+        priv->vert_coeff = 1;
+    }
+    else if ((horizRes < vertRes) && (vertRes > 0)) {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = horizRes / (double) vertRes;
+    }
+    else {
+        priv->horiz_coeff = 1;
+        priv->vert_coeff = 1;
+    }
+}
+#endif
diff -pruN 1.9.1-1/.pc/applied-patches 1.9.1-1ubuntu3/.pc/applied-patches
--- 1.9.1-1/.pc/applied-patches	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/applied-patches	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1,11 @@
+02-do-not-use-synaptics-for-keyboards.patch
+03-bug-return-val.patch
+101_resolution_detect_option.patch
+103_enable_cornertapping.patch
+104_always_enable_tapping.patch
+106_always_enable_vert_edge_scroll.patch
+115_evdev_only.patch
+118_quell_error_msg.patch
+124_syndaemon_events.patch
+128_disable_three_click_action.patch
+129_disable_three_touch_tap.patch
diff -pruN 1.9.1-1/.pc/.quilt_patches 1.9.1-1ubuntu3/.pc/.quilt_patches
--- 1.9.1-1/.pc/.quilt_patches	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/.quilt_patches	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1 @@
+debian/patches
diff -pruN 1.9.1-1/.pc/.quilt_series 1.9.1-1ubuntu3/.pc/.quilt_series
--- 1.9.1-1/.pc/.quilt_series	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/.quilt_series	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1 @@
+series
diff -pruN 1.9.1-1/.pc/.version 1.9.1-1ubuntu3/.pc/.version
--- 1.9.1-1/.pc/.version	1970-01-01 00:00:00.000000000 +0000
+++ 1.9.1-1ubuntu3/.pc/.version	2020-04-09 16:11:16.000000000 +0000
@@ -0,0 +1 @@
+2
diff -pruN 1.9.1-1/src/properties.c 1.9.1-1ubuntu3/src/properties.c
--- 1.9.1-1/src/properties.c	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/src/properties.c	2020-04-09 16:11:16.000000000 +0000
@@ -85,6 +85,7 @@ Atom prop_palm_dim = 0;
 Atom prop_coastspeed = 0;
 Atom prop_pressuremotion = 0;
 Atom prop_pressuremotion_factor = 0;
+Atom prop_resolution_detect = 0;
 Atom prop_grab = 0;
 Atom prop_gestures = 0;
 Atom prop_capabilities = 0;
@@ -345,6 +346,10 @@ InitDeviceProperties(InputInfoPtr pInfo)
         InitFloatAtom(pInfo->dev, SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 2,
                       fvalues);
 
+    prop_resolution_detect =
+        InitAtom(pInfo->dev, SYNAPTICS_PROP_RESOLUTION_DETECT, 8, 1,
+                 &para->resolution_detect);
+
     prop_grab =
         InitAtom(pInfo->dev, SYNAPTICS_PROP_GRAB, 8, 1,
                  &para->grab_event_device);
@@ -747,6 +752,12 @@ SetProperty(DeviceIntPtr dev, Atom prope
         para->press_motion_min_factor = press[0];
         para->press_motion_max_factor = press[1];
     }
+    else if (property == prop_resolution_detect) {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->resolution_detect = *(BOOL*)prop->data;
+    }
     else if (property == prop_grab) {
         if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
             return BadMatch;
diff -pruN 1.9.1-1/src/synaptics.c 1.9.1-1ubuntu3/src/synaptics.c
--- 1.9.1-1/src/synaptics.c	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/src/synaptics.c	2020-04-09 16:11:16.000000000 +0000
@@ -622,19 +622,19 @@ set_default_parameters(InputInfoPtr pInf
     palmMinWidth = priv->minw + range * (10.0 / 16);
     emulateTwoFingerMinW = priv->minw + range * (7.0 / 16);
 
-    /* Enable tap if we don't have a phys left button */
-    tapButton1 = priv->has_left ? 0 : 1;
-    tapButton2 = priv->has_left ? 0 : 3;
-    tapButton3 = priv->has_left ? 0 : 2;
+    /* Enable tap */
+    tapButton1 = 1;
+    tapButton2 = 3;
+    tapButton3 = 0; /* Disabled by default so three-touch gestures work */
 
     /* Enable multifinger-click if only have one physical button,
        otherwise clickFinger is always button 1. */
     clickFinger1 = 1;
     clickFinger2 = (priv->has_right || priv->has_middle) ? 1 : 3;
-    clickFinger3 = (priv->has_right || priv->has_middle) ? 1 : 2;
+    clickFinger3 = 0; /* Disabled by default so three-touch gestures work */
 
-    /* Enable vert edge scroll if we can't detect doubletap */
-    vertEdgeScroll = priv->has_double ? FALSE : TRUE;
+    /* Enable vert edge scroll */
+    vertEdgeScroll = TRUE;
     horizEdgeScroll = FALSE;
 
     /* Enable twofinger scroll if we can detect doubletap */
@@ -717,8 +717,8 @@ set_default_parameters(InputInfoPtr pInf
 
     pars->locked_drags = xf86SetBoolOption(opts, "LockedDrags", FALSE);
     pars->locked_drag_time = xf86SetIntOption(opts, "LockedDragTimeout", 5000);
-    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 0);
-    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 0);
+    pars->tap_action[RT_TAP] = xf86SetIntOption(opts, "RTCornerButton", 2);
+    pars->tap_action[RB_TAP] = xf86SetIntOption(opts, "RBCornerButton", 3);
     pars->tap_action[LT_TAP] = xf86SetIntOption(opts, "LTCornerButton", 0);
     pars->tap_action[LB_TAP] = xf86SetIntOption(opts, "LBCornerButton", 0);
     pars->tap_action[F1_TAP] = xf86SetIntOption(opts, "TapButton1", tapButton1);
@@ -742,6 +742,7 @@ set_default_parameters(InputInfoPtr pInf
         xf86SetIntOption(opts, "PressureMotionMinZ", pressureMotionMinZ);
     pars->press_motion_max_z =
         xf86SetIntOption(opts, "PressureMotionMaxZ", pressureMotionMaxZ);
+    pars->resolution_detect = xf86SetBoolOption(opts, "ResolutionDetect", TRUE);
 
     pars->min_speed = xf86SetRealOption(opts, "MinSpeed", 0.4);
     pars->max_speed = xf86SetRealOption(opts, "MaxSpeed", 0.7);
@@ -1290,7 +1291,7 @@ DeviceInit(DeviceIntPtr dev)
     }
 
     /* X valuator */
-    if (priv->minx < priv->maxx) {
+    if (priv->minx < priv->maxx && priv->synpara.resolution_detect) {
         min = priv->minx;
         max = priv->maxx;
     }
@@ -1305,7 +1306,7 @@ DeviceInit(DeviceIntPtr dev)
     xf86InitValuatorDefaults(dev, 0);
 
     /* Y valuator */
-    if (priv->miny < priv->maxy) {
+    if (priv->miny < priv->maxy && priv->synpara.resolution_detect) {
         min = priv->miny;
         max = priv->maxy;
     }
@@ -2639,7 +2640,9 @@ clickpad_guess_clickfingers(SynapticsPri
     uint32_t close_point = 0; /* 1 bit for each point close to another one */
     int i, j;
 
+#ifdef BUG_RETURN_VAL
     BUG_RETURN_VAL(hw->num_mt_mask > sizeof(close_point) * 8, 0);
+#endif
 
     for (i = 0; i < hw->num_mt_mask - 1; i++) {
         ValuatorMask *f1;
diff -pruN 1.9.1-1/src/synapticsstr.h 1.9.1-1ubuntu3/src/synapticsstr.h
--- 1.9.1-1/src/synapticsstr.h	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/src/synapticsstr.h	2020-04-09 16:11:16.000000000 +0000
@@ -222,6 +222,7 @@ typedef struct _SynapticsParameters {
     int press_motion_max_z;     /* finger pressure at which maximum pressure motion factor is applied */
     double press_motion_min_factor;     /* factor applied on speed when finger pressure is at minimum */
     double press_motion_max_factor;     /* factor applied on speed when finger pressure is at minimum */
+    Bool resolution_detect;     /* report pad size to xserver? */
     Bool grab_event_device;     /* grab event device for exclusive use? */
     Bool tap_and_drag_gesture;  /* Switches the tap-and-drag gesture on/off */
     unsigned int resolution_horiz;      /* horizontal resolution of touchpad in units/mm */
diff -pruN 1.9.1-1/tools/synclient.c 1.9.1-1ubuntu3/tools/synclient.c
--- 1.9.1-1/tools/synclient.c	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/tools/synclient.c	2020-04-09 16:11:16.000000000 +0000
@@ -130,6 +130,7 @@ static struct Parameter params[] = {
     {"PressureMotionMaxZ",    PT_INT,    1, 255,   SYNAPTICS_PROP_PRESSURE_MOTION,	32,	1},
     {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	0},
     {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	1},
+    {"ResolutionDetect",      PT_BOOL,   0, 1,     SYNAPTICS_PROP_RESOLUTION_DETECT,	8,      0},
     {"GrabEventDevice",       PT_BOOL,   0, 1,     SYNAPTICS_PROP_GRAB,	8,	0},
     {"TapAndDragGesture",     PT_BOOL,   0, 1,     SYNAPTICS_PROP_GESTURES,	8,	0},
     {"AreaLeftEdge",          PT_INT,    0, 10000, SYNAPTICS_PROP_AREA,	32,	0},
@@ -297,9 +298,7 @@ dp_get_device(Display * dpy)
  unwind:
     XFree(properties);
     XFreeDeviceList(info);
-    if (!dev)
-        fprintf(stderr, "Unable to find a synaptics device.\n");
-    else if (error && dev) {
+    if (error && dev) {
         XCloseDevice(dpy, dev);
         dev = NULL;
     }
diff -pruN 1.9.1-1/tools/syndaemon.c 1.9.1-1ubuntu3/tools/syndaemon.c
--- 1.9.1-1/tools/syndaemon.c	2018-05-29 03:18:45.000000000 +0000
+++ 1.9.1-1ubuntu3/tools/syndaemon.c	2020-04-09 16:11:16.000000000 +0000
@@ -466,6 +466,14 @@ record_main_loop(Display * display, doub
                         event.type);
             }
 
+            /* If there are any events left over, they are in error. Drain them
+             * from the connection queue so we don't get stuck. */
+            while (XEventsQueued(dpy_data, QueuedAlready) > 0) {
+                XEvent event;
+                XNextEvent(dpy_data, &event);
+                fprintf(stderr, "bad event received, major opcode %d\n", event.type);
+            }
+
             if (!ignore_modifier_keys && cbres.key_event) {
                 disable_event = 1;
             }
@@ -553,9 +561,7 @@ dp_get_device(Display * dpy)
  unwind:
     XFree(properties);
     XFreeDeviceList(info);
-    if (!dev)
-        fprintf(stderr, "Unable to find a synaptics device.\n");
-    else if (error && dev) {
+    if (error && dev) {
         XCloseDevice(dpy, dev);
         dev = NULL;
     }
