From 71dd7b51ae6b64dd51ed517757a5c21daa9bf5cb Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 11 May 2017 17:17:54 +1000 Subject: [PATCH 1/4] tools: add a command to analyze trackpoints Trackpoints can send very different ranges between the various pressures. Collect the data and print it out to get an idea of what ranges are realistic. Signed-off-by: Peter Hutterer --- meson.build | 9 + tools/libinput-measure-trackpoint-range | 192 ++++++++++++++++++++ tools/libinput-measure-trackpoint-range.man | 31 ++++ tools/libinput-measure.man | 3 + tools/libinput.man | 3 + 5 files changed, 238 insertions(+) create mode 100755 tools/libinput-measure-trackpoint-range create mode 100644 tools/libinput-measure-trackpoint-range.man diff --git a/meson.build b/meson.build index 0a8d6a26..cf10b35f 100644 --- a/meson.build +++ b/meson.build @@ -440,6 +440,15 @@ configure_file(input : 'tools/libinput-measure-touch-size.man', install_dir : join_paths(get_option('mandir'), 'man1') ) +install_data('tools/libinput-measure-trackpoint-range', + install_dir : libinput_tool_path) +configure_file(input : 'tools/libinput-measure-trackpoint-range.man', + output : 'libinput-measure-trackpoint-range.1', + configuration : man_config, + install : true, + install_dir : join_paths(get_option('mandir'), 'man1') + ) + if get_option('debug-gui') dep_gtk = dependency('gtk+-3.0') dep_cairo = dependency('cairo') diff --git a/tools/libinput-measure-trackpoint-range b/tools/libinput-measure-trackpoint-range new file mode 100755 index 00000000..53dc67ec --- /dev/null +++ b/tools/libinput-measure-trackpoint-range @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# vim: set expandtab shiftwidth=4: +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ +# +# Copyright © 2017 Red Hat, Inc. +# +# 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. +# + +import sys +import argparse +import evdev +import evdev.ecodes +import pyudev + +MINIMUM_EVENT_COUNT = 1000 + + +class InvalidDeviceError(Exception): + pass + + +class Delta(object): + def __init__(self, x=0, y=0): + self.x = x + self.y = y + + def __bool_(self): + return self.x != 0 or self.y != 0 + + +class Device(object): + def __init__(self, path): + if path is None: + path = self._find_trackpoint_device() + self.path = path + + self.device = evdev.InputDevice(self.path) + + self.deltas = [] + self.nxdeltas = 0 + self.nydeltas = 0 + + self.current_delta = Delta() + self.max_delta = Delta(0, 0) + + def _find_trackpoint_device(self): + context = pyudev.Context() + for device in context.list_devices(subsystem='input'): + if not device.get('ID_INPUT_POINTINGSTICK', 0): + continue + + if not device.device_node or \ + not device.device_node.startswith('/dev/input/event'): + continue + + return device.device_node + + raise InvalidDeviceError("Unable to find a trackpoint device") + + def handle_rel(self, event): + if event.code == evdev.ecodes.REL_X: + self.current_delta.x = event.value + if self.max_delta.x < abs(event.value): + self.max_delta.x = abs(event.value) + elif event.code == evdev.ecodes.REL_Y: + self.current_delta.y = event.value + if self.max_delta.y < abs(event.value): + self.max_delta.y = abs(event.value) + + def handle_syn(self, event): + self.deltas.append(self.current_delta) + if self.current_delta.x != 0: + self.nxdeltas += 1 + if self.current_delta.y != 0: + self.nydeltas += 1 + + self.current_delta = Delta() + + print("\rTrackpoint sends: max x:{:3d}, max y:{:3} samples [{}, {}]" + .format( + self.max_delta.x, self.max_delta.y, + self.nxdeltas, self.nydeltas, + ), end="") + + def read_events(self): + for event in self.device.read_loop(): + if event.type == evdev.ecodes.EV_REL: + self.handle_rel(event) + elif event.type == evdev.ecodes.EV_SYN: + self.handle_syn(event) + + def print_summary(self): + print("\n") # undo the \r from the status line + if not self.deltas: + return + + if len(self.deltas) < MINIMUM_EVENT_COUNT: + print("WARNING: *******************************************\n" + "WARNING: Insufficient samples, data is not reliable\n" + "WARNING: *******************************************\n") + + print("Histogram for x axis deltas, in counts of 5") + xs = [d.x for d in self.deltas] + minx = min(xs) + maxx = max(xs) + for i in range(minx, maxx + 1): + xc = len([x for x in xs if x == i]) + xc = int(xc/5) # counts of 5 is enough + print("{:4}: {}".format(i, "+" * xc, end="")) + + print("Histogram for y axis deltas, in counts of 5") + ys = [d.y for d in self.deltas] + miny = min(ys) + maxy = max(ys) + for i in range(miny, maxy + 1): + yc = len([y for y in ys if y == i]) + yc = int(yc/5) # counts of 5 is enough + print("{:4}: {}".format(i, "+" * yc, end="")) + + axs = sorted([abs(x) for x in xs]) + ays = sorted([abs(y) for y in ys]) + + avgx = int(sum(axs)/len(axs)) + avgy = int(sum(ays)/len(ays)) + + medx = axs[int(len(axs)/2)] + medy = ays[int(len(ays)/2)] + + pc95x = axs[int(len(axs) * 0.95)] + pc95y = ays[int(len(ays) * 0.95)] + + print("Average for abs deltas: x: {:3} y: {:3}".format(avgx, avgy)) + print("Median for abs deltas: x: {:3} y: {:3}".format(medx, medy)) + print("95% percentile for abs deltas: x: {:3} y: {:3}" + .format(pc95x, pc95y) + ) + + +def main(args): + parser = argparse.ArgumentParser( + description="Measure the trackpoint delta coordinate range" + ) + parser.add_argument('path', metavar='/dev/input/event0', + nargs='?', type=str, help='Path to device (optional)') + + args = parser.parse_args() + + try: + device = Device(args.path) + + print( + "This tool measures the commonly used pressure range of the\n" + "trackpoint. Push the trackpoint:\n" + "- Four times around the screen edges\n" + "- From the top left to the bottom right and back, twice\n" + "- From the top right to the bottom left and back, twice\n" + "A minimum of {} events for each axis is required\n" + "\n" + "Movements should emulate fast pointer movement on the screen\n" + "but not use excessive pressure that would not be used\n" + "during day-to-day movement. For best results, run this tool \n" + "several times to get an idea of the common range.\n" + "\n".format(MINIMUM_EVENT_COUNT)) + device.read_events() + except KeyboardInterrupt: + device.print_summary() + except (PermissionError, OSError): + print("Error: failed to open device") + except InvalidDeviceError as e: + print("Error: {}".format(e)) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/tools/libinput-measure-trackpoint-range.man b/tools/libinput-measure-trackpoint-range.man new file mode 100644 index 00000000..94f20404 --- /dev/null +++ b/tools/libinput-measure-trackpoint-range.man @@ -0,0 +1,31 @@ +.TH LIBINPUT-MEASURE-TRACKPOINT-RANGE "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual" +.SH NAME +libinput\-measure\-trackpoint\-range \- measure the delta range of a trackpoint +.SH SYNOPSIS +.B libinput measure trackpoint\-range [\-\-help] [/dev/input/event0] +.SH DESCRIPTION +.PP +The +.B "libinput measure trackpoint\-range" +tool measures the delta range of a trackpoint. This is +an interactive tool. When executed, the tool will prompt the user to +interact with the trackpoint. On termination, the tool prints a summary of +the trackpoint deltas seen. This data should be attached to any bug report +relating to the trackpoint's speed. +.PP +This is a debugging tool only, its output may change at any time. Do not +rely on the output. +.PP +This tool usually needs to be run as root to have access to the +/dev/input/eventX nodes. +.SH OPTIONS +If a device node is given, this tool opens that device node. Otherwise, this +tool searches for the first node that looks like a trackpoint and uses that +node. +.TP 8 +.B \-\-help +Print help +.SH LIBINPUT +Part of the +.B libinput(1) +suite diff --git a/tools/libinput-measure.man b/tools/libinput-measure.man index 703900b7..9e52e5e1 100644 --- a/tools/libinput-measure.man +++ b/tools/libinput-measure.man @@ -30,6 +30,9 @@ Measure tap-to-click time .TP 8 .B libinput\-measure\-touchpad\-pressure(1) Measure touch pressure +.TP 8 +.B libinput\-measure\-trackpoint\-range(1) +Measure the delta range of a trackpoint. .SH LIBINPUT Part of the .B libinput(1) diff --git a/tools/libinput.man b/tools/libinput.man index 16cf7bd7..ee129fca 100644 --- a/tools/libinput.man +++ b/tools/libinput.man @@ -53,6 +53,9 @@ Measure tap-to-click time .TP 8 .B libinput\-measure\-touchpad\-pressure(1) Measure touch pressure +.TP 8 +.B libinput-measure-trackpoint-range(1) +Measure the delta range of a trackpoint .SH LIBINPUT Part of the .B libinput(1) From 87b5682824b3137101f520b3b29fe396f80f57a8 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 12 May 2017 14:39:24 +1000 Subject: [PATCH 2/4] filter: add a custom trackpoint accelerator 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 --- doc/pointer-acceleration.dox | 16 +- src/evdev.c | 8 +- src/evdev.h | 1 + src/filter.c | 293 +++++++++++++++++++++++++++++------ src/filter.h | 5 +- test/test-pointer.c | 6 +- tools/ptraccel-debug.c | 38 ++++- 7 files changed, 304 insertions(+), 63 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 24bfad07..4166091c 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..e32e0ece 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 = {0}; + + 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/test/test-pointer.c b/test/test-pointer.c index b152dc8b..94be21a0 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -2076,9 +2076,9 @@ litest_setup_tests_pointer(void) struct range axis_range = {ABS_X, ABS_Y + 1}; struct range compass = {0, 7}; /* cardinal directions */ - litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_ANY); + litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add_for_device("pointer:motion", pointer_motion_relative_zero, LITEST_MOUSE); - litest_add_ranged("pointer:motion", pointer_motion_relative_min_decel, LITEST_RELATIVE, LITEST_ANY, &compass); + litest_add_ranged("pointer:motion", pointer_motion_relative_min_decel, LITEST_RELATIVE, LITEST_POINTINGSTICK, &compass); litest_add("pointer:motion", pointer_motion_absolute, LITEST_ABSOLUTE, LITEST_ANY); litest_add("pointer:motion", pointer_motion_unaccel, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD); @@ -2111,7 +2111,7 @@ litest_setup_tests_pointer(void) litest_add("pointer:accel", pointer_accel_invalid, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:accel", pointer_accel_defaults_absolute, LITEST_ABSOLUTE, LITEST_RELATIVE); litest_add("pointer:accel", pointer_accel_defaults_absolute_relative, LITEST_ABSOLUTE|LITEST_RELATIVE, LITEST_ANY); - litest_add("pointer:accel", pointer_accel_direction_change, LITEST_RELATIVE, LITEST_ANY); + litest_add("pointer:accel", pointer_accel_direction_change, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add("pointer:accel", pointer_accel_profile_defaults, LITEST_RELATIVE, LITEST_TOUCHPAD); litest_add("pointer:accel", pointer_accel_profile_defaults_noprofile, LITEST_TOUCHPAD, LITEST_ANY); litest_add("pointer:accel", pointer_accel_profile_invalid, LITEST_RELATIVE, LITEST_ANY); 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 @@ -169,6 +169,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) { @@ -184,6 +202,7 @@ usage(void) "--steps= ... 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" + "--trackpoint_range= ... range of the trackpoint deltas (default: 30)\n" "--filter= \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); From 3669fa10dff95371658647272ef7ac7a3ef29a61 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 16 May 2017 13:01:32 +1000 Subject: [PATCH 3/4] trackpoint: drop handling of CONST_ACCEL and undo SENSITIVITY This was to counteract hardware that doesn't work well out of the box, resulting in quite different behavior across devices. Specifically, only some trackpoints even have the sensitivity setting. Change to take over all of the pointer acceleration on trackpoints, so we can control the actual behavior mostly independent of the system setting. So we drop the CONST_ACCEL parsing (which never was handled as const accel anyway) and undo the effect that the SENSITIVITY udev property has. [1] We take a default range at the default sensitivity and multiply it by the proportion of the current sensitivity. This seems to be accurate enough. [1] In the future, we should read not only the property but also the sysfs file to make sure we're handling the right value, but for now this will do. Signed-off-by: Peter Hutterer --- src/evdev.c | 46 +++++++++++++++++++++------------------------ src/libinput-util.h | 2 ++ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/evdev.c b/src/evdev.c index 4166091c..f9f26a64 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -2208,32 +2208,32 @@ evdev_read_wheel_tilt_props(struct evdev_device *device) } static inline int -evdev_get_trackpoint_dpi(struct evdev_device *device) +evdev_get_trackpoint_range(struct evdev_device *device) { - const char *trackpoint_accel; - double accel = DEFAULT_TRACKPOINT_ACCEL; + const char *prop; + int range = DEFAULT_TRACKPOINT_RANGE; - /* - * parse the sensitivity property, and undo whatever it does. - */ + if (!(device->tags & EVDEV_TAG_TRACKPOINT)) + return DEFAULT_TRACKPOINT_RANGE; - trackpoint_accel = udev_device_get_property_value( - device->udev_device, "POINTINGSTICK_CONST_ACCEL"); - if (trackpoint_accel) { - accel = parse_trackpoint_accel_property(trackpoint_accel); - if (accel == 0.0) { + prop = udev_device_get_property_value(device->udev_device, + "POINTINGSTICK_SENSITIVITY"); + if (prop) { + int sensitivity; + + if (!safe_atoi(prop, &sensitivity) || + (sensitivity < 0.0 || sensitivity > 255)) { evdev_log_error(device, - "trackpoint accel property is present but invalid, " - "using %.2f instead\n", - DEFAULT_TRACKPOINT_ACCEL); - accel = DEFAULT_TRACKPOINT_ACCEL; + "trackpoint sensitivity property is present but invalid, " + "using %d instead\n", + DEFAULT_TRACKPOINT_SENSITIVITY); + sensitivity = DEFAULT_TRACKPOINT_SENSITIVITY; } - evdev_log_info(device, "set to const accel %.2f\n", accel); + range = 1.0 * DEFAULT_TRACKPOINT_RANGE * + sensitivity/DEFAULT_TRACKPOINT_SENSITIVITY; } - device->trackpoint_range = 20; /* FIXME */ - - return DEFAULT_MOUSE_DPI / accel; + return range; } static inline int @@ -2242,13 +2242,8 @@ evdev_read_dpi_prop(struct evdev_device *device) const char *mouse_dpi; int dpi = DEFAULT_MOUSE_DPI; - /* - * Trackpoints do not have dpi, instead hwdb may contain a - * POINTINGSTICK_CONST_ACCEL value to compensate for sensitivity - * differences between models, we translate this to a fake dpi. - */ if (device->tags & EVDEV_TAG_TRACKPOINT) - return evdev_get_trackpoint_dpi(device); + return DEFAULT_MOUSE_DPI; mouse_dpi = udev_device_get_property_value(device->udev_device, "MOUSE_DPI"); @@ -2666,6 +2661,7 @@ evdev_configure_device(struct evdev_device *device) evdev_tag_external_mouse(device, device->udev_device); evdev_tag_trackpoint(device, device->udev_device); device->dpi = evdev_read_dpi_prop(device); + device->trackpoint_range = evdev_get_trackpoint_range(device); device->seat_caps |= EVDEV_DEVICE_POINTER; diff --git a/src/libinput-util.h b/src/libinput-util.h index d4d68abe..bf632a5c 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -53,6 +53,8 @@ /* The HW DPI rate we normalize to before calculating pointer acceleration */ #define DEFAULT_MOUSE_DPI 1000 +#define DEFAULT_TRACKPOINT_RANGE 20 +#define DEFAULT_TRACKPOINT_SENSITIVITY 128 #define ANSI_HIGHLIGHT "\x1B[0;1;39m" #define ANSI_RED "\x1B[0;31m" From 2cadb5f4877e88198c543c76afa9d97bdd82f2da Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 16 May 2017 13:10:07 +1000 Subject: [PATCH 4/4] trackpoint: parse a trackpoint range property Add parsing for a LIBINPUT_ATTR_TRACKPOINT_RANGE property to enable hardware-dependent ranges. These take precedence over the sensitivity parsing. Signed-off-by: Peter Hutterer --- src/evdev.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/evdev.c b/src/evdev.c index f9f26a64..f9e23a9f 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -2216,6 +2216,20 @@ evdev_get_trackpoint_range(struct evdev_device *device) if (!(device->tags & EVDEV_TAG_TRACKPOINT)) return DEFAULT_TRACKPOINT_RANGE; + prop = udev_device_get_property_value(device->udev_device, + "LIBINPUT_ATTR_TRACKPOINT_RANGE"); + if (prop) { + if (!safe_atoi(prop, &range) || + (range < 0.0 || range > 100)) { + evdev_log_error(device, + "trackpoint range property is present but invalid, " + "using %d instead\n", + DEFAULT_TRACKPOINT_RANGE); + range = DEFAULT_TRACKPOINT_RANGE; + } + goto out; + } + prop = udev_device_get_property_value(device->udev_device, "POINTINGSTICK_SENSITIVITY"); if (prop) { @@ -2233,6 +2247,8 @@ evdev_get_trackpoint_range(struct evdev_device *device) sensitivity/DEFAULT_TRACKPOINT_SENSITIVITY; } +out: + evdev_log_info(device, "trackpoint device set to range %d\n", range); return range; }