The standard method of scrolling in X is tailored to mouse wheels and
proceeds in coarse steps.  Wheel events are mapped to button events, and on
receiving such an event, an application moves the view of its data by some
fixed distance - usually the height of a line of text, or of a couple of
lines.

Version 2.1 of the X Input Protocol has introduced a more precise
alternative.  It defines additional types of motion events.  In essence,
their values represent fractions of a complete scroll unit, and newer
applications may move their views by distances that are proportional to the
event values.  For applications that don't support this, X generates the
standard button events whenever the values add up to the complete unit.

synaptics(4) supports the newer method since long.

The diffs below add the feature to ws and wstpad.  The kernel part defines
two new event types in wsconsio.h, and it adapts the scrolling functions of
the touchpad input driver.  The xenocara part adds the new "axes" and event
handlers to ws.

There is a little twist to the implementation.  While synaptics(4)
initializes the scroll axes with the scroll distance in device units, the
constant 4096 is used in the new ws code, and event values represent the
fraction (motion_delta / scroll_unit) in [*.12] fixed-point format.  That
way, no queries for the device- and configuration-dependent scroll unit
are necessary.

The X Input Protocol calls the method "smooth scrolling", but it seems
that nowadays, this term is used exclusively for the rendering technique
that displays a little animation when the document position changes, so
"precision scrolling" might be a better choice.

Tests, comments, and OKs would be welcome.


