Index | Thread | Search

From:
Marc Lasch <mlasch@mailbox.org>
Subject:
pms(4): enable extended W mode on Synaptics image sensors, report second contact
To:
tech@openbsd.org
Date:
Sat, 26 Sep 2026 23:07:04 +0200

Download raw body.

Thread
  • Marc Lasch:

    pms(4): enable extended W mode on Synaptics image sensors, report second contact

Hi,

on Synaptics clickpads, that are image sensors, 
(SYNAPTICS_EXT_CAP_REPORTS_V), e.g. the ThinkPad X1 Carbon Gen 5 
(firmware 8.2), click-and-drag with two fingers doesn't work. This is a 
long standing issue with my Thinkpad. With one finger pressing the pad 
and another dragging, the pointer doesn't move. Standard packets only 
carry the position of the first contact.

The second contact is reported in extended W mode packets (w == 2, "AGM" 
packet type 1). pms enables AGM, but on W-mode pads it never sets mode 
bit 2, which enables extended W mode on devices with 
SYNAPTICS_EXT_MODEL_EW_MODE. So these packets are never sent. Linux 
(SYN_BIT_DISABLE_GESTURE, set by default) and FreeBSD (mode byte 0xc5) 
both set this bit.

This diff:

- sets bit 2 in W mode if the device advertises EW mode
- parses type-1 AGM packets instead of dropping them
- for image sensors, reports both contacts via wsmouse_mtframe() with 2 
MT slots, so wsmouse/wstpad pick the moving contact as the pointer and 
treat the resting one as the clicking finger.

Tested on an x1 carbon gen5, click-and-drag, two-finger scrolling and 
general pointer movement work.

Known limitation: three contacts are reported as two (no position for a 
third contact).

This patch was created with the help of Claude. I'm happy that I finally 
can use OpenBSD properly on my Thinkpad. I hope it can be integrated 
upstream.

Cheers Marc

--- sys/dev/pckbc/pms.c.orig    Tue Jul 15 15:40:02 2025
+++ sys/dev/pckbc/pms.c    Fri Sep 25 21:57:01 2026
@@ -98,6 +98,8 @@

      u_int sec_buttons;

+    struct mtpoint agm;    /* second contact, from AGM packets */
+
  #define SYNAPTICS_PRESSURE_HI        30
  #define SYNAPTICS_PRESSURE_LO        25
  #define SYNAPTICS_PRESSURE        SYNAPTICS_PRESSURE_HI
@@ -1067,6 +1069,15 @@
      hw->hw_type = (syn->ext_capabilities & SYNAPTICS_EXT_CAP_CLICKPAD)
          ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD;

+    /*
+     * Image sensors report the position of a second contact in AGM
+     * packets, report it as MT input.
+     */
+    if (syn->ext_capabilities & SYNAPTICS_EXT_CAP_REPORTS_V) {
+        hw->mt_slots = 2;
+        hw->flags |= WSMOUSEHW_MT_TRACKING;
+    }
+
      if (resolution & SYNAPTICS_RESOLUTION_VALID) {
          hw->h_res = SYNAPTICS_RESOLUTION_X(resolution);
          hw->v_res = SYNAPTICS_RESOLUTION_Y(resolution);
@@ -1216,9 +1227,12 @@
       * some older Synaptics models do not report finger counts without it.
       */
      mode = SYNAPTICS_ABSOLUTE_MODE | SYNAPTICS_HIGH_RATE;
-    if (syn->capabilities & SYNAPTICS_CAP_EXTENDED)
+    if (syn->capabilities & SYNAPTICS_CAP_EXTENDED) {
          mode |= SYNAPTICS_W_MODE;
-    else if (SYNAPTICS_ID_MAJOR(syn->identify) >= 4)
+        /* In W mode, this bit enables the extended W mode. */
+        if (syn->ext_model & SYNAPTICS_EXT_MODEL_EW_MODE)
+            mode |= SYNAPTICS_DISABLE_GESTURE;
+    } else if (SYNAPTICS_ID_MAJOR(syn->identify) >= 4)
          mode |= SYNAPTICS_DISABLE_GESTURE;
      if (synaptics_set_mode(sc, mode, 0))
          goto err;
@@ -1293,8 +1307,9 @@
  pms_proc_synaptics(struct pms_softc *sc)
  {
      struct synaptics_softc *syn = sc->synaptics;
+    struct mtpoint pt[2];
      u_int buttons;
-    int x, y, z, w, fingerwidth;
+    int x, y, z, w, fingerwidth, n;

      w = ((sc->packet[0] & 0x30) >> 2) | ((sc->packet[0] & 0x04) >> 1) |
          ((sc->packet[3] & 0x04) >> 2);
@@ -1322,8 +1337,18 @@
      if ((sc->sc_dev_enable & PMS_DEV_PRIMARY) == 0)
          return;

-    if (w == 2)
-        return;    /* EW-mode packets are not expected here. */
+    if (w == 2) {
+        /* AGM packet, type 1 contains the second contact. */
+        if (((sc->packet[5] & 0x30) >> 4) == 1) {
+            syn->agm.x = (((sc->packet[4] & 0x0f) << 8) |
+                sc->packet[1]) << 1;
+            syn->agm.y = (((sc->packet[4] & 0xf0) << 4) |
+                sc->packet[2]) << 1;
+            syn->agm.pressure = ((sc->packet[3] & 0x30) |
+                (sc->packet[5] & 0x0f)) << 1;
+        }
+        return;
+    }

      x = ((sc->packet[3] & 0x10) << 8) | ((sc->packet[1] & 0x0f) << 8) |
          sc->packet[4];
@@ -1388,6 +1413,24 @@
          w = 0;
      }
      wsmouse_set(sc->sc_wsmousedev, WSMOUSE_TOUCH_WIDTH, fingerwidth, 0);
+
+    if (syn->ext_capabilities & SYNAPTICS_EXT_CAP_REPORTS_V) {
+        n = 0;
+        if (z) {
+            pt[n].x = x;
+            pt[n].y = y;
+            pt[n++].pressure = z;
+            if (w >= 2 && syn->agm.pressure)
+                pt[n++] = syn->agm;
+        }
+        if (w < 2)
+            syn->agm.pressure = 0;
+        wsmouse_buttons(sc->sc_wsmousedev, buttons);
+        wsmouse_mtframe(sc->sc_wsmousedev, pt, n);
+        wsmouse_input_sync(sc->sc_wsmousedev);
+        return;
+    }
+
      WSMOUSE_TOUCH(sc->sc_wsmousedev, buttons, x, y, z, w);
  }