[PATCH evdev] Add initial multitouch support using Xi 2.1

Daniel Stone daniel at fooishbar.org
Sun Sep 19 22:03:07 PDT 2010


This uses the new xserver touch event API to provide support for
multitouch in evdev, for devices that provide a hardware tracking ID.
All ABS_MT_* events are sent as independent touch events, rather than
valuators.

Signed-off-by: Daniel Stone <daniel at fooishbar.org>
---
 src/evdev.c |  210 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 src/evdev.h |    9 +++
 2 files changed, 205 insertions(+), 14 deletions(-)

diff --git a/src/evdev.c b/src/evdev.c
index ae20de3..54bc9cb 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -25,6 +25,7 @@
  *	Adam Jackson (ajax at redhat.com)
  *	Peter Hutterer (peter.hutterer at redhat.com)
  *	Oliver McFadden (oliver.mcfadden at nokia.com)
+ *	Daniel Stone (daniel at fooishbar.org)
  */
 
 #ifdef HAVE_CONFIG_H
@@ -73,6 +74,8 @@
 #endif
 
 #define ArrayLength(a) (sizeof(a) / (sizeof((a)[0])))
+#define TestBit(bit, array) ((array[(bit) / LONG_BITS]) & (1L << ((bit) % LONG_BITS)))
+#define SetBitLong(bit, array) ((array[(bit) / LONG_BITS]) |= (1L << ((bit) % LONG_BITS)))
 
 #define MIN_KEYCODE 8
 #define GLYPHS_PER_KEY 2
@@ -95,6 +98,30 @@ static char *evdevDefaults[] = {
     NULL
 };
 
+static int abs_mt_axis_map[] = {
+    ABS_MT_POSITION_X,
+    ABS_MT_POSITION_Y,
+    ABS_MT_TOUCH_MAJOR,
+    ABS_MT_TOUCH_MINOR,
+    ABS_MT_WIDTH_MAJOR,
+    ABS_MT_WIDTH_MINOR,
+    ABS_MT_ORIENTATION,
+    ABS_MT_TRACKING_ID,
+    ABS_MT_TOOL_TYPE,
+};
+static inline int mt_axis_to_evdev(int axis)
+{
+    int i;
+
+    for (i = 0; i < ArrayLength(abs_mt_axis_map); i++)
+        if (abs_mt_axis_map[i] == axis)
+            return i;
+
+    return -1;
+}
+#define mt_cached_axis(_evdev, _axis) \
+    (_evdev->mt_tp_axes[mt_axis_to_evdev(_axis)])
+
 static int EvdevOn(DeviceIntPtr);
 static int EvdevCacheCompare(InputInfoPtr pInfo, BOOL compare);
 static void EvdevKbdCtrl(DeviceIntPtr device, KeybdCtrl *ctrl);
@@ -534,11 +561,22 @@ EvdevProcessAbsoluteMotionEvent(InputInfoPtr pInfo, struct input_event *ev)
     /* Get the signed value, earlier kernels had this as unsigned */
     value = ev->value;
 
-    /* Ignore EV_ABS events if we never set up for them. */
-    if (!(pEvdev->flags & EVDEV_ABSOLUTE_EVENTS))
+    /* Process multitouch events first, since they don't interfere
+     * with abs/rel. */
+    if (ev->code >= ABS_MT_TOUCH_MAJOR && ev->code <= ABS_MT_TRACKING_ID)
+    {
+        int axis = mt_axis_to_evdev(ev->code);
+
+        if (axis == -1 || !(pEvdev->flags & EVDEV_MULTITOUCH))
+            return;
+
+        pEvdev->mt_tp_axes[axis] = value;
+        SetBitLong(ev->code, pEvdev->mt_tp_axismask);
         return;
+    }
 
-    if (ev->code > ABS_MAX)
+    /* Ignore EV_ABS events if we never set up for them. */
+    if (!(pEvdev->flags & EVDEV_ABSOLUTE_EVENTS))
         return;
 
     if (EvdevWheelEmuFilterMotion(pInfo, ev))
