From ee7ad89ecbe365a6a9705b34a4c4b697cbff333e Mon Sep 17 00:00:00 2001
From: Takashi Iwai <tiwai@suse.de>
Date: Wed, 21 Apr 2010 13:33:41 +0200
Subject: [PATCH 2/2] Support LED and tap-on-LED feature

This patch adds the control of the embedded LED on the top-left corner
of new Synaptics devices.  For LED control, it requires the patch to
Linux synaptics input driver,
	https://patchwork.kernel.org/patch/93858/

When a sysfs file /sys/class/leds/psmouse::synaptics exists, the driver
assumes it supports the embeded LED control.  This corresponds to the
touchpad-off state.  When touchpad is disabled, LED is turned on.

For linking between the touchpad state and the LED state, a new callback
UpdateHardware is introduced.  Only eventcomm.c supports this (naturally).

A new feature for the LED-equipped device is that user can double-tap
on the LED to toggle the touchpad state on the fly.  This is also linked
with the touchpad-off state.

There is a new parameter for controlling the LED double-tap behavior, too.
It specifies the double-tap time.  Passing zero disables the double-tap
feature.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 include/synaptics-properties.h |    3 +
 man/synaptics.man              |   13 ++++++
 src/eventcomm.c                |   29 ++++++++++++++
 src/properties.c               |   26 +++++++++++++
 src/synaptics.c                |   80 +++++++++++++++++++++++++++++++++++++++++
 src/synapticsstr.h             |    6 +++
 src/synproto.h                 |    1 
 tools/synclient.c              |    1 
 8 files changed, 158 insertions(+), 1 deletion(-)

--- a/include/synaptics-properties.h
+++ b/include/synaptics-properties.h
@@ -155,4 +155,7 @@
 /* 32 bit, 4 values, left, right, top, bottom */
 #define SYNAPTICS_PROP_AREA "Synaptics Area"
 
+/* 32 bit */
+#define SYNAPTICS_PROP_LED_DOUBLE_TAP "Synaptics LED Double Tap"
+
 #endif /* _SYNAPTICS_PROPERTIES_H_ */
--- a/man/synaptics.man
+++ b/man/synaptics.man
@@ -509,6 +509,19 @@
 The option is disabled by default and can be enabled by setting the
 AreaBottomEdge option to any integer value other than zero. Property: "Synaptics Area"
 .
+.TP
+.BI "Option \*qLEDDoubleTap\*q \*q" integer \*q
+.
+The double-tap time for toggling the touchpad-control on the top-left
+corner LED.
+.
+Some devices have an LED on the top-left corner to indicate the
+touchpad state.  User can double-tap on the LED to toggle the touchpad
+state.  This option controls the double-tap time in milli-seconds.
+When it's zero, the LED-tapping feature is disabled.  The default
+value is 400.
+Property: "Synaptics LED Double Tap"
+.
 .LP
 A tap event happens when the finger is touched and released in a time
 interval shorter than MaxTapTime, and the touch and release
--- a/src/eventcomm.c
+++ b/src/eventcomm.c
@@ -51,6 +51,8 @@
 #define LONG(x)  ((x) / LONG_BITS)
 #define TEST_BIT(bit, array) (array[LONG(bit)] & (1 << OFF(bit)))
 
+#define SYNAPTICS_LED_SYS_FILE	"/sys/class/leds/psmouse::synaptics/brightness"
+
 /*****************************************************************************
  *	Function Definitions
  ****************************************************************************/
@@ -166,6 +168,29 @@
     }
 }
 
+static void
+event_query_led(LocalDevicePtr local)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *)local->private;
+
+    priv->has_led = !access(SYNAPTICS_LED_SYS_FILE, W_OK);
+}
+
+static void EventUpdateHardware(LocalDevicePtr local)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *)local->private;
+
+    if (priv->has_led) {
+        char *val = priv->synpara.touchpad_off ? "255" : "0";
+        int fd = open(SYNAPTICS_LED_SYS_FILE, O_WRONLY);
+        if (fd < 0)
+            return;
+        if (write(fd, val, strlen(val)) < 0)
+            return;
+        close(fd);
+    }
+}
+
 /* Query device for axis ranges */
 static void
 event_query_axis_ranges(LocalDevicePtr local)
@@ -430,6 +455,7 @@
     if (event_query_is_touchpad(local->fd, (need_grab) ? *need_grab : TRUE))
 	event_query_axis_ranges(local);
     event_query_info(local);
+    event_query_led(local);
 }
 
 static Bool
@@ -499,5 +525,6 @@
     EventQueryHardware,
     EventReadHwState,
     EventAutoDevProbe,
