diff --git a/doc/Makefile.am b/doc/Makefile.am index c9dccfda..a8d41822 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -18,6 +18,7 @@ header_files = \ $(srcdir)/normalization-of-relative-motion.dox \ $(srcdir)/palm-detection.dox \ $(srcdir)/page-hierarchy.dox \ + $(srcdir)/pointer-acceleration.dox \ $(srcdir)/reporting-bugs.dox \ $(srcdir)/scrolling.dox \ $(srcdir)/seats.dox \ @@ -39,6 +40,10 @@ diagram_files = \ $(srcdir)/svg/edge-scrolling.svg \ $(srcdir)/svg/palm-detection.svg \ $(srcdir)/svg/pinch-gestures.svg \ + $(srcdir)/svg/ptraccel-linear.svg \ + $(srcdir)/svg/ptraccel-low-dpi.svg \ + $(srcdir)/svg/ptraccel-touchpad.svg \ + $(srcdir)/svg/ptraccel-trackpoint.svg \ $(srcdir)/svg/swipe-gestures.svg \ $(srcdir)/svg/tap-n-drag.svg \ $(srcdir)/svg/thumb-detection.svg \ diff --git a/doc/page-hierarchy.dox b/doc/page-hierarchy.dox index 1e82535f..3fdb1f72 100644 --- a/doc/page-hierarchy.dox +++ b/doc/page-hierarchy.dox @@ -31,5 +31,6 @@ - @subpage test-suite - @subpage tools +- @subpage pointer-acceleration */ diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox new file mode 100644 index 00000000..a372e154 --- /dev/null +++ b/doc/pointer-acceleration.dox @@ -0,0 +1,110 @@ +/** +@page pointer-acceleration Pointer acceleration + +libinput uses device-specific pointer acceleration methods, with the default +being the @ref ptraccel-linear. The methods share common properties, such as +@ref ptraccel-velocity. + +This page explains the high-level concepts used in the code. It aims to +provide an overview for developers and is not necessarily useful for +users. + +@section ptraccel-velocity Velocity calculation + +The device's speed of movement is measured across multiple input events +through so-called "trackers". Each event prepends a the tracker item, each +subsequent tracker contains the delta of that item to the current position, +the timestamp of the event that created it and the cardinal direction of the +movement at the time. If a device moves into the same direction, the +velocity is calculated across multiple trackers. For example, if a device +moves steadily for 10 events to the left, the velocity is calculated across +all 10 events. + +Whenever the movement changes direction or significantly changes speed, the +velocity is calculated from the direction/speed change only. For example, if +a device moves steadily for 8 events to the left and then 2 events to the +right, the velocity is only that of the last 2 events. + +An extra time limit prevents events that are too old to factor into the +velocity calculation. For example, if a device moves steadily for 5 events +to the left, then pauses, then moves again for 5 events to the left, only +the last 5 events are used for velocity calculation. + +The velocity is then used to calculate the acceleration factor + +@section ptraccel-factor Acceleration factor + +The acceleration factor is the final outcome of the pointer acceleration +calculations. It is a unitless factor that is applied to the current delta, +a factor of 2 doubles the delta (i.e. speeds up the movement), a factor of +less than 1 reduces the delta (i.e. slows the movement). + +Any factor less than 1 requires the user to move the device further to move +the visible pointer. This is called deceleration and enables high precision +target selection through subpixel movements. libinput's current maximum +deceleration factor is 0.3 (i.e. slow down to 30% of the pointer speed). + +A factor higher than 1 moves the pointer further than the physical device +moves. This is acceleration and allows a user to cross the screen quickly +but effectively skips pixels. libinput's current maximum acceleration factor +is 3.5. + +@section ptraccel-linear Linear pointer acceleration + +The linear pointer acceleration method is the default for most pointer +devices. It provides deceleration at very slow movements, a 1:1 mapping for +regular movements and a linear increase to the maximum acceleration factor +for fast movements. + +Linear pointer acceleration applies to devices with above 1000dpi resolution +and after @ref motion_normalization is applied. + +@image html ptraccel-linear.svg "Linear pointer acceleration" + +The image above shows the linear pointer acceleration settings at various +speeds. The line for 0.0 is the default acceleration curve, speed settings +above 0.0 accelerate sooner, faster and to a higher maximum acceleration. +Speed settings below 0 delay when acceleration kicks in, how soon the +maximum acceleration is reached and the maximum acceleration factor. + +Extremely low speed settings provide no acceleration and additionally +decelerate all movement by a constant factor. + +@section ptraccel-low-dpi Pointer acceleration for low-dpi devices + +Low-dpi devices are those with a physical resolution of less than 1000 dots +per inch (dpi). The pointer acceleration is adjusted to provide roughly the +same feel for all devices at normal to high speeds. At slow speeds, the +pointer acceleration works on device-units rather than normalized +coordinates (see @ref motion_normalization). + +@image html ptraccel-low-dpi.svg "Pointer acceleration for low-dpi devices" + +The image above shows the default pointer acceleration curve for a speed of +0.0 at different DPI settings. A device with low DPI has the acceleration +applied sooner and with a stronger acceleration factor. + +@section ptraccel-touchpad Pointer acceleration on touchpads + +Touchpad pointer acceleration uses the @ref ptraccel-linear profile, with a +constant deceleration factor applied. The user expectation of how much a +pointer should move in response to finger movement is different to that of a +mouse device, hence the constant deceleration factor. + +@image html ptraccel-touchpad.svg "Pointer acceleration curve for touchpads" + +The image above shows the touchpad acceleration profile in comparison to the +@ref ptraccel-linear. The shape of the curve is identical but vertically squashed. + +@section ptraccel-trackpoint Pointer acceleration on trackpoints + +Trackpoint pointer acceleration uses the @ref ptraccel-low-dpi profile, with a +constant deceleration factor taking the place of the DPI settings. + +@image html ptraccel-trackpoint.svg "Pointer acceleration curves for trackpoints" + +The image above shows the trackpoint acceleration profile in comparison to the +@ref ptraccel-linear. The constant acceleration factor, usually applied by +udev, shapes the acceleration profile. + +*/ diff --git a/doc/svg/ptraccel-linear.svg b/doc/svg/ptraccel-linear.svg new file mode 100644 index 00000000..cb575f21 --- /dev/null +++ b/doc/svg/ptraccel-linear.svg @@ -0,0 +1,5486 @@ + + + + +Gnuplot +Produced by GNUPLOT 5.0 patchlevel 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 0.5 + + + + + 1 + + + + + 1.5 + + + + + 2 + + + + + 2.5 + + + + + 3 + + + + + 0 + + + + + 0.0005 + + + + + 0.001 + + + + + 0.0015 + + + + + 0.002 + + + + + 0.0025 + + + + + 0.003 + + + + + + + + + accel factor + + + + + speed in units/us + + + + + -1.0 + + + -1.0 + + + + + + -0.75 + + + -0.75 + + + + + + -0.5 + + + -0.5 + + + + + + -0.25 + + + -0.25 + + + + + + 0.0 + + + 0.0 + + + + + + 0.5 + + + 0.5 + + + + + + 1.0 + + + 1.0 + + + + + + + + + + + + + + + + + + diff --git a/doc/svg/ptraccel-low-dpi.svg b/doc/svg/ptraccel-low-dpi.svg new file mode 100644 index 00000000..e31eaa1a --- /dev/null +++ b/doc/svg/ptraccel-low-dpi.svg @@ -0,0 +1,3748 @@ + + + + +Gnuplot +Produced by GNUPLOT 5.0 patchlevel 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 0 + + + + + 0.0005 + + + + + 0.001 + + + + + 0.0015 + + + + + 0.002 + + + + + 0.0025 + + + + + 0.003 + + + + + + + + + accel factor + + + + + speed in units/us + + + + + 200dpi + + + 200dpi + + + + + + 400dpi + + + 400dpi + + + + + + 800dpi + + + 800dpi + + + + + + 1000dpi + + + 1000dpi + + + + + + + + + + + + + + + + + + diff --git a/doc/svg/ptraccel-touchpad.svg b/doc/svg/ptraccel-touchpad.svg new file mode 100644 index 00000000..1befa1e5 --- /dev/null +++ b/doc/svg/ptraccel-touchpad.svg @@ -0,0 +1,1723 @@ + + + + +Gnuplot +Produced by GNUPLOT 5.0 patchlevel 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 0.5 + + + + + 1 + + + + + 1.5 + + + + + 2 + + + + + 2.5 + + + + + 3 + + + + + 0 + + + + + 0.0005 + + + + + 0.001 + + + + + 0.0015 + + + + + 0.002 + + + + + 0.0025 + + + + + 0.003 + + + + + + + + + accel factor + + + + + speed in units/us + + + + + linear (mouse) + + + linear (mouse) + + + + + + touchpad + + + touchpad + + + + + + + + + + + + + + + + + + diff --git a/doc/svg/ptraccel-trackpoint.svg b/doc/svg/ptraccel-trackpoint.svg new file mode 100644 index 00000000..79589c24 --- /dev/null +++ b/doc/svg/ptraccel-trackpoint.svg @@ -0,0 +1,3689 @@ + + + + +Gnuplot +Produced by GNUPLOT 5.0 patchlevel 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 0 + + + + + 0.0005 + + + + + 0.001 + + + + + 0.0015 + + + + + 0.002 + + + + + 0.0025 + + + + + 0.003 + + + + + + + + + accel factor + + + + + speed in units/us + + + + + linear (mouse) + + + linear (mouse) + + + + + + const accel 1 + + + const accel 1 + + + + + + const accel 2 + + + const accel 2 + + + + + + const accel 3 + + + const accel 3 + + + + + + + + + + + + + + + + + + diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 872da989..a32a771c 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -1510,7 +1510,7 @@ static int tp_init_accel(struct tp_dispatch *tp, double diagonal) { int res_x, res_y; - accel_profile_func_t profile; + struct motion_filter *filter; res_x = tp->device->abs.absinfo_x->resolution; res_y = tp->device->abs.absinfo_y->resolution; @@ -1526,14 +1526,14 @@ tp_init_accel(struct tp_dispatch *tp, double diagonal) tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y; if (tp->device->model_flags & EVDEV_MODEL_LENOVO_X230) - profile = touchpad_lenovo_x230_accel_profile; + filter = create_pointer_accelerator_filter_lenovo_x230(tp->device->dpi); else - profile = touchpad_accel_profile_linear; + filter = create_pointer_accelerator_filter_touchpad(tp->device->dpi); - if (evdev_device_init_pointer_acceleration(tp->device, profile) == -1) + if (!filter) return -1; - return 0; + return evdev_device_init_pointer_acceleration(tp->device, filter); } static uint32_t diff --git a/src/evdev.c b/src/evdev.c index 225c3acc..9414d9d6 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1408,12 +1408,9 @@ evdev_accel_config_get_default_speed(struct libinput_device *device) int evdev_device_init_pointer_acceleration(struct evdev_device *device, - accel_profile_func_t profile) + struct motion_filter *filter) { - device->pointer.filter = create_pointer_accelerator_filter(profile, - device->dpi); - if (!device->pointer.filter) - return -1; + device->pointer.filter = filter; device->pointer.config.available = evdev_accel_config_available; device->pointer.config.set_speed = evdev_accel_config_set_speed; @@ -1862,14 +1859,19 @@ evdev_configure_mt_device(struct evdev_device *device) static inline int evdev_init_accel(struct evdev_device *device) { - accel_profile_func_t profile; + struct motion_filter *filter; - if (device->dpi < DEFAULT_MOUSE_DPI) - profile = pointer_accel_profile_linear_low_dpi; + if (device->tags & EVDEV_TAG_TRACKPOINT) + filter = create_pointer_accelerator_filter_trackpoint(device->dpi); + else if (device->dpi < DEFAULT_MOUSE_DPI) + filter = create_pointer_accelerator_filter_linear_low_dpi(device->dpi); else - profile = pointer_accel_profile_linear; + filter = create_pointer_accelerator_filter_linear(device->dpi); - return evdev_device_init_pointer_acceleration(device, profile); + if (!filter) + return -1; + + return evdev_device_init_pointer_acceleration(device, filter); } static int diff --git a/src/evdev.h b/src/evdev.h index c951671e..9f026b89 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -281,7 +281,7 @@ evdev_device_create(struct libinput_seat *seat, int evdev_device_init_pointer_acceleration(struct evdev_device *device, - accel_profile_func_t profile); + struct motion_filter *filter); struct evdev_dispatch * evdev_touchpad_create(struct evdev_device *device); diff --git a/src/filter-private.h b/src/filter-private.h index 0e7afa1f..f5e8b7f7 100644 --- a/src/filter-private.h +++ b/src/filter-private.h @@ -38,11 +38,11 @@ struct motion_filter_interface { uint64_t time); void (*destroy)(struct motion_filter *filter); bool (*set_speed)(struct motion_filter *filter, - double speed); + double speed_adjustment); }; struct motion_filter { - double speed; /* normalized [-1, 1] */ + double speed_adjustment; /* normalized [-1, 1] */ struct motion_filter_interface *interface; }; diff --git a/src/filter.c b/src/filter.c index e11d58a9..7054fafa 100644 --- a/src/filter.c +++ b/src/filter.c @@ -36,6 +36,20 @@ #include "libinput-util.h" #include "filter-private.h" +/* Convert speed/velocity from units/us to units/ms */ +static inline double +v_us2ms(double units_per_us) +{ + return units_per_us * 1000.0; +} + +/* Convert speed/velocity from units/ms to units/us */ +static inline double +v_ms2us(double units_per_ms) +{ + return units_per_ms/1000.0; +} + struct normalized_coords filter_dispatch(struct motion_filter *filter, const struct normalized_coords *unaccelerated, @@ -62,31 +76,36 @@ filter_destroy(struct motion_filter *filter) bool filter_set_speed(struct motion_filter *filter, - double speed) + double speed_adjustment) { - return filter->interface->set_speed(filter, speed); + return filter->interface->set_speed(filter, speed_adjustment); } double filter_get_speed(struct motion_filter *filter) { - return filter->speed; + return filter->speed_adjustment; } /* * Default parameters for pointer acceleration profiles. */ -#define DEFAULT_THRESHOLD 0.4 /* in units/ms */ -#define MINIMUM_THRESHOLD 0.2 /* in units/ms */ +#define DEFAULT_THRESHOLD v_ms2us(0.4) /* in units/us */ +#define MINIMUM_THRESHOLD v_ms2us(0.2) /* in units/us */ #define DEFAULT_ACCELERATION 2.0 /* unitless factor */ #define DEFAULT_INCLINE 1.1 /* unitless factor */ +/* for the Lenovo x230 custom accel. do not touch */ +#define X230_THRESHOLD v_ms2us(0.4) /* in units/us */ +#define X230_ACCELERATION 2.0 /* unitless factor */ +#define X230_INCLINE 1.1 /* unitless factor */ + /* * Pointer acceleration filter constants */ -#define MAX_VELOCITY_DIFF 1 /* units/ms */ +#define MAX_VELOCITY_DIFF v_ms2us(1) /* units/us */ #define MOTION_TIMEOUT ms2us(1000) #define NUM_POINTER_TRACKERS 16 @@ -96,20 +115,18 @@ struct pointer_tracker { int dir; }; -struct pointer_accelerator; struct pointer_accelerator { struct motion_filter base; accel_profile_func_t profile; - double velocity; /* units/ms */ - double last_velocity; /* units/ms */ - struct normalized_coords last; + double velocity; /* units/us */ + double last_velocity; /* units/us */ struct pointer_tracker *trackers; int cur_tracker; - double threshold; /* units/ms */ + double threshold; /* units/us */ double accel; /* unitless factor */ double incline; /* incline of the function */ @@ -151,7 +168,7 @@ static double calculate_tracker_velocity(struct pointer_tracker *tracker, uint64_t time) { double tdelta = time - tracker->time + 1; - return normalized_length(tracker->delta) / tdelta * 1000.0; /* units/ms */ + return normalized_length(tracker->delta) / tdelta; /* units/us */ } static inline double @@ -221,7 +238,7 @@ calculate_velocity(struct pointer_accelerator *accel, uint64_t time) } } - return result; /* units/ms */ + return result; /* units/us */ } static double @@ -254,14 +271,55 @@ calculate_acceleration(struct pointer_accelerator *accel, return factor; /* unitless factor */ } +static inline double +calculate_acceleration_factor(struct pointer_accelerator *accel, + const struct normalized_coords *unaccelerated, + void *data, + uint64_t time) +{ + double velocity; /* units/us */ + double accel_factor; + + feed_trackers(accel, unaccelerated, time); + velocity = calculate_velocity(accel, time); + accel_factor = calculate_acceleration(accel, + data, + velocity, + accel->last_velocity, + time); + accel->last_velocity = velocity; + + return accel_factor; +} + static struct normalized_coords accelerator_filter(struct motion_filter *filter, const struct normalized_coords *unaccelerated, - void *data, uint64_t time /* in us */) + void *data, uint64_t time) +{ + struct pointer_accelerator *accel = + (struct pointer_accelerator *) filter; + double accel_value; /* unitless factor */ + struct normalized_coords accelerated; + + accel_value = calculate_acceleration_factor(accel, + unaccelerated, + data, + time); + + accelerated.x = accel_value * unaccelerated->x; + accelerated.y = accel_value * unaccelerated->y; + + return accelerated; +} + +static struct normalized_coords +accelerator_filter_low_dpi(struct motion_filter *filter, + const struct normalized_coords *unaccelerated, + void *data, uint64_t time) { struct pointer_accelerator *accel = (struct pointer_accelerator *) filter; - double velocity; /* units/ms */ double accel_value; /* unitless factor */ struct normalized_coords accelerated; struct normalized_coords unnormalized; @@ -273,21 +331,70 @@ accelerator_filter(struct motion_filter *filter, unnormalized.x = unaccelerated->x * dpi_factor; unnormalized.y = unaccelerated->y * dpi_factor; - feed_trackers(accel, &unnormalized, time); - velocity = calculate_velocity(accel, time); - accel_value = calculate_acceleration(accel, - data, - velocity, - accel->last_velocity, - time); + accel_value = calculate_acceleration_factor(accel, + &unnormalized, + data, + time); accelerated.x = accel_value * unnormalized.x; accelerated.y = accel_value * unnormalized.y; - accel->last = unnormalized; + return accelerated; +} +static struct normalized_coords +accelerator_filter_trackpoint(struct motion_filter *filter, + const struct normalized_coords *unaccelerated, + void *data, uint64_t time) +{ + struct pointer_accelerator *accel = + (struct pointer_accelerator *) filter; + double accel_value; /* unitless factor */ + struct normalized_coords accelerated; + struct normalized_coords unnormalized; + double dpi_factor = accel->dpi_factor; + + /* trackpoints with a dpi factor have a const accel set, remove that + * and restore device units. The accel profile takes const accel + * into account */ + dpi_factor = min(1.0, dpi_factor); + unnormalized.x = unaccelerated->x * dpi_factor; + unnormalized.y = unaccelerated->y * dpi_factor; + + accel_value = calculate_acceleration_factor(accel, + &unnormalized, + data, + time); + + accelerated.x = accel_value * unnormalized.x; + accelerated.y = accel_value * unnormalized.y; + + return accelerated; +} + +static struct normalized_coords +accelerator_filter_x230(struct motion_filter *filter, + const struct normalized_coords *unaccelerated, + void *data, uint64_t time) +{ + struct pointer_accelerator *accel = + (struct pointer_accelerator *) filter; + double accel_factor; /* unitless factor */ + struct normalized_coords accelerated; + double velocity; /* units/us */ + + feed_trackers(accel, unaccelerated, time); + velocity = calculate_velocity(accel, time); + accel_factor = calculate_acceleration(accel, + data, + velocity, + accel->last_velocity, + time); accel->last_velocity = velocity; + accelerated.x = accel_factor * unaccelerated->x; + accelerated.y = accel_factor * unaccelerated->y; + return accelerated; } @@ -326,65 +433,32 @@ accelerator_destroy(struct motion_filter *filter) static bool accelerator_set_speed(struct motion_filter *filter, - double speed) + double speed_adjustment) { struct pointer_accelerator *accel_filter = (struct pointer_accelerator *)filter; - assert(speed >= -1.0 && speed <= 1.0); + assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0); + + /* Note: the numbers below are nothing but trial-and-error magic, + don't read more into them other than "they mostly worked ok" */ /* delay when accel kicks in */ - accel_filter->threshold = DEFAULT_THRESHOLD - speed / 4.0; + accel_filter->threshold = DEFAULT_THRESHOLD - + v_ms2us(0.25) * speed_adjustment; if (accel_filter->threshold < MINIMUM_THRESHOLD) accel_filter->threshold = MINIMUM_THRESHOLD; /* adjust max accel factor */ - accel_filter->accel = DEFAULT_ACCELERATION + speed * 1.5; + accel_filter->accel = DEFAULT_ACCELERATION + speed_adjustment * 1.5; /* higher speed -> faster to reach max */ - accel_filter->incline = DEFAULT_INCLINE + speed * 0.75; + accel_filter->incline = DEFAULT_INCLINE + speed_adjustment * 0.75; - filter->speed = speed; + filter->speed_adjustment = speed_adjustment; return true; } -struct motion_filter_interface accelerator_interface = { - accelerator_filter, - accelerator_restart, - accelerator_destroy, - accelerator_set_speed, -}; - -struct motion_filter * -create_pointer_accelerator_filter(accel_profile_func_t profile, - int dpi) -{ - struct pointer_accelerator *filter; - - filter = zalloc(sizeof *filter); - if (filter == NULL) - return NULL; - - filter->base.interface = &accelerator_interface; - - filter->profile = profile; - filter->last_velocity = 0.0; - filter->last.x = 0; - filter->last.y = 0; - - filter->trackers = - calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers); - filter->cur_tracker = 0; - - filter->threshold = DEFAULT_THRESHOLD; - filter->accel = DEFAULT_ACCELERATION; - filter->incline = DEFAULT_INCLINE; - - filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_DPI; - - return &filter->base; -} - /** * Custom acceleration function for mice < 1000dpi. * At slow motion, a single device unit causes a one-pixel movement. @@ -398,25 +472,32 @@ create_pointer_accelerator_filter(accel_profile_func_t profile, double pointer_accel_profile_linear_low_dpi(struct motion_filter *filter, void *data, - double speed_in, /* in device units (units/ms) */ - uint64_t time /* in us */) + double speed_in, /* in device units (units/us) */ + uint64_t time) { struct pointer_accelerator *accel_filter = (struct pointer_accelerator *)filter; - double s1, s2; double max_accel = accel_filter->accel; /* unitless factor */ - const double threshold = accel_filter->threshold; /* units/ms */ + double threshold = accel_filter->threshold; /* units/us */ const double incline = accel_filter->incline; - double factor; + double factor; /* unitless */ double dpi_factor = accel_filter->dpi_factor; + /* dpi_factor is always < 1.0, increase max_accel, reduce + the threshold so it kicks in earlier */ max_accel /= dpi_factor; + threshold *= dpi_factor; - s1 = min(1, 0.3 + speed_in * 10.0); - s2 = 1 + (speed_in - threshold * dpi_factor) * incline; + /* see pointer_accel_profile_linear for a long description */ + if (v_us2ms(speed_in) < 0.07) + factor = 10 * v_us2ms(speed_in) + 0.3; + else if (speed_in < threshold) + factor = 1; + else + factor = incline * v_us2ms(speed_in - threshold) + 1; - factor = min(max_accel, s2 > 1 ? s2 : s1); + factor = min(max_accel, factor); return factor; } @@ -425,54 +506,104 @@ double pointer_accel_profile_linear(struct motion_filter *filter, void *data, double speed_in, /* 1000-dpi normalized */ - uint64_t time /* in us */) + uint64_t time) { struct pointer_accelerator *accel_filter = (struct pointer_accelerator *)filter; - - double s1, s2; const double max_accel = accel_filter->accel; /* unitless factor */ - const double threshold = accel_filter->threshold; /* units/ms */ + const double threshold = accel_filter->threshold; /* units/us */ const double incline = accel_filter->incline; - double factor; + double factor; /* unitless */ - s1 = min(1, 0.3 + speed_in * 10); - s2 = 1 + (speed_in - threshold) * incline; + /* + Our acceleration function calculates a factor to accelerate input + deltas with. The function is a double incline with a plateau, + with a rough shape like this: - factor = min(max_accel, s2 > 1 ? s2 : s1); + accel + factor + ^ + | / + | _____/ + | / + |/ + +-------------> speed in + + The two inclines are linear functions in the form + y = ax + b + where y is speed_out + x is speed_in + a is the incline of acceleration + b is minimum acceleration factor + + for speeds up to 0.07 u/ms, we decelerate, down to 30% of input + speed. + hence 1 = a * 0.07 + 0.3 + 0.3 = a * 0.00 + 0.3 => a := 10 + deceleration function is thus: + y = 10x + 0.3 + + Note: + * 0.07u/ms as threshold is a result of trial-and-error and + has no other intrinsic meaning. + * 0.3 is chosen simply because it is above the Nyquist frequency + for subpixel motion within a pixel. + */ + if (v_us2ms(speed_in) < 0.07) { + factor = 10 * v_us2ms(speed_in) + 0.3; + /* up to the threshold, we keep factor 1, i.e. 1:1 movement */ + } else if (speed_in < threshold) { + factor = 1; + + } else { + /* Acceleration function above the threshold: + y = ax' + b + where T is threshold + x is speed_in + x' is speed + and + y(T) == 1 + hence 1 = ax' + 1 + => x' := (x - T) + */ + factor = incline * v_us2ms(speed_in - threshold) + 1; + } + + /* Cap at the maximum acceleration factor */ + factor = min(max_accel, factor); return factor; } double touchpad_accel_profile_linear(struct motion_filter *filter, - void *data, - double speed_in, - uint64_t time /* in us */) + void *data, + double speed_in, /* units/us */ + uint64_t time) { /* Once normalized, touchpads see the same acceleration as mice. that is technically correct but subjectively wrong, we expect a touchpad to be a lot slower than a mouse. Apply a magic factor here and proceed as normal. */ - const double TP_MAGIC_SLOWDOWN = 0.4; - double speed_out; + const double TP_MAGIC_SLOWDOWN = 0.4; /* unitless */ + double factor; /* unitless */ speed_in *= TP_MAGIC_SLOWDOWN; - speed_out = pointer_accel_profile_linear(filter, data, speed_in, time); + factor = pointer_accel_profile_linear(filter, data, speed_in, time); - return speed_out * TP_MAGIC_SLOWDOWN; + return factor * TP_MAGIC_SLOWDOWN; } double touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, void *data, double speed_in, - uint64_t time /* in us */) + uint64_t time) { /* Keep the magic factor from touchpad_accel_profile_linear. */ - const double TP_MAGIC_SLOWDOWN = 0.4; + const double TP_MAGIC_SLOWDOWN = 0.4; /* unitless */ /* Those touchpads presents an actual lower resolution that what is * advertised. We see some jumps from the cursor due to the big steps @@ -480,24 +611,207 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, * Apply a factor to minimize those jumps at low speed, and try * keeping the same feeling as regular touchpads at high speed. * It still feels slower but it is usable at least */ - const double TP_MAGIC_LOW_RES_FACTOR = 4.0; - double speed_out; + const double TP_MAGIC_LOW_RES_FACTOR = 4.0; /* unitless */ + double factor; /* unitless */ struct pointer_accelerator *accel_filter = (struct pointer_accelerator *)filter; - double s1, s2; + double f1, f2; /* unitless */ const double max_accel = accel_filter->accel * TP_MAGIC_LOW_RES_FACTOR; /* unitless factor */ const double threshold = accel_filter->threshold / - TP_MAGIC_LOW_RES_FACTOR; /* units/ms */ + TP_MAGIC_LOW_RES_FACTOR; /* units/us */ const double incline = accel_filter->incline * TP_MAGIC_LOW_RES_FACTOR; + /* Note: the magic values in this function are obtained by + * trial-and-error. No other meaning should be interpreted. + * The calculation is a compressed form of + * pointer_accel_profile_linear(), look at the git history of that + * function for an explaination of what the min/max/etc. does. + */ speed_in *= TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR; - s1 = min(1, speed_in * 5); - s2 = 1 + (speed_in - threshold) * incline; + f1 = min(1, v_us2ms(speed_in) * 5); + f2 = 1 + (v_us2ms(speed_in) - v_us2ms(threshold)) * incline; - speed_out = min(max_accel, s2 > 1 ? s2 : s1); + factor = min(max_accel, f2 > 1 ? f2 : f1); - return speed_out * TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR; + return factor * TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR; +} + +double +trackpoint_accel_profile(struct motion_filter *filter, + void *data, + double speed_in, /* 1000-dpi normalized */ + uint64_t time) +{ + struct pointer_accelerator *accel_filter = + (struct pointer_accelerator *)filter; + double max_accel = accel_filter->accel; /* unitless factor */ + double threshold = accel_filter->threshold; /* units/ms */ + const double incline = accel_filter->incline; + double factor; + double dpi_factor = accel_filter->dpi_factor; + + /* dpi_factor is always < 1.0, increase max_accel, reduce + the threshold so it kicks in earlier */ + max_accel /= dpi_factor; + threshold *= dpi_factor; + + /* see pointer_accel_profile_linear for a long description */ + if (v_us2ms(speed_in) < 0.07) + factor = 10 * v_us2ms(speed_in) + 0.3; + else if (speed_in < threshold) + factor = 1; + else + factor = incline * v_us2ms(speed_in - threshold) + 1; + + factor = min(max_accel, factor); + + return factor; +} + +struct motion_filter_interface accelerator_interface = { + accelerator_filter, + accelerator_restart, + accelerator_destroy, + accelerator_set_speed, +}; + +static struct pointer_accelerator * +create_default_filter(int dpi) +{ + struct pointer_accelerator *filter; + + filter = zalloc(sizeof *filter); + if (filter == NULL) + return NULL; + + filter->last_velocity = 0.0; + + filter->trackers = + calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers); + filter->cur_tracker = 0; + + filter->threshold = DEFAULT_THRESHOLD; + filter->accel = DEFAULT_ACCELERATION; + filter->incline = DEFAULT_INCLINE; + + filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_DPI; + + return filter; +} + +struct motion_filter * +create_pointer_accelerator_filter_linear(int dpi) +{ + struct pointer_accelerator *filter; + + filter = create_default_filter(dpi); + if (!filter) + return NULL; + + filter->base.interface = &accelerator_interface; + filter->profile = pointer_accel_profile_linear; + + return &filter->base; +} + +struct motion_filter_interface accelerator_interface_low_dpi = { + accelerator_filter_low_dpi, + accelerator_restart, + accelerator_destroy, + accelerator_set_speed, +}; + +struct motion_filter * +create_pointer_accelerator_filter_linear_low_dpi(int dpi) +{ + struct pointer_accelerator *filter; + + filter = create_default_filter(dpi); + if (!filter) + return NULL; + + filter->base.interface = &accelerator_interface_low_dpi; + filter->profile = pointer_accel_profile_linear_low_dpi; + + return &filter->base; +} + +struct motion_filter * +create_pointer_accelerator_filter_touchpad(int dpi) +{ + struct pointer_accelerator *filter; + + filter = create_default_filter(dpi); + if (!filter) + return NULL; + + filter->base.interface = &accelerator_interface; + filter->profile = touchpad_accel_profile_linear; + + return &filter->base; +} + +struct motion_filter_interface accelerator_interface_x230 = { + accelerator_filter_x230, + accelerator_restart, + accelerator_destroy, + accelerator_set_speed, +}; + +/* The Lenovo x230 has a bad touchpad. This accel method has been + * trial-and-error'd, any changes to it will require re-testing everything. + * Don't touch this. + */ +struct motion_filter * +create_pointer_accelerator_filter_lenovo_x230(int dpi) +{ + struct pointer_accelerator *filter; + + filter = zalloc(sizeof *filter); + if (filter == NULL) + return NULL; + + filter->base.interface = &accelerator_interface_x230; + filter->profile = touchpad_lenovo_x230_accel_profile; + filter->last_velocity = 0.0; + + filter->trackers = + calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers); + filter->cur_tracker = 0; + + filter->threshold = X230_THRESHOLD; + filter->accel = X230_ACCELERATION; /* unitless factor */ + filter->incline = X230_INCLINE; /* incline of the acceleration function */ + + filter->dpi_factor = 1; /* unused for this accel method */ + + return &filter->base; +} + +struct motion_filter_interface accelerator_interface_trackpoint = { + accelerator_filter_trackpoint, + accelerator_restart, + accelerator_destroy, + accelerator_set_speed, +}; + +struct motion_filter * +create_pointer_accelerator_filter_trackpoint(int dpi) +{ + struct pointer_accelerator *filter; + + filter = create_default_filter(dpi); + if (!filter) + return NULL; + + filter->base.interface = &accelerator_interface_trackpoint; + filter->profile = trackpoint_accel_profile; + filter->threshold = DEFAULT_THRESHOLD; + filter->accel = DEFAULT_ACCELERATION; + filter->incline = DEFAULT_INCLINE; + + return &filter->base; } diff --git a/src/filter.h b/src/filter.h index 617fab1f..fd36da49 100644 --- a/src/filter.h +++ b/src/filter.h @@ -57,9 +57,22 @@ typedef double (*accel_profile_func_t)(struct motion_filter *filter, double velocity, uint64_t time); +/* Pointer acceleration types */ + struct motion_filter * -create_pointer_accelerator_filter(accel_profile_func_t filter, - int dpi); +create_pointer_accelerator_filter_linear(int dpi); + +struct motion_filter * +create_pointer_accelerator_filter_linear_low_dpi(int dpi); + +struct motion_filter * +create_pointer_accelerator_filter_touchpad(int dpi); + +struct motion_filter * +create_pointer_accelerator_filter_lenovo_x230(int dpi); + +struct motion_filter * +create_pointer_accelerator_filter_trackpoint(int dpi); /* * Pointer acceleration profiles. @@ -85,4 +98,9 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, void *data, double speed_in, uint64_t time); +double +trackpoint_accel_profile(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time); #endif /* FILTER_H */ diff --git a/tools/Makefile.am b/tools/Makefile.am index 68e60cbb..8f72db99 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -39,3 +39,5 @@ event_gui_LDADD = ../src/libinput.la libshared.la $(CAIRO_LIBS) $(GTK_LIBS) $(LI event_gui_CFLAGS = $(CAIRO_CFLAGS) $(GTK_CFLAGS) $(LIBUDEV_CFLAGS) event_gui_LDFLAGS = -no-install endif + +EXTRA_DIST = make-ptraccel-graphs.sh diff --git a/tools/make-ptraccel-graphs.sh b/tools/make-ptraccel-graphs.sh new file mode 100755 index 00000000..901baf9c --- /dev/null +++ b/tools/make-ptraccel-graphs.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +tool=`dirname $0`/ptraccel-debug +gnuplot=/usr/bin/gnuplot + +outfile="ptraccel-linear" +for speed in -1 -0.75 -0.5 -0.25 0 0.5 1; do + $tool --mode=accel --dpi=1000 --filter=linear --speed=$speed > $outfile-$speed.gnuplot +done +$gnuplot < $outfile-$dpi.gnuplot +done + +$gnuplot < $outfile-mouse.gnuplot +$tool --mode=accel --dpi=1000 --filter=touchpad > $outfile-touchpad.gnuplot +$gnuplot < $outfile-mouse.gnuplot +for constaccel in 1 2 3; do + dpi=$((1000/$constaccel)) + $tool --mode=accel --dpi=$dpi --filter=trackpoint > $outfile-trackpoint-$constaccel.gnuplot +done +$gnuplot < ... in motion and delta modes only. Increase dx by step each round\n" "--speed= ... accel speed [-1, 1], default 0\n" "--dpi= ... device resolution in DPI (default: 1000)\n" + "--filter= \n" + " linear ... the default motion filter\n" + " low-dpi ... low-dpi filter, use --dpi with this argument\n" + " touchpad ... the touchpad motion filter\n" + " x230 ... custom filter for the Lenovo x230 touchpad\n" + " trackpoint... trackpoint motion filter\n" "\n" "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n" "and the arguments are interpreted as sequence of delta x coordinates\n" @@ -195,6 +198,8 @@ main(int argc, char **argv) double custom_deltas[1024]; double speed = 0.0; int dpi = 1000; + const char *filter_type = "linear"; + accel_profile_func_t profile = NULL; enum { OPT_MODE = 1, @@ -203,6 +208,7 @@ main(int argc, char **argv) OPT_STEP, OPT_SPEED, OPT_DPI, + OPT_FILTER, }; while (1) { @@ -215,6 +221,7 @@ main(int argc, char **argv) {"step", 1, 0, OPT_STEP }, {"speed", 1, 0, OPT_SPEED }, {"dpi", 1, 0, OPT_DPI }, + {"filter", 1, 0, OPT_FILTER}, {0, 0, 0, 0} }; @@ -265,6 +272,9 @@ main(int argc, char **argv) case OPT_DPI: dpi = strtod(optarg, NULL); break; + case OPT_FILTER: + filter_type = optarg; + break; default: usage(); exit(1); @@ -272,8 +282,26 @@ main(int argc, char **argv) } } - filter = create_pointer_accelerator_filter(pointer_accel_profile_linear, - dpi); + if (streq(filter_type, "linear")) { + filter = create_pointer_accelerator_filter_linear(dpi); + profile = pointer_accel_profile_linear; + } else if (streq(filter_type, "low-dpi")) { + filter = create_pointer_accelerator_filter_linear_low_dpi(dpi); + profile = pointer_accel_profile_linear_low_dpi; + } else if (streq(filter_type, "touchpad")) { + filter = create_pointer_accelerator_filter_touchpad(dpi); + profile = touchpad_accel_profile_linear; + } else if (streq(filter_type, "x230")) { + filter = create_pointer_accelerator_filter_lenovo_x230(dpi); + profile = touchpad_lenovo_x230_accel_profile; + } else if (streq(filter_type, "trackpoint")) { + filter = create_pointer_accelerator_filter_trackpoint(dpi); + profile = trackpoint_accel_profile; + } else { + fprintf(stderr, "Invalid filter type %s\n", filter_type); + return 1; + } + assert(filter != NULL); filter_set_speed(filter, speed); @@ -297,7 +325,7 @@ main(int argc, char **argv) } if (print_accel) - print_accel_func(filter); + print_accel_func(filter, profile); else if (print_delta) print_ptraccel_deltas(filter, step); else if (print_motion)