Download raw body.
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);
}
pms(4): enable extended W mode on Synaptics image sensors, report second contact