blob: a996550068feb06753a1fff85121744b6ee1a998 [file] [log] [blame]
From: Ang Pan <oscarpan@chromium.org>
Date: Thu, 15 Nov 2012 15:38:44 +0800
Subject: [PATCH] Accelerate wheel scrolling
---
include/evdev-properties.h | 4 +
src/evdev.c | 198 ++++++++++++++++++++++++++++++++++++++++-----
src/evdev.h | 13 +++
3 files changed, 196 insertions(+), 19 deletions(-)
diff --git a/include/evdev-properties.h b/include/evdev-properties.h
index 745a1ba..66e8a75 100644
--- a/include/evdev-properties.h
+++ b/include/evdev-properties.h
@@ -87,4 +87,8 @@
*/
#define EVDEV_PROP_FUNCTION_KEYS "Evdev Function Keys"
+/* Mouse scroll wheel axes acceleration. */
+/* BOOL */
+#define EVDEV_PROP_WHEEL_ACCEL_AXES "Evdev Wheel Axes Acceleration"
+
#endif
diff --git a/src/evdev.c b/src/evdev.c
index 6cc7f2e..2856e3b 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -99,6 +99,9 @@
#define ABS_MT_TRACKING_ID 0x39
#endif
+#define AXIS_LABEL_PROP_ABS_DBL_START_TIME "Abs Dbl Start Timestamp"
+#define AXIS_LABEL_PROP_ABS_DBL_END_TIME "Abs Dbl End Timestamp"
+
static const char *evdevDefaults[] = {
"XkbRules", "evdev",
"XkbModel", "evdev",
@@ -156,12 +159,17 @@ static Atom prop_axis_label;
static Atom prop_btn_label;
static Atom prop_device;
static Atom prop_virtual;
+static Atom prop_wheel_accel_axes;
/* All devices the evdev driver has allocated and knows about.
* MAXDEVICES is safe as null-terminated array, as two devices (VCP and VCK)
* cannot be used by evdev, leaving us with a space of 2 at the end. */
static EvdevPtr evdev_devices[MAXDEVICES] = {NULL};
+static double TimevalToDouble(const struct timeval* tv) {
+ return (double)(tv->tv_sec) + (double)(tv->tv_usec) / 1000000.0;
+}
+
static int EvdevSwitchMode(ClientPtr client, DeviceIntPtr device, int mode)
{
InputInfoPtr pInfo;
@@ -702,6 +710,103 @@ EvdevProcessButtonEvent(InputInfoPtr pInfo, struct input_event *ev)
EvdevQueueKbdEvent(pInfo, ev, value);
}
+#ifndef HAVE_SMOOTH_SCROLLING
+/**
+ * Normal CDF seems like a good curve to simulate scroll wheel acceleration
+ * curve. Use following methods to generate the coefficients of a degree-4
+ * polynomial regression for a specific normal cdf in matlab.
+ *
+ * Note: x for click_speed, y for scroll pixels.
+ * In reality, x ranges from 1 to 120+ for an Apple Mighty Mouse, use range
+ * greater than that to minimize approximation error at the end points.
+ * In our case, the range is [-50, 200].
+ *
+ * matlab/octave code to generate accel_params below:
+ * x = [-50:200];
+ * y = 580 * normcdf(x,100,40) + 20;
+ * a = polyfit(x,y,4);
+ */
+static const double accel_params[] = {
+ -1.0012e-06,
+ 1.6313e-04,
+ 1.9727e-02,
+ 2.5547e-01,
+ 1.5937e+01,
+};
+
+/* when x is 177, the polynomial curve gives 450, the max pixels to scroll */
+static const double max_allowed_click_speed = 177;
+
+/**
+ * Get the number of pixels to scroll, used for axes_scroll.
+ */
+static double
+EvdevGetPixelsToScroll(double click_speed)
+{
+ double pixels = 0;
+ double term = 1;
+ double allowed_click_speed = fabs(click_speed);
+ if (allowed_click_speed > max_allowed_click_speed)
+ allowed_click_speed = max_allowed_click_speed;
+ int i;
+ for (i = 1; i <= ArrayLength(accel_params); i++)
+ {
+ pixels += term * accel_params[ArrayLength(accel_params) - i];
+ term *= allowed_click_speed;
+ }
+ if (click_speed < 0)
+ pixels *= -1;
+ return pixels;
+}
+
+/**
+ * Mouse scroll acceleration.
+ */
+static void
+EvdevAccelWheel(InputInfoPtr pInfo, struct input_event *ev)
+{
+ double pixels;
+ double dt; /* seconds */
+ double click_speed; /* clicks / second */
+ EvdevPtr pEvdev = pInfo->private;
+ double start_time;
+ double end_time = TimevalToDouble(&ev->time);
+ int map;
+ EvdevRelWheelPtr wheel = (ev->code == REL_WHEEL) ? &pEvdev->wheel :
+ &pEvdev->hwheel;
+
+ /* Check if this scroll is in same direction as previous scroll event */
+ if ((wheel->value < 0 && ev->value < 0) ||
+ (wheel->value > 0 && ev->value > 0)) {
+ start_time = wheel->time;
+ } else {
+ start_time = end_time;
+ }
+
+ /* If start_time == end_time, compute click_speed using dt = 1 second */
+ dt = (end_time - start_time) ?: 1.0;
+ click_speed = ev->value / dt;
+
+ wheel->value = ev->value;
+ wheel->time = end_time;
+
+ pixels = EvdevGetPixelsToScroll(click_speed);
+ /* For historical reasons the vertical wheel (REL_WHEEL) is inverted */
+ pixels *= (ev->code == REL_WHEEL) ? -1 : 1;
+
+ valuator_mask_zero(pEvdev->vals);
+ map = pEvdev->axis_map[ev->code];
+ valuator_mask_set_double(pEvdev->vals, map, pixels);
+ if (pEvdev->start_time_valuator_index >= 0)
+ valuator_mask_set_double(pEvdev->vals,
+ pEvdev->start_time_valuator_index, start_time);
+ if (pEvdev->end_time_valuator_index >= 0)
+ valuator_mask_set_double(pEvdev->vals,
+ pEvdev->end_time_valuator_index, end_time);
+ xf86PostMotionEventM(pInfo->dev, TRUE, pEvdev->vals);
+}
+#endif
+
/**
* Take the relative motion input event and process it accordingly.
*/
@@ -712,6 +817,14 @@ EvdevProcessRelativeMotionEvent(InputInfoPtr pInfo, struct input_event *ev)
EvdevPtr pEvdev = pInfo->private;
int map;
+#ifndef HAVE_SMOOTH_SCROLLING
+ if (pEvdev->scroll_axes && (ev->code == REL_WHEEL ||
+ ev->code == REL_HWHEEL)) {
+ EvdevAccelWheel(pInfo, ev);
+ return;
+ }
+#endif
+
/* Get the signed value, earlier kernels had this as unsigned */
value = ev->value;
@@ -1898,12 +2011,6 @@ EvdevAddRelValuatorClass(DeviceIntPtr device)
goto out;
#ifndef HAVE_SMOOTH_SCROLLING
- /* Wheels are special, we post them as button events. So let's ignore them
- * in the axes list too */
- if (EvdevBitIsSet(pEvdev->rel_bitmask, REL_WHEEL))
- num_axes--;
- if (EvdevBitIsSet(pEvdev->rel_bitmask, REL_HWHEEL))
- num_axes--;
if (EvdevBitIsSet(pEvdev->rel_bitmask, REL_DIAL))
num_axes--;
@@ -1911,17 +2018,20 @@ EvdevAddRelValuatorClass(DeviceIntPtr device)
goto out;
#endif
- if (num_axes > MAX_VALUATORS) {
- xf86IDrvMsg(pInfo, X_WARNING, "found %d axes, limiting to %d.\n", num_axes, MAX_VALUATORS);
- num_axes = MAX_VALUATORS;
+ /* -2 to leave room for start and end timestamps */
+ if (num_axes > MAX_VALUATORS - 2) {
+ xf86IDrvMsg(pInfo, X_WARNING, "found %d axes, limiting to %d.\n", num_axes, MAX_VALUATORS - 2);
+ num_axes = MAX_VALUATORS - 2;
}
- pEvdev->num_vals = num_axes;
- if (num_axes > 0) {
- pEvdev->vals = valuator_mask_new(num_axes);
- if (!pEvdev->vals)
- goto out;
- }
+ pEvdev->start_time_valuator_index = num_axes;
+ pEvdev->end_time_valuator_index = num_axes + 1;
+
+ /* +2 for timestamp valuators */
+ pEvdev->num_vals = num_axes + 2;
+ pEvdev->vals = valuator_mask_new(pEvdev->num_vals);
+ if (!pEvdev->vals)
+ goto out;
atoms = malloc(pEvdev->num_vals * sizeof(Atom));
for (axis = REL_X; i < MAX_VALUATORS && axis <= REL_MAX; axis++)
@@ -1929,7 +2039,7 @@ EvdevAddRelValuatorClass(DeviceIntPtr device)
pEvdev->axis_map[axis] = -1;
#ifndef HAVE_SMOOTH_SCROLLING
/* We don't post wheel events, so ignore them here too */
- if (axis == REL_WHEEL || axis == REL_HWHEEL || axis == REL_DIAL)
+ if (axis == REL_DIAL)
continue;
#endif
if (!EvdevBitIsSet(pEvdev->rel_bitmask, axis))
@@ -1938,9 +2048,20 @@ EvdevAddRelValuatorClass(DeviceIntPtr device)
i++;
}
- EvdevInitAxesLabels(pEvdev, Relative, pEvdev->num_vals, atoms);
+ /* Initialize all axis label atoms except Start & End Timestamps */
+ EvdevInitAxesLabels(pEvdev, Relative, num_axes, atoms);
+
+ /* Create atoms for Start & End Timestamps */
+ atoms[pEvdev->start_time_valuator_index] =
+ MakeAtom(AXIS_LABEL_PROP_ABS_DBL_START_TIME,
+ strlen(AXIS_LABEL_PROP_ABS_DBL_START_TIME),
+ TRUE);
+ atoms[pEvdev->end_time_valuator_index] =
+ MakeAtom(AXIS_LABEL_PROP_ABS_DBL_END_TIME,
+ strlen(AXIS_LABEL_PROP_ABS_DBL_END_TIME),
+ TRUE);
- if (!InitValuatorClassDeviceStruct(device, num_axes, atoms,
+ if (!InitValuatorClassDeviceStruct(device, pEvdev->num_vals, atoms,
GetMotionHistorySize(), Relative)) {
xf86IDrvMsg(pInfo, X_ERROR, "failed to initialize valuator class device.\n");
goto out;
@@ -1955,11 +2076,18 @@ EvdevAddRelValuatorClass(DeviceIntPtr device)
for (axis = REL_X; axis <= REL_MAX; axis++)
{
int axnum = pEvdev->axis_map[axis];
+ int mode = Relative;
if (axnum == -1)
continue;
+
+#ifndef HAVE_SMOOTH_SCROLLING
+ if (axis == REL_WHEEL || axis == REL_HWHEEL)
+ mode = Absolute;
+#endif
+
xf86InitValuatorAxisStruct(device, axnum, atoms[axnum], -1, -1, 1, 0, 1,
- Relative);
+ mode);
xf86InitValuatorDefaults(device, axnum);
#ifdef HAVE_SMOOTH_SCROLLING
if (axis == REL_WHEEL)
@@ -1971,6 +2099,17 @@ EvdevAddRelValuatorClass(DeviceIntPtr device)
#endif
}
+#ifndef HAVE_SMOOTH_SCROLLING
+ /* Initialize valuators for scroll wheel Start & End Timestamps */
+ xf86InitValuatorAxisStruct(device, pEvdev->start_time_valuator_index,
+ atoms[pEvdev->start_time_valuator_index],
+ 0, INT_MAX, 1, 0, 1, Absolute);
+
+ xf86InitValuatorAxisStruct(device, pEvdev->end_time_valuator_index,
+ atoms[pEvdev->end_time_valuator_index],
+ 0, INT_MAX, 1, 0, 1, Absolute);
+#endif
+
free(atoms);
return Success;
@@ -2132,6 +2271,9 @@ EvdevInit(DeviceIntPtr device)
for(i = 0; i < max(ABS_CNT,REL_CNT); i++)
pEvdev->axis_map[i]=-1;
+ pEvdev->start_time_valuator_index = -1;
+ pEvdev->end_time_valuator_index = -1;
+
if (pEvdev->flags & EVDEV_KEYBOARD_EVENTS)
EvdevAddKeyClass(device);
if (pEvdev->flags & EVDEV_BUTTON_EVENTS)
@@ -3222,6 +3364,7 @@ EvdevInitProperty(DeviceIntPtr dev)
if (pEvdev->flags & (EVDEV_RELATIVE_EVENTS | EVDEV_ABSOLUTE_EVENTS))
{
BOOL invert[2];
+ int axis_accel_conf_val;
invert[0] = pEvdev->invert_x;
invert[1] = pEvdev->invert_y;
@@ -3268,6 +3411,16 @@ EvdevInitProperty(DeviceIntPtr dev)
XISetDevicePropertyDeletable(dev, prop_swap, FALSE);
+ prop_wheel_accel_axes = MakeAtom(EVDEV_PROP_WHEEL_ACCEL_AXES,
+ strlen(EVDEV_PROP_WHEEL_ACCEL_AXES), TRUE);
+ axis_accel_conf_val = xf86SetBoolOption(pInfo->options, EVDEV_PROP_WHEEL_ACCEL_AXES, 0);
+ pEvdev->scroll_axes = axis_accel_conf_val != 0;
+ rc = XIChangeDeviceProperty(dev, prop_wheel_accel_axes, XA_INTEGER, 8,
+ PropModeReplace, 1, &pEvdev->scroll_axes, FALSE);
+ if (rc != Success)
+ return;
+
+ XISetDevicePropertyDeletable(dev, prop_wheel_accel_axes, FALSE);
/* Axis labelling */
if ((pEvdev->num_vals > 0) && (prop_axis_label = XIGetKnownProperty(AXIS_LABEL_PROP)))
{
@@ -3337,6 +3490,13 @@ EvdevSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
if (!checkonly)
pEvdev->swap_axes = *((BOOL*)val->data);
+ } else if (atom == prop_wheel_accel_axes)
+ {
+ if (val->format != 8 || val->type != XA_INTEGER || val->size != 1)
+ return BadMatch;
+
+ if (!checkonly)
+ pEvdev->scroll_axes = *((BOOL*)val->data);
} else if (atom == prop_axis_label || atom == prop_btn_label ||
atom == prop_product_id || atom == prop_device ||
atom == prop_virtual)
diff --git a/src/evdev.h b/src/evdev.h
index 33f0ea5..5d6045d 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -155,6 +155,12 @@ typedef struct {
#endif
} EventQueueRec, *EventQueuePtr;
+/* Mouse scroll wheel state for one axis (REL_WHEEL or REL_WHEEL) */
+typedef struct {
+ int value; /* last scroll wheel value */
+ double time; /* evdev timestamp of last scroll event */
+} EvdevRelWheelRec, *EvdevRelWheelPtr;
+
typedef struct {
unsigned short id_vendor;
unsigned short id_product;
@@ -252,6 +258,9 @@ typedef struct {
unsigned long led_bitmask[NLONGS(LED_CNT)];
struct input_absinfo absinfo[ABS_CNT];
+ int start_time_valuator_index;
+ int end_time_valuator_index;
+
/* minor/major number */
dev_t min_maj;
@@ -266,6 +275,10 @@ typedef struct {
struct timeval before_sync_time;
struct timeval after_sync_time;
int32_t cached_tid[MAX_SLOT_COUNT];
+
+ BOOL scroll_axes;
+ EvdevRelWheelRec wheel; /* scroll state for REL_WHEEL */
+ EvdevRelWheelRec hwheel; /* scroll state for REL_HWHEEL */
} EvdevRec, *EvdevPtr;
typedef struct {
--
1.8.4.1