[PATCH v2] Input: Add smooth-scrolling support

Peter Hutterer peter.hutterer at who-t.net
Thu Sep 22 18:22:26 PDT 2011


I found a bug in the stack of scrolling patches that caused a server lockup.
Introduced by a recent rebase, but I found a few other issues and
incorporated daniels' comments into this patch and squashed them all
together. This is just one big patch to add smooth scrolling support instead
of the several major as before (before the tree included daniel's patch to
add scrolling support and then mine to rewrite it for the XIScrollClass
changes).

A large chunk of this patch is test code, so it's not as scary as it appears
at first. If anything, it's probably easier to understand since it's just
one implementation instead of one that changes twice.

Real changes to previous versions (aside from squashing):
- a few style fixes (breaking up long lines, etc)
- comment the b--; trick to get the right button number for scroll events
- fix delta calculation so we don't end up with an infinite loop for events
  on negative increments

---

For scroll wheel support, we used to send buttons 4/5 and 6/7 for
horizontal/vertical positive/negative scroll events.  For touchpads, we
really want more fine-grained scroll values.  GetPointerEvents now
accepts both old-school scroll button presses, and new-style scroll axis
events, while emitting both types of events to support both old and new
clients.

This works with the new XIScrollClass to mark axes as scrolling axes.
Drivers mark any valuators that send scroll events with SetScrollValuator.
(Currently missing: the XIDeviceChangeEvent being sent when a driver changes
a scroll axis at run-time. This can be added later.)

Note: the SCROLL_TYPE enums are intentionally different values to the XI2
proto values to avoid copy/overlapping range bugs.

Co-authored-by: Daniel Stone <daniel at fooishbar.org>
Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 Xi/exevents.c                     |   49 ++++++++++
 Xi/xiquerydevice.c                |   74 +++++++++++++++-
 Xi/xiquerydevice.h                |    1 +
 dix/devices.c                     |   12 +++
 dix/getevents.c                   |  176 ++++++++++++++++++++++++++++++++++++-
 include/exevents.h                |   23 +++++
 include/inputstr.h                |   20 ++++
 test/input.c                      |   57 ++++++++++++
 test/xi2/protocol-common.c        |   63 +++++++++++++-
 test/xi2/protocol-xiquerydevice.c |   49 +++++++++-
 10 files changed, 513 insertions(+), 11 deletions(-)

diff --git a/Xi/exevents.c b/Xi/exevents.c
index a6455e6..55672f8 100644
--- a/Xi/exevents.c
+++ b/Xi/exevents.c
@@ -1091,6 +1091,55 @@ InitValuatorAxisStruct(DeviceIntPtr dev, int axnum, Atom label, int minval, int
     if (mode & OutOfProximity)
         dev->proximity->in_proximity = FALSE;
 
+    return SetScrollValuator(dev, axnum, SCROLL_TYPE_NONE, 0, SCROLL_FLAG_NONE);
+}
+
+/**
+ * Set the given axis number as a scrolling valuator.
+ */
+Bool
+SetScrollValuator(DeviceIntPtr dev, int axnum, enum ScrollType type, double increment, int flags)
+{
+    AxisInfoPtr ax;
+    int *current_ax;
+
+    if (!dev || !dev->valuator || axnum >= dev->valuator->numAxes)
+        return FALSE;
+
+    switch (type)
+    {
+        case SCROLL_TYPE_VERTICAL:
+            current_ax = &dev->valuator->v_scroll_axis;
+            break;
+        case SCROLL_TYPE_HORIZONTAL:
+            current_ax = &dev->valuator->h_scroll_axis;
+            break;
+        case SCROLL_TYPE_NONE:
+            ax = &dev->valuator->axes[axnum];
+            ax->scroll.type = type;
+            return TRUE;
+        default:
+            return FALSE;
+    }
+
+    if (increment == 0.0)
+        return FALSE;
+
+    if (*current_ax != -1 && axnum != *current_ax)
+    {
+        ax = &dev->valuator->axes[*current_ax];
+        if (ax->scroll.type == type &&
+            (flags & SCROLL_FLAG_PREFERRED) == (ax->scroll.flags & SCROLL_FLAG_PREFERRED))
+            return FALSE;
+    }
+    *current_ax = axnum;
+
+    ax = &dev->valuator->axes[axnum];
+    ax->scroll.type = type;
+    ax->scroll.increment = increment;
+    ax->scroll.flags = flags;
+    /* FIXME: generate DeviceChanged Events */
+
     return TRUE;
 }
 
