| diff --git a/include/evdev-properties.h b/include/evdev-properties.h |
| index 745a1ba..72a2162 100644 |
| --- a/include/evdev-properties.h |
| +++ b/include/evdev-properties.h |
| @@ -87,4 +87,12 @@ |
| */ |
| #define EVDEV_PROP_FUNCTION_KEYS "Evdev Function Keys" |
| |
| +/* Mouse scroll wheel button acceleration. */ |
| +/* BOOL */ |
| +#define EVDEV_PROP_WHEEL_ACCEL_BUTTON "Evdev Wheel Button Acceleration" |
| + |
| +/* 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 9f3a22a..d775de4 100644 |
| --- a/src/evdev.c |
| +++ b/src/evdev.c |
| @@ -93,6 +93,12 @@ |
| #define ABS_MT_TRACKING_ID 0x39 |
| #endif |
| |
| +#ifndef AXIS_LABEL_PROP_REL_START_TIME |
| +#define AXIS_LABEL_PROP_REL_START_TIME "Rel Start Timestamp" |
| +#endif |
| +#ifndef AXIS_LABEL_PROP_REL_END_TIME |
| +#define AXIS_LABEL_PROP_REL_END_TIME "Rel End Timestamp" |
| +#endif |
| static char *evdevDefaults[] = { |
| "XkbRules", "evdev", |
| "XkbModel", "evdev", |
| @@ -133,6 +139,8 @@ static Atom prop_axis_label; |
| static Atom prop_btn_label; |
| static Atom prop_device; |
| static Atom prop_virtual; |
| +static Atom prop_wheel_accel_btn; |
| +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) |
| @@ -669,6 +677,110 @@ EvdevProcessButtonEvent(InputInfoPtr pInfo, struct input_event *ev) |
| EvdevQueueKbdEvent(pInfo, ev, value); |
| } |
| |
| +#ifndef HAVE_SMOOTH_SCROLLING |
| +static int steps[] = {0, 10, 25, 42, 63, 100}; |
| +static double slopes[] = {1.0, 1.2, 1.0, 0.8, 0.6, 5.0}; |
| +static double bounds[ArrayLength(steps)]; |
| + |
| +/** |
| + * Initialize the bound array used for scroll acceleration. |
| + */ |
| +static void |
| +EvdevInitScrollAccelBound() |
| +{ |
| + bounds[0] = 0; |
| + |
| + // populate the parameters used for the step function. |
| + for (int i = 1; i < ArrayLength(bounds); i++) |
| + bounds[i] = bounds[i - 1] + slopes[i - 1] * (steps[i] - steps[i - 1]); |
| +} |
| + |
| +/** |
| + * 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 code to generate accel_params below: |
| + * x=[-50:200]; |
| + * y=300*normcdf(x,120,60); |
| + * a=polyfit(x,y,4); |
| + */ |
| +static double accel_params[] = { |
| + -0.000000252977550, |
| + 0.000049601916510, |
| + 0.005539772199555, |
| + 0.246492103384611, |
| + 6.283184444592136 |
| +}; |
| + |
| +/** |
| + * Get the number of pixels to scroll, used for axes_scroll. |
| + */ |
| +static int |
| +EvdevGetPixelsToScroll(double click_speed) |
| +{ |
| + double pixels = 0; |
| + double term = 1; |
| + int i; |
| + for (i = 1; i <= ArrayLength(accel_params); i++) |
| + { |
| + pixels += term * accel_params[ArrayLength(accel_params) - i]; |
| + term *= click_speed; |
| + } |
| + return ceil(pixels); |
| +} |
| + |
| +/* In Chrome, number of pixels per button scroll is 53. */ |
| +#define PIXELS_PER_SCROLL_BUTTON 53 |
| + |
| +/** |
| + * Mouse scroll acceleration. |
| + */ |
| +static void |
| +EvdevAccelWheel(InputInfoPtr pInfo, int wheel_button, int value, |
| + double start_time, double end_time) |
| +{ |
| + int pixels; |
| + double dt; /* seconds */ |
| + double click_speed; /* clicks / second */ |
| + unsigned int start, end; |
| + EvdevPtr pEvdev; |
| + |
| + pEvdev = pInfo->private; |
| + dt = end_time - start_time; |
| + if (!pEvdev->wheelAccelRec.scroll_btn && |
| + !pEvdev->wheelAccelRec.scroll_axes) |
| + { |
| + EvdevQueueButtonClicks(pInfo, wheel_button, abs(value)); |
| + return; |
| + } |
| + click_speed = dt == 0 ? 1 : 1/dt; |
| + pixels = EvdevGetPixelsToScroll(click_speed); |
| + pixels = value > 0 ? pixels : pixels * -1; |
| + if (pEvdev->wheelAccelRec.scroll_axes) |
| + { |
| + start = (unsigned long long)(1000.0L * start_time) & 0x0FFFFFFFFLL; |
| + end = (unsigned long long)(1000.0L * end_time) & 0x0FFFFFFFFLL; |
| + if (wheel_button == wheel_down_button || |
| + wheel_button == wheel_up_button) |
| + xf86PostMotionEvent(pInfo->dev, 1, 2, 4, 0, -pixels, start, end); |
| + else |
| + xf86PostMotionEvent(pInfo->dev, 1, 2, 4, pixels, 0, start, end); |
| + } |
| + if (pEvdev->wheelAccelRec.scroll_btn) |
| + { |
| + EvdevQueueButtonClicks(pInfo, wheel_button, |
| + abs((pixels + PIXELS_PER_SCROLL_BUTTON/2)/ |
| + PIXELS_PER_SCROLL_BUTTON)); |
| + } |
| +} |
| +#endif |
| + |
| /** |
| * Take the relative motion input event and process it accordingly. |
| */ |
| @@ -678,27 +790,50 @@ EvdevProcessRelativeMotionEvent(InputInfoPtr pInfo, struct input_event *ev) |
| int value; |
| EvdevPtr pEvdev = pInfo->private; |
| int map; |
| + int last_wheel_button, current_wheel_button; |
| + double start_time, end_time; |
| |
| /* Get the signed value, earlier kernels had this as unsigned */ |
| value = ev->value; |
| + end_time = ev->time.tv_sec + ev->time.tv_usec / 1000000.0; |
| |
| switch (ev->code) { |
| #ifndef HAVE_SMOOTH_SCROLLING |
| case REL_WHEEL: |
| - if (value > 0) |
| - EvdevQueueButtonClicks(pInfo, wheel_up_button, value); |
| - else if (value < 0) |
| - EvdevQueueButtonClicks(pInfo, wheel_down_button, -value); |
| + last_wheel_button = pEvdev->wheelAccelRec.lastWheelButtonVertical; |
| + current_wheel_button = (value >= 0) ? |
| + wheel_up_button : wheel_down_button; |
| + start_time = (last_wheel_button == current_wheel_button) ? |
| + pEvdev->wheelAccelRec.lastWheelClickTimeVertical : |
| + end_time; |
| + EvdevAccelWheel(pInfo, current_wheel_button, value, start_time, |
| + end_time); |
| + pEvdev->wheelAccelRec.lastWheelButtonVertical = |
| + current_wheel_button; |
| + pEvdev->wheelAccelRec.lastWheelClickTimeVertical = end_time; |
| + break; |
| + case REL_HWHEEL: |
| + last_wheel_button = pEvdev->wheelAccelRec.lastWheelButtonHorizontal; |
| + current_wheel_button = (value >= 0) ? |
| + wheel_right_button : wheel_left_button; |
| + start_time = (last_wheel_button == current_wheel_button) ? |
| + pEvdev->wheelAccelRec.lastWheelClickTimeHorizontal : |
| + end_time; |
| + EvdevAccelWheel(pInfo, current_wheel_button, value, start_time, |
| + end_time); |
| + pEvdev->wheelAccelRec.lastWheelButtonHorizontal = |
| + current_wheel_button; |
| + pEvdev->wheelAccelRec.lastWheelClickTimeHorizontal = end_time; |
| break; |
| |
| case REL_DIAL: |
| - case REL_HWHEEL: |
| if (value > 0) |
| EvdevQueueButtonClicks(pInfo, wheel_right_button, value); |
| else if (value < 0) |
| EvdevQueueButtonClicks(pInfo, wheel_left_button, -value); |
| break; |
| - /* We don't post wheel events as axis motion. */ |
| + /* We don't post DIAL events as axis motion. */ |
| + |
| #endif |
| default: |
| /* Ignore EV_REL events if we never set up for them. */ |
| @@ -1434,6 +1569,8 @@ out: |
| return !Success; |
| } |
| |
| +#define AXIS_LABEL_ACCEL_START_IDX 6 |
| +#define AXIS_LABEL_ACCEL_END_IDX 10 |
| static int |
| EvdevAddRelValuatorClass(DeviceIntPtr device) |
| { |
| @@ -1453,12 +1590,7 @@ 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--; |
| + EvdevInitScrollAccelBound(); |
| if (EvdevBitIsSet(pEvdev->rel_bitmask, REL_DIAL)) |
| num_axes--; |
| |
| @@ -1471,6 +1603,7 @@ EvdevAddRelValuatorClass(DeviceIntPtr device) |
| num_axes = MAX_VALUATORS; |
| } |
| |
| + num_axes += 2; /* For START and END time axes */ |
| pEvdev->num_vals = num_axes; |
| if (num_axes > 0) { |
| pEvdev->vals = valuator_mask_new(num_axes); |
| @@ -1484,10 +1617,12 @@ 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)) |
| + if (!EvdevBitIsSet(pEvdev->rel_bitmask, axis) && |
| + (axis < AXIS_LABEL_ACCEL_START_IDX || |
| + axis > AXIS_LABEL_ACCEL_END_IDX)) |
| continue; |
| pEvdev->axis_map[axis] = i; |
| i++; |
| @@ -2508,6 +2643,8 @@ static char* rel_labels[] = { |
| AXIS_LABEL_PROP_REL_HWHEEL, |
| AXIS_LABEL_PROP_REL_DIAL, |
| AXIS_LABEL_PROP_REL_WHEEL, |
| + AXIS_LABEL_PROP_REL_START_TIME, |
| + AXIS_LABEL_PROP_REL_END_TIME, |
| AXIS_LABEL_PROP_REL_MISC |
| }; |
| |
| @@ -2619,10 +2756,10 @@ static void EvdevInitAxesLabels(EvdevPtr pEvdev, int natoms, Atom *atoms) |
| continue; |
| |
| atom = XIGetKnownProperty(labels[axis]); |
| - if (!atom) /* Should not happen */ |
| - continue; |
| - |
| - atoms[pEvdev->axis_map[axis]] = atom; |
| + atoms[pEvdev->axis_map[axis]] = atom ? atom : |
| + MakeAtom(labels[axis], |
| + strlen(labels[axis]), |
| + TRUE); |
| } |
| } |
| |
| @@ -2716,6 +2853,7 @@ EvdevInitProperty(DeviceIntPtr dev) |
| if (pEvdev->flags & (EVDEV_RELATIVE_EVENTS | EVDEV_ABSOLUTE_EVENTS)) |
| { |
| BOOL invert[2]; |
| + int btn_accel_conf_val, axis_accel_conf_val; |
| invert[0] = pEvdev->invert_x; |
| invert[1] = pEvdev->invert_y; |
| |
| @@ -2762,6 +2900,27 @@ EvdevInitProperty(DeviceIntPtr dev) |
| |
| XISetDevicePropertyDeletable(dev, prop_swap, FALSE); |
| |
| + prop_wheel_accel_btn = MakeAtom(EVDEV_PROP_WHEEL_ACCEL_BUTTON, |
| + strlen(EVDEV_PROP_WHEEL_ACCEL_BUTTON), TRUE); |
| + btn_accel_conf_val = xf86SetBoolOption(pInfo->options, EVDEV_PROP_WHEEL_ACCEL_BUTTON, 0); |
| + pEvdev->wheelAccelRec.scroll_btn = btn_accel_conf_val != 0; |
| + rc = XIChangeDeviceProperty(dev, prop_wheel_accel_btn, XA_INTEGER, 8, |
| + PropModeReplace, 1, &pEvdev->wheelAccelRec.scroll_btn, FALSE); |
| + if (rc != Success) |
| + return; |
| + |
| + XISetDevicePropertyDeletable(dev, prop_wheel_accel_btn, 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->wheelAccelRec.scroll_axes = axis_accel_conf_val != 0; |
| + rc = XIChangeDeviceProperty(dev, prop_wheel_accel_axes, XA_INTEGER, 8, |
| + PropModeReplace, 1, &pEvdev->wheelAccelRec.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))) |
| { |
| @@ -2819,6 +2978,20 @@ EvdevSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, |
| |
| if (!checkonly) |
| pEvdev->swap_axes = *((BOOL*)val->data); |
| + } else if (atom == prop_wheel_accel_btn) |
| + { |
| + if (val->format != 8 || val->type != XA_INTEGER || val->size != 1) |
| + return BadMatch; |
| + |
| + if (!checkonly) |
| + pEvdev->wheelAccelRec.scroll_btn = *((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->wheelAccelRec.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 acd4289..2a0dd79 100644 |
| --- a/src/evdev.h |
| +++ b/src/evdev.h |
| @@ -247,6 +247,16 @@ typedef struct { |
| EventQueueRec queue[EVDEV_MAXQUEUE]; |
| |
| enum fkeymode fkeymode; |
| + |
| + /* Mouse scroll wheel acceleration information. */ |
| + struct { |
| + int lastWheelButtonVertical; |
| + int lastWheelButtonHorizontal; |
| + double lastWheelClickTimeVertical; |
| + double lastWheelClickTimeHorizontal; |
| + BOOL scroll_btn; /* 0: off */ |
| + BOOL scroll_axes; /* 0: off */ |
| + } wheelAccelRec; |
| } EvdevRec, *EvdevPtr; |
| |
| /* Event posting functions */ |