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/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/src/evdev.c b/src/evdev.c index 24bfad07..f9e23a9f 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 @@ -2208,26 +2208,48 @@ 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; - 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) { + 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 accel property is present but invalid, " - "using %.2f instead\n", - DEFAULT_TRACKPOINT_ACCEL); - accel = DEFAULT_TRACKPOINT_ACCEL; + "trackpoint range property is present but invalid, " + "using %d instead\n", + DEFAULT_TRACKPOINT_RANGE); + range = DEFAULT_TRACKPOINT_RANGE; } - evdev_log_info(device, "set to const accel %.2f\n", accel); + goto out; } - return DEFAULT_MOUSE_DPI / accel; + 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 sensitivity property is present but invalid, " + "using %d instead\n", + DEFAULT_TRACKPOINT_SENSITIVITY); + sensitivity = DEFAULT_TRACKPOINT_SENSITIVITY; + } + range = 1.0 * DEFAULT_TRACKPOINT_RANGE * + sensitivity/DEFAULT_TRACKPOINT_SENSITIVITY; + } + +out: + evdev_log_info(device, "trackpoint device set to range %d\n", range); + return range; } static inline int @@ -2236,13 +2258,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"); @@ -2660,6 +2677,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/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/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" 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/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) 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);