---
 include/synaptics-properties.h |    9 ++
 include/synaptics.h            |    3 
 src/eventcomm.c                |   45 ++++++++++++-
 src/properties.c               |   43 ++++++++++++
 src/synaptics.c                |  140 ++++++++++++++++++++++++++++++++++++++++-
 src/synapticsstr.h             |    8 ++
 src/synproto.h                 |    1 
 tools/synclient.c              |    6 +
 8 files changed, 251 insertions(+), 4 deletions(-)

--- a/src/eventcomm.c
+++ b/src/eventcomm.c
@@ -148,6 +148,44 @@
     }
 }
 
+static void event_query_clickpad(LocalDevicePtr local)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *)local->private;
+    unsigned long evbits[NBITS(EV_MAX)] = {0};
+    int rc;
+
+    priv->is_clickpad = FALSE;
+    priv->has_led = FALSE;
+
+    if (priv->has_left && !priv->has_right && !priv->has_middle && !priv->has_double) {
+	priv->is_clickpad = TRUE;
+	xf86Msg(X_INFO, "%s: is Clickpad device\n", local->name);
+    }
+
+    SYSCALL(rc = ioctl(local->fd, EVIOCGBIT(0, sizeof(evbits)), evbits));
+    if (TEST_BIT(EV_LED, evbits)) {
+	unsigned long ledbits[NBITS(LED_MAX)] = {0};
+	SYSCALL(rc = ioctl(local->fd, EVIOCGBIT(EV_LED, sizeof(ledbits)), ledbits));
+	if (TEST_BIT(LED_MUTE, ledbits)) {
+	    xf86Msg(X_INFO, "%s: has LED control\n", local->name);
+	    priv->has_led = TRUE;
+	}
+    }
+}
+
+static void EventUpdateHardware(LocalDevicePtr local)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *)local->private;
+    if (priv->has_led) {
+	struct input_event ev;
+	gettimeofday(&ev.time, NULL);
+	ev.type = EV_LED;
+	ev.code = LED_MUTE;
+	ev.value = priv->synpara->touchpad_off != 0;
+	write(local->fd, &ev, sizeof(ev));
+    }
+}
+
 /* Query device for axis ranges */
 static void
 event_query_axis_ranges(LocalDevicePtr local)
@@ -229,6 +267,8 @@
 	   strcat(buf, " triple");
 	xf86Msg(X_INFO, "%s: buttons:%s\n", local->name, buf);
     }
+
+    event_query_clickpad(local);
 }
 
 static Bool
@@ -375,6 +415,8 @@
 		break;
 	    }
 	    break;
+	case EV_LED:
+	    return TRUE;
 	}
     }
     return FALSE;
@@ -463,5 +505,6 @@
     EventQueryHardware,
     EventReadHwState,
     EventAutoDevProbe,
-    EventReadDevDimensions
+    EventReadDevDimensions,
+    EventUpdateHardware,
 };
--- a/src/properties.c
+++ b/src/properties.c
@@ -80,6 +80,9 @@
 Atom prop_pressuremotion        = 0;
 Atom prop_pressuremotion_factor = 0;
 Atom prop_grab                  = 0;
+Atom prop_led_double_tap        = 0;
+Atom prop_touch_button_area     = 0;
+Atom prop_touch_button_sense    = 0;
 
 static Atom
 InitAtom(DeviceIntPtr dev, char *name, int format, int nvalues, int *values)
@@ -251,6 +254,12 @@
 
     prop_grab = InitAtom(local->dev, SYNAPTICS_PROP_GRAB, 8, 1, &para->grab_event_device);
 
+    prop_led_double_tap = InitAtom(local->dev, SYNAPTICS_PROP_LED_DOUBLE_TAP, 32, 1, &para->led_double_tap);
+
+    prop_touch_button_area = InitAtom(local->dev, SYNAPTICS_PROP_TOUCH_BUTTON_AREA, 8, 1, &para->touch_button_area);
+
+    prop_touch_button_sense = InitAtom(local->dev, SYNAPTICS_PROP_TOUCH_BUTTON_SENSE, 8, 1, &para->touch_button_sense);
+
 }
 
 int