diff --git a/Xi/xiquerydevice.c b/Xi/xiquerydevice.c
index a768d49..cfc5a90 100644
--- a/Xi/xiquerydevice.c
+++ b/Xi/xiquerydevice.c
@@ -233,7 +233,16 @@ SizeDeviceClasses(DeviceIntPtr dev)
     }
 
     if (dev->valuator)
-        len += sizeof(xXIValuatorInfo) * dev->valuator->numAxes;
+    {
+        int i;
+        len += (sizeof(xXIValuatorInfo)) * dev->valuator->numAxes;
+
+        for (i = 0; i < dev->valuator->numAxes; i++) {
+            if (dev->valuator->axes[i].scroll.type != SCROLL_TYPE_NONE)
+                len += sizeof(xXIScrollInfo);
+        }
+    }
+
 
     return len;
 }
@@ -376,6 +385,57 @@ SwapValuatorInfo(DeviceIntPtr dev, xXIValuatorInfo* info)
     swaps(&info->sourceid, n);
 }
 
+int
+ListScrollInfo(DeviceIntPtr dev, xXIScrollInfo *info, int axisnumber)
+{
+    ValuatorClassPtr v = dev->valuator;
+    AxisInfoPtr axis = &v->axes[axisnumber];
+
+    if (axis->scroll.type == SCROLL_TYPE_NONE)
+        return 0;
+
+    info->type = XIScrollClass;
+    info->length = sizeof(xXIScrollInfo)/4;
+    info->number = axisnumber;
+    switch(axis->scroll.type)
+    {
+        case SCROLL_TYPE_VERTICAL:
+            info->scroll_type = XIScrollTypeVertical;
+            break;
+        case SCROLL_TYPE_HORIZONTAL:
+            info->scroll_type = XIScrollTypeHorizontal;
+            break;
+        default:
+            ErrorF("[Xi] Unknown scroll type %d. This is a bug.\n", axis->scroll.type);
+            break;
+    }
+    info->increment.integral = (int)axis->scroll.increment;
+    info->increment.frac = (unsigned int)(axis->scroll.increment * (1UL << 32));
+    info->sourceid = v->sourceid;
+
+    info->flags = 0;
+
+    if (axis->scroll.flags & SCROLL_FLAG_DONT_EMULATE)
+        info->flags |= XIScrollFlagNoEmulation;
+    if (axis->scroll.flags & SCROLL_FLAG_PREFERRED)
+        info->flags |= XIScrollFlagPreferred;
+
+    return info->length * 4;
+}
+
+static void
+SwapScrollInfo(DeviceIntPtr dev, xXIScrollInfo* info)
+{
+    char n;
+    swaps(&info->type, n);
+    swaps(&info->length, n);
+    swaps(&info->number, n);
+    swaps(&info->sourceid, n);
+    swaps(&info->scroll_type, n);
+    swapl(&info->increment.integral, n);
+    swapl(&info->increment.frac, n);
+}
+
 int GetDeviceUse(DeviceIntPtr dev, uint16_t *attachment)
 {
     DeviceIntPtr master = GetMaster(dev, MASTER_ATTACHED);
@@ -465,6 +525,15 @@ ListDeviceClasses(ClientPtr client, DeviceIntPtr dev,
         total_len += len;
     }
 
+    for (i = 0; dev->valuator && i < dev->valuator->numAxes; i++)
+    {
+        len = ListScrollInfo(dev, (xXIScrollInfo*)any, i);
+        if (len)
+            (*nclasses)++;
+        any += len;
+        total_len += len;
+    }
+
     return total_len;
 }
 
@@ -492,6 +561,9 @@ SwapDeviceInfo(DeviceIntPtr dev, xXIDeviceInfo* info)
             case XIValuatorClass:
                 SwapValuatorInfo(dev, (xXIValuatorInfo*)any);
                 break;