Index: dev/wscons/wsconsio.h
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wsconsio.h,v
retrieving revision 1.90
diff -u -p -r1.90 wsconsio.h
--- dev/wscons/wsconsio.h       10 Nov 2018 14:27:51 -0000      1.90
+++ dev/wscons/wsconsio.h       12 Mar 2019 21:55:11 -0000
@@ -112,6 +112,12 @@ struct wscons_event {
 #define        WSCONS_EVENT_TOUCH_RESET        25      /* (no value) */

 /*
+ * Precision Scrolling
+ */
+#define WSCONS_EVENT_HSCROLL           26      /* dx * 4096 / scroll_unit */
+#define WSCONS_EVENT_VSCROLL           27      /* dy * 4096 / scroll_unit */
+
+/*
  * Keyboard ioctls (0 - 31)
  */

Index: dev/wscons/wsmouse.c
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wsmouse.c,v
retrieving revision 1.51
diff -u -p -r1.51 wsmouse.c
--- dev/wscons/wsmouse.c        19 Feb 2019 07:01:02 -0000      1.51
+++ dev/wscons/wsmouse.c        12 Mar 2019 21:55:11 -0000
@@ -1034,10 +1034,18 @@ wsmouse_motion_sync(struct wsmouseinput
                        wsmouse_evq_put(evq, DELTA_X_EV(input), dx);
                if (dy)
                        wsmouse_evq_put(evq, DELTA_Y_EV(input), dy);
-               if (motion->dz)
-                       wsmouse_evq_put(evq, DELTA_Z_EV, motion->dz);
-               if (motion->dw)
-                       wsmouse_evq_put(evq, DELTA_W_EV, motion->dw);
+               if (motion->dz) {
+                       if (IS_TOUCHPAD(input))
+                               wsmouse_evq_put(evq, VSCROLL_EV, motion->dz);
+                       else
+                               wsmouse_evq_put(evq, DELTA_Z_EV, motion->dz);
+               }
+               if (motion->dw) {
+                       if (IS_TOUCHPAD(input))
+                               wsmouse_evq_put(evq, HSCROLL_EV, motion->dw);
+                       else
+                               wsmouse_evq_put(evq, DELTA_W_EV, motion->dw);
+               }
        }
        if (motion->sync & SYNC_POSITION) {
                if (motion->sync & SYNC_X) {
Index: dev/wscons/wsmouseinput.h
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wsmouseinput.h,v
retrieving revision 1.12
diff -u -p -r1.12 wsmouseinput.h
--- dev/wscons/wsmouseinput.h   10 Nov 2018 14:27:51 -0000      1.12
+++ dev/wscons/wsmouseinput.h   12 Mar 2019 21:55:12 -0000
@@ -210,6 +210,8 @@ int wstpad_set_param(struct wsmouseinput
     WSCONS_EVENT_MOUSE_ABSOLUTE_X : WSCONS_EVENT_MOUSE_ABSOLUTE_Y)
 #define DELTA_Z_EV     WSCONS_EVENT_MOUSE_DELTA_Z
 #define DELTA_W_EV     WSCONS_EVENT_MOUSE_DELTA_W
+#define VSCROLL_EV     WSCONS_EVENT_VSCROLL
+#define HSCROLL_EV     WSCONS_EVENT_HSCROLL
 #define ABS_Z_EV       WSCONS_EVENT_TOUCH_PRESSURE
 #define ABS_W_EV       WSCONS_EVENT_TOUCH_CONTACTS
 #define BTN_DOWN_EV    WSCONS_EVENT_MOUSE_DOWN
Index: dev/wscons/wstpad.c
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wstpad.c,v
retrieving revision 1.22
diff -u -p -r1.22 wstpad.c
--- dev/wscons/wstpad.c 29 Dec 2018 21:03:58 -0000      1.22
+++ dev/wscons/wstpad.c 12 Mar 2019 21:55:12 -0000
@@ -167,8 +167,6 @@ struct wstpad {
        u_int mtcycle;
        u_int ignore;

-       int dx;
-       int dy;
        int contacts;
        int prev_contacts;
        u_int btns;
@@ -223,12 +221,11 @@ struct wstpad {
        } tap;

        struct {
-               int acc_dx;
-               int acc_dy;
                int dz;
                int dw;
                int hdist;
                int vdist;
+               int mag;
        } scroll;
 };

@@ -435,8 +432,8 @@ set_freeze_ts(struct wstpad *tp, int sec


 /* Return TRUE if two-finger- or edge-scrolling would be valid. */
-static inline int
-chk_scroll_state(struct wsmouseinput *input)
+int
+wstpad_scroll_coords(struct wsmouseinput *input, int *dx, int *dy)
 {
        struct wstpad *tp = input->tp;

@@ -451,40 +448,43 @@ chk_scroll_state(struct wsmouseinput *in
         * a short delay, is only applied initially, a touch that stops and
         * resumes scrolling is not affected.
         */
-       if (tp->scroll.dz || tp->scroll.dw || wstpad_is_stable(input, tp->t))
-               return (tp->dx || tp->dy);
+       if (tp->scroll.dz || tp->scroll.dw || wstpad_is_stable(input, tp->t)) {
+               *dx = normalize_rel(&input->filter.h, input->motion.pos.dx);
+               *dy = normalize_rel(&input->filter.v, input->motion.pos.dy);
+               return (*dx || *dy);
+       }

        return (0);
 }

 void
-wstpad_scroll(struct wstpad *tp, int dx, int dy, u_int *cmds)
+wstpad_scroll(struct wstpad *tp, int dx, int dy, int mag, u_int *cmds)
 {
-       int sign;
+       int dz, dw, n = 1;

-       /* Scrolling is either horizontal or vertical, but not both. */
-
-       sign = (dy > 0) - (dy < 0);
-       if (sign) {
-               if (tp->scroll.dz != -sign) {
-                       tp->scroll.dz = -sign;
-                       tp->scroll.acc_dy = -tp->scroll.vdist;
-               }
-               tp->scroll.acc_dy += abs(dy);
-               if (tp->scroll.acc_dy >= 0) {
-                       tp->scroll.acc_dy -= tp->scroll.vdist;
-                       *cmds |= 1 << VSCROLL;
-               }
-       } else if ((sign = (dx > 0) - (dx < 0))) {
-               if (tp->scroll.dw != sign) {
-                       tp->scroll.dw = sign;
-                       tp->scroll.acc_dx = -tp->scroll.hdist;
-               }
-               tp->scroll.acc_dx += abs(dx);
-               if (tp->scroll.acc_dx >= 0) {
-                       tp->scroll.acc_dx -= tp->scroll.hdist;
-                       *cmds |= 1 << HSCROLL;
-               }
+       /*
+        * The function applies strong deceleration, but only to input with
+        * very low speeds.  A higher threshold might make applications
+        * without support for precision scrolling appear unresponsive.
+        */
+       mag = tp->scroll.mag = imin(MAG_MEDIUM,
+           (mag + 3 * tp->scroll.mag) / 4);
+       if (mag < MAG_LOW)
+               n = (MAG_LOW - mag) / 4096 + 1;
+
+       if (dy && tp->scroll.vdist) {
+               dz = -dy * 4096 / (tp->scroll.vdist * n);
+               if (tp->scroll.dz && (dy < 0 == tp->scroll.dz > 0))
+                       dz = (dz + 3 * tp->scroll.dz) / 4;
+               tp->scroll.dz = dz;
+               *cmds |= 1 << VSCROLL;
+
+       } else if (dx && tp->scroll.hdist) {
+               dw = dx * 4096 / (tp->scroll.hdist * n);
+               if (tp->scroll.dw && (dx > 0 == tp->scroll.dw > 0))
+                       dw = (dw + 3 * tp->scroll.dw) / 4;
+               tp->scroll.dw = dw;
+               *cmds |= 1 << HSCROLL;
        }
 }

@@ -502,12 +502,14 @@ wstpad_f2scroll(struct wsmouseinput *inp
                return;
        }

-       if (!chk_scroll_state(input))
+       if (!wstpad_scroll_coords(input, &dx, &dy))
                return;

        dir = tp->t->dir;
-       dy = NORTH(dir) || SOUTH(dir) ? tp->dy : 0;
-       dx = EAST(dir) || WEST(dir) ? tp->dx : 0;
+       if (!(NORTH(dir) || SOUTH(dir)))
+               dy = 0;
+       if (!(EAST(dir) || WEST(dir)))
+               dx = 0;

        if (dx || dy) {
                centered = CENTERED(tp->t);
@@ -526,7 +528,8 @@ wstpad_f2scroll(struct wsmouseinput *inp
                        centered |= CENTERED(t2);
                }
                if (centered) {
-                       wstpad_scroll(tp, dx, dy, cmds);
+                       wstpad_scroll(tp, dx, dy,
+                           magnitude(input, dx, dy), cmds);
                        set_freeze_ts(tp, 0, FREEZE_MS);
                }
        }
@@ -540,17 +543,19 @@ wstpad_edgescroll(struct wsmouseinput *i
        u_int v_edge, b_edge;
        int dx, dy;

-       if (tp->contacts != 1 || !chk_scroll_state(input))
+       if (!wstpad_scroll_coords(input, &dx, &dy) || tp->contacts != 1)
                return;

        v_edge = (tp->features & WSTPAD_SWAPSIDES) ? L_EDGE : R_EDGE;
        b_edge = (tp->features & WSTPAD_HORIZSCROLL) ? B_EDGE : 0;

-       dy = (t->flags & v_edge) ? tp->dy : 0;
-       dx = (t->flags & b_edge) ? tp->dx : 0;
+       if ((t->flags & v_edge) == 0)
+               dy = 0;
+       if ((t->flags & b_edge) == 0)
+               dx = 0;

        if (dx || dy)
-               wstpad_scroll(tp, dx, dy, cmds);
+               wstpad_scroll(tp, dx, dy, magnitude(input, dx, dy), cmds);
 }

 static inline u_int
@@ -1121,20 +1126,7 @@ wstpad_touch_inputs(struct wsmouseinput
 {
        struct wstpad *tp = input->tp;
        struct tpad_touch *t;
-       int slot;
-
-       /*
-        * Use the normalized, hysteresis-filtered, but otherwise untransformed
-        * relative coordinates of the pointer-controlling touch for filtering
-        * and scrolling.
-        */
-       if ((input->motion.sync & SYNC_POSITION)
-           && !wsmouse_hysteresis(input, &input->motion.pos)) {
-               tp->dx = normalize_rel(&input->filter.h, input->motion.pos.dx);
-               tp->dy = normalize_rel(&input->filter.v, input->motion.pos.dy);
-       } else {
-               tp->dx = tp->dy = 0;
-       }
+       int slot, x, y, dx, dy;

        tp->btns = input->btn.buttons;
        tp->btns_sync = input->btn.sync;
@@ -1158,8 +1150,6 @@ wstpad_touch_inputs(struct wsmouseinput
                wstpad_mt_masks(input);
        } else {
                t = tp->t;
-               t->x = normalize_abs(&input->filter.h, t->pos->x);
-               t->y = normalize_abs(&input->filter.v, t->pos->y);
                if (tp->contacts)
                        t->state = (tp->prev_contacts ?
                            TOUCH_UPDATE : TOUCH_BEGIN);
@@ -1167,17 +1157,25 @@ wstpad_touch_inputs(struct wsmouseinput
                        t->state = (tp->prev_contacts ?
                            TOUCH_END : TOUCH_NONE);

+               dx = dy = 0;
+               x = normalize_abs(&input->filter.h, t->pos->x);
+               y = normalize_abs(&input->filter.v, t->pos->y);
                if (t->state == TOUCH_BEGIN) {
-                       t->orig.x = t->x;
-                       t->orig.y = t->y;
+                       t->x = t->orig.x = x;
+                       t->y = t->orig.y = y;
                        memcpy(&t->orig.time, &tp->time,
                            sizeof(struct timespec));
-                       t->flags = edge_flags(tp, t->x, t->y);
-               } else {
-                       t->flags &= (~EDGES | edge_flags(tp, t->x, t->y));
+                       t->flags = edge_flags(tp, x, y);
+               } else if (input->motion.sync & SYNC_POSITION) {
+                       if (!wsmouse_hysteresis(input, t->pos)) {
+                               dx = x - t->x;
+                               dy = y - t->y;
+                       }
+                       t->x = x;
+                       t->y = y;
+                       t->flags &= (~EDGES | edge_flags(tp, x, y));
                }
-
-               wstpad_set_direction(tp, t, tp->dx, tp->dy);
+               wstpad_set_direction(tp, t, dx, dy);
        }
 }


Index: driver/xf86-input-ws/src/ws.c
===================================================================
RCS file: /cvs/xenocara/driver/xf86-input-ws/src/ws.c,v
retrieving revision 1.63
diff -u -p -r1.63 ws.c
--- driver/xf86-input-ws/src/ws.c       31 Dec 2017 23:31:41 -0000      1.63
+++ driver/xf86-input-ws/src/ws.c       11 Mar 2019 21:09:28 -0000
@@ -363,6 +363,10 @@ wsDeviceInit(DeviceIntPtr pWS)
                axes_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
                axes_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
        }
+       axes_labels[HSCROLL_AXIS] =
+           XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
+       axes_labels[VSCROLL_AXIS] =
+           XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
        if (!InitValuatorClassDeviceStruct(pWS,
            NAXES, axes_labels, GetMotionHistorySize(),
            priv->type == WSMOUSE_TYPE_TPANEL ? Absolute : Relative))
@@ -382,6 +386,25 @@ wsDeviceInit(DeviceIntPtr pWS)
            priv->type == WSMOUSE_TYPE_TPANEL ? Absolute : Relative);
        xf86InitValuatorDefaults(pWS, 1);

+       xf86InitValuatorAxisStruct(pWS, HSCROLL_AXIS,
+           axes_labels[HSCROLL_AXIS], 0, -1, 0, 0, 0, Relative);
+       xf86InitValuatorAxisStruct(pWS, VSCROLL_AXIS,
+           axes_labels[VSCROLL_AXIS], 0, -1, 0, 0, 0, Relative);
+       priv->scroll_mask = valuator_mask_new(MAX_VALUATORS);
+       if (!priv->scroll_mask) {
+               free(axes_labels);
+               return !Success;
+       }
+
+       /*
+        * The value of an HSCROLL or VSCROLL event is the fraction
+        *         motion_delta / scroll_distance
+        * in [*.12] fixed-point format.  The 'increment' attribute of the
+        * scroll axes is constant:
+        */
+       SetScrollValuator(pWS, HSCROLL_AXIS, SCROLL_TYPE_HORIZONTAL, 4096, 0);
+       SetScrollValuator(pWS, VSCROLL_AXIS, SCROLL_TYPE_VERTICAL, 4096, 0);
+
        pWS->public.on = FALSE;
        if (wsOpen(pInfo) != Success) {
                return !Success;
@@ -579,6 +602,14 @@ wsReadHwState(InputInfoPtr pInfo, wsHwSt
                case WSCONS_EVENT_SYNC:
                        DBG(4, ErrorF("Sync\n"));
                        return TRUE;
+               case WSCONS_EVENT_HSCROLL:
+                       hw->hscroll = event->value;
+                       DBG(4, ErrorF("Horiz. Scrolling %d\n", event->value));
+                       return TRUE;
+               case WSCONS_EVENT_VSCROLL:
+                       hw->vscroll = event->value;
+                       DBG(4, ErrorF("Vert. Scrolling %d\n", event->value));
+                       return TRUE;
                default:
                        xf86IDrvMsg(pInfo, X_WARNING,
                            "bad wsmouse event type=%d\n", event->type);
@@ -623,6 +654,14 @@ wsReadInput(InputInfoPtr pInfo)
                wbutton = (hw.dw < 0) ? priv->W.negative : priv->W.positive;
                DBG(4, ErrorF("W -> button %d (%d)\n", wbutton, abs(hw.dw)));
                wsButtonClicks(pInfo, wbutton, abs(hw.dw));
+       }
+       if (hw.hscroll || hw.vscroll) {
+               valuator_mask_zero(priv->scroll_mask);
+               valuator_mask_set_double(priv->scroll_mask,
+                   HSCROLL_AXIS, (double) hw.hscroll);
+               valuator_mask_set_double(priv->scroll_mask,
+                   VSCROLL_AXIS, (double) hw.vscroll);
+               xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_mask);
        }
        if (priv->lastButtons != hw.buttons) {
                /* button event */
Index: driver/xf86-input-ws/src/ws.h
===================================================================
RCS file: /cvs/xenocara/driver/xf86-input-ws/src/ws.h,v
retrieving revision 1.15
diff -u -p -r1.15 ws.h
--- driver/xf86-input-ws/src/ws.h       31 Dec 2017 23:31:41 -0000      1.15
+++ driver/xf86-input-ws/src/ws.h       11 Mar 2019 21:09:28 -0000
@@ -26,7 +26,10 @@ extern int ws_debug_level;
 # define DBG(lvl, f)
 #endif

-#define NAXES          2       /* X and Y axes only */
+#define NAXES          4       /* X, Y, horizontal and vertical scrolling */
+#define HSCROLL_AXIS   2
+#define VSCROLL_AXIS   3
+
 #define NBUTTONS       32      /* max theoretical buttons */
 #define DFLTBUTTONS    3       /* default number of buttons */

@@ -45,6 +48,7 @@ typedef struct {
        unsigned int buttons;
        int dx, dy, dz, dw;
        int ax, ay;
+       int hscroll, vscroll;
 } wsHwState;

 typedef struct WSDevice {
@@ -86,6 +90,8 @@ typedef struct WSDevice {
                Time expires;           /* time of expiry */
                Time timeout;
        } emulateWheel;
+
+       ValuatorMask *scroll_mask;

        OsTimerPtr      remove_timer;   /* Callback for removal on EIO */

Reply via email to