-    EventReadDevDimensions
+    EventReadDevDimensions,
+    EventUpdateHardware,
 };
--- a/src/properties.c
+++ b/src/properties.c
@@ -84,6 +84,7 @@
 Atom prop_capabilities          = 0;
 Atom prop_resolution            = 0;
 Atom prop_area                  = 0;
+Atom prop_led_double_tap        = 0;
 
 static Atom
 InitAtom(DeviceIntPtr dev, char *name, int format, int nvalues, int *values)
@@ -274,6 +275,9 @@
     values[2] = para->area_top_edge;
     values[3] = para->area_bottom_edge;
     prop_area = InitAtom(local->dev, SYNAPTICS_PROP_AREA, 32, 4, values);
+
+    prop_led_double_tap = InitAtom(local->dev, SYNAPTICS_PROP_LED_DOUBLE_TAP, 32, 1, &para->led_double_tap);
+
 }
 
 int
@@ -490,6 +494,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)
@@ -642,10 +651,27 @@
         para->area_right_edge  = area[1];
         para->area_top_edge    = area[2];
         para->area_bottom_edge = area[3];
+    } 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;
     }
 
     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/synaptics.c
+++ b/src/synaptics.c
@@ -138,6 +138,7 @@
 void InitDeviceProperties(LocalDevicePtr local);
 int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
                 BOOL checkonly);
+void SynapticsToggleOffProperty(DeviceIntPtr dev, Bool off);
 #endif
 
 InputDriverRec SYNAPTICS = {
@@ -549,6 +550,7 @@
     pars->tap_and_drag_gesture = xf86SetBoolOption(opts, "TapAndDragGesture", TRUE);
     pars->resolution_horiz = xf86SetIntOption(opts, "HorizResolution", horizResolution);
     pars->resolution_vert = xf86SetIntOption(opts, "VertResolution", vertResolution);
+    pars->led_double_tap = xf86SetIntOption(opts, "LEDDoubleTap", 400);
 
     /* Warn about (and fix) incorrectly configured TopEdge/BottomEdge parameters */
     if (pars->top_edge > pars->bottom_edge) {
@@ -805,6 +807,10 @@
 
     xf86AddEnabledDevice(local);
 
+    /* update LED */
+    if (priv->proto_ops && priv->proto_ops->UpdateHardware)
+        priv->proto_ops->UpdateHardware(local);
+
     return Success;
 }
 
@@ -2120,6 +2126,72 @@
     }
 }
 
+#define LED_TOGGLE_X_AREA	0.10
+#define LED_TOGGLE_Y_AREA	0.08
+
+static int
+in_led_toggle_area(LocalDevicePtr local, struct SynapticsHwState *hw)
+{
+    SynapticsPrivate *priv = (SynapticsPrivate *) (local->private);
+    int click_led_x, click_led_y;
+
+    click_led_x = (priv->maxx - priv->minx) * LED_TOGGLE_X_AREA + priv->minx;
+    click_led_y = (priv->maxy - priv->miny) * LED_TOGGLE_Y_AREA + priv->miny;
+    return (hw->x < click_led_x && hw->y < click_led_y);
+}
+
+/* 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);
+    SynapticsParameters *para = &priv->synpara;
+    int diff;
+
+    if (finger) {
+        if (!in_led_toggle_area(local, hw)) {
+            /* 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 */
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3
+            SynapticsToggleOffProperty(local->dev, para->touchpad_off);
+#endif
+            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)
@@ -2267,6 +2339,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, inside_active_area);
     if (timeleft > 0)
--- a/src/synapticsstr.h
+++ b/src/synapticsstr.h
@@ -149,6 +149,7 @@
     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 led_double_tap;			    /* double-tap period in ms for touchpad LED control */
 } SynapticsParameters;
 
 
@@ -223,6 +224,11 @@
     Bool has_pressure;			/* device reports pressure */
     Bool is_clickpad;			/* is Clickpad device (one-button) */
     struct SynapticsHwState prev_hw;	/* previous h/w state (for clickpad) */
+    Bool has_led;			/* has LED indicator */
+    Bool led_touch_state;
+    Bool led_tapped;
+    int led_touch_millis;
+    int led_tap_millis;
 
     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/tools/synclient.c
+++ b/tools/synclient.c
@@ -143,6 +143,7 @@
     {"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},
+    {"LEDDoubleTap",          PT_INT,    0, 1000,  SYNAPTICS_PROP_LED_DOUBLE_TAP,	32,	0},
     { NULL, 0, 0, 0, 0 }
 };
 
