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