@@ -660,6 +698,40 @@ static void EvdevPostQueuedEvents(InputInfoPtr pInfo, int *num_v, int *first_v,
 }
 
 /**
+ * If a touchpoint hasn't appeared between EV_SYN::SYN_REPORT pairs, it means
+ * that it's gone; ABS_MT_TRACKING_ID is always guaranteed to appear
+ * if a touchpoint is live, even if it hasn't moved.
+ */
+static void
+EvdevGCTouchPoints(InputInfoPtr pInfo)
+{
+    EvdevPtr pEvdev = pInfo->private;
+    int i, j;
+
+    if (!(pEvdev->flags & EVDEV_MULTITOUCH))
+        return;
+
+    for (i = 0; i < pEvdev->mt_max_tps; i++)
+    {
+        if (!pEvdev->mt_all_tps[i])
+            continue;
+
+        for (j = 0; j < pEvdev->mt_max_tps; j++)
+            if (pEvdev->mt_active_tps[j] == pEvdev->mt_all_tps[i])
+                break;
+        if (j == pEvdev->mt_max_tps)
+        {
+            xf86FiniTouchPoint(pInfo->dev, pEvdev->mt_all_tps[i]);
+            pEvdev->mt_all_tps[i] = 0;
+        }
+    }
+
+    memcpy(pEvdev->mt_all_tps, pEvdev->mt_active_tps,
+           pEvdev->mt_max_tps * sizeof(int));
+    memset(pEvdev->mt_active_tps, 0, pEvdev->mt_max_tps * sizeof(int));
+}
+
+/**
  * Take the synchronization input event and process it accordingly; the motion
  * notify events are sent first, then any button/key press/release events.
  */
@@ -668,19 +740,66 @@ EvdevProcessSyncEvent(InputInfoPtr pInfo, struct input_event *ev)
 {
     int num_v = 0, first_v = 0;
     int v[MAX_VALUATORS] = {};
+    int i;
     EvdevPtr pEvdev = pInfo->private;
 
-    EvdevProcessValuators(pInfo, v, &num_v, &first_v);
+    /* This means we've finished receiving all the events for a given
+     * touchpoint; collate them, update the valuators, and bail. */
+    if (ev->code == SYN_MT_REPORT) {
+        int id = mt_cached_axis(pEvdev, ABS_MT_TRACKING_ID);
+        int mask = 0;
 
-    EvdevPostRelativeMotionEvents(pInfo, &num_v, &first_v, v);
-    EvdevPostAbsoluteMotionEvents(pInfo, &num_v, &first_v, v);
-    EvdevPostQueuedEvents(pInfo, &num_v, &first_v, v);
+        if (id == -1 || !(pEvdev->flags & EVDEV_MULTITOUCH))
+            return;
 
-    memset(pEvdev->delta, 0, sizeof(pEvdev->delta));
-    memset(pEvdev->queue, 0, sizeof(pEvdev->queue));
-    pEvdev->num_queue = 0;
-    pEvdev->abs = 0;
-    pEvdev->rel = 0;
+        for (i = 0; i < pEvdev->mt_max_tps; i++) {
+            if (pEvdev->mt_active_tps[i] == 0) {
+                pEvdev->mt_active_tps[i] = id;
+                break;
+            }
+        }
+
+        if (TestBit(ABS_MT_POSITION_X, pEvdev->mt_tp_axismask))
+            mask |= XITouchXMask;
+        if (TestBit(ABS_MT_POSITION_Y, pEvdev->mt_tp_axismask))
+            mask |= XITouchYMask;
+        if (TestBit(ABS_MT_TOUCH_MAJOR, pEvdev->mt_tp_axismask) ||
+            TestBit(ABS_MT_TOUCH_MINOR, pEvdev->mt_tp_axismask))
+            mask |= XITouchTouchSizeMask;
+        if (TestBit(ABS_MT_WIDTH_MAJOR, pEvdev->mt_tp_axismask) ||
+            TestBit(ABS_MT_WIDTH_MINOR, pEvdev->mt_tp_axismask))
+            mask |= XITouchToolSizeMask;
+        if (TestBit(ABS_MT_ORIENTATION, pEvdev->mt_tp_axismask))
+            mask |= XITouchOrientationMask;
+
+        xf86PostTouchMotion(pInfo->dev, id,
+                            mt_cached_axis(pEvdev, ABS_MT_TOOL_TYPE),
+                            mask,
+                            mt_cached_axis(pEvdev, ABS_MT_POSITION_X),
+                            mt_cached_axis(pEvdev, ABS_MT_POSITION_Y),
+                            mt_cached_axis(pEvdev, ABS_MT_TOUCH_MAJOR),
+                            mt_cached_axis(pEvdev, ABS_MT_TOUCH_MINOR),
+                            mt_cached_axis(pEvdev, ABS_MT_WIDTH_MAJOR),
+                            mt_cached_axis(pEvdev, ABS_MT_WIDTH_MINOR),
+                            mt_cached_axis(pEvdev, ABS_MT_ORIENTATION));
+
+        memset(pEvdev->mt_tp_axes, 0, pEvdev->mt_max_tps * sizeof(int));
+        memset(pEvdev->mt_tp_axismask, 0, sizeof(pEvdev->mt_tp_axismask));
+    } else if (ev->code == SYN_REPORT) {
+        EvdevGCTouchPoints(pInfo);
+
+        EvdevProcessValuators(pInfo, v, &num_v, &first_v);
+
+        EvdevPostRelativeMotionEvents(pInfo, &num_v, &first_v, v);
+        EvdevPostAbsoluteMotionEvents(pInfo, &num_v, &first_v, v);
+        EvdevPostQueuedEvents(pInfo, &num_v, &first_v, v);
+
+        memset(pEvdev->delta, 0, sizeof(pEvdev->delta));
+        memset(pEvdev->queue, 0, sizeof(pEvdev->queue));
+        pEvdev->num_queue = 0;
+        pEvdev->abs = 0;
+        pEvdev->rel = 0;
+    }
 }
 
 /**
@@ -722,6 +841,7 @@ EvdevReadInput(InputInfoPtr pInfo)
     while (len == sizeof(ev))
     {
         len = read(pInfo->fd, &ev, sizeof(ev));
+
         if (len <= 0)
         {
             if (errno == ENODEV) /* May happen after resume */