+            case XIScrollClass:
+                SwapScrollInfo(dev, (xXIScrollInfo*)any);
+                break;
         }
 
         any += len * 4;
diff --git a/Xi/xiquerydevice.h b/Xi/xiquerydevice.h
index 02f0659..9db6aa2 100644
--- a/Xi/xiquerydevice.h
+++ b/Xi/xiquerydevice.h
@@ -44,4 +44,5 @@ int ListButtonInfo(DeviceIntPtr dev, xXIButtonInfo* info, Bool reportState);
 int ListKeyInfo(DeviceIntPtr dev, xXIKeyInfo* info);
 int ListValuatorInfo(DeviceIntPtr dev, xXIValuatorInfo* info,
 		     int axisnumber, Bool reportState);
+int ListScrollInfo(DeviceIntPtr dev, xXIScrollInfo* info, int axisnumber);
 #endif /* QUERYDEV_H */
diff --git a/dix/devices.c b/dix/devices.c
index ab8a648..64557aa 100644
--- a/dix/devices.c
+++ b/dix/devices.c
@@ -260,6 +260,8 @@ AddInputDevice(ClientPtr client, DeviceProc deviceProc, Bool autoStart)
 					  offsetof(DeviceIntRec, devPrivates), PRIVATE_DEVICE);
     if (!dev)
 	return (DeviceIntPtr)NULL;
+
+    dev->last.scroll = NULL;
     dev->id = devid;
     dev->public.processInputProc = ProcessOtherEvent;
     dev->public.realInputProc = ProcessOtherEvent;
@@ -939,6 +941,7 @@ CloseDevice(DeviceIntPtr dev)
 
     free(dev->deviceGrab.sync.event);
     free(dev->config_info);     /* Allocated in xf86ActivateDevice. */
+    free(dev->last.scroll);
     dev->config_info = NULL;
     dixFreeObjectWithPrivates(dev, PRIVATE_DEVICE);
 }