@@ -467,6 +476,11 @@
             return BadValue;
 
         para->touchpad_off = off;
+	if (!checkonly) {
+	    if (priv->proto_ops && priv->proto_ops->UpdateHardware)
+		priv->proto_ops->UpdateHardware(local);
+	}
+
     } else if (property == prop_guestmouse)
     {
         if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
@@ -588,10 +602,39 @@
             return BadMatch;
 
         para->grab_event_device = *(BOOL*)prop->data;
+    } else if (property == prop_led_double_tap)
+    {
+        if (prop->size != 1 || prop->format != 32 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->led_double_tap = *(INT32*)prop->data;
+    } else if (property == prop_touch_button_area)
+    {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->touch_button_area = *(CARD8*)prop->data;
+    } else if (property == prop_touch_button_sense)
+    {
+        if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER)
+            return BadMatch;
+
+        para->touch_button_sense = *(BOOL*)prop->data;
     }
 
     return Success;
 }
 
+void SynapticsToggleOffProperty(DeviceIntPtr dev, Bool off)
+{
+	uint8_t val;
+
+	if (!prop_off)
+		return;
+	val = off;
+	XIChangeDeviceProperty(dev, prop_off, XA_INTEGER, 8,
+			       PropModeReplace, 1, &val, FALSE);
+}
+
 #endif
 
--- a/src/synapticsstr.h
+++ b/src/synapticsstr.h
@@ -152,6 +152,14 @@
     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 is_clickpad;			/* is Clickpad device (one-button) */
+    Bool has_led;			/* has LED indicator */
+    Bool led_touch_state;
+    Bool led_tapped;
+    int prop_change_pending;
+    int led_touch_millis;
+    int led_tap_millis;
+    struct SynapticsHwState prev_hw;
 
     enum TouchpadModel model;          /* The detected model */
 } SynapticsPrivate;
--- a/src/synproto.h
+++ b/src/synproto.h
@@ -97,6 +97,7 @@
 			struct CommData *comm, struct SynapticsHwState *hwRet);
     Bool (*AutoDevProbe)(LocalDevicePtr local);
     void (*ReadDevDimensions)(LocalDevicePtr local);
+    void (*UpdateHardware)(LocalDevicePtr local);
 };
 
 extern struct SynapticsProtocolOperations psaux_proto_operations;
--- a/src/synaptics.c
+++ b/src/synaptics.c
@@ -129,6 +129,7 @@
 void InitDeviceProperties(LocalDevicePtr local);
 int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
                 BOOL checkonly);
