[PATCH v2] Support LED and tap-on-LED feature

Takashi Iwai tiwai at suse.de
Wed Apr 21 06:22:35 PDT 2010


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 at suse.de>
---

The new patch is based on the sysfs LED class interface we chose
instead of input evdev LED bits.  Also including a few clean-ups and
untabified.

 include/synaptics-properties.h |    3 +
 man/synaptics.man              |   13 ++++++
 src/eventcomm.c                |   29 +++++++++++++-
 src/properties.c               |   26 ++++++++++++
 src/synaptics.c                |   85 +++++++++++++++++++++++++++++++++++++++-
 src/synapticsstr.h             |    6 +++
 src/synproto.h                 |    1 +
 tools/synclient.c              |    1 +
 8 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/include/synaptics-properties.h b/include/synaptics-properties.h
index cf330d8..d24ed9e 100644
--- 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_ */
diff --git a/man/synaptics.man b/man/synaptics.man
index 59fbaac..0c79721 100644
--- a/man/synaptics.man
+++ b/man/synaptics.man
@@ -509,6 +509,19 @@ Ignore movements, scrolling and tapping which take place 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. 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
diff --git a/src/eventcomm.c b/src/eventcomm.c
index 10d183d..ffcacf1 100644
--- 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 @@ event_query_info(LocalDevicePtr local)
     }
 }
 
+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)
@@ -434,6 +459,7 @@ EventReadDevDimensions(LocalDevicePtr local)
     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
@@ -493,5 +519,6 @@ struct SynapticsProtocolOperations event_proto_operations = {
     EventQueryHardware,
     EventReadHwState,
     EventAutoDevProbe,
-    EventReadDevDimensions
+    EventReadDevDimensions,
+    EventUpdateHardware,
 };
diff --git a/src/properties.c b/src/properties.c
index 4366034..d7bc3bd 100644
--- a/src/properties.c
+++ b/src/properties.c
@@ -84,6 +84,7 @@ Atom prop_gestures              = 0;
 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 @@ InitDeviceProperties(LocalDevicePtr local)
     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 @@ SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
             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 @@ SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
         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
 
diff --git a/src/synaptics.c b/src/synaptics.c
index 951b3fa..8087c81 100644
--- a/src/synaptics.c
+++ b/src/synaptics.c
@@ -134,6 +134,7 @@ static void CalculateScalingCoeffs(SynapticsPrivate *priv);
 void InitDeviceProperties(LocalDevicePtr local);
 int SetProperty(DeviceIntPtr dev, Atom property, XIPropertyValuePtr prop,
                 BOOL checkonly);
+void SynapticsToggleOffProperty(DeviceIntPtr dev, Bool off);
 #endif
 
 InputDriverRec SYNAPTICS = {
@@ -544,6 +545,7 @@ static void set_default_parameters(LocalDevicePtr local)
     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) {
@@ -776,6 +778,11 @@ DeviceOn(DeviceIntPtr dev)
     }
 
     xf86AddEnabledDevice(local);
+
+    /* update LED */
+    if (priv->proto_ops && priv->proto_ops->UpdateHardware)
+        priv->proto_ops->UpdateHardware(local);
+
     dev->public.on = TRUE;
 
     return Success;
@@ -2064,6 +2071,72 @@ HandleClickWithFingers(SynapticsParameters *para, struct SynapticsHwState *hw)
     }
 }
 
+#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, edge_type edge)
@@ -2164,13 +2237,13 @@ HandleState(LocalDevicePtr local, struct SynapticsHwState *hw)
     }
 
     /* If touchpad is switched off, we skip the whole thing and return delay */
-    if (para->touchpad_off == 1)
+    if (para->touchpad_off == 1 && !(priv->has_led && para->led_double_tap))
 	return delay;
 
     edge = edge_detection(priv, hw->x, hw->y);
 
     /* Clickpad handling for button area */
-    if (priv->is_clickpad)
+    if (para->touchpad_off != 1 && priv->is_clickpad)
 	HandleClickpad(local, hw, edge);
 
     /* Treat the first two multi buttons as up/down for now. */
@@ -2227,6 +2300,14 @@ HandleState(LocalDevicePtr local, struct SynapticsHwState *hw)
 
     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)
diff --git a/src/synapticsstr.h b/src/synapticsstr.h
index 05e43d3..54c0edf 100644
--- a/src/synapticsstr.h
+++ b/src/synapticsstr.h
@@ -160,6 +160,7 @@ typedef struct _SynapticsParameters
     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;
 
 
@@ -234,6 +235,11 @@ typedef struct _SynapticsPrivateRec
     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;
diff --git a/src/synproto.h b/src/synproto.h
index f7be819..1611a12 100644
--- a/src/synproto.h
+++ b/src/synproto.h
@@ -97,6 +97,7 @@ struct SynapticsProtocolOperations {
 			struct CommData *comm, struct SynapticsHwState *hwRet);
     Bool (*AutoDevProbe)(LocalDevicePtr local);
     void (*ReadDevDimensions)(LocalDevicePtr local);
+    void (*UpdateHardware)(LocalDevicePtr local);
 };
 
 extern struct SynapticsProtocolOperations psaux_proto_operations;
diff --git a/tools/synclient.c b/tools/synclient.c
index 316ae2c..700bcea 100644
--- a/tools/synclient.c
+++ b/tools/synclient.c
@@ -143,6 +143,7 @@ static struct Parameter params[] = {
     {"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 }
 };
 
-- 
1.7.0.4



More information about the xorg-devel mailing list