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.

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

Reply via email to