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;