Switch to a pure factor with a max scaled after a function. The offset is just 0 now (will be removed eventually). Both are determined with a function based on a linear/exponential regression of a sample set of data pairs.
Signed-off-by: Peter Hutterer <[email protected]> --- doc/pointer-acceleration.dox | 16 ++- src/evdev.c | 8 +- src/evdev.h | 1 + src/filter.c | 293 ++++++++++++++++++++++++++++++++++++------- src/filter.h | 5 +- tools/ptraccel-debug.c | 38 +++++- 6 files changed, 301 insertions(+), 60 deletions(-) diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox index f6237c6b..57be6cdc 100644 --- a/doc/pointer-acceleration.dox +++ b/doc/pointer-acceleration.dox @@ -109,9 +109,16 @@ The image above shows the touchpad acceleration profile in comparison to the @section ptraccel-trackpoint Pointer acceleration on trackpoints -Trackpoint hardware is quite varied in how it reacts to user pressure and -unlike other devices it cannot easily be normalized for physical properties. -Measuring pressure objectively across a variety of hardware is nontrivial. +The main difference between trackpoint hardware and mice or touchpads is +that trackpoint speed is a function of pressure rather than moving speed. +But trackpoint hardware is quite varied in how it reacts to user pressure +and unlike other devices it cannot easily be normalized for physical +properties. Measuring pressure objectively across a variety of hardware is +nontrivial. + +libinput's pointer acceleration is a function of the total available +pressure range on a device. + libinput relies on some sytem-wide configured properties, specifically the @ref udev_config. The two properties that influence trackpoint acceleration ````POINTINGSTICK_CONST_ACCEL```` which is a constant factor applied to the @@ -125,9 +132,6 @@ builtin is expected to apply this to the device, i.e. libinput does not handle this property. Once applied, the sensitivity adjusts the deltas coming out of the hardware. -Trackpoint pointer acceleration uses the @ref ptraccel-low-dpi profile, with a -constant acceleration 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 diff --git a/src/evdev.c b/src/evdev.c index 0d861190..e07d5f7e 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1990,7 +1990,7 @@ evdev_init_accel(struct evdev_device *device, if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) filter = create_pointer_accelerator_filter_flat(device->dpi); else if (device->tags & EVDEV_TAG_TRACKPOINT) - filter = create_pointer_accelerator_filter_trackpoint(device->dpi); + filter = create_pointer_accelerator_filter_trackpoint(device->trackpoint_range); else if (device->dpi < DEFAULT_MOUSE_DPI) filter = create_pointer_accelerator_filter_linear_low_dpi(device->dpi); else @@ -2213,6 +2213,10 @@ evdev_get_trackpoint_dpi(struct evdev_device *device) const char *trackpoint_accel; double accel = DEFAULT_TRACKPOINT_ACCEL; + /* + * parse the sensitivity property, and undo whatever it does. + */ + trackpoint_accel = udev_device_get_property_value( device->udev_device, "POINTINGSTICK_CONST_ACCEL"); if (trackpoint_accel) { @@ -2227,6 +2231,8 @@ evdev_get_trackpoint_dpi(struct evdev_device *device) evdev_log_info(device, "set to const accel %.2f\n", accel); } + device->trackpoint_range = 20; /* FIXME */ + return DEFAULT_MOUSE_DPI / accel; } diff --git a/src/evdev.h b/src/evdev.h index b891f906..6524adf0 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -158,6 +158,7 @@ struct evdev_device { bool is_mt; bool is_suspended; int dpi; /* HW resolution */ + int trackpoint_range; /* trackpoint max delta */ struct ratelimit syn_drop_limit; /* ratelimit for SYN_DROPPED logging */ struct ratelimit nonpointer_rel_limit; /* ratelimit for REL_* events from non-pointer devices */ uint32_t model_flags; diff --git a/src/filter.c b/src/filter.c index 65e83197..022a7040 100644 --- a/src/filter.c +++ b/src/filter.c @@ -147,6 +147,12 @@ filter_get_type(struct motion_filter *filter) #define X230_MAGIC_SLOWDOWN 0.4 /* unitless */ #define X230_TP_MAGIC_LOW_RES_FACTOR 4.0 /* unitless */ +/* Trackpoint acceleration */ +#define TRACKPOINT_DEFAULT_MAX_ACCEL 2.0 /* in units/us */ +#define TRACKPOINT_DEFAULT_MAX_DELTA 60 +/* As measured on a Lenovo T440 at kernel-default sensitivity 128 */ +#define TRACKPOINT_DEFAULT_RANGE 20 /* max value */ + /* * Pointer acceleration filter constants */ @@ -195,6 +201,20 @@ struct tablet_accelerator_flat { yres_scale; /* 1000dpi : tablet res */ }; +struct trackpoint_accelerator { + struct motion_filter base; + + struct device_float_coords history[4]; + size_t history_size; + + double scale_factor; + double max_accel; + double max_delta; + + double incline; /* incline of the function */ + double offset; /* offset of the function */ +}; + static void feed_trackers(struct pointer_accelerator *accel, const struct device_float_coords *delta, @@ -904,38 +924,6 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, return factor * X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR; } -double -trackpoint_accel_profile(struct motion_filter *filter, - void *data, - double speed_in, /* device units/µs */ - 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 dpi_factor = accel_filter->dpi/(double)DEFAULT_MOUSE_DPI; - double 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 = { .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, .filter = accelerator_filter_pre_normalized, @@ -1063,30 +1051,245 @@ create_pointer_accelerator_filter_lenovo_x230(int dpi) return &filter->base; } +double +trackpoint_accel_profile(struct motion_filter *filter, + void *data, + double delta) +{ + struct trackpoint_accelerator *accel_filter = + (struct trackpoint_accelerator *)filter; + const double max_accel = accel_filter->max_accel; + double factor; + + delta = fabs(delta); + + /* This is almost the equivalent of the xserver acceleration + at sensitivity 128 and speed 0.0 */ + factor = delta * accel_filter->incline + accel_filter->offset; + factor = min(factor, max_accel); + + return factor; +} + +/** + * Average the deltas, they are messy and can provide sequences like 7, 7, + * 9, 8, 14, 7, 9, 8 ... The outliers cause unpredictable jumps, so average + * them out. + */ +static inline struct device_float_coords +trackpoint_average_delta(struct trackpoint_accelerator *filter, + const struct device_float_coords *unaccelerated) +{ + size_t i; + struct device_float_coords avg; + + memmove(&filter->history[1], + &filter->history[0], + sizeof(*filter->history) * (filter->history_size - 1)); + filter->history[0] = *unaccelerated; + + for (i = 0; i < filter->history_size; i++) { + avg.x += filter->history[i].x; + avg.y += filter->history[i].y; + } + avg.x /= filter->history_size; + avg.y /= filter->history_size; + + return avg; +} + +/** + * Undo any system-wide magic scaling, so we're behaving the same regardless + * of the trackpoint hardware. This way we can apply our profile independent + * of any other configuration that messes with things. + */ +static inline struct device_float_coords +trackpoint_normalize_deltas(const struct trackpoint_accelerator *accel_filter, + const struct device_float_coords *delta) +{ + struct device_float_coords scaled = *delta; + + scaled.x *= accel_filter->scale_factor; + scaled.y *= accel_filter->scale_factor; + + return scaled; +} + +/** + * We set a max delta per event, to avoid extreme jumps once we exceed the + * expected pressure. Trackpoint hardware is inconsistent once the pressure + * gets high, so we can expect sequences like 30, 40, 35, 55, etc. This may + * be caused by difficulty keeping up high consistent pressures or just + * measuring errors in the hardware. Either way, we cap to a max delta so + * once we hit the high pressures, movement is capped and consistent. + */ +static inline struct normalized_coords +trackpoint_clip_to_max_delta(const struct trackpoint_accelerator *accel_filter, + struct normalized_coords coords) +{ + const double max_delta = accel_filter->max_delta; + + if (abs(coords.x) > max_delta) + coords.x = copysign(max_delta, coords.x); + if (abs(coords.y) > max_delta) + coords.y = copysign(max_delta, coords.y); + + return coords; +} + +static struct normalized_coords +trackpoint_accelerator_filter(struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + void *data, uint64_t time) +{ + struct trackpoint_accelerator *accel_filter = + (struct trackpoint_accelerator *)filter; + struct device_float_coords scaled; + struct device_float_coords avg; + struct normalized_coords coords; + double f; + double delta; + + scaled = trackpoint_normalize_deltas(accel_filter, unaccelerated); + avg = trackpoint_average_delta(accel_filter, &scaled); + + delta = hypot(avg.x, avg.y); + + f = trackpoint_accel_profile(filter, data, delta); + + coords.x = avg.x * f; + coords.y = avg.y * f; + + coords = trackpoint_clip_to_max_delta(accel_filter, coords); + + return coords; +} + +static struct normalized_coords +trackpoint_accelerator_filter_noop(struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + void *data, uint64_t time) +{ + + struct trackpoint_accelerator *accel_filter = + (struct trackpoint_accelerator *)filter; + struct device_float_coords scaled; + struct device_float_coords avg; + struct normalized_coords coords; + + scaled = trackpoint_normalize_deltas(accel_filter, unaccelerated); + avg = trackpoint_average_delta(accel_filter, &scaled); + + coords.x = avg.x; + coords.y = avg.y; + + coords = trackpoint_clip_to_max_delta(accel_filter, coords); + + return coords; +} + +static bool +trackpoint_accelerator_set_speed(struct motion_filter *filter, + double speed_adjustment) +{ + struct trackpoint_accelerator *accel_filter = + (struct trackpoint_accelerator*)filter; + double incline, offset, max; + + assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0); + + /* Helloooo, magic numbers. + + These numbers were obtained by finding an acceleration curve that + provides precision at slow speeds but still provides a good + acceleration at higher pressure - and a quick ramp-up to that + acceleration. + + Trackpoints have built-in acceleration curves already, so we + don't put a new function on top, we merely scale the output from + those curves (re-calculating the pressure values from the + firmware-defined curve and applying a new curve is unreliable). + + For that basic scaling, we assume a constant factor f based on + the speed setting together with a maximum factor m (for this + speed setting). Delta acceleration is thus: + factor = max(m, f) + accelerated_delta = delta * factor; + + Trial and error showed a couple of pairs that work well for the + various speed settings (Lenovo T440, sensitivity 128): + + -1.0: f = 0.3, m = 1 + -0.5: f = 0.6, m = 2 + 0.0: f = 1.0, m = 6 + 0.5: f = 1.4, m = 8 + 1.0: f = 1.9, m = 15 + + Note: if f >= 2.0, some pixels are unaddressable + + Those pairs were fed into the linear/exponential regression tool + at http://www.xuru.org/rt/LR.asp and show two functions that map + speed settings to the respective f and m. + Given a speed setting s in [-1.0, 1.0] + f(s) = 0.8 * s + 1.04 + m(s) = 4.6 * e**(1.2 * s) + These are close enough to the tested pairs. + */ + + max = 4.6 * pow(M_E, 1.2 * speed_adjustment); + incline = 0.8 * speed_adjustment + 1.04; + offset = 0; + + accel_filter->max_accel = max; + accel_filter->incline = incline; + accel_filter->offset = offset; + filter->speed_adjustment = speed_adjustment; + + return true; +} + +static void +trackpoint_accelerator_destroy(struct motion_filter *filter) +{ + struct trackpoint_accelerator *accel_filter = + (struct trackpoint_accelerator *)filter; + + free(accel_filter); +} + struct motion_filter_interface accelerator_interface_trackpoint = { .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, - .filter = accelerator_filter_unnormalized, - .filter_constant = accelerator_filter_noop, - .restart = accelerator_restart, - .destroy = accelerator_destroy, - .set_speed = accelerator_set_speed, + .filter = trackpoint_accelerator_filter, + .filter_constant = trackpoint_accelerator_filter_noop, + .restart = NULL, + .destroy = trackpoint_accelerator_destroy, + .set_speed = trackpoint_accelerator_set_speed, }; struct motion_filter * -create_pointer_accelerator_filter_trackpoint(int dpi) +create_pointer_accelerator_filter_trackpoint(int max_hw_delta) { - struct pointer_accelerator *filter; + struct trackpoint_accelerator *filter; - filter = create_default_filter(dpi); + /* Trackpoints are special. They don't have a movement speed like a + * mouse or a finger, instead they send a constant stream of events + * based on the pressure applied. + * + * Physical ranges on a trackpoint are the max values for relative + * deltas, but these are highly device-specific. + * + */ + + filter = zalloc(sizeof *filter); if (!filter) return NULL; + filter->history_size = ARRAY_LENGTH(filter->history); + filter->scale_factor = 1.0 * TRACKPOINT_DEFAULT_RANGE / max_hw_delta; + filter->max_accel = TRACKPOINT_DEFAULT_MAX_ACCEL; + filter->max_delta = TRACKPOINT_DEFAULT_MAX_DELTA; + filter->base.interface = &accelerator_interface_trackpoint; - filter->profile = trackpoint_accel_profile; - filter->threshold = DEFAULT_THRESHOLD; - filter->accel = DEFAULT_ACCELERATION; - filter->incline = DEFAULT_INCLINE; - filter->dpi = dpi; return &filter->base; } diff --git a/src/filter.h b/src/filter.h index e24c20d4..60d3728a 100644 --- a/src/filter.h +++ b/src/filter.h @@ -120,7 +120,7 @@ struct motion_filter * create_pointer_accelerator_filter_lenovo_x230(int dpi); struct motion_filter * -create_pointer_accelerator_filter_trackpoint(int dpi); +create_pointer_accelerator_filter_trackpoint(int max_delta); struct motion_filter * create_pointer_accelerator_filter_tablet(int xres, int yres); @@ -152,6 +152,5 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, double trackpoint_accel_profile(struct motion_filter *filter, void *data, - double speed_in, - uint64_t time); + double delta); #endif /* FILTER_H */ diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c index acb82c69..052769be 100644 --- a/tools/ptraccel-debug.c +++ b/tools/ptraccel-debug.c @@ -170,6 +170,24 @@ print_accel_func(struct motion_filter *filter, } static void +print_accel_func_trackpoint(struct motion_filter *filter, + int max) +{ + printf("# gnuplot:\n"); + printf("# set xlabel \"deltas (units)\"\n"); + printf("# set ylabel \"raw accel factor\"\n"); + printf("# set style data lines\n"); + printf("# plot \"gnuplot.data\" using 1:2 title 'accel factor'\n"); + printf("#\n"); + printf("# data: delta(units) factor\n"); + for (double delta = 0; delta < max; delta += 0.2) { + double factor = trackpoint_accel_profile(filter, NULL, delta); + + printf("%.2f %f\n", delta, factor); + } +} + +static void usage(void) { printf("Usage: %s [options] [dx1] [dx2] [...] > gnuplot.data\n", program_invocation_short_name); @@ -184,6 +202,7 @@ usage(void) "--steps=<double> ... in motion and delta modes only. Increase dx by step each round\n" "--speed=<double> ... accel speed [-1, 1], default 0\n" "--dpi=<int> ... device resolution in DPI (default: 1000)\n" + "--trackpoint_range=<int> ... range of the trackpoint deltas (default: 30)\n" "--filter=<linear|low-dpi|touchpad|x230|trackpoint> \n" " linear ... the default motion filter\n" " low-dpi ... low-dpi filter, use --dpi with this argument\n" @@ -219,6 +238,7 @@ main(int argc, char **argv) int dpi = 1000; const char *filter_type = "linear"; accel_profile_func_t profile = NULL; + int tp_range_max = 20; enum { OPT_HELP = 1, @@ -229,6 +249,7 @@ main(int argc, char **argv) OPT_SPEED, OPT_DPI, OPT_FILTER, + OPT_TRACKPOINT_RANGE, }; while (1) { @@ -243,6 +264,7 @@ main(int argc, char **argv) {"speed", 1, 0, OPT_SPEED }, {"dpi", 1, 0, OPT_DPI }, {"filter", 1, 0, OPT_FILTER }, + {"trackpoint-range", 1, 0, OPT_TRACKPOINT_RANGE }, {0, 0, 0, 0} }; @@ -300,6 +322,9 @@ main(int argc, char **argv) case OPT_FILTER: filter_type = optarg; break; + case OPT_TRACKPOINT_RANGE: + tp_range_max = strtod(optarg, NULL); + break; default: usage(); exit(1); @@ -320,8 +345,8 @@ main(int argc, char **argv) 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; + filter = create_pointer_accelerator_filter_trackpoint(tp_range_max); + profile = NULL; /* trackpoint is special */ } else { fprintf(stderr, "Invalid filter type %s\n", filter_type); return 1; @@ -349,9 +374,12 @@ main(int argc, char **argv) custom_deltas[nevents++] = strtod(argv[optind++], NULL); } - if (print_accel) - print_accel_func(filter, profile, dpi); - else if (print_delta) + if (print_accel) { + if (!profile) /* trackpoint */ + print_accel_func_trackpoint(filter, tp_range_max); + else + print_accel_func(filter, profile, dpi); + } else if (print_delta) print_ptraccel_deltas(filter, step); else if (print_motion) print_ptraccel_movement(filter, nevents, max_dx, step); -- 2.13.0 _______________________________________________ wayland-devel mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/wayland-devel