@@ -1277,10 +1280,19 @@ InitValuatorClassDeviceStruct(DeviceIntPtr dev, int numAxes, Atom *labels,
     if (!valc)
         return FALSE;
 
+    dev->last.scroll = valuator_mask_new(numAxes);
+    if (!dev->last.scroll)
+    {
+        free(valc);
+        return FALSE;
+    }
+
     valc->sourceid = dev->id;
     valc->motion = NULL;
     valc->first_motion = 0;
     valc->last_motion = 0;
+    valc->h_scroll_axis = -1;
+    valc->v_scroll_axis = -1;
 
     valc->numMotionEvents = numMotionEvents;
     valc->motionHintWindow = NullWindow;
diff --git a/dix/getevents.c b/dix/getevents.c
index a1d5e52..e4351a9 100644
--- a/dix/getevents.c
+++ b/dix/getevents.c
@@ -2,6 +2,7 @@
  * Copyright © 2006 Nokia Corporation
  * Copyright © 2006-2007 Daniel Stone
  * Copyright © 2008 Red Hat, Inc.
+ * Copyright © 2011 The Chromium Authors
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -603,8 +604,10 @@ GetMaximumEventsNum(void) {
     /* One raw event
      * One device event
      * One possible device changed event
+     * Lots of possible separate button scroll events (horiz + vert)
+     * Lots of possible separate raw button scroll events (horiz + vert)
      */
-    return 3;
+    return 51;
 }
 
 
@@ -1171,6 +1174,98 @@ fill_pointer_events(InternalEvent *events, DeviceIntPtr pDev, int type,
 }
 
 /**
+ * Generate events for each scroll axis that changed between before/after
+ * for the device.
+ *
+ * @param events The pointer to the event list to fill the events
+ * @param dev The device to generate the events for
+ * @param axis The axis number to generate events for
+ * @param mask State before this event in absolute coords
+ * @param[in,out] last Last scroll state posted in absolute coords (modified
+ * in-place)
+ * @param ms Current time in ms
+ * @param max_events Max number of events to be generated
+ * @return The number of events generated
+ */
+static int
+emulate_scroll_button_events(InternalEvent *events,
+                             DeviceIntPtr dev,
+                             int axis,
+                             const ValuatorMask *mask,
+                             ValuatorMask *last,
+                             CARD32 ms,
+                             int max_events)
+{
+    AxisInfoPtr ax;
+    double delta;
+    double incr;
+    int num_events = 0;
+    double total;
+    int b;
+
+    if (dev->valuator->axes[axis].scroll.type == SCROLL_TYPE_NONE)
+        return 0;
+
+    if (!valuator_mask_isset(mask, axis))
+        return 0;
+
+    ax = &dev->valuator->axes[axis];
+    incr = ax->scroll.increment;
+
+    if (!valuator_mask_isset(last, axis))
+    {
+        valuator_mask_set_double(last, axis, valuator_mask_get_double(mask, axis));
+        return 0;
+    }
+
+    delta = valuator_mask_get_double(mask, axis) - valuator_mask_get_double(last, axis);
+    total = delta;
+    b = (ax->scroll.type == SCROLL_TYPE_VERTICAL) ? 5 : 7;
+
+    if ((incr > 0 && delta < 0) ||
+        (incr < 0 && delta > 0))
+        b--; /* we're scrolling up or left → button 4 or 6 */
+
+    while (fabs(delta) >= fabs(incr))
+    {
+        int nev_tmp;
+
+        if (delta > 0)
+            delta -= fabs(incr);
+        else if (delta < 0)
+            delta += fabs(incr);
+
+        /* fill_pointer_events() generates four events: one normal and one raw
+         * event each for the emulated button press and release both.
+         * We may get a bigger scroll delta than we can generate events
+         * for. In that case, we keep decreasing delta, but skip events.
+         */
+        if (num_events + 4 < max_events)
+        {
+            nev_tmp = fill_pointer_events(events, dev, ButtonPress, b, ms,
+                                          POINTER_EMULATED, NULL);
+            events += nev_tmp;
+            num_events += nev_tmp;
+            nev_tmp = fill_pointer_events(events, dev, ButtonRelease, b, ms,
+                                          POINTER_EMULATED, NULL);
+            events += nev_tmp;
+            num_events += nev_tmp;
+        }
+    }
+
+    /* We emulated, update last.scroll */
+    if (total != delta)
+    {
+        total -= delta;
+        valuator_mask_set_double(last, axis,
+                                 valuator_mask_get_double(last, axis) + total);
+    }
+
+    return num_events;
+}
+
+
+/**
  * Generate a complete series of InternalEvents (filled into the EventList)
  * representing pointer motion, or button presses.  If the device is a slave
  * device, also potentially generate a DeviceClassesChangedEvent to update
@@ -1193,7 +1288,12 @@ GetPointerEvents(InternalEvent *events, DeviceIntPtr pDev, int type,
                  int buttons, int flags, const ValuatorMask *mask_in)
 {
     CARD32 ms = GetTimeInMillis();
-    int num_events = 0;
+    int num_events = 0, nev_tmp;
+    int h_scroll_axis = pDev->valuator->h_scroll_axis;
+    int v_scroll_axis = pDev->valuator->v_scroll_axis;
+    ValuatorMask mask;
+    ValuatorMask scroll;
+    int i;
 
     /* refuse events from disabled devices */
     if (!pDev->enabled)
@@ -1204,8 +1304,76 @@ GetPointerEvents(InternalEvent *events, DeviceIntPtr pDev, int type,
 
     events = UpdateFromMaster(events, pDev, DEVCHANGE_POINTER_EVENT,
                               &num_events);
-    num_events += fill_pointer_events(events, pDev, type, buttons, ms, flags,
-                                      mask_in);
+
+    valuator_mask_copy(&mask, mask_in);
+
+    /* Turn a scroll button press into a smooth-scrolling event if
+     * necessary. This only needs to cater for the XIScrollFlagPreferred
+     * axis (if more than one scrolling axis is present) */
+    if (type == ButtonPress)
+    {
+        double val, adj;
+        int axis;
+
+        switch (buttons) {
+        case 4:
+            adj = 1.0;
+            axis = v_scroll_axis;
+            break;
+        case 5:
+            adj = -1.0;
+            axis = v_scroll_axis;
+            break;
+        case 6:
+            adj = 1.0;
+            axis = h_scroll_axis;
+            break;
+        case 7:
+            adj = -1.0;
+            axis = h_scroll_axis;
+            break;
+        default:
+            adj = 0.0;
+            axis = -1;
+            break;
+        }
+
+        if (adj != 0.0 && axis != -1)
+        {
+            adj *= pDev->valuator->axes[axis].scroll.increment;
+            val = valuator_mask_get_double(&mask, axis) + adj;
+            valuator_mask_set_double(&mask, axis, val);
+            type = MotionNotify;
+            buttons = 0;
+        }
+    }
+
+    /* First fill out the original event set, with smooth-scrolling axes. */
+    nev_tmp = fill_pointer_events(events, pDev, type, buttons, ms, flags,
+                                  &mask);
+    events += nev_tmp;
+    num_events += nev_tmp;
+
+    /* Now turn the smooth-scrolling axes back into emulated button presses
+     * for legacy clients, based on the integer delta between before and now */
+    for (i = 0; i < valuator_mask_size(&mask); i++) {
+        if (!valuator_mask_isset(&mask, i))
+            continue;
+
+        valuator_mask_set_double(&scroll, i, pDev->last.valuators[i]);
+
+        /* fill_pointer_events() generates four events: one normal and one raw
+         * event each for the emulated button press and release both. */
+        if (num_events + 4 >= GetMaximumEventsNum())
+            break;
+
+        nev_tmp = emulate_scroll_button_events(events, pDev, i, &scroll,
+                                               pDev->last.scroll, ms,
+                                               GetMaximumEventsNum() - num_events);
+        events += nev_tmp;
+        num_events += nev_tmp;
+    }
+
     return num_events;
 }
 
diff --git a/include/exevents.h b/include/exevents.h
index 731f31e..4fe6c61 100644
--- a/include/exevents.h
+++ b/include/exevents.h
@@ -37,6 +37,22 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *              Interface available to drivers                 *
  ***************************************************************/
 
+/**
+ * Scroll flags for ::SetScrollValuator.
+ */
+enum ScrollFlags {
+    SCROLL_FLAG_NONE            = 0,
+    /**
+     * Do not emulate legacy button events for valuator events on this axis.
+     */
+    SCROLL_FLAG_DONT_EMULATE    = (1 << 1),
+    /**
+     * This axis is the preferred axis for valuator emulation for this axis'
+     * scroll type.
+     */
+    SCROLL_FLAG_PREFERRED       = (1 << 2)
+};
+
 extern _X_EXPORT int InitProximityClassDeviceStruct(
 	DeviceIntPtr           /* dev */);
 
@@ -51,6 +67,13 @@ extern _X_EXPORT Bool InitValuatorAxisStruct(
 	int                    /* max_res */,
 	int                    /* mode */);
 
+extern _X_EXPORT Bool SetScrollValuator(
+	DeviceIntPtr           /* dev */,
+	int                    /* axnum */,
+	enum ScrollType        /* type */,
+	double                 /* increment */,
+	int                    /* flags */);
+
 /* Input device properties */
 extern _X_EXPORT void XIDeleteAllDeviceProperties(
         DeviceIntPtr            /* device */
diff --git a/include/inputstr.h b/include/inputstr.h
index c25f5c6..9d4108e 100644
--- a/include/inputstr.h
+++ b/include/inputstr.h
@@ -75,6 +75,16 @@ extern _X_EXPORT int CountBits(const uint8_t *mask, int len);
 #define XI2MASKSIZE     ((XI2LASTEVENT + 7)/8) /* no of bits for masks */
 
 /**
+ * Scroll types for ::SetScrollValuator and the scroll type in the
+ * ::ScrollInfoPtr.
+ */
+enum ScrollType {
+    SCROLL_TYPE_NONE = 0,           /**< Not a scrolling valuator */
+    SCROLL_TYPE_VERTICAL = 8,
+    SCROLL_TYPE_HORIZONTAL = 9,
+};
+
+/**
  * This struct stores the core event mask for each client except the client
  * that created the window.
  *
@@ -252,6 +262,12 @@ typedef struct _KeyClassRec {
     struct _XkbSrvInfo *xkbInfo;
 } KeyClassRec, *KeyClassPtr;
 
+typedef struct _ScrollInfo {
+    enum ScrollType	type;
+    double		increment;
+    int			flags;
+} ScrollInfo, *ScrollInfoPtr;
+
 typedef struct _AxisInfo {
     int		resolution;
     int		min_resolution;
@@ -260,6 +276,7 @@ typedef struct _AxisInfo {
     int		max_value;
     Atom	label;
     CARD8	mode;
+    ScrollInfo  scroll;
 } AxisInfo, *AxisInfoPtr;
 
 typedef struct _ValuatorAccelerationRec {
@@ -283,6 +300,8 @@ typedef struct _ValuatorClassRec {
     unsigned short	  numAxes;
     double		  *axisVal; /* always absolute, but device-coord system */
     ValuatorAccelerationRec	accelScheme;
+    int                   h_scroll_axis; /* horiz smooth-scrolling axis */
+    int                   v_scroll_axis; /* vert smooth-scrolling axis */
 } ValuatorClassRec;
 
 typedef struct _ButtonClassRec {
@@ -524,6 +543,7 @@ typedef struct _DeviceIntRec {
         double          valuators[MAX_VALUATORS];
         int             numValuators;
         DeviceIntPtr    slave;
+        ValuatorMask    *scroll;
     } last;
 
     /* Input device property handling. */
diff --git a/test/input.c b/test/input.c
index 2501d59..d33fcf8 100644
--- a/test/input.c
+++ b/test/input.c
@@ -52,6 +52,7 @@ static void dix_init_valuators(void)
 {
     DeviceIntRec dev;
     ValuatorClassPtr val;
+    AxisInfoPtr axis;
     const int num_axes = 2;
     int i;
     Atom atoms[MAX_VALUATORS] = { 0 };
@@ -78,6 +79,62 @@ static void dix_init_valuators(void)
     }
 
     assert(dev.last.numValuators == num_axes);
+
+    /* invalid increment */
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_VERTICAL, 0.0, SCROLL_FLAG_NONE) == FALSE);
+    /* invalid type */
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_VERTICAL - 1, 1.0, SCROLL_FLAG_NONE) == FALSE);
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_HORIZONTAL + 1, 1.0, SCROLL_FLAG_NONE) == FALSE);
+    /* invalid axisnum */
+    assert(SetScrollValuator(&dev, 2, SCROLL_TYPE_HORIZONTAL, 1.0, SCROLL_FLAG_NONE) == FALSE);
+
+    /* valid */
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_VERTICAL, 3.0, SCROLL_FLAG_NONE) == TRUE);
+    axis = &dev.valuator->axes[0];
+    assert(axis->scroll.increment == 3.0);
+    assert(axis->scroll.type == SCROLL_TYPE_VERTICAL);
+    assert(axis->scroll.flags == 0);
+
+    /* valid */
+    assert(SetScrollValuator(&dev, 1, SCROLL_TYPE_HORIZONTAL, 2.0, SCROLL_FLAG_NONE) == TRUE);
+    axis = &dev.valuator->axes[1];
+    assert(axis->scroll.increment == 2.0);
+    assert(axis->scroll.type == SCROLL_TYPE_HORIZONTAL);
+    assert(axis->scroll.flags == 0);
+
+    /* cannot overwrite with other axis */
+    assert(SetScrollValuator(&dev, 1, SCROLL_TYPE_VERTICAL, 5.0, SCROLL_FLAG_NONE) == FALSE);
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_HORIZONTAL, 5.0, SCROLL_FLAG_NONE) == FALSE);
+
+    /* can overwrite with Preferred */
+    assert(SetScrollValuator(&dev, 1, SCROLL_TYPE_VERTICAL, 5.5, SCROLL_FLAG_PREFERRED) == TRUE);
+    axis = &dev.valuator->axes[1];
+    assert(axis->scroll.increment == 5.5);
+    assert(axis->scroll.type == SCROLL_TYPE_VERTICAL);
+    assert(axis->scroll.flags == SCROLL_FLAG_PREFERRED);
+
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_HORIZONTAL, 8.8, SCROLL_FLAG_PREFERRED) == TRUE);
+    axis = &dev.valuator->axes[0];
+    assert(axis->scroll.increment == 8.8);
+    assert(axis->scroll.type == SCROLL_TYPE_HORIZONTAL);
+    assert(axis->scroll.flags == SCROLL_FLAG_PREFERRED);
+
+    /* can overwrite as none */
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_NONE, 5.0,
+                SCROLL_FLAG_NONE) == TRUE);
+    axis = &dev.valuator->axes[0];
+    assert(axis->scroll.type == SCROLL_TYPE_NONE);
+
+    /* can overwrite axis with new settings */
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_VERTICAL, 5.0, SCROLL_FLAG_NONE) == TRUE);
+    axis = &dev.valuator->axes[0];
+    assert(axis->scroll.type == SCROLL_TYPE_VERTICAL);
+    assert(axis->scroll.increment == 5.0);
+    assert(axis->scroll.flags == SCROLL_FLAG_NONE);
+    assert(SetScrollValuator(&dev, 0, SCROLL_TYPE_VERTICAL, 3.0, SCROLL_FLAG_NONE) == TRUE);
+    assert(axis->scroll.type == SCROLL_TYPE_VERTICAL);
+    assert(axis->scroll.increment == 3.0);
+    assert(axis->scroll.flags == SCROLL_FLAG_NONE);
 }
 
 /* just check the known success cases, and that error cases set the client's
diff --git a/test/xi2/protocol-common.c b/test/xi2/protocol-common.c
index 4234533..56d6bd2 100644
--- a/test/xi2/protocol-common.c
+++ b/test/xi2/protocol-common.c
@@ -29,6 +29,8 @@
 #include "extinit.h" /* for XInputExtensionInit */
 #include "exglobals.h"
 #include "xkbsrv.h" /* for XkbInitPrivates */