+void SynapticsToggleOffProperty(DeviceIntPtr dev, Bool off);
 #endif
 
 InputDriverRec SYNAPTICS = {
@@ -510,6 +511,9 @@
     pars->press_motion_min_factor = xf86SetRealOption(opts, "PressureMotionMinFactor", 1.0);
     pars->press_motion_max_factor = xf86SetRealOption(opts, "PressureMotionMaxFactor", 1.0);
     pars->grab_event_device = xf86SetBoolOption(opts, "GrabEventDevice", TRUE);
+    pars->led_double_tap = xf86SetIntOption(opts, "LEDDoubleTap", 400);
+    pars->touch_button_area = xf86SetIntOption(opts, "TouchButtonArea", 20);
+    pars->touch_button_sense = xf86SetBoolOption(opts, "TouchButtonSense", FALSE);
 
     /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
     if (pars->top_edge > pars->bottom_edge) {
@@ -761,6 +765,10 @@
 
     xf86AddEnabledDevice(local);
 
+    /* update LED */
+    if (priv->proto_ops && priv->proto_ops->UpdateHardware)
+	priv->proto_ops->UpdateHardware(local);
+
     return Success;
 }
 
@@ -970,6 +978,7 @@
 {
     LocalDevicePtr local = (LocalDevicePtr) (arg);
     SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
+    SynapticsSHM *para = priv->synpara;
     struct SynapticsHwState hw;
     int delay;
     int sigstate;
@@ -982,6 +991,12 @@
     hw.millis = now;
     delay = HandleState(local, &hw);
 
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3
+    if (priv->prop_change_pending) {
+	SynapticsToggleOffProperty(local->dev, para->touchpad_off);
+	priv->prop_change_pending = 0;
+    }
+#endif
     /*
      * Workaround for wraparound bug in the TimerSet function. This bug is already
      * fixed in CVS, but this driver needs to work with XFree86 versions 4.2.x and
@@ -1034,6 +1049,10 @@
 	hw.millis = GetTimeInMillis();
 	priv->hwState = hw;
 	delay = HandleState(local, &hw);
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3
+	if (priv->prop_change_pending)
+	    delay = MIN(10, delay);
+#endif
 	newDelay = TRUE;
     }
 
@@ -1969,6 +1988,108 @@
     }
 }
 
+/* clicpad button toggle point:
+ * some devices have a LED at the upper-left corner, and double-tapping it
+ * toggles the touchpad enable/disable
+ */
+static int
+HandleToggleLED(LocalDevicePtr local, struct SynapticsHwState *hw, int finger)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
+    SynapticsSHM *para = priv->synpara;
+    int click_led_x = (priv->maxx - priv->minx) * 1 / 10 + priv->minx;
+    int click_led_y = priv->maxy / 8 + priv->miny;
+    int diff;
+
+    if (finger) {
+	if (hw->x >= click_led_x || hw->y >= click_led_y) {
+	    /* outside the toggle area */
+	    priv->led_touch_state = FALSE;
+	    priv->led_tapped = FALSE;
+	    return finger;
+	}
+	if (!priv->led_touch_state) {
+	    /* touch start */
+	    priv->led_touch_millis = hw->millis;
+	    priv->led_touch_state = TRUE;
+	}
+	return 0; /* already processed; ignore this finger event */
+    }
+
+    if (!priv->led_touch_state)
+	return finger; /* nothing happened */
+
+    /* touch-released */
+    priv->led_touch_state = FALSE;
+    diff = TIME_DIFF(priv->led_touch_millis + para->tap_time, hw->millis);
+    if (diff < 0) /* non-tap? */
+	return finger;
+    if (priv->led_tapped) {
+	/* double-tapped? */
+	diff = TIME_DIFF(priv->led_tap_millis + para->led_double_tap, hw->millis);
+	if (diff >= 0) {
+	    para->touchpad_off = !para->touchpad_off;
+	    if (priv->proto_ops && priv->proto_ops->UpdateHardware)
+		priv->proto_ops->UpdateHardware(local); /* update LED */
+	    priv->prop_change_pending = 1;
+	    priv->led_tapped = FALSE;
+	}
+    } else
+	priv->led_tapped = TRUE;
+    priv->led_tap_millis = hw->millis;
+    return 0; /* already processed; ignore this finger event */
+}
+
+/* clickpad event handling */
+static void
+HandleClickpad(LocalDevicePtr local, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
+    SynapticsSHM *para = priv->synpara;
+    int width = priv->maxx - priv->minx;
+    int height = priv->maxy - priv->miny;
+    int button_area_y, x_vscroll;
+    int left_button_x, right_button_x;
+
+    /* left and right clickpad button ranges;
+     * the gap between them is interpreted as a middle-button click
+     */
+    left_button_x = width * 2/ 5 + priv->minx;
+    right_button_x = width * 3 / 5 + priv->minx;
+    /* button area */
+    button_area_y = priv->maxy - height * para->touch_button_area / 100;
+    /* allow position change for the v-scroll area exceptionally */
+    x_vscroll = priv->maxx - width / 15;
+
+    if (hw->y >= button_area_y) {
+	/* button area */
+	if (!para->touch_button_sense) {
+	    if (hw->left ||
+		/* in h- or v-scrolling area, allow sensing */
+		(!para->scroll_edge_horiz &&
+		 (hw->x < x_vscroll || !para->scroll_edge_vert)))
+	    hw->z = 0; /* don't move pointer */
+	}
+	/* clickpad reports only one button, and we need
+	 * to fake left/right buttons depending on the touch position
+	 */
+	if (hw->left) { /* clicked? */
+	    hw->left = 0;
+	    if (hw->x < left_button_x)
+		hw->left = 1;
+	    else if (hw->x > right_button_x)
+		hw->right = 1;
+	    else
+		hw->middle = 1;
+	}
+    } else if (hw->left) {
+	/* dragging */
+	hw->left = priv->prev_hw.left;
+	hw->right = priv->prev_hw.right;
+	hw->middle = priv->prev_hw.middle;
+    }
+    priv->prev_hw = *hw;
+}
 
 /*
  * React on changes in the hardware state. This function is called every time
@@ -2010,9 +2131,14 @@
     para->guest_dx = hw->guest_dx;
     para->guest_dy = hw->guest_dy;
 
-    /* If touchpad is switched off, we skip the whole thing and return delay */
-    if (para->touchpad_off == 1)
-	return delay;
+     /* If touchpad is switched off, we skip the whole thing and return delay */
+    if (para->touchpad_off == 1 && !(priv->has_led && para->led_double_tap))
+ 	return delay;
+
+    /* Clickpad handling for button area */
+    if (para->touchpad_off != 1 && priv->is_clickpad &&
+	para->touch_button_area > 0)
+	HandleClickpad(local, hw);
 
     /* Treat the first two multi buttons as up/down for now. */
     hw->up |= hw->multi[0];
@@ -2068,6 +2194,14 @@
 
     finger = SynapticsDetectFinger(priv, hw);
 
+    if (priv->has_led && para->led_double_tap) {
+	finger = HandleToggleLED(local, hw, finger);
+	if (para->touchpad_off == 1) {
+	    priv->finger_state = finger;
+	    return delay;
+	}
+    }
+
     /* tap and drag detection */
     timeleft = HandleTapProcessing(priv, hw, edge, finger);
     if (timeleft > 0)
--- a/include/synaptics.h
+++ b/include/synaptics.h
@@ -134,6 +134,9 @@
     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? */
+    int led_double_tap;			    /* double-tap period in ms for touchpad LED control */
+    int touch_button_area;                  /* touch button (clickpad) area in percent */
+    Bool touch_button_sense;                /* touch button (clickpad) area sensing */
 } SynapticsSHM;
 
 /*
--- a/include/synaptics-properties.h
+++ b/include/synaptics-properties.h
@@ -142,4 +142,13 @@
 /* 8 bit (BOOL) */
 #define SYNAPTICS_PROP_GRAB "Synaptics Grab Event Device"
 
