Currently pms and the wsconscomm module of the synaptics driver offer a
somewhat limited support for Elantech clickpads with hardware version 4.
Above all, I missed the options of performing click-and-drag operations
with two fingers and of using a "soft button area" for the emulation of
right button clicks (tap-and-drag and two-finger tapping are no
alternatives for me, usually I don't enable tapping).

I have written two patches that provide these options (I'm using them
on an Acer V5-131 netbook with OpenBSD 5.6/amd64, the clickpad hardware
and firmware is identified as "Elantech Clickpad, version 4, firmware
0x461f02"). There is, however, an open question concerning wsconscomm.
If someone is interested in testing and using the patches, or in
assessing whether they can be useful for the OpenBSD project, some
explanations might help.

Two-finger scrolling and click-and-drag actions on clickpads require
that the multitouch input is filtered or "translated" into coherent
sequences of coordinate pairs. The pms patch - which I have added
below - implements a simple filter that checks the motion deltas and
ensures that if there is at least one finger moving on the clickpad, a
moving one will be tracked. Some care is needed when the input "slot"
changes, but this works well even within the infrastructure of wscons,
which doesn't define and handle "MT" events.

The problem with wsconscomm is related to the X/Linux way of handling
multitouches. If I understand it correctly, it produces MT events with
slot data as well as standard events with coordinates for "pointer
control", and the latter are determined by the "oldest" touch. That
mechanism is sufficient if two fingers are moving in parallel, but it
cannot cover the click-and-drag case because usually only one finger
is moving, and it is not necessarily the one that has touched the
clickpad first. This might be the reason why the synaptics driver
implements an additional mapping. It accumulates the motion deltas
of the multitouch slots in a special coordinate pair. As long as no
button is pressed, the pair isn't used, and its values will be
synchronized with the standard coordinates. When clickpad support is
enabled ("$ synclient ClickPad=1") and a button is down, the
"cumulative" values will be used. This makes the cursor movement
independent of the order of touches (and you can create, e.g., a
diagonal cursor movement by combining a horizontal and a vertical
finger movement).

It seems that the current development version of the synaptics driver
applies the cumulative coordinates to two-finger-scrolling as well
(independently of clickpad support).

In OpenBSD wsconscomm updates the cumulative coordinates when no
button is being pressed, but it does nothing with them otherwise. If
clickpad support is enabled, this has the effect that the X cursor
"freezes" as long as the clickpad button is down, and no click-and-
drag operation - not even the one-finger variant - is possible (for
this reason I couldn't use soft button areas, which require the
clickpad support).

As wsconscomm doesn't deal with MT events, couldn't it simply always
keep the coordinates in sync? To make click-and-drag work in my
installation I have applied the following patch to wsconscomm.c:

diff --git a/wsconscomm.c b/wsconscomm.c
index 0e0c81d..a6540db 100644
--- a/wsconscomm.c
+++ b/wsconscomm.c
@@ -131,12 +131,6 @@ WSConsReadHwState(InputInfoPtr pInfo,
     struct wscons_event event;
     Bool v;

-    /* Reset cumulative values if buttons were not previously pressed */
-    if (!hw->left && !hw->right && !hw->middle) {
-        hw->cumulative_dx = hw->x;
-        hw->cumulative_dy = hw->y;
-    }
-
     while (WSConsReadEvent(pInfo, &event)) {
         switch (event.type) {
         case WSCONS_EVENT_MOUSE_UP:
@@ -186,9 +180,11 @@ WSConsReadHwState(InputInfoPtr pInfo,
             break;
         case WSCONS_EVENT_MOUSE_ABSOLUTE_X:
             hw->x = event.value;
+            hw->cumulative_dx = hw->x;
             break;
         case WSCONS_EVENT_MOUSE_ABSOLUTE_Y:
             hw->y = priv->maxy - event.value + priv->miny;
+            hw->cumulative_dy = hw->y;
             break;
         case WSCONS_EVENT_MOUSE_ABSOLUTE_Z:
             hw->z = event.value;

Please note that this patch might "unmask" an inconsistent treatment
of multitouches. For example, if I use it without applying the pms
patch, an attempt to click-and-drag can lead to jumps of the cursor
and inverted vertical movements. Could something similar happen with
other hardware where clickpad support would make sense? And if this
is the case, could the change in wsconscomm be restricted in a
reasonable way?

If the pms patch is applied without changing wsconscomm, it would only
fix minor flaws in the clickpad behaviour (currently it is possible to
produce a "tap" by putting two fingers on the clickpad and lifting them
successively within a tap interval; two-finger scrolling may start with
a jump).

The diffs are against the (5.6) release versions, I hope it doesn't
matter.


diff --git a/pms.c b/pms.c
index 77904ae..697b3ef 100644
--- a/pms.c
+++ b/pms.c
@@ -125,8 +125,15 @@ struct elantech_softc {
        struct {
                unsigned int x;
                unsigned int y;
+               unsigned int z;
        } mt[ELANTECH_MAX_FINGERS];
-       int fingers[ELANTECH_MAX_FINGERS];
+       int mt_slots;
+       int mt_count;
+       int mt_filter;
+       int mt_lastid;
+       int mt_lastcount;
+       int mt_buttons;
+
        int width;

        u_char parity[256];
@@ -295,6 +302,7 @@ int elantech_set_absolute_mode_v2(struct pms_softc *);
 int    elantech_set_absolute_mode_v3(struct pms_softc *);
 int    elantech_set_absolute_mode_v4(struct pms_softc *);

+void   elantech_send_mt_input(struct pms_softc *, int);

 struct cfattach pms_ca = {
        sizeof(struct pms_softc), pmsprobe, pmsattach, NULL,
@@ -2215,77 +2223,67 @@ void
 pms_proc_elantech_v4(struct pms_softc *sc)
 {
        struct elantech_softc *elantech = sc->elantech;
-       int z, delta_x1 = 0, delta_x2 = 0, delta_y1 = 0, delta_y2 = 0;
-       int i, weight, finger, fingers = 0, id, sid;
+       int n, id, slots, weight, dx, dy;

        switch (sc->packet[3] & 0x1f) {
        case ELANTECH_V4_PKT_STATUS:
-               fingers = sc->packet[1] & 0x1f;
-               for (i = 0; i < ELANTECH_MAX_FINGERS; i++) {
-                       finger = ((fingers & (1 << i)) != 0);
-                       if (elantech->fingers[i] && !finger)
-                               /* notify that we lifted */
-                               elantech_send_input(sc, elantech->mt[i].x,
-                                   elantech->mt[i].y, 0, 0);
-
-                       elantech->fingers[i] = finger;
-               }
+               if (elantech->mt_slots == 0)
+                       elantech->mt_lastid = -1;
+               slots = sc->packet[1] & 0x1f;
+               if (slots == 0 && elantech->mt_lastid > -1)
+                       /* Notify that we lifted. */
+                       elantech_send_input(sc,
+                           elantech->mt[elantech->mt_lastid].x,
+                           elantech->mt[elantech->mt_lastid].y, 0, 0);
+
+               elantech->mt_filter = elantech->mt_slots = slots;
+
+               for (elantech->mt_count = 0; slots != 0; slots >>= 1)
+                       elantech->mt_count += (1 & slots);

                break;

        case ELANTECH_V4_PKT_HEAD:
                id = ((sc->packet[3] & 0xe0) >> 5) - 1;
-               if (id < 0)
-                       return;
-
-               for (i = 0; i < ELANTECH_MAX_FINGERS; i++)
-                       if (elantech->fingers[i])
-                               fingers++;
-
-               elantech->mt[id].x = ((sc->packet[1] & 0x0f) << 8) |
-                   sc->packet[2];
-               elantech->mt[id].y = (((sc->packet[4] & 0x0f) << 8) |
-                   sc->packet[5]);
-               z = (sc->packet[1] & 0xf0) | ((sc->packet[4] & 0xf0) >> 4);
-
-               elantech_send_input(sc, elantech->mt[id].x, elantech->mt[id].y,
-                   z, fingers);
-
+               if (id > -1 && id < ELANTECH_MAX_FINGERS) {
+                       elantech->mt[id].x =
+                           ((sc->packet[1] & 0x0f) << 8) | sc->packet[2];
+                       elantech->mt[id].y =
+                           ((sc->packet[4] & 0x0f) << 8) | sc->packet[5];
+                       elantech->mt[id].z =
+                           (sc->packet[1] & 0xf0)
+                           | ((sc->packet[4] & 0xf0) >> 4);
+
+                       if (elantech->mt_filter & (1 << id)) {
+                               elantech_send_mt_input(sc, id);
+                               elantech->mt_filter = (1 << id);
+                       }
+               }
                break;

        case ELANTECH_V4_PKT_MOTION:
-               id = ((sc->packet[0] & 0xe0) >> 5) - 1;
-               if (id < 0)
-                       return;
-
-               sid = ((sc->packet[3] & 0xe0) >> 5) - 1;
                weight = (sc->packet[0] & 0x10) ? ELANTECH_V4_WEIGHT_VALUE : 1;
-
-               delta_x1 = (signed char)sc->packet[1];
-               delta_y1 = (signed char)sc->packet[2];
-               delta_x2 = (signed char)sc->packet[4];
-               delta_y2 = (signed char)sc->packet[5];
-
-               elantech->mt[id].x += delta_x1 * weight;
-               elantech->mt[id].y -= delta_y1 * weight;
-
-               for (i = 0; i < ELANTECH_MAX_FINGERS; i++)
-                       if (elantech->fingers[i])
-                               fingers++;
-
-               elantech_send_input(sc, elantech->mt[id].x, elantech->mt[id].y,
-                   1, fingers);
-
-               if (sid >= 0) {
-                       elantech->mt[sid].x += delta_x2 * weight;
-                       elantech->mt[sid].y -= delta_y2 * weight;
-                       /* XXX: can only send one finger of input */
-                       /*
-                       elantech_send_input(sc, elantech->mt[sid].x,
-                           elantech->mt[sid].y, 1, fingers);
-                       */
+               for (n = 0; n < 6; n += 3) {
+                       id = ((sc->packet[n] & 0xe0) >> 5) - 1;
+                       if (id < 0 || id >= ELANTECH_MAX_FINGERS)
+                               continue;
+                       dx = weight * (signed char)sc->packet[n + 1];
+                       dy = weight * (signed char)sc->packet[n + 2];
+                       elantech->mt[id].x += dx;
+                       elantech->mt[id].y += dy;
+                       elantech->mt[id].z = 1;
+                       if (elantech->mt_filter & (1 << id)) {
+                               if ((dx | dy)
+                                   || elantech->mt_count !=
+                                   elantech->mt_lastcount
+                                   || (sc->packet[0] & 3) !=
+                                   elantech->mt_buttons)
+                                       elantech_send_mt_input(sc, id);
+
+                               elantech->mt_filter = (dx | dy) ?
+                                   (1 << id) : elantech->mt_slots;
+                       }
                }
-
                break;

        default:
@@ -2296,6 +2294,37 @@ pms_proc_elantech_v4(struct pms_softc *sc)
 }

 void
+elantech_send_mt_input(struct pms_softc *sc, int id)
+{
+       struct elantech_softc *elantech = sc->elantech;
+
+       if (id != elantech->mt_lastid) {
+               /* Correct for compatibility mode, but not useful yet: */
+               elantech->old_x = elantech->mt[id].x;
+               elantech->old_y = elantech->mt[id].y;
+               /*
+                * To avoid a jump of the cursor, simulate a change of the
+                * number of touches (without producing tapping gestures
+                * accidentally). It should suffice to do that only if
+                * mt_count hasn't changed, but we cannot rely on the
+                * synaptics driver, which alters its finger counts when
+                * handling click-and-drag actions (see HandleTapProcessing
+                * and ComputeDeltas in synaptics.c).
+                */
+               if (elantech->mt_lastid > -1)
+                       elantech_send_input(sc,
+                           elantech->mt[id].x, elantech->mt[id].y,
+                           elantech->mt[id].z, ELANTECH_MAX_FINGERS);
+               elantech->mt_lastid = id;
+       }
+       elantech->mt_lastcount = elantech->mt_count;
+       elantech->mt_buttons = sc->packet[0] & 3;
+       elantech_send_input(sc,
+           elantech->mt[id].x, elantech->mt[id].y,
+           elantech->mt[id].z, elantech->mt_count);
+}
+
+void
 elantech_send_input(struct pms_softc *sc, int x, int y, int z, int w)
 {
        struct elantech_softc *elantech = sc->elantech;

Reply via email to