diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox index c120565a..4ed2d231 100644 --- a/doc/pointer-acceleration.dox +++ b/doc/pointer-acceleration.dox @@ -157,4 +157,70 @@ Pointer acceleration for relative motion on tablet devices is a flat acceleration, with the speed seeting slowing down or speeding up the pointer motion by a constant factor. Tablets do not allow for switchable profiles. +@section ptraccel-device-speed Speed-dependent acceleration curve + +When the @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE profile is +selected, it is the caller's responsibility to load an acceleration profile +into the device that maps the device's movement into an accelerated +movement. + +This profile maps input speed in **device units** to an acceleration +factor. libinput calculates the device's speed based on the deltas and their +timestamps and applies the factor provided by the acceleration profile to +the current delta. + +@note This profile uses data in device units, an acceleration curve loaded +by the caller only applies to that device and will not behave the same way +for other devices. + +@dot +digraph +{ + rankdir="LR"; + node [shape="box";] + subgraph cluster0 { + history[shape=record,label=" delta(t-1)| delta(t-2)| delta(t-3)| ..."]; + label = "motion history"; + } + delta[label="delta"]; + velocity[shape="ellipse"]; + factor[shape="ellipse"]; + accel[label="accelerated delta"]; + curve[label="acceleration curve"]; + + delta->velocity; + delta->factor; + factor->accel; + history->velocity; + + velocity->curve; + curve->factor; +} +@enddot + +For example, assume the current delta is (2, 4) which maps to a current +movement velocity of 10 units per microsecond. libinput looks up the custom +acceleration function for 10 which may return 3. The accelerated delta thus +becomes (6, 12). + +The profile itself is a curve defined by a number of points on the curve +mapping speed to an acceleration factor. Between each two curve points, +libinput provides linear interpolation, most acceleration profiles will thus +only need to provide a handful of curve points. The following illustration +shows the acceleration curve defined for the points (10, 0.25), (20, 0.4) +and (35, 3.0). + +@image html ptraccel-curve-example.svg "Interpolation of the acceleration curve for three defined points" + +As seen in the picture above: given an acceleration factor f(x) and a curve +defined by the caller for the points +a, b, and c where a < b < c: +- if x <= a, f(x) == f(a) +- if x >= c, f(x) == f(c) +- if a < x < b: f(x) == the the linear interpolation value between f(a) and f(b) +- if b < x < c: f(x) == the the linear interpolation value between f(b) and f(c) + +The behavior of a a curve is implementation-defined until the caller sets +curve points. + */ diff --git a/doc/svg/ptraccel-curve-example.svg b/doc/svg/ptraccel-curve-example.svg new file mode 100644 index 00000000..b098e142 --- /dev/null +++ b/doc/svg/ptraccel-curve-example.svg @@ -0,0 +1,290 @@ + + + +Gnuplot +Produced by GNUPLOT 5.0 patchlevel 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + 0.5 + + + + + 1 + + + + + 1.5 + + + + + 2 + + + + + 2.5 + + + + + 3 + + + + + 3.5 + + + + + 0 + + + + + 5 + + + + + 10 + + + + + 15 + + + + + 20 + + + + + 25 + + + + + 30 + + + + + 35 + + + + + 40 + + + + + + + + + acceleration factor + + + + + speed (device units/ms) + + + + + factor + + + factor + + + + + + + + + + + + + + + + + diff --git a/meson.build b/meson.build index dfbac572..c2cd0a6f 100644 --- a/meson.build +++ b/meson.build @@ -151,6 +151,7 @@ dep_libinput_util = declare_dependency(link_with : libinput_util) ############ libfilter.a ############ src_libfilter = [ 'src/filter.c', + 'src/filter-custom.c', 'src/filter-flat.c', 'src/filter-low-dpi.c', 'src/filter-mouse.c', diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index c56838df..963a1edc 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -2569,26 +2569,8 @@ tp_init_slots(struct tp_dispatch *tp, static uint32_t tp_accel_config_get_profiles(struct libinput_device *libinput_device) { - return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; -} - -static enum libinput_config_status -tp_accel_config_set_profile(struct libinput_device *libinput_device, - enum libinput_config_accel_profile profile) -{ - return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; -} - -static enum libinput_config_accel_profile -tp_accel_config_get_profile(struct libinput_device *libinput_device) -{ - return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; -} - -static enum libinput_config_accel_profile -tp_accel_config_get_default_profile(struct libinput_device *libinput_device) -{ - return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; + return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE | + LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE; } static bool @@ -2625,12 +2607,9 @@ tp_init_accel(struct tp_dispatch *tp) evdev_device_init_pointer_acceleration(tp->device, filter); - /* we override the profile hooks for accel configuration with hooks - * that don't allow selection of profiles */ + /* override the profile hooks for get_profile because we don't + * have the flat profile on touchpads */ device->pointer.config.get_profiles = tp_accel_config_get_profiles; - device->pointer.config.set_profile = tp_accel_config_set_profile; - device->pointer.config.get_profile = tp_accel_config_get_profile; - device->pointer.config.get_default_profile = tp_accel_config_get_default_profile; return true; } diff --git a/src/evdev.c b/src/evdev.c index 27c14713..98e937ee 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -943,7 +943,9 @@ evdev_init_accel(struct evdev_device *device, { struct motion_filter *filter; - if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) + if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) + filter = create_pointer_accelerator_filter_custom_device_speed(); + else 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->trackpoint_range); @@ -1002,7 +1004,8 @@ evdev_accel_config_get_profiles(struct libinput_device *libinput_device) return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE | - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT | + LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE; } static enum libinput_config_status @@ -1051,6 +1054,27 @@ evdev_accel_config_get_default_profile(struct libinput_device *libinput_device) return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; } +static enum libinput_config_status +evdev_accel_config_set_curve_point(struct libinput_device *libinput_device, + double a, + double fa) +{ + struct evdev_device *device = evdev_device(libinput_device); + struct motion_filter *filter = device->pointer.filter; + + if (evdev_accel_config_get_profile(libinput_device) != + LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) + return LIBINPUT_CONFIG_STATUS_INVALID; + + if (a < 0 || a > 50000) + return LIBINPUT_CONFIG_STATUS_INVALID; + + if (!filter_set_curve_point(filter, a, fa)) + return LIBINPUT_CONFIG_STATUS_INVALID; + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + void evdev_device_init_pointer_acceleration(struct evdev_device *device, struct motion_filter *filter) @@ -1068,6 +1092,7 @@ evdev_device_init_pointer_acceleration(struct evdev_device *device, device->pointer.config.set_profile = evdev_accel_config_set_profile; device->pointer.config.get_profile = evdev_accel_config_get_profile; device->pointer.config.get_default_profile = evdev_accel_config_get_default_profile; + device->pointer.config.set_curve_point = evdev_accel_config_set_curve_point; device->base.config.accel = &device->pointer.config; default_speed = evdev_accel_config_get_default_speed(&device->base); diff --git a/src/filter-custom.c b/src/filter-custom.c new file mode 100644 index 00000000..7768aa2c --- /dev/null +++ b/src/filter-custom.c @@ -0,0 +1,216 @@ +/* + * Copyright © 2018 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "filter.h" +#include "libinput-util.h" +#include "filter-private.h" + +struct acceleration_curve_point { + double x, fx; +}; + +struct custom_accelerator { + struct motion_filter base; + struct acceleration_curve_point points[32]; + size_t npoints; + + double last_velocity; + struct pointer_trackers trackers; +}; + +double +custom_accel_profile(struct motion_filter *filter, + void *data, + double speed_in, /* in device units/µs */ + uint64_t time) +{ + struct custom_accelerator *f = + (struct custom_accelerator*)filter; + double fx = 1; + + speed_in *= 1000; + + if (f->npoints == 0) + return 1.0; + + if (f->points[0].x >= speed_in) + return f->points[0].fx; + + for (size_t i = 0; i < f->npoints - 1; i++) { + double a, b, fa, fb; + double k, d; + + if (f->points[i + 1].x < speed_in) + continue; + + /* + We haves points f(i), f(i+1), defining two points on the + curve. linear function in the form y = kx+d: + + y = kx + d + + y1 = kx1 + d -> d = y1 - kx1 + y2 = kx2 + d -> d = y2 - kx2 + + y1 - kx1 = y2 - kx2 + y1 - y2 = kx1 - kx2 + k = y1-y2/(x1 - x2) + + */ + a = f->points[i].x; + fa = f->points[i].fx; + b = f->points[i+1].x; + fb = f->points[i+1].fx; + + k = (fa - fb)/(a - b); + d = fa - k * a; + + fx = k * speed_in + d; + + return fx; + } + + return f->points[f->npoints - 1].fx; +} + +static struct normalized_coords +custom_accelerator_filter(struct motion_filter *filter, + const struct device_float_coords *units, + void *data, uint64_t time) +{ + struct custom_accelerator *f = + (struct custom_accelerator*)filter; + struct normalized_coords norm; + double velocity; /* units/us in device-native dpi*/ + double accel_factor; + + trackers_feed(&f->trackers, units, time); + velocity = trackers_velocity(&f->trackers, time); + accel_factor = calculate_acceleration_simpsons(filter, + custom_accel_profile, + data, + velocity, + f->last_velocity, + time); + f->last_velocity = velocity; + + norm.x = accel_factor * units->x; + norm.y = accel_factor * units->y; + + return norm; +} + +static bool +custom_accelerator_set_speed(struct motion_filter *filter, + double speed_adjustment) +{ + assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0); + + /* noop, this function has no effect in the custom interface */ + + return true; +} + +static void +custom_accelerator_destroy(struct motion_filter *filter) +{ + struct custom_accelerator *accel_filter = + (struct custom_accelerator*)filter; + + trackers_free(&accel_filter->trackers); + free(accel_filter); +} + +static bool +custom_accelerator_set_curve_point(struct motion_filter *filter, + double a, double fa) +{ + struct custom_accelerator *f = + (struct custom_accelerator*)filter; + + if (f->npoints == ARRAY_LENGTH(f->points)) + return false; + + if (a < 0 || a > 50000) + return false; + + if (f->npoints == 0) { + f->points[0].x = a; + f->points[0].fx = fa; + f->npoints = 1; + return true; + } else if (f->points[f->npoints - 1].x < a) { + f->points[f->npoints].x = a; + f->points[f->npoints].fx = fa; + f->npoints++; + return true; + } + + for (size_t i = 0; i < f->npoints; i++) { + if (f->points[i].x == a) { + f->points[i].fx = fa; + break; + } else if (f->points[i].x > a) { + f->npoints++; + for (size_t j = f->npoints - 1; j > i; j--) + f->points[j] = f->points[j-1]; + f->points[i] = (struct acceleration_curve_point){ a, fa }; + break; + } + } + + return true; +} + +struct motion_filter_interface accelerator_interface_custom = { + .type = LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE, + .filter = custom_accelerator_filter, + .filter_constant = NULL, + .restart = NULL, + .destroy = custom_accelerator_destroy, + .set_speed = custom_accelerator_set_speed, + .set_curve_point = custom_accelerator_set_curve_point, +}; + +struct motion_filter * +create_pointer_accelerator_filter_custom_device_speed(void) +{ + struct custom_accelerator *filter; + + filter = zalloc(sizeof *filter); + trackers_init(&filter->trackers); + + filter->base.interface = &accelerator_interface_custom; + + return &filter->base; +} diff --git a/src/filter-private.h b/src/filter-private.h index fa2fea40..71415dd9 100644 --- a/src/filter-private.h +++ b/src/filter-private.h @@ -44,6 +44,8 @@ struct motion_filter_interface { void (*destroy)(struct motion_filter *filter); bool (*set_speed)(struct motion_filter *filter, double speed_adjustment); + bool (*set_curve_point)(struct motion_filter *filter, + double a, double fa); }; struct motion_filter { diff --git a/src/filter.c b/src/filter.c index fe60c62a..15cbf44b 100644 --- a/src/filter.c +++ b/src/filter.c @@ -90,6 +90,15 @@ filter_get_type(struct motion_filter *filter) return filter->interface->type; } +bool +filter_set_curve_point(struct motion_filter *filter, double a, double fa) +{ + if (!filter->interface->set_curve_point) + return false; + + return filter->interface->set_curve_point(filter, a, fa); +} + void trackers_init(struct pointer_trackers *trackers) { diff --git a/src/filter.h b/src/filter.h index 506ab123..22d98e09 100644 --- a/src/filter.h +++ b/src/filter.h @@ -95,6 +95,9 @@ filter_set_speed(struct motion_filter *filter, double filter_get_speed(struct motion_filter *filter); +bool +filter_set_curve_point(struct motion_filter *filter, double a, double fa); + enum libinput_config_accel_profile filter_get_type(struct motion_filter *filter); @@ -104,6 +107,9 @@ typedef double (*accel_profile_func_t)(struct motion_filter *filter, uint64_t time); /* Pointer acceleration types */ +struct motion_filter * +create_pointer_accelerator_filter_custom_device_speed(void); + struct motion_filter * create_pointer_accelerator_filter_flat(int dpi); @@ -152,6 +158,12 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter, double speed_in, uint64_t time); double +custom_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 delta); diff --git a/src/libinput-private.h b/src/libinput-private.h index d50154ef..a6938ba6 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -213,6 +213,8 @@ struct libinput_device_config_accel { enum libinput_config_accel_profile); enum libinput_config_accel_profile (*get_profile)(struct libinput_device *device); enum libinput_config_accel_profile (*get_default_profile)(struct libinput_device *device); + enum libinput_config_status (*set_curve_point)(struct libinput_device *device, + double a, double fa); }; struct libinput_device_config_natural_scroll { diff --git a/src/libinput-util.h b/src/libinput-util.h index c6d40efc..1f6994be 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -560,4 +560,67 @@ strv_free(char **strv) { free (strv); } +struct key_value_double { + double key; + double value; +}; + +static inline ssize_t +kv_double_from_string(const char *string, + const char *pair_separator, + const char *kv_separator, + struct key_value_double **result_out) + +{ + char **pairs; + char **pair; + struct key_value_double *result = NULL; + ssize_t npairs = 0; + unsigned int idx = 0; + + if (!pair_separator || pair_separator[0] == '\0' || + !kv_separator || kv_separator[0] == '\0') + return -1; + + pairs = strv_from_string(string, pair_separator); + if (!pairs) + return -1; + + for (pair = pairs; *pair; pair++) + npairs++; + + if (npairs == 0) + return -1; + + result = zalloc(npairs * sizeof *result); + + for (pair = pairs; *pair; pair++) { + char **kv = strv_from_string(*pair, kv_separator); + double k, v; + + if (!kv || !kv[0] || !kv[1] || kv[2] || + !safe_atod(kv[0], &k) || + !safe_atod(kv[1], &v)) { + strv_free(kv); + goto error; + } + + result[idx].key = k; + result[idx].value = v; + idx++; + + strv_free(kv); + } + + strv_free(pairs); + + *result_out = result; + + return npairs; + +error: + strv_free(pairs); + free(result); + return -1; +} #endif /* LIBINPUT_UTIL_H */ diff --git a/src/libinput.c b/src/libinput.c index 5e675dd9..e3af17d3 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -3679,6 +3679,19 @@ libinput_device_config_accel_get_default_speed(struct libinput_device *device) return device->config.accel->get_default_speed(device); } +LIBINPUT_EXPORT enum libinput_config_status +libinput_device_config_accel_set_curve_point( + struct libinput_device *device, + double a, double fa) +{ + if (libinput_device_config_accel_get_profile(device) != + LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + return device->config.accel->set_curve_point(device, a, fa); +} + LIBINPUT_EXPORT uint32_t libinput_device_config_accel_get_profiles(struct libinput_device *device) { @@ -3713,6 +3726,7 @@ libinput_device_config_accel_set_profile(struct libinput_device *device, switch (profile) { case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT: case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE: + case LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE: break; default: return LIBINPUT_CONFIG_STATUS_INVALID; diff --git a/src/libinput.h b/src/libinput.h index 828cf6b4..857dcdfa 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -4494,6 +4494,12 @@ libinput_device_config_accel_is_available(struct libinput_device *device); * range. libinput picks the semantically closest acceleration step if the * requested value does not match a discrete setting. * + * If the current acceleration profile is @ref + * LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE, the behavior of the + * device will not change but future calls to + * libinput_device_config_accel_get_speed() will reflect the updated speed + * setting. + * * @param device The device to configure * @param speed The normalized speed, in a range of [-1, 1] * @@ -4542,6 +4548,44 @@ libinput_device_config_accel_get_speed(struct libinput_device *device); double libinput_device_config_accel_get_default_speed(struct libinput_device *device); +/** + * @ingroup config + * + * Sets a curve point on the custom acceleration function for this device. + * This function must be called after setting the type of the acceleration + * to @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE and sets + * exactly one point on the device's acceleration curve. + * + * This function must be called multiple times to define a full acceleration + * curve. libinput uses linear interpolation between each defined curve + * point to calculate the appropriate factor. Any speed below or above the + * lowest or highest point defined is capped to the factor at the lowest or + * highest point, respectively. See @ref ptraccel-device-speed for a + * detailed explanation on this behavior. + * + * The behavior of the acceleration function depends on the type of the + * profile: + * - @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE : the input data + * a is velocity in device units per millisecond, f(a) is a unitless + * factor. This factor is applied to the incoming delta so that a delta + * (x, y) is accelerated to the delta (f(a) * x, f(a) *y). The velocity + * is calculated by libinput based on the current and previous deltas and + * their timestamps. See @ref ptraccel-device-speed for details. + * + * @note libinput has a maximum limit for how many curve points may be set + * and will quietly drop curve points exceeding this limit. This limit is + * not expected to be hit by any reasonable caller. + * + * Submitting a curve point with the same value as a previous curve point + * overwrites that value. There is no facility to remove curve points, + * switch the device to a different profile and back again to reset. + * + * @return 0 on success or nonzero otherwise + */ +enum libinput_config_status +libinput_device_config_accel_set_curve_point(struct libinput_device *device, + double a, double fa); + /** * @ingroup config */ @@ -4565,6 +4609,11 @@ enum libinput_config_accel_profile { * on the input speed. This is the default profile for most devices. */ LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE = (1 << 1), + /** + * A custom user-provided profile. See + * libinput_acceleration_profile_set_curve_point() for details. + */ + LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE = (1 << 2), }; /** @@ -4586,6 +4635,13 @@ libinput_device_config_accel_get_profiles(struct libinput_device *device); * Set the pointer acceleration profile of this pointer device to the given * mode. * + * If the given profile is + * @ref LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE and it is + * different to the current profile, the acceleration curve is reset to an + * implementation-defined curve. The caller should call + * libinput_device_config_accel_set_curve_point() to + * define the curve points of the acceleration profile. + * * @param device The device to configure * @param mode The mode to set the device to. * diff --git a/src/libinput.sym b/src/libinput.sym index aa2794e9..f991aeea 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -295,5 +295,6 @@ LIBINPUT_1.9 { } LIBINPUT_1.7; LIBINPUT_1.11 { + libinput_device_config_accel_set_curve_point; libinput_device_touch_get_touch_count; } LIBINPUT_1.9; diff --git a/test/test-misc.c b/test/test-misc.c index 608bba61..9cd96d8c 100644 --- a/test/test-misc.c +++ b/test/test-misc.c @@ -1291,6 +1291,58 @@ START_TEST(strsplit_test) } END_TEST +struct kvsplit_dbl_test { + const char *string; + const char *psep; + const char *kvsep; + ssize_t nresults; + struct { + double a; + double b; + } results[32]; +}; + +START_TEST(kvsplit_double_test) +{ + struct kvsplit_dbl_test tests[] = { + { "1:2;3:4;5:6", ";", ":", 3, { {1, 2}, {3, 4}, {5, 6}}}, + { "1.0x2.3 -3.2x4.5 8.090909x-6.00", " ", "x", 3, { {1.0, 2.3}, {-3.2, 4.5}, {8.090909, -6}}}, + + { "1:2", "x", ":", 1, {{1, 2}}}, + { "1:2", ":", "x", -1, {}}, + { "1:2", NULL, "x", -1, {}}, + { "1:2", "", "x", -1, {}}, + { "1:2", "x", NULL, -1, {}}, + { "1:2", "x", "", -1, {}}, + { "a:b", "x", ":", -1, {}}, + { "", " ", "x", -1, {}}, + { "1.2.3.4.5", ".", "", -1, {}}, + { NULL } + }; + struct kvsplit_dbl_test *t = tests; + + while (t->string) { + struct key_value_double *result = NULL; + ssize_t npairs; + + npairs = kv_double_from_string(t->string, + t->psep, + t->kvsep, + &result); + ck_assert_int_eq(npairs, t->nresults); + + for (ssize_t i = 0; i < npairs; i++) { + ck_assert_double_eq(t->results[i].a, result[i].key); + ck_assert_double_eq(t->results[i].b, result[i].value); + } + + + free(result); + t++; + } +} +END_TEST + static int open_restricted_leak(const char *path, int flags, void *data) { return *(int*)data; @@ -1527,6 +1579,7 @@ TEST_COLLECTION(misc) litest_add_no_device("misc:parser", safe_atoi_base_8_test); litest_add_no_device("misc:parser", safe_atod_test); litest_add_no_device("misc:parser", strsplit_test); + litest_add_no_device("misc:parser", kvsplit_double_test); litest_add_no_device("misc:time", time_conversion); litest_add_no_device("misc:fd", fd_no_event_leak); diff --git a/test/test-pointer.c b/test/test-pointer.c index d6ff4ffe..300c428b 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -1365,7 +1365,49 @@ START_TEST(pointer_accel_direction_change) } END_TEST +static inline void +verify_set_profile(struct libinput_device *device, + enum libinput_config_accel_profile profile) +{ + enum libinput_config_accel_profile p; + enum libinput_config_status status; + + status = libinput_device_config_accel_set_profile(device, profile); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + p = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, p); +} + START_TEST(pointer_accel_profile_defaults) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_accel_profile profile; + uint32_t profiles; + + ck_assert(libinput_device_config_accel_is_available(device)); + + profile = libinput_device_config_accel_get_default_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + + profiles = libinput_device_config_accel_get_profiles(device); + ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); + ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE); + + verify_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); + + profile = libinput_device_config_accel_get_default_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + + verify_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); +} +END_TEST + +START_TEST(pointer_accel_profile_defaults_touchpad) { struct litest_device *dev = litest_current_device(); struct libinput_device *device = dev->libinput_device; @@ -1383,55 +1425,19 @@ START_TEST(pointer_accel_profile_defaults) profiles = libinput_device_config_accel_get_profiles(device); ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); - ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); - - status = libinput_device_config_accel_set_profile(device, - LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); - ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); + ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE); + verify_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + verify_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE); profile = libinput_device_config_accel_get_default_profile(device); ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); - status = libinput_device_config_accel_set_profile(device, - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); - ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); -} -END_TEST - -START_TEST(pointer_accel_profile_defaults_noprofile) -{ - struct litest_device *dev = litest_current_device(); - struct libinput_device *device = dev->libinput_device; - enum libinput_config_status status; - enum libinput_config_accel_profile profile; - uint32_t profiles; - - ck_assert(libinput_device_config_accel_is_available(device)); - - profile = libinput_device_config_accel_get_default_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - profiles = libinput_device_config_accel_get_profiles(device); - ck_assert_int_eq(profiles, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - + verify_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); status = libinput_device_config_accel_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); - - status = libinput_device_config_accel_set_profile(device, - LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); - ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); - profile = libinput_device_config_accel_get_profile(device); - ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } END_TEST @@ -2577,7 +2583,7 @@ TEST_COLLECTION(pointer) 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_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_defaults_touchpad, LITEST_TOUCHPAD, LITEST_ANY); litest_add("pointer:accel", pointer_accel_profile_invalid, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:accel", pointer_accel_profile_noaccel, LITEST_ANY, LITEST_TOUCHPAD|LITEST_RELATIVE|LITEST_TABLET); litest_add("pointer:accel", pointer_accel_profile_flat_motion_relative, LITEST_RELATIVE, LITEST_TOUCHPAD); diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man index 8a63821e..07674990 100644 --- a/tools/libinput-debug-events.man +++ b/tools/libinput-debug-events.man @@ -68,6 +68,11 @@ Enable or disable middle button emulation .B \-\-enable\-dwt|\-\-disable\-dwt Enable or disable disable-while-typing .TP 8 +.B \-\-set\-accel-curve-points="x1:y1;x2:y2" +Sets the curve points for the \fIcustom-speed\fR acceleration profile. The +set of curve points is a semicolon-separate lists of key-value pairs, each +separated by a colon. Each value is a non-negative double. +.TP 8 .B \-\-set\-click\-method=[none|clickfinger|buttonareas] Set the desired click method .TP 8 @@ -77,8 +82,9 @@ Set the desired scroll method .B \-\-set\-scroll\-button=BTN_MIDDLE Set the button to the given button code .TP 8 -.B \-\-set\-profile=[adaptive|flat] -Set pointer acceleration profile +.B \-\-set\-profile=[adaptive|flat|custom-speed] +Set pointer acceleration profile. If the \fIcustom-speed\fR profile is +selected, use \fB\-\-set-accel-curve-points\fR to specify the curve points. .TP 8 .B \-\-set\-speed= Set pointer acceleration speed. The allowed range is [-1, 1]. diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c index aa225ca0..060d4b29 100644 --- a/tools/libinput-list-devices.c +++ b/tools/libinput-list-devices.c @@ -205,11 +205,13 @@ accel_profiles(struct libinput_device *device) profile = libinput_device_config_accel_get_default_profile(device); xasprintf(&str, - "%s%s %s%s", + "%s%s %s%s %s%s", (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? "*" : "", (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? "flat" : "", (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "*" : "", - (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "adaptive" : ""); + (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "adaptive" : "", + (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) ? "*" : "", + (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) ? "custom-speed" : ""); return str; } diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c index 93be523f..ab7bc13e 100644 --- a/tools/ptraccel-debug.c +++ b/tools/ptraccel-debug.c @@ -161,11 +161,12 @@ print_accel_func(struct motion_filter *filter, printf("# set style data lines\n"); printf("# plot \"gnuplot.data\" using 1:2 title 'accel factor'\n"); printf("#\n"); - printf("# data: velocity(mm/s) factor velocity(units/us)\n"); + printf("# data: velocity(mm/s) factor velocity(units/us) velocity(units/ms)\n"); for (mmps = 0.0; mmps < 1000.0; mmps += 1) { double units_per_us = mmps_to_upus(mmps, dpi); + double units_per_ms = units_per_us * 1000; double result = profile(filter, NULL, units_per_us, 0 /* time */); - printf("%.8f\t%.4f\t%.8f\n", mmps, result, units_per_us); + printf("%.8f\t%.4f\t%.8f\t%.8f\n", mmps, result, units_per_us, units_per_ms); } } @@ -239,6 +240,7 @@ main(int argc, char **argv) const char *filter_type = "linear"; accel_profile_func_t profile = NULL; int tp_range_max = 20; + const char *curve_points = NULL; enum { OPT_HELP = 1, @@ -250,6 +252,7 @@ main(int argc, char **argv) OPT_DPI, OPT_FILTER, OPT_TRACKPOINT_RANGE, + OPT_CURVE_POINTS, }; while (1) { @@ -265,6 +268,7 @@ main(int argc, char **argv) {"dpi", 1, 0, OPT_DPI }, {"filter", 1, 0, OPT_FILTER }, {"trackpoint-range", 1, 0, OPT_TRACKPOINT_RANGE }, + {"curve-points", 1, 0, OPT_CURVE_POINTS }, {0, 0, 0, 0} }; @@ -325,6 +329,9 @@ main(int argc, char **argv) case OPT_TRACKPOINT_RANGE: tp_range_max = strtod(optarg, NULL); break; + case OPT_CURVE_POINTS: + curve_points = optarg; + break; default: usage(); exit(1); @@ -347,6 +354,24 @@ main(int argc, char **argv) } else if (streq(filter_type, "trackpoint")) { filter = create_pointer_accelerator_filter_trackpoint(tp_range_max); profile = NULL; /* trackpoint is special */ + } else if (streq(filter_type, "custom-speed")) { + struct key_value_double *points; + ssize_t npoints; + + filter = create_pointer_accelerator_filter_custom_device_speed(); + profile = custom_accel_profile; + + npoints = kv_double_from_string(curve_points, ";", ":", &points); + if (npoints <= 0) + return 1; + + for (ssize_t idx = 0; idx < npoints; idx++){ + filter_set_curve_point(filter, + points[idx].key, + points[idx].value); + } + + free(points); } else { fprintf(stderr, "Invalid filter type %s\n", filter_type); return 1; diff --git a/tools/shared.c b/tools/shared.c index c1ce6473..85c0d739 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -218,6 +218,17 @@ tools_parse_option(int option, "%s", optarg); break; + case OPT_CURVE_POINTS: + if (!optarg) + return 1; + + options->ncurve_points = kv_double_from_string( + optarg, + ";", ":", + &options->curve_points); + if (options->ncurve_points < 0) + return 1; + break; } return 0; @@ -386,6 +397,16 @@ tools_device_apply_config(struct libinput_device *device, libinput_device_config_send_events_set_mode(device, LIBINPUT_CONFIG_SEND_EVENTS_DISABLED); } + + if (libinput_device_config_accel_get_profile(device) == + LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) { + for (ssize_t idx = 0; idx < options->ncurve_points; idx++) { + double x = options->curve_points[idx].key, + fx = options->curve_points[idx].value; + + libinput_device_config_accel_set_curve_point(device, x, fx); + } + } } static char* diff --git a/tools/shared.h b/tools/shared.h index 55e15409..dc61b5b7 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -50,6 +50,7 @@ enum configuration_options { OPT_SPEED, OPT_PROFILE, OPT_DISABLE_SENDEVENTS, + OPT_CURVE_POINTS, }; #define CONFIGURATION_OPTIONS \ @@ -73,7 +74,8 @@ enum configuration_options { { "set-scroll-button", required_argument, 0, OPT_SCROLL_BUTTON }, \ { "set-profile", required_argument, 0, OPT_PROFILE }, \ { "set-tap-map", required_argument, 0, OPT_TAP_MAP }, \ - { "set-speed", required_argument, 0, OPT_SPEED } + { "set-speed", required_argument, 0, OPT_SPEED }, \ + { "set-accel-curve-points", required_argument, 0, OPT_CURVE_POINTS } enum tools_backend { BACKEND_DEVICE, @@ -95,6 +97,9 @@ struct tools_options { int dwt; enum libinput_config_accel_profile profile; char disable_pattern[64]; + + struct key_value_double *curve_points; + ssize_t ncurve_points; }; void tools_init_options(struct tools_options *options);