+/* 32 bit */
+#define SYNAPTICS_PROP_LED_DOUBLE_TAP "Synaptics LED Double Tap"
+
+/* 8 bit (0-100) */
+#define SYNAPTICS_PROP_TOUCH_BUTTON_AREA "Synaptics Touch Button Area"
+
+/* 8 bit (BOOL) */
+#define SYNAPTICS_PROP_TOUCH_BUTTON_SENSE "Synaptics Touch Button Sense"
+
 #endif /* _SYNAPTICS_PROPERTIES_H_ */
--- a/tools/synclient.c
+++ b/tools/synclient.c
@@ -209,6 +209,12 @@
 		SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR,	0 /*float*/,	1),
     DEFINE_PAR("GrabEventDevice",      grab_event_device,       PT_BOOL,   0, 1,
 		SYNAPTICS_PROP_GRAB,	8,	0),
+    DEFINE_PAR("LEDDoubleTap",         led_double_tap,          PT_INT,    0, 1000,
+		SYNAPTICS_PROP_LED_DOUBLE_TAP,	32,	0),
+    DEFINE_PAR("TouchButtonArea",      touch_button_area,       PT_INT,    0, 100,
+		SYNAPTICS_PROP_TOUCH_BUTTON_AREA,	8,	0),
+    DEFINE_PAR("TouchButtonSense",     touch_button_sense,      PT_BOOL,   0, 1,
+		SYNAPTICS_PROP_TOUCH_BUTTON_SENSE,	8,	0),
     { NULL, 0, 0, 0, 0 }
 };
 
