On Sun, Mar 31, 2013 at 10:04:44PM +0200, Martin Minarik wrote:
> Computes an accurate velocity instead of approximation.
> 
> Changes:
> Add disclaimer. Implements 8 acceleration profiles taken from X ptrveloc.c.
> The sampling has been fixed to handle eventual time overflow.

Since we added them I can't remember seeing a configuration that uses a
non-default acceleration profile. I don't think implementing multiple
profiles is really worthwhile. The only other profile that is really in use
is the synaptics one (which uses pressure-based accel) and that too 
because it's the default.

Try to find a single accel method that is good enough for most cases and the
vast majority of users won't notice.

Cheers,
   Peter

> 
> Configuration:
> Tune the values in mouse_init().
> 
> The tune_constant_coefficient is simply a multiplier of how fast
> the pointer moves. The 1.0 is the native device speed. Recommended:
> 
>  0.03125 0.0625 0.25 0.5 0.75 1.0 1.5 2.0 2.5 3.0 3.5
> 
> The tune_acceleration_profile is used to select one of the 8 profiles:
> http://www.x.org/wiki/Development/Documentation/PointerAcceleration#AccelerationProfiles
> To disable acceleration, use no_profile, number 0.
> 
> tune_acceleration_treshold - should be a bit larger than 1.
> tune_min_multiplier - the multiplier when moving slow.
> tune_acc_multiplier - the multiplier when moving fast.
> ---
>  src/Makefile.am   |    4 +
>  src/evdev-mouse.c |  467 
> +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  src/evdev.c       |    3 +
>  src/evdev.h       |    3 +
>  4 files changed, 477 insertions(+)
>  create mode 100644 src/evdev-mouse.c
> 
> diff --git a/src/Makefile.am b/src/Makefile.am
> index d56daa0..2c8b3eb 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -138,6 +138,7 @@ drm_backend_la_SOURCES =                  \
>       udev-seat.h                             \
>       evdev.c                                 \
>       evdev.h                                 \
> +     evdev-mouse.c                           \
>       evdev-touchpad.c                        \
>       launcher-util.c                         \
>       launcher-util.h                         \
> @@ -177,7 +178,9 @@ rpi_backend_la_SOURCES =                  \
>       tty.c                                   \
>       evdev.c                                 \
>       evdev.h                                 \
> +     evdev-mouse.c                           \
>       evdev-touchpad.c
> +
>  endif
>  
>  if ENABLE_HEADLESS_COMPOSITOR
> @@ -211,6 +214,7 @@ fbdev_backend_la_SOURCES = \
>       evdev.c \
>       evdev.h \
>       evdev-touchpad.c \
> +     evdev-mouse.c \
>       launcher-util.c
>  endif
>  
> diff --git a/src/evdev-mouse.c b/src/evdev-mouse.c
> new file mode 100644
> index 0000000..351eadc
> --- /dev/null
> +++ b/src/evdev-mouse.c
> @@ -0,0 +1,467 @@
> +/*
> + * Copyright ?? 2013 Martin Minarik
> + * Copyright ?? 2006-2009 Simon Thum
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
> + * DEALINGS IN THE SOFTWARE.
> + */
> +
> +#include <stdlib.h>
> +#include <stdbool.h>
> +
> +#include "compositor.h"
> +#include "evdev.h"
> +
> +#define DEFAULT_AXIS_STEP_DISTANCE wl_fixed_from_int(10)
> +#define MOUSE_MOTION_SAMPLING_PERIOD 32
> +
> +typedef double (*accel_velocity_func_t)(double min_acc, double velocity,
> +                                     double threshold, double acc);
> +
> +/**
> + * just a smooth function in [0..1] -> [0..1]
> + * - point symmetry at 0.5
> + * - f'(0) = f'(1) = 0
> + * - starts faster than a sinoid
> + * - smoothness C1 (Cinf if you dare to ignore endpoints)
> + */
> +static inline double
> +calc_penumbral_gradient(double x)
> +{
> +     x *= 2.0f;
> +     x -= 1.0f;
> +     return 0.5f + (x * sqrt(1.0 - x * x) + asin(x)) / M_PI;
> +}
> +
> +/*****************************************
> + * Acceleration functions and profiles
> + ****************************************/
> +
> +/**
> + * acceleration function similar to classic accelerated/unaccelerated,
> + * but with smooth transition in between (and towards zero for adaptive 
> dec.).
> + */
> +static double
> +simple_smooth_profile(double min_acc,
> +                     double velocity, double threshold, double acc)
> +{
> +     if (velocity < 1.0f)
> +             return calc_penumbral_gradient(0.5 + velocity * 0.5) * 2.0f - 
> 1.0f;
> +     if (threshold < 1.0f)
> +             threshold = 1.0f;
> +     if (velocity <= threshold)
> +             return 1;
> +     velocity /= threshold;
> +     if (velocity >= acc)
> +             return acc;
> +     else
> +             return 1.0f + (calc_penumbral_gradient(velocity / acc) * (acc - 
> 1.0f));
> +}
> +
> +/**
> + * Polynomial function similar previous one, but with f(1) = 1
> + */
> +static double
> +polynomial_acceleration_profile(double min_acc,
> +                             double velocity, double ignored, double acc)
> +{
> +     return pow(velocity, (acc - 1.0) * 0.5);
> +}
> +
> +/**
> + * returns acceleration for velocity.
> + * This profile selects the two functions like the old scheme did
> + */
> +static double
> +classic_profile(double min_acc, double velocity, double threshold, double 
> acc)
> +{
> +     if (threshold > 0)
> +             return simple_smooth_profile(min_acc, velocity, threshold, acc);
> +     else
> +             return polynomial_acceleration_profile(min_acc, velocity, 0, 
> acc);
> +}
> +
> +/**
> + * Power profile
> + * This has a completely smooth transition curve, i.e. no jumps in the
> + * derivatives.
> + *
> + * This has the expense of overall response dependency on min-acceleration.
> + * In effect, min_acc mimics const_acceleration in this profile.
> + */
> +static double
> +power_profile(double min_acc, double velocity, double threshold, double acc)
> +{
> +     double vel_dist;
> +
> +     acc = (acc - 1.0) * 0.1f + 1.0;/* without this, acc of 2 is unuseable */
> +
> +     if (velocity <= threshold)
> +             return min_acc;
> +     vel_dist = velocity - threshold;
> +             return (pow(acc, vel_dist)) * min_acc;
> +}
> +
> +/**
> + * This profile uses the first half of the penumbral gradient as a start
> + * and then scales linearly.
> + */
> +static double
> +smooth_linear_profile(double min_acc, double velocity, double threshold, 
> double acc)
> +{
> +     double res, nv;
> +
> +     if (acc > 1.0f)
> +             acc -= 1.0f;    /*this is so acc = 1 is no acceleration */
> +     else
> +             return 1.0f;
> +
> +     nv = (velocity - threshold) * acc * 0.5f;
> +
> +     if (nv < 0) {
> +             res = 0;
> +     }
> +     else if (nv < 2) {
> +             res = calc_penumbral_gradient(nv * 0.25f) * 2.0f;
> +     } else {
> +             nv -= 2.0f;
> +             res = nv * 2.0f / M_PI  /* steepness of gradient at 0.5 */
> +             + 1.0f; /* gradient crosses 2|1 */
> +     }
> +     res += min_acc;
> +     return res;
> +}
> +
> +/**
> + * From 0 to threshold, the response graduates smoothly from min_accel to
> + * acceleration. Beyond threshold it is exactly the specified acceleration.
> + */
> +static double
> +smooth_limited_profile(double min_acc, double velocity, double threshold, 
> double acc)
> +{
> +     double res;
> +
> +     if (velocity >= threshold || threshold == 0.0f)
> +             return acc;
> +
> +     velocity /= threshold;  /* should be [0..1[ now */
> +
> +     res = calc_penumbral_gradient(velocity) * (acc - min_acc);
> +
> +     return min_acc + res;
> +}
> +
> +static double
> +linear_profile(double min_acc, double velocity, double threshold, double acc)
> +{
> +     return acc * velocity;
> +}
> +
> +static double
> +no_profile(double min_acc, double velocity, double threshold, double acc)
> +{
> +     return 1.0f;
> +}
> +
> +static accel_velocity_func_t accel_profiles[8] = {
> +     no_profile,
> +     classic_profile,
> +     polynomial_acceleration_profile,
> +     smooth_linear_profile,
> +     simple_smooth_profile,
> +     power_profile,
> +     linear_profile,
> +     smooth_limited_profile
> +};
> +
> +/********************************************************/
> +
> +struct mouse_motion {
> +     int32_t dx;
> +     int32_t dy;
> +};
> +
> +struct mouse_dispatch {
> +     struct evdev_dispatch base;
> +     struct evdev_device *device;
> +
> +     double actual_acceleration_factor;
> +
> +     struct mouse_motion motion_sample;
> +     uint32_t motion_period_id;
> +     uint32_t motion_period_next;
> +     double last_velocity;
> +     double last_computed_acc;
> +
> +     double tune_constant_coefficient;
> +     accel_velocity_func_t tune_acceleration_profile;
> +     double tune_min_multiplier;
> +     double tune_acc_multiplier;
> +     double tune_acceleration_treshold;
> +};
> +
> +static inline void
> +mouse_process_relative(struct mouse_dispatch *mouse,
> +                    struct evdev_device *device,
> +                    struct input_event *event, uint32_t time)
> +{
> +     switch (event->code) {
> +     case REL_X:
> +             if (mouse->tune_acceleration_profile != no_profile)
> +                     mouse->motion_sample.dx += event->value;
> +             device->rel.dx += wl_fixed_from_double((double) event->value *
> +                                     mouse->actual_acceleration_factor);
> +
> +             device->pending_events |= EVDEV_RELATIVE_MOTION;
> +             break;
> +     case REL_Y:
> +             if (mouse->tune_acceleration_profile != no_profile)
> +                     mouse->motion_sample.dy += event->value;
> +             device->rel.dy += wl_fixed_from_double((double) event->value *
> +                                     mouse->actual_acceleration_factor);
> +
> +             device->pending_events |= EVDEV_RELATIVE_MOTION;
> +             break;
> +     case REL_WHEEL:
> +             switch (event->value) {
> +             case -1:
> +                     /* Scroll down */
> +             case 1:
> +                     /* Scroll up */
> +                     notify_axis(device->seat,
> +                                 time,
> +                                 WL_POINTER_AXIS_VERTICAL_SCROLL,
> +                                 -1 * event->value * 
> DEFAULT_AXIS_STEP_DISTANCE);
> +                     break;
> +             default:
> +                     break;
> +             }
> +             break;
> +     case REL_HWHEEL:
> +             switch (event->value) {
> +             case -1:
> +                     /* Scroll left */
> +             case 1:
> +                     /* Scroll right */
> +                     notify_axis(device->seat,
> +                                 time,
> +                                 WL_POINTER_AXIS_HORIZONTAL_SCROLL,
> +                                 event->value * DEFAULT_AXIS_STEP_DISTANCE);
> +                     break;
> +             default:
> +                     break;
> +
> +             }
> +     }
> +}
> +
> +static inline void
> +mouse_process_key(struct mouse_dispatch *mouse,
> +         struct evdev_device *device,
> +         struct input_event *e,
> +         uint32_t time)
> +{
> +     if (e->value == 2)
> +             return;
> +
> +     switch (e->code) {
> +     case BTN_LEFT:
> +     case BTN_RIGHT:
> +     case BTN_MIDDLE:
> +     case BTN_SIDE:
> +     case BTN_EXTRA:
> +     case BTN_FORWARD:
> +     case BTN_BACK:
> +     case BTN_TASK:
> +             notify_button(device->seat,
> +                           time, e->code,
> +                           e->value ? WL_POINTER_BUTTON_STATE_PRESSED :
> +                                      WL_POINTER_BUTTON_STATE_RELEASED);
> +             break;
> +
> +     default:
> +             notify_key(device->seat,
> +                        time, e->code,
> +                        e->value ? WL_KEYBOARD_KEY_STATE_PRESSED :
> +                                   WL_KEYBOARD_KEY_STATE_RELEASED,
> +                        STATE_UPDATE_AUTOMATIC);
> +             break;
> +     }
> +}
> +
> +static void
> +mouse_sampler_reset(struct mouse_dispatch *m)
> +{
> +     m->actual_acceleration_factor = m->tune_constant_coefficient *
> +                                     m->tune_min_multiplier;
> +     m->motion_sample.dx = 0;
> +     m->motion_sample.dy = 0;
> +     m->last_velocity = 0.0;
> +     m->last_computed_acc = 0.0;
> +}
> +
> +static void
> +mouse_sampler_advance_period(struct mouse_dispatch *m, uint32_t time)
> +{
> +     const unsigned long int period = MOUSE_MOTION_SAMPLING_PERIOD;
> +     const uint32_t uint32max = ~0;
> +
> +     m->motion_period_id = time / period;
> +     m->motion_period_next = (uint32max & (time + period)) / period;
> +}
> +
> +static double
> +mouse_compute_acceleration(struct mouse_dispatch *m, double vel)
> +{
> +     double computed;
> +     computed = m->tune_acceleration_profile(m->tune_min_multiplier, vel,
> +                                             m->tune_acceleration_treshold,
> +                                             m->tune_acc_multiplier);
> +
> +     if (computed < m->tune_min_multiplier)
> +             return m->tune_min_multiplier;
> +
> +     return computed;
> +}
> +
> +static void
> +mouse_sampler_apply_acceleration_mult(struct mouse_dispatch *m, uint32_t 
> time)
> +{
> +     struct mouse_motion *sample = &m->motion_sample;
> +
> +     const unsigned long int period = MOUSE_MOTION_SAMPLING_PERIOD;
> +
> +     unsigned long int distance_sq;
> +     double velocity;
> +     double velocity_avg;
> +     double comp_acc;
> +     double comp_acc_avg;
> +     double mult;
> +
> +     distance_sq = sample->dx * sample->dx + sample->dy * sample->dy;
> +
> +     velocity = sqrtl(distance_sq) / period;
> +     velocity_avg = (velocity + m->last_velocity) / 2.0;
> +
> +     comp_acc = mouse_compute_acceleration(m, velocity);
> +     comp_acc_avg = mouse_compute_acceleration(m, velocity_avg);
> +
> +     /* Use Simpson's rule to calculate the avarage acceleration between
> +      * the previous motion and the most recent. */
> +     mult = (comp_acc + 4.0 * comp_acc_avg + m->last_computed_acc) * 0.16666;
> +
> +     m->last_velocity = velocity;
> +     m->last_computed_acc = comp_acc;
> +     m->actual_acceleration_factor = m->tune_constant_coefficient * mult;
> +     sample->dx = 0;
> +     sample->dy = 0;
> +}
> +
> +static void
> +mouse_sampler_refresh(struct mouse_dispatch *m, uint32_t time)
> +{
> +     uint32_t this_period_id = time / MOUSE_MOTION_SAMPLING_PERIOD;
> +
> +     if (this_period_id != m->motion_period_id) {
> +             if (this_period_id == m->motion_period_next) {
> +                     mouse_sampler_apply_acceleration_mult(m, time);
> +             } else {
> +                     mouse_sampler_reset(m);
> +             }
> +             mouse_sampler_advance_period(m, time);
> +     }
> +}
> +
> +static void
> +mouse_process(struct evdev_dispatch *dispatch,
> +              struct evdev_device *device,
> +              struct input_event *event,
> +              uint32_t time)
> +{
> +     struct mouse_dispatch *mouse =
> +             (struct mouse_dispatch *) dispatch;
> +
> +     switch (event->type) {
> +     case EV_REL:
> +             mouse_process_relative(mouse, device, event, time);
> +             break;
> +     case EV_KEY:
> +             mouse_process_key(mouse, device, event, time);
> +             break;
> +     case EV_SYN:
> +             device->pending_events |= EVDEV_SYN;
> +
> +             if (mouse->tune_acceleration_profile != no_profile)
> +                     mouse_sampler_refresh(mouse, time);
> +             break;
> +     }
> +}
> +
> +static void
> +mouse_destroy(struct evdev_dispatch *dispatch)
> +{
> +     struct mouse_dispatch *mouse =
> +             (struct mouse_dispatch *) dispatch;
> +
> +     free(mouse);
> +}
> +
> +struct evdev_dispatch_interface mouse_interface = {
> +     mouse_process,
> +     mouse_destroy
> +};
> +
> +static int
> +mouse_init(struct mouse_dispatch *mouse, struct evdev_device *device)
> +{
> +     mouse->base.interface = &mouse_interface;
> +     mouse->device = device;
> +
> +     /* Configure mouse speed */
> +     mouse->tune_constant_coefficient = 1.0;
> +
> +     /* Configure mouse acceleration */
> +     mouse->tune_acceleration_profile = accel_profiles[2];
> +     mouse->tune_acceleration_treshold = 1.5;
> +     mouse->tune_min_multiplier = 1.0;
> +     mouse->tune_acc_multiplier = 3.0;
> +
> +     /* Prepare motion sampling */
> +     mouse_sampler_reset(mouse);
> +     mouse_sampler_advance_period(mouse, 0);
> +
> +     return 0;
> +}
> +
> +struct evdev_dispatch *
> +evdev_mouse_create(struct evdev_device *device)
> +{
> +     struct mouse_dispatch *mouse;
> +
> +     mouse = malloc(sizeof *mouse);
> +     if (mouse == NULL)
> +             return NULL;
> +
> +     if (mouse_init(mouse, device) != 0) {
> +             free(mouse);
> +             return NULL;
> +     }
> +
> +     return &mouse->base;
> +}
> diff --git a/src/evdev.c b/src/evdev.c
> index d2954b5..bbad5ad 100644
> --- a/src/evdev.c
> +++ b/src/evdev.c
> @@ -459,6 +459,9 @@ evdev_handle_device(struct evdev_device *device)
>                   !TEST_BIT(key_bits, BTN_TOOL_PEN) &&
>                   has_abs)
>                       device->dispatch = evdev_touchpad_create(device);
> +             else if (TEST_BIT(key_bits, BTN_LEFT) && !has_abs)
> +                     device->dispatch = evdev_mouse_create(device);
> +
>               for (i = KEY_ESC; i < KEY_MAX; i++) {
>                       if (i >= BTN_MISC && i < KEY_OK)
>                               continue;
> diff --git a/src/evdev.h b/src/evdev.h
> index eb5c868..f670682 100644
> --- a/src/evdev.h
> +++ b/src/evdev.h
> @@ -111,6 +111,9 @@ struct evdev_dispatch {
>  struct evdev_dispatch *
>  evdev_touchpad_create(struct evdev_device *device);
>  
> +struct evdev_dispatch *
> +evdev_mouse_create(struct evdev_device *device);
> +
>  void
>  evdev_led_update(struct evdev_device *device, enum weston_led leds);
>  
> -- 
> 1.7.10.4
> 

> _______________________________________________
> wayland-devel mailing list
> [email protected]
> http://lists.freedesktop.org/mailman/listinfo/wayland-devel

_______________________________________________
wayland-devel mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/wayland-devel

Reply via email to