+#include "xserver-properties.h"
+#include <X11/extensions/XI2.h>
 
 #include "protocol-common.h"
 
@@ -63,6 +65,65 @@ static void fake_init_sprite(DeviceIntPtr dev)
     sprite->physLimits.y2 = screen.height;
 }
 
+/* This is essentially CorePointerProc with ScrollAxes added */
+static int
+TestPointerProc(DeviceIntPtr pDev, int what)
+{
+#define NBUTTONS 10
+#define NAXES 4
+    BYTE map[NBUTTONS + 1];
+    int i = 0;
+    Atom btn_labels[NBUTTONS] = {0};
+    Atom axes_labels[NAXES] = {0};
+
+    switch (what) {
+    case DEVICE_INIT:
+        for (i = 1; i <= NBUTTONS; i++)
+            map[i] = i;
+
+	btn_labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
+	btn_labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
+	btn_labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
+	btn_labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
+	btn_labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
+	btn_labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
+	btn_labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
+	/* don't know about the rest */
+
+	axes_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
+	axes_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
+	axes_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
+	axes_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+
+        if (!InitPointerDeviceStruct((DevicePtr)pDev, map, NBUTTONS, btn_labels,
+                                (PtrCtrlProcPtr)NoopDDA,
+                                GetMotionHistorySize(), NAXES, axes_labels))
+        {
+            ErrorF("Could not initialize device '%s'. Out of memory.\n",
+                   pDev->name);
+            return BadAlloc;
+        }
+        pDev->valuator->axisVal[0] = screenInfo.screens[0]->width / 2;
+        pDev->last.valuators[0] = pDev->valuator->axisVal[0];
+        pDev->valuator->axisVal[1] = screenInfo.screens[0]->height / 2;
+        pDev->last.valuators[1] = pDev->valuator->axisVal[1];
+
+        SetScrollValuator(pDev, 2, SCROLL_TYPE_VERTICAL, 2.4, SCROLL_FLAG_NONE);
+        SetScrollValuator(pDev, 3, SCROLL_TYPE_HORIZONTAL, 3.5, SCROLL_FLAG_PREFERRED);
+        break;
+
+    case DEVICE_CLOSE:
+        break;
+
+    default:
+        break;
+    }
+
+    return Success;
+
+#undef NBUTTONS
+#undef NAXES
+}
 /**
  * Create and init 2 master devices (VCP + VCK) and two slave devices, one
  * default mouse, one default keyboard.
@@ -84,7 +145,7 @@ struct devices init_devices(void)
     EnableDevice(devices.vck, FALSE);
 
     AllocDevicePair(&client, "", &devices.mouse, &devices.kbd,
-                    CorePointerProc, CoreKeyboardProc, FALSE);
+                    TestPointerProc, CoreKeyboardProc, FALSE);
     ActivateDevice(devices.mouse, FALSE);
     ActivateDevice(devices.kbd, FALSE);
     EnableDevice(devices.mouse, FALSE);
diff --git a/test/xi2/protocol-xiquerydevice.c b/test/xi2/protocol-xiquerydevice.c
index cb1cc81..8bc191a 100644
--- a/test/xi2/protocol-xiquerydevice.c
+++ b/test/xi2/protocol-xiquerydevice.c
@@ -129,7 +129,7 @@ static void reply_XIQueryDevice_data(ClientPtr client, int len, char *data, void
                 dev = devices.mouse;
                 assert(info->use == XISlavePointer);
                 assert(info->attachment == devices.vcp->id);
-                assert(info->num_classes == 3); /* 2 axes + button */
+                assert(info->num_classes == 7); /* 4 axes + button + 2 scroll*/
                 break;
             case 5:  /* keyboard */
                 dev = devices.kbd;