@@ -752,8 +872,6 @@ EvdevReadInput(InputInfoPtr pInfo)
     }
 }
 
-#define TestBit(bit, array) ((array[(bit) / LONG_BITS]) & (1L << ((bit) % LONG_BITS)))
-
 static void
 EvdevPtrCtrlProc(DeviceIntPtr device, PtrCtrl *ctrl)
 {
@@ -1165,8 +1283,12 @@ EvdevAddAbsClass(DeviceIntPtr device)
 
     for (axis = ABS_X; i < MAX_VALUATORS && axis <= ABS_MAX; axis++) {
         pEvdev->axis_map[axis] = -1;
+        /* Ignore axes the device doesn't expose, or are stolen for MT. */
         if (!TestBit(axis, pEvdev->abs_bitmask))
             continue;
+        if ((pEvdev->flags & EVDEV_MULTITOUCH) &&
+            axis >= ABS_MT_TOUCH_MAJOR && axis <= ABS_MT_TRACKING_ID)
+                continue;
         pEvdev->axis_map[axis] = i;
         i++;
     }
@@ -1424,6 +1546,39 @@ EvdevInitAbsClass(DeviceIntPtr device, EvdevPtr pEvdev)
 }
 
 static void
+EvdevInitMTClass(DeviceIntPtr device, EvdevPtr pEvdev)
+{
+    Bool ret;
+    double min_x, max_x, min_y, max_y, min_touch_width, max_touch_width;
+
+    min_x = pEvdev->absinfo[ABS_MT_POSITION_X].minimum;
+    max_x = pEvdev->absinfo[ABS_MT_POSITION_X].maximum;
+    min_y = pEvdev->absinfo[ABS_MT_POSITION_Y].minimum;
+    max_y = pEvdev->absinfo[ABS_MT_POSITION_Y].maximum;
+
+    if (TestBit(ABS_MT_TOUCH_MAJOR, pEvdev->abs_bitmask)) {
+        min_touch_width = pEvdev->absinfo[ABS_MT_TOUCH_MAJOR].minimum;
+        max_touch_width = pEvdev->absinfo[ABS_MT_TOUCH_MAJOR].maximum;
+    } else {
+        min_touch_width = -1;
+        max_touch_width = -1;
+    }
+
+    ret = InitTouchClassDeviceStruct(device, pEvdev->mt_max_tps, Absolute,
+                                     Relative, min_x, max_x, min_y, max_y,
+                                     min_touch_width, max_touch_width);
+    if (ret == TRUE) {
+        xf86Msg(X_INFO, "%s: initialised multitouch support\n", device->name);
+    } else {
+        xf86Msg(X_ERROR, "%s: failed to initialise multitouch support\n",
+                device->name);
+    }
+
+    memset(pEvdev->mt_tp_axes, 0, pEvdev->mt_max_tps * sizeof(int));
+    memset(pEvdev->mt_tp_axismask, 0, sizeof(pEvdev->mt_tp_axismask));
+}
+
+static void
 EvdevInitRelClass(DeviceIntPtr device, EvdevPtr pEvdev)
 {
     int has_abs_axes = pEvdev->flags & EVDEV_ABSOLUTE_EVENTS;
@@ -1506,6 +1661,9 @@ EvdevInit(DeviceIntPtr device)
     else if (pEvdev->flags & EVDEV_ABSOLUTE_EVENTS)
         EvdevInitAbsClass(device, pEvdev);
 
+    if (pEvdev->flags & EVDEV_MULTITOUCH)
+        EvdevInitMTClass(device, pEvdev);
+
 #ifdef HAVE_PROPERTIES
     /* We drop the return value, the only time we ever want the handlers to
      * unregister is when the device dies. In which case we don't have to
@@ -1766,6 +1924,7 @@ EvdevProbe(InputInfoPtr pInfo)
 {
     int i, has_rel_axes, has_abs_axes, has_keys, num_buttons, has_scroll;
     int has_lmr; /* left middle right */
+    int has_mt;
     int ignore_abs = 0, ignore_rel = 0;
     EvdevPtr pEvdev = pInfo->private;
 
@@ -1795,6 +1954,7 @@ EvdevProbe(InputInfoPtr pInfo)
     has_keys = FALSE;
     has_scroll = FALSE;
     has_lmr = FALSE;
+    has_mt = FALSE;
     num_buttons = 0;
 
     /* count all buttons */
@@ -1898,6 +2058,10 @@ EvdevProbe(InputInfoPtr pInfo)
                 }
             }
         }
+
+        if (TestBit(ABS_MT_POSITION_X, pEvdev->abs_bitmask) &&
+            TestBit(ABS_MT_POSITION_Y, pEvdev->abs_bitmask))
+            has_mt = TRUE;
     }
 
     for (i = 0; i < BTN_MISC; i++) {
@@ -1964,6 +2128,24 @@ EvdevProbe(InputInfoPtr pInfo)
         pEvdev->flags |= EVDEV_RELATIVE_EVENTS;
     }
 
+    if (has_mt && (has_abs_axes || has_rel_axes) &&
+        (pInfo->flags & XI86_CONFIGURED))
+    {
+        pEvdev->flags |= EVDEV_MULTITOUCH;
+        pEvdev->mt_max_tps = xf86SetIntOption(pInfo->options,
+                                              "MaxMTTouchPoints", 10);
+        xf86Msg(X_INFO, "%s: Adding multitouch support (%d touchpoints)\n",
+                pInfo->name, pEvdev->mt_max_tps);
+        pEvdev->mt_active_tps = calloc(pEvdev->mt_max_tps, sizeof(int));
+        pEvdev->mt_all_tps = calloc(pEvdev->mt_max_tps, sizeof(int));
+        if (!pEvdev->mt_active_tps || !pEvdev->mt_all_tps)
+        {
+            xf86Msg(X_ERROR, "%s: Couldn't allocate MT axis values\n",
+                    pInfo->name);
+            return BadAlloc;
+        }
+    }
+
     if ((pInfo->flags & XI86_CONFIGURED) == 0) {
         xf86Msg(X_WARNING, "%s: Don't know how to use device\n",
 		pInfo->name);
diff --git a/src/evdev.h b/src/evdev.h
index 4945140..d6c747c 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -55,6 +55,8 @@
 #define LED_CNT (LED_MAX+1)
 #endif
 
+#define MT_NUM_AXES (ABS_MT_TRACKING_ID - ABS_MT_TOUCH_MAJOR)
+
 #define EVDEV_MAXBUTTONS 32
 #define EVDEV_MAXQUEUE 32
 
@@ -71,6 +73,7 @@
 #define EVDEV_UNIGNORE_ABSOLUTE (1 << 9) /* explicitly unignore abs axes */
 #define EVDEV_UNIGNORE_RELATIVE (1 << 10) /* explicitly unignore rel axes */
 #define EVDEV_RELATIVE_MODE	(1 << 11) /* Force relative events for devices with absolute axes */
+#define EVDEV_MULTITOUCH        (1 << 12) /* Multitouch type B device */
 
 #if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3
 #define HAVE_PROPERTIES 1
@@ -123,6 +126,12 @@ typedef struct {
     int vals[MAX_VALUATORS];
     int old_vals[MAX_VALUATORS]; /* Translate absolute inputs to relative */
 
+    int *mt_active_tps; /* Active touchpoints in this event stream only */
+    int *mt_all_tps; /* All currently active touchpoints */
+    int mt_max_tps; /* Maximum number of active touchpoints */
+    int mt_tp_axes[MT_NUM_AXES]; /* Valuators for the current touchpoint */
+    unsigned long mt_tp_axismask[NLONGS(ABS_CNT)];
+
     int flags;
     int tool;
     int num_buttons;            /* number of buttons */
-- 
1.7.1



More information about the xorg-devel mailing list