| 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 |
| |