@@ -185,11 +185,48 @@ static void reply_XIQueryDevice_data(ClientPtr client, int len, char *data, void
                         }
                         break;
                     }
-                case 2: /* VCP and mouse have the same properties */
                 case 4:
                     {
                         assert(any->type == XIButtonClass ||
-                                any->type == XIValuatorClass);
+                               any->type == XIValuatorClass ||
+                               any->type == XIScrollClass);
+
+                        if (any->type == XIScrollClass)
+                        {
+                            xXIScrollInfo *si = (xXIScrollInfo*)any;
+
+                            if (client->swapped)
+                            {
+                                swaps(&si->number, n);
+                                swaps(&si->scroll_type, n);
+                                swapl(&si->increment.integral, n);
+                                swapl(&si->increment.frac, n);
+                            }
+                            assert(si->length == 6);
+                            assert(si->number == 2 || si->number == 3);
+                            if (si->number == 2) {
+                                assert(si->scroll_type == XIScrollTypeVertical);
+                                assert(!si->flags);
+                            }
+                            if (si->number == 3) {
+                                assert(si->scroll_type == XIScrollTypeHorizontal);
+                                assert(si->flags & XIScrollFlagPreferred);
+                                assert(!(si->flags & ~XIScrollFlagPreferred));
+                            }
+
+                            assert(si->increment.integral == si->number);
+                            /* FIXME: frac testing with float/FP issues? */
+                            assert(si->increment.frac > 0.3  * (1UL << 32));
+                            assert(si->increment.frac < 0.6  * (1UL << 32));
+                        }
+
+                    }
+                    /* fall through */
+                case 2: /* VCP and mouse have the same properties except for scroll */
+                    {
+                        if (info->deviceid == 2 ) /* VCP */
+                            assert(any->type == XIButtonClass ||
+                                   any->type == XIValuatorClass);
 
                         if (any->type == XIButtonClass)
                         {
@@ -219,8 +256,10 @@ static void reply_XIQueryDevice_data(ClientPtr client, int len, char *data, void
                             }
 
                             assert(vi->length == 11);
-                            assert(vi->number == 0 ||
-                                     vi->number == 1);
+                            assert(vi->number >= 0 && vi->number < 4);
+                            if (info->deviceid == 2) /* VCP */
+                                assert(vi->number < 2);
+
                             assert(vi->mode == XIModeRelative);
                             /* device was set up as relative, so standard
                              * values here. */
-- 
1.7.6



More information about the xorg-devel mailing list