From 5324f425a1635fb95356461c34e43d72590c9023 Mon Sep 17 00:00:00 2001 From: Yinon Burgansky Date: Tue, 13 Dec 2022 00:23:59 +0200 Subject: [PATCH] Introduce custom acceleration profile The custom acceleration profile allow the user to define custom acceleration functions for each movement type per device, giving full control over accelerations behavior at different speeds. This commit introduces 2 movement types which corresponds to the 2 profiles currently in use by libinput. regular filter is Motion type. constant filter is Fallback type. This allows possible expansion of new movement types for the different devices. The custom pointer acceleration profile gives the user full control over the acceleration behavior at different speeds. The user needs to provide a custom acceleration function f(x) where the x-axis is the device speed and the y-axis is the pointer speed. The user should take into account the native device dpi and screen dpi in order to achieve the desired behavior/feel of the acceleration. The custom acceleration function is defined using n points which are spaced uniformly along the x-axis, starting from 0 and continuing in constant steps. There by the points defining the custom function are: (0 * step, f[0]), (1 * step, f[1]), ..., ((n-1) * step, f[n-1]) where f is a list of n unitless values defining the acceleration factor for each velocity. When a velocity value does not lie exactly on those points, a linear interpolation of the two closest points will be calculated. When a velocity value is greater than the max point defined, a linear extrapolation of the two biggest points will be calculated. Signed-off-by: Yinon Burgansky <51504-Yinon@users.noreply.gitlab.freedesktop.org> Signed-off-by: Peter Hutterer --- doc/user/pointer-acceleration.rst | 73 +++++- meson.build | 1 + src/evdev-mt-touchpad.c | 24 +- src/evdev.c | 22 +- src/filter-custom.c | 393 ++++++++++++++++++++++++++++++ src/filter-private.h | 2 + src/filter.c | 12 + src/filter.h | 17 ++ src/libinput-private.h | 35 +++ src/libinput.c | 116 +++++++++ src/libinput.h | 174 +++++++++++++ src/libinput.sym | 7 + src/util-strings.h | 41 ++++ src/util-time.h | 6 + test/test-pointer.c | 91 +++++++ test/test-utils.c | 45 ++++ tools/libinput-debug-events.man | 19 +- tools/libinput-list-devices.c | 6 +- tools/ptraccel-debug.c | 50 ++++ tools/shared.c | 53 +++- tools/shared.h | 12 +- 21 files changed, 1183 insertions(+), 16 deletions(-) create mode 100644 src/filter-custom.c diff --git a/doc/user/pointer-acceleration.rst b/doc/user/pointer-acceleration.rst index f5339700..1d60702a 100644 --- a/doc/user/pointer-acceleration.rst +++ b/doc/user/pointer-acceleration.rst @@ -19,12 +19,16 @@ Pointer acceleration profiles ------------------------------------------------------------------------------ The profile decides the general method of pointer acceleration. -libinput currently supports two profiles: **"adaptive"** and **"flat"**. +libinput currently supports three profiles: **"adaptive"**, **"flat"** and +**custom**. - The **adaptive** profile is the default profile for all devices and takes the current speed of the device into account when deciding on acceleration. - The **flat** profile is simply a constant factor applied to all device deltas, regardless of the speed of motion (see :ref:`ptraccel-profile-flat`). +- The **custom** profile allows the user to define a custom acceleration + function, giving full control over accelerations behavior at different speed + (see :ref:`ptraccel-profile-custom`). Most of this document describes the adaptive pointer acceleration. @@ -198,3 +202,70 @@ Pointer acceleration on tablets Pointer acceleration for relative motion on tablet devices is a flat acceleration, with the speed setting slowing down or speeding up the pointer motion by a constant factor. Tablets do not allow for switchable profiles. + +.. _ptraccel-profile-custom: + +------------------------------------------------------------------------------ +The custom acceleration profile +------------------------------------------------------------------------------ + +libinput supports a user-defined custom acceleration profile, which can be +adjusted for different movement types supported by a device. Movement types +include pointer movement, scrolling, etc. but the set of supported +movement types depends on the device. + +The custom pointer acceleration profile gives users full control over the +acceleration behavior at different speeds. libinput exposes +an acceleration function ``f(x)`` where the x axis is the device speed in +device units per millisecond and the y axis is the pointer speed. By +supplying the y axis values for this function, users can control the +behavior of the device. + +The user should take into account the native device dpi and screen dpi in +order to achieve the desired behavior/feel. + +The custom acceleration function is defined using ``n`` points which are spaced +uniformly along the x axis, starting from 0 and continuing in constant steps. +At least two points must be defined and there is an implementation-defined +limit on how many points may be added. + +Thus the points defining the custom function are: +``(0 * step, f[0]), (1 * step, f[1]), ..., ((n-1) * step, f[n-1])`` +where ``f`` is a list of ``n`` unitless values defining the acceleration +factor for each velocity. +When a velocity value does not lie exactly on those points, a linear +interpolation of the two closest points will be calculated. +When a velocity value is greater than the max point defined, a linear +extrapolation of the two biggest points will be calculated. + +An example is the curve of ``0.0, 1.0`` with a step of ``1.0``. This curve +is the equivalent of the flat acceleration profile with any input speed `N` +mapped to the same pointer speed `N`. The curve `1.0, 1.0` neutralizes +any input speed differences and results in a fixed pointer speed. + +Supported Movement types: + ++---------------+---------------------------------+----------------------+ +| Movement type | Uses | supported by | ++===============+=================================+======================+ +| Fallback | Catch-all default movement type | All devices | ++---------------+---------------------------------+----------------------+ +| Motion | Used for pointer motion | All devices | ++---------------+---------------------------------+----------------------+ + +If a user does not provide the fallback custom acceleration function, a +flat acceleration function is used, i.e. no acceleration. + +The fallback acceleration may be used for different types of movements, it is +strongly recommended that this acceleration function is a constant function. + +For example, a physical mouse usually has two movement types: pointer +movement and scroll (wheel) movement. As there is no separate movement +type for scroll yet, scroll movement is be accelerated using the Fallback +acceleration function. Pointer movements is accelerated using the Motion +acceleration function. If no Motion acceleration function is set, the +Fallback acceleration function is used. + +When using custom acceleration profile, any calls to set the speed have no +effect on the behavior of the custom acceleration function, but any future calls to +get the speed will reflect the requested speed setting. diff --git a/meson.build b/meson.build index 9e6b2c27..ef454283 100644 --- a/meson.build +++ b/meson.build @@ -292,6 +292,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 1ccf8037..40c5696e 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -2988,18 +2988,26 @@ tp_init_accel(struct tp_dispatch *tp, enum libinput_config_accel_profile which) tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y; tp->accel.xy_scale_coeff = 1.0 * res_x/res_y; - if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) + if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) { filter = create_pointer_accelerator_filter_touchpad_flat(dpi); - else if (evdev_device_has_model_quirk(device, QUIRK_MODEL_LENOVO_X230) || - tp->device->model_flags & EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81) + } else if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM) { + filter = create_custom_accelerator_filter(); + } else if (evdev_device_has_model_quirk(device, QUIRK_MODEL_LENOVO_X230) || + tp->device->model_flags & EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81) { filter = create_pointer_accelerator_filter_lenovo_x230(dpi, use_v_avg); - else if (libevdev_get_id_bustype(device->evdev) == BUS_BLUETOOTH) + } else { + uint64_t eds_threshold = 0; + uint64_t eds_value = 0; + + if (libevdev_get_id_bustype(device->evdev) == BUS_BLUETOOTH) { + eds_threshold = ms2us(50); + eds_value = ms2us(10); + } filter = create_pointer_accelerator_filter_touchpad(dpi, - ms2us(50), - ms2us(10), + eds_threshold, + eds_value, use_v_avg); - else - filter = create_pointer_accelerator_filter_touchpad(dpi, 0, 0, use_v_avg); + } if (!filter) return false; diff --git a/src/evdev.c b/src/evdev.c index 64290335..524ae9a1 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1187,7 +1187,9 @@ evdev_init_accel(struct evdev_device *device, { struct motion_filter *filter = NULL; - if (device->tags & EVDEV_TAG_TRACKPOINT) { + if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM) + filter = create_custom_accelerator_filter(); + else if (device->tags & EVDEV_TAG_TRACKPOINT) { if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) filter = create_pointer_accelerator_filter_trackpoint_flat(device->trackpoint_multiplier); else @@ -1255,7 +1257,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_CUSTOM; } static enum libinput_config_status @@ -1304,6 +1307,20 @@ evdev_accel_config_get_default_profile(struct libinput_device *libinput_device) return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; } +static enum libinput_config_status +evdev_set_accel_config(struct libinput_device *libinput_device, + struct libinput_config_accel *accel_config) +{ + assert(evdev_accel_config_get_profile(libinput_device) == accel_config->profile); + + struct evdev_device *dev = evdev_device(libinput_device); + + if (!filter_set_accel_config(dev->pointer.filter, accel_config)) + return LIBINPUT_CONFIG_STATUS_INVALID; + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + void evdev_device_init_pointer_acceleration(struct evdev_device *device, struct motion_filter *filter) @@ -1321,6 +1338,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_accel_config = evdev_set_accel_config; 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..57cdea0e --- /dev/null +++ b/src/filter-custom.c @@ -0,0 +1,393 @@ +/* + * 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 "filter.h" +#include "filter-private.h" + +#define MOTION_TIMEOUT ms2us(1000) +#define FIRST_MOTION_TIME_INTERVAL ms2us(7) /* random but good enough interval for very first event */ + +struct custom_accel_function { + uint64_t last_time; + double step; + size_t npoints; + double points[]; +}; + +static struct custom_accel_function * +create_custom_accel_function(double step, const double *points, size_t npoints) +{ + if (npoints < LIBINPUT_ACCEL_NPOINTS_MIN || + npoints > LIBINPUT_ACCEL_NPOINTS_MAX) + return NULL; + + if (step <= 0 || step > LIBINPUT_ACCEL_STEP_MAX) + return NULL; + + struct custom_accel_function *cf = zalloc(sizeof(*cf) + npoints * sizeof(*points)); + cf->last_time = 0; + cf->step = step; + cf->npoints = npoints; + memcpy(cf->points, points, sizeof(*points) * npoints); + + return cf; +} + +static void +custom_accel_function_destroy(struct custom_accel_function *cf) +{ + if (cf == NULL) + return; + + free(cf); +} + +static double +custom_accel_function_calculate_speed(struct custom_accel_function *cf, + const struct device_float_coords *unaccelerated, + uint64_t time) +{ + /* Although most devices have a constant polling rate, and for fast + * movements these distances do represent the actual speed, + * for slow movements it is not the case. + * + * Since all devices have a finite resolution, real world events + * for a slow smooth movement could look like: + * Event 1 - (0, 1) - time 0 + * Event 2 - (0, 0) - time 7 - filtered (zero event) + * Event 3 - (1, 0) - time 14 + * Event 4 - (0, 0) - time 21 - filtered (zero event) + * Event 5 - (0, 0) - time 28 - filtered (zero event) + * Event 6 - (0, 1) - time 35 + * + * Not taking the time into account would mean interpreting those events as: + * Move 1 unit over 7 ms + * Pause for 7 ms + * Move 1 unit over 7 ms + * Pause for 14 ms + * Move 1 unit over 7ms + * + * Where in reality this was one smooth movement without pauses, + * so after normalizing for time we get: + * Move 1 unit over 7 ms + * Move 1 unit over 14 ms + * Move 1 unit over 21ms + * + * which should give us better speed estimation. + */ + + /* calculate speed based on time passed since last event */ + double distance = hypot(unaccelerated->x, unaccelerated->y); + /* handle first event in a motion */ + if (time - cf->last_time > MOTION_TIMEOUT) + cf->last_time = time - FIRST_MOTION_TIME_INTERVAL; + + double dt = us2ms_f(time - cf->last_time); + double speed = distance / dt; /* speed is in device-units per ms */ + cf->last_time = time; + + return speed; +} + +static double +custom_accel_function_profile(struct custom_accel_function *cf, + double speed_in) +{ + size_t npoints = cf->npoints; + double step = cf->step; + double *points = cf->points; + + /* calculate the index of the first point used for interpolation */ + size_t i = speed_in / step; + + /* if speed is greater than custom curve's max speed, + use last 2 points for linear extrapolation + (same calculation as linear interpolation) */ + i = min(i, npoints - 2); + + /* the 2 points used for linear interpolation */ + double x0 = step * i; + double x1 = step * (i + 1); + double y0 = points[i]; + double y1 = points[i + 1]; + + /* linear interpolation */ + double speed_out = (y0 * (x1 - speed_in) + y1 * (speed_in - x0)) / step; + + /* We moved (dx, dy) device units within the last N ms. This gives us a + * given speed S in units/ms, that's our accel input. Our curve says map + * that speed S to some other speed S'. + * + * Our device delta is represented by the vector, that vector needs to + * be modified to represent our intended speed. + * + * Example: we moved a delta of 7 over the last 7ms. Our speed is + * thus 1 u/ms, our out speed is 2 u/ms because we want to double our + * speed (points: [0.0, 2.0]). Our delta must thus be 14 - factor of 2, + * or out-speed/in-speed. + * + * Example: we moved a delta of 1 over the last 7ms. Our input speed is + * 1/7 u/ms, our out speed is 1/7ms because we set up a flat accel + * curve (points: [0.0, 1.0]). Our delta must thus be 1 - factor of 1, + * or out-speed/in-speed. + * + * Example: we moved a delta of 1 over the last 21ms. Our input speed is + * 1/21 u/ms, our out speed is 1u/ms because we set up a fixed-speed + * curve (points: [1.0, 1.0]). Our delta must thus be 21 - factor of 21, + * or out-speed/in-speed. + * + * Example: we moved a delta of 21 over the last 7ms. Our input speed is + * 3 u/ms, our out speed is 1u/ms because we set up a fixed-speed + * curve (points: [1.0, 1.0]). Our delta must thus be 7 - factor of 1/3, + * or out-speed/in-speed. + */ + + /* calculate the acceleration factor based on the user desired speed out */ + double accel_factor = speed_out / speed_in; + + return accel_factor; +} + +static struct normalized_coords +custom_accel_function_filter(struct custom_accel_function *cf, + const struct device_float_coords *unaccelerated, + uint64_t time) +{ + double speed = custom_accel_function_calculate_speed(cf, unaccelerated, time); + + double accel_factor = custom_accel_function_profile(cf, speed); + + struct normalized_coords accelerated = { + .x = unaccelerated->x * accel_factor, + .y = unaccelerated->y * accel_factor, + }; + + return accelerated; +} + +struct custom_accelerator { + struct motion_filter base; + struct { + struct custom_accel_function *fallback; + struct custom_accel_function *motion; + } funcs; +}; + +static struct custom_accel_function * +custom_accelerator_get_custom_function(struct custom_accelerator *f, + enum libinput_config_accel_type accel_type) +{ + switch (accel_type) { + case LIBINPUT_ACCEL_TYPE_FALLBACK: + return f->funcs.fallback; + case LIBINPUT_ACCEL_TYPE_MOTION: + return f->funcs.motion ? f->funcs.motion : f->funcs.fallback; + } + + return f->funcs.fallback; +} + +static double +custom_accelerator_profile(enum libinput_config_accel_type accel_type, + struct motion_filter *filter, + double speed_in) +{ + struct custom_accelerator *f = (struct custom_accelerator *)filter; + struct custom_accel_function *cf; + + cf = custom_accelerator_get_custom_function(f, accel_type); + + return custom_accel_function_profile(cf, speed_in); +} + +static struct normalized_coords +custom_accelerator_filter(enum libinput_config_accel_type accel_type, + struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + uint64_t time) +{ + struct custom_accelerator *f = (struct custom_accelerator *)filter; + struct custom_accel_function *cf; + + cf = custom_accelerator_get_custom_function(f, accel_type); + + return custom_accel_function_filter(cf, unaccelerated, time); +} + +static void +custom_accelerator_restart(struct motion_filter *filter, + void *data, + uint64_t time) +{ + /* noop, this function has no effect in the custom interface */ +} + +static void +custom_accelerator_destroy(struct motion_filter *filter) +{ + struct custom_accelerator *f = + (struct custom_accelerator *)filter; + + /* destroy all custom movement functions */ + custom_accel_function_destroy(f->funcs.fallback); + custom_accel_function_destroy(f->funcs.motion); + free(f); +} + +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 bool +custom_accelerator_set_accel_config(struct motion_filter *filter, + struct libinput_config_accel *config) +{ + struct custom_accelerator *f = + (struct custom_accelerator *)filter; + + struct custom_accel_function *fallback = NULL, + *motion = NULL; + + if (config->custom.fallback) { + fallback = create_custom_accel_function(config->custom.fallback->step, + config->custom.fallback->points, + config->custom.fallback->npoints); + if (!fallback) + goto out; + } + + if (config->custom.motion) { + motion = create_custom_accel_function(config->custom.motion->step, + config->custom.motion->points, + config->custom.motion->npoints); + if (!motion) + goto out; + } + + custom_accel_function_destroy(f->funcs.fallback); + custom_accel_function_destroy(f->funcs.motion); + + f->funcs.fallback = fallback; + f->funcs.motion = motion; + + return true; + +out: + custom_accel_function_destroy(fallback); + custom_accel_function_destroy(motion); + + return false; +} + +/* custom profiles and filters for the different accel types: */ + +double +custom_accel_profile_fallback(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time) +{ + return custom_accelerator_profile(LIBINPUT_ACCEL_TYPE_FALLBACK, + filter, + speed_in); +} + +static struct normalized_coords +custom_accelerator_filter_fallback(struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + void *data, + uint64_t time) +{ + return custom_accelerator_filter(LIBINPUT_ACCEL_TYPE_FALLBACK, + filter, + unaccelerated, + time); +} + +double +custom_accel_profile_motion(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time) +{ + return custom_accelerator_profile(LIBINPUT_ACCEL_TYPE_MOTION, + filter, + speed_in); +} + +static struct normalized_coords +custom_accelerator_filter_motion(struct motion_filter *filter, + const struct device_float_coords *unaccelerated, + void *data, + uint64_t time) +{ + return custom_accelerator_filter(LIBINPUT_ACCEL_TYPE_MOTION, + filter, + unaccelerated, + time); +} + +struct motion_filter_interface custom_accelerator_interface = { + .type = LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM, + .filter = custom_accelerator_filter_motion, + .filter_constant = custom_accelerator_filter_fallback, + .restart = custom_accelerator_restart, + .destroy = custom_accelerator_destroy, + .set_speed = custom_accelerator_set_speed, + .set_accel_config = custom_accelerator_set_accel_config, +}; + +struct motion_filter * +create_custom_accelerator_filter(void) +{ + struct custom_accelerator *f = zalloc(sizeof(*f)); + + /* the unit function by default, speed in = speed out, + i.e. no acceleration */ + const double default_step = 1.0; + const double default_points[2] = {0.0, 1.0}; + + /* initialize default acceleration, used as fallback */ + f->funcs.fallback = create_custom_accel_function(default_step, + default_points, + ARRAY_LENGTH(default_points)); + /* Don't initialize other acceleration functions. Those will be + initialized if the user sets their points, otherwise the fallback + acceleration function is used */ + + f->base.interface = &custom_accelerator_interface; + + return &f->base; +} diff --git a/src/filter-private.h b/src/filter-private.h index cee65e0b..ed969f04 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_accel_config)(struct motion_filter *filter, + struct libinput_config_accel *accel_config); }; struct motion_filter { diff --git a/src/filter.c b/src/filter.c index e39839a2..aa60540a 100644 --- a/src/filter.c +++ b/src/filter.c @@ -89,6 +89,18 @@ filter_get_type(struct motion_filter *filter) return filter->interface->type; } +bool +filter_set_accel_config(struct motion_filter *filter, + struct libinput_config_accel *accel_config) +{ + assert(filter_get_type(filter) == accel_config->profile); + + if (!filter->interface->set_accel_config) + return false; + + return filter->interface->set_accel_config(filter, accel_config); +} + void trackers_init(struct pointer_trackers *trackers, int ntrackers) { diff --git a/src/filter.h b/src/filter.h index 7824fe9b..a293e5a6 100644 --- a/src/filter.h +++ b/src/filter.h @@ -103,6 +103,10 @@ typedef double (*accel_profile_func_t)(struct motion_filter *filter, double velocity, uint64_t time); +bool +filter_set_accel_config(struct motion_filter *filter, + struct libinput_config_accel *accel_config); + /* Pointer acceleration types */ struct motion_filter * create_pointer_accelerator_filter_flat(int dpi); @@ -134,6 +138,9 @@ create_pointer_accelerator_filter_trackpoint_flat(double multiplier); struct motion_filter * create_pointer_accelerator_filter_tablet(int xres, int yres); +struct motion_filter * +create_custom_accelerator_filter(void); + /* * Pointer acceleration profiles. */ @@ -163,4 +170,14 @@ trackpoint_accel_profile(struct motion_filter *filter, void *data, double velocity, uint64_t time); +double +custom_accel_profile_fallback(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time); +double +custom_accel_profile_motion(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time); #endif /* FILTER_H */ diff --git a/src/libinput-private.h b/src/libinput-private.h index f20944bb..042ef07f 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -231,6 +231,39 @@ struct libinput_device_config_send_events { enum libinput_config_send_events_mode (*get_default_mode)(struct libinput_device *device); }; +/** + * Custom acceleration function min number of points + * At least 2 points are required for linear interpolation + */ +#define LIBINPUT_ACCEL_NPOINTS_MIN 2 + +/** + * Custom acceleration function max number of points + * an arbitrary limit of sample points + * it should be more than enough for everyone + */ +#define LIBINPUT_ACCEL_NPOINTS_MAX 64 + +/** + * Custom acceleration function max step size + */ +#define LIBINPUT_ACCEL_STEP_MAX 10000 + +struct libinput_config_accel_custom_func { + double step; + size_t npoints; + double points[LIBINPUT_ACCEL_NPOINTS_MAX]; +}; + +struct libinput_config_accel { + enum libinput_config_accel_profile profile; + + struct { + struct libinput_config_accel_custom_func *fallback; + struct libinput_config_accel_custom_func *motion; + } custom; +}; + struct libinput_device_config_accel { int (*available)(struct libinput_device *device); enum libinput_config_status (*set_speed)(struct libinput_device *device, @@ -243,6 +276,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_accel_config)(struct libinput_device *device, + struct libinput_config_accel *accel_config); }; struct libinput_device_config_natural_scroll { diff --git a/src/libinput.c b/src/libinput.c index 9eebbdec..37451fa1 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -4159,6 +4159,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_CUSTOM: break; default: return LIBINPUT_CONFIG_STATUS_INVALID; @@ -4171,6 +4172,121 @@ libinput_device_config_accel_set_profile(struct libinput_device *device, return device->config.accel->set_profile(device, profile); } +static inline struct libinput_config_accel_custom_func * +libinput_config_accel_custom_func_create(void) +{ + struct libinput_config_accel_custom_func *func = zalloc(sizeof(*func)); + + func->step = 1.0; + func->npoints = 2; + func->points[0] = 0.0; /* default to a flat unaccelerated function */ + func->points[1] = 1.0; + + return func; +} + +static inline void +libinput_config_accel_custom_func_destroy(struct libinput_config_accel_custom_func * func) +{ + free(func); +} + +LIBINPUT_EXPORT struct libinput_config_accel * +libinput_config_accel_create(enum libinput_config_accel_profile profile) +{ + struct libinput_config_accel *config = zalloc(sizeof(*config)); + + config->profile = profile; + + switch (profile) { + case LIBINPUT_CONFIG_ACCEL_PROFILE_NONE: + break; + case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT: + case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE: + return config; + case LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM: + config->custom.fallback = libinput_config_accel_custom_func_create(); + return config; + } + + free(config); + return NULL; +} + +LIBINPUT_EXPORT void +libinput_config_accel_destroy(struct libinput_config_accel *accel_config) +{ + libinput_config_accel_custom_func_destroy(accel_config->custom.fallback); + libinput_config_accel_custom_func_destroy(accel_config->custom.motion); + free(accel_config); +} + +LIBINPUT_EXPORT enum libinput_config_status +libinput_device_config_accel_apply(struct libinput_device *device, + struct libinput_config_accel *accel_config) +{ + enum libinput_config_status status; + status = libinput_device_config_accel_set_profile(device, accel_config->profile); + if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) + return status; + + switch (accel_config->profile) { + case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT: + case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE: + { + double speed = libinput_device_config_accel_get_default_speed(device); + return libinput_device_config_accel_set_speed(device, speed); + } + case LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM: + return device->config.accel->set_accel_config(device, accel_config); + + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } +} + +LIBINPUT_EXPORT enum libinput_config_status +libinput_config_accel_set_points(struct libinput_config_accel *config, + enum libinput_config_accel_type accel_type, + double step, size_t npoints, double *points) +{ + if (config->profile != LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM) + return LIBINPUT_CONFIG_STATUS_INVALID; + + switch (accel_type) { + case LIBINPUT_ACCEL_TYPE_FALLBACK: + case LIBINPUT_ACCEL_TYPE_MOTION: + break; + default: + return LIBINPUT_CONFIG_STATUS_INVALID; + } + + if (step <= 0 || step > LIBINPUT_ACCEL_STEP_MAX) + return LIBINPUT_CONFIG_STATUS_INVALID; + + if (npoints < LIBINPUT_ACCEL_NPOINTS_MIN || npoints > LIBINPUT_ACCEL_NPOINTS_MAX) + return LIBINPUT_CONFIG_STATUS_INVALID; + + struct libinput_config_accel_custom_func *func = libinput_config_accel_custom_func_create(); + + func->step = step; + func->npoints = npoints; + memcpy(func->points, points, sizeof(*points) * npoints); + + switch (accel_type) { + case LIBINPUT_ACCEL_TYPE_FALLBACK: + libinput_config_accel_custom_func_destroy(config->custom.fallback); + config->custom.fallback = func; + break; + case LIBINPUT_ACCEL_TYPE_MOTION: + libinput_config_accel_custom_func_destroy(config->custom.motion); + config->custom.motion = func; + break; + } + + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + LIBINPUT_EXPORT int libinput_device_config_scroll_has_natural_scroll(struct libinput_device *device) { diff --git a/src/libinput.h b/src/libinput.h index 9f4aeb31..1d477b68 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -5172,6 +5172,12 @@ libinput_device_config_accel_set_speed(struct libinput_device *device, * returned value is normalized to a range of [-1, 1]. * See libinput_device_config_accel_set_speed() for details. * + * If the current acceleration profile is @ref + * LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM, 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 * * @return The current speed, range -1 to 1 @@ -5225,8 +5231,176 @@ 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 acceleration profile. Device movement acceleration depends + * on user defined custom acceleration functions for each movement + * type. + * + * @see libinput_device_config_accel_set_points + */ + LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM = (1 << 2), }; +/** + * @ingroup config + * + * A handle for configuration pointer acceleration. + * + * @warning Unlike other structs pointer acceleration configuration is + * considered transient and not refcounted. Calling + * libinput_config_accel_destroy() will destroy the configuration. + * + * To configure pointer acceleration, first create a config of a desired + * acceleration profile with libinput_config_accel_create(), then + * configure the profile-specific acceleration properties. + * + * In this version of libinput, this pointer acceleration configuration + * only provides configuration for @ref LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM. + * + * For @ref LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM use + * @ref libinput_config_accel_set_points. + * + * Once set up, apply the configuration to a device using + * libinput_device_config_accel_apply(). Once applied, + * destroy it with libinput_config_accel_destroy(). + * + * @since 1.23 + */ +struct libinput_config_accel; + +/** + * @ingroup config + * + * Create an acceleration configuration of a given profile. + * + * Note that in this version of libinput, only the + * @ref LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM profile provides configuration + * options. All other acceleration profiles, when applied, will merely switch + * the profile and reset any profile-specific options to the default values. + * + * @param profile The profile of the newly created acceleration configuration. + * + * @return The newly created acceleration configuration or NULL on error. + * + * @warning Unlike other structs pointer acceleration configuration is + * considered transient and not refcounted. Calling + * libinput_config_accel_destroy() will destroy the configuration. + * + * @see libinput_config_accel + * @since 1.23 + */ +struct libinput_config_accel * +libinput_config_accel_create(enum libinput_config_accel_profile profile); + +/** + * @ingroup config + * + * Destroy an acceleration configuration. + * + * @warning Unlike other structs pointer acceleration configuration is + * considered transient and not refcounted. Calling + * libinput_config_accel_destroy() will destroy the configuration. + * + * @param accel_config The acceleration configuration to destroy. + * + * @see libinput_config_accel + * @since 1.23 + */ +void +libinput_config_accel_destroy(struct libinput_config_accel *accel_config); + +/** + * @ingroup config + * + * Apply this pointer acceleration configuration to the device. This changes the + * device's pointer acceleration method to the method given in + * libinput_config_accel_create() and applies all other configuration settings. + * + * Once applied, call libinput_config_accel_destroy() to destroy the + * configuration struct. + * + * @param device The device to configure. + * @param accel_config The acceleration configuration. + * + * @return A config status code. + * + * @see libinput_config_accel + * @since 1.23 + */ +enum libinput_config_status +libinput_device_config_accel_apply(struct libinput_device *device, + struct libinput_config_accel *accel_config); + +/** + * @ingroup config + * + * Acceleration types are categories of movement by a device that may have + * specific acceleration functions applied. A device always supports the + * @ref LIBINPUT_ACCEL_TYPE_MOTION type (for regular pointer motion). Other + * types (e.g. scrolling) may be added in the future. + * + * The special type @ref LIBINPUT_ACCEL_TYPE_FALLBACK specifies the acceleration + * function to be moved for any movement produced by the device that does not + * have a specific acceleration type defined. + * + * Use to specify the acceleration function type in + * @ref libinput_config_accel_set_points + * + * Each device implements a subset of those types, see a list of supported + * devices for each movement type definition. + * + * @see LIBINPUT_ACCEL_ARG_TYPE + * @since 1.23 + */ +enum libinput_config_accel_type { + /** + * The default acceleration type used as a fallback when other + * acceleration types are not provided. + */ + LIBINPUT_ACCEL_TYPE_FALLBACK = 0, + /** + * Acceleration type for regular pointer movement. This + * type is always supported. + */ + LIBINPUT_ACCEL_TYPE_MOTION, +}; + +/** + * @ingroup config + * + * Defines the acceleration function for a given movement type + * in an acceleration configuration with the profile + * @ref LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM. + * + * Movement types are specific to each device, @see libinput_config_accel_type. + * + * Each custom acceleration function is defined by ``n`` points spaced uniformly + * along the x-axis starting from 0 and continuing in a constant step size. + * There by the function is defined by the following points: + * (0 * step, f[0]), (1 * step, f[1]), ..., ((n - 1) * step, f[n - 1]). + * The x-axis represents the device-speed in device units per millisecond. + * The y-axis represents the pointer-speed. + * + * It is up to the user to define those values in accordance with device DPI + * and screen DPI. + * + * @param accel_config The acceleration configuration to modify. + * @param accel_type The movement type to configure a custom function for. + * @param step The distance between each point along the x-axis. + * @param npoints The number of points of the custom acceleration function. + * @param points The points' y-values of the custom acceleration function. + * + * @return A config status code. + * + * @see libinput_config_accel + * @since 1.23 + */ +enum libinput_config_status +libinput_config_accel_set_points(struct libinput_config_accel *accel_config, + enum libinput_config_accel_type accel_type, + double step, size_t npoints, double *points); + /** * @ingroup config * diff --git a/src/libinput.sym b/src/libinput.sym index 434e9241..b89059c1 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -326,3 +326,10 @@ LIBINPUT_1.21 { libinput_device_config_dwtp_get_enabled; libinput_device_config_dwtp_get_default_enabled; } LIBINPUT_1.19; + +LIBINPUT_1.23 { + libinput_config_accel_create; + libinput_config_accel_destroy; + libinput_device_config_accel_apply; + libinput_config_accel_set_points; +} LIBINPUT_1.21; \ No newline at end of file diff --git a/src/util-strings.h b/src/util-strings.h index 0bbd6f67..4d11a0f0 100644 --- a/src/util-strings.h +++ b/src/util-strings.h @@ -274,6 +274,47 @@ strv_free(char **strv) { free (strv); } +/** + * parse a string containing a list of doubles into a double array. + * + * @param in string to parse + * @param separator string used to separate double in list e.g. "," + * @param result double array + * @param length length of double array + * @return true when parsed successfully otherwise false + */ +static inline double * +double_array_from_string(const char *in, + const char *separator, + size_t *length) +{ + double *result = NULL; + *length = 0; + + size_t nelem; + char **strv = strv_from_string(in, separator, &nelem); + if (!strv) + return result; + + double *numv = zalloc(sizeof(double) * nelem); + for (size_t idx = 0; idx < nelem; idx++) { + double val; + if (!safe_atod(strv[idx], &val)) + goto out; + + numv[idx] = val; + } + + result = numv; + numv = NULL; + *length = nelem; + +out: + strv_free(strv); + free(numv); + return result; +} + struct key_value_str{ char *key; char *value; diff --git a/src/util-time.h b/src/util-time.h index a5ac4b57..ec1f9937 100644 --- a/src/util-time.h +++ b/src/util-time.h @@ -75,6 +75,12 @@ us2ms(uint64_t us) return (uint32_t)(us / 1000); } +static inline double +us2ms_f(uint64_t us) +{ + return (double)us / 1000.0; +} + static inline uint64_t tv2us(const struct timeval *tv) { diff --git a/test/test-pointer.c b/test/test-pointer.c index ee675f39..c9f2e344 100644 --- a/test/test-pointer.c +++ b/test/test-pointer.c @@ -2220,6 +2220,7 @@ 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); + ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); status = libinput_device_config_accel_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); @@ -2235,6 +2236,90 @@ START_TEST(pointer_accel_profile_defaults) 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); + + status = libinput_device_config_accel_set_profile(device, + LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); + 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_CUSTOM); +} +END_TEST + +START_TEST(pointer_accel_config_reset_to_defaults) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + double default_speed = libinput_device_config_accel_get_default_speed(device); + + /* There are no settings for these profiles to toggle, so we expect it + * to simply reset to defaults */ + enum libinput_config_accel_profile profiles[] = { + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + }; + + ARRAY_FOR_EACH(profiles, profile) { + ck_assert_int_eq(libinput_device_config_accel_set_speed(device, 1.0), + LIBINPUT_CONFIG_STATUS_SUCCESS); + + ck_assert_double_eq(libinput_device_config_accel_get_speed(device), 1.0); + + struct libinput_config_accel *config = + libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + ck_assert_int_eq(libinput_device_config_accel_apply(device, config), + LIBINPUT_CONFIG_STATUS_SUCCESS); + ck_assert_double_eq(libinput_device_config_accel_get_speed(device), + default_speed); + libinput_config_accel_destroy(config); + } +} +END_TEST + +START_TEST(pointer_accel_config) +{ + 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; + double custom_speed[] = {0.1234, -0.567, 0.89}; + double custom_step[] = {0.5, 0.003, 2.7}; + double custom_npoints = 4; + double custom_points[3][4] = {{1.0, 2.0, 2.5, 2.6}, + {0.1, 0.3, 0.4, 0.45}, + {1.0, 3.0, 4.5, 4.5}}; + + ck_assert(libinput_device_config_accel_is_available(device)); + + struct libinput_config_accel *config_custom_default = + libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); + struct libinput_config_accel *config_custom_changed = + libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); + + ck_assert_ptr_nonnull(config_custom_default); + ck_assert_ptr_nonnull(config_custom_changed); + + + for (size_t idx = 0; idx < ARRAY_LENGTH(custom_speed); idx++) { + status = libinput_config_accel_set_points(config_custom_changed, + LIBINPUT_ACCEL_TYPE_FALLBACK, + custom_step[idx], + custom_npoints, + custom_points[idx]); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + status = libinput_device_config_accel_apply(device, config_custom_changed); + 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_CUSTOM); + + status = libinput_device_config_accel_apply(device, config_custom_default); + 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_CUSTOM); + } + + libinput_config_accel_destroy(config_custom_default); + libinput_config_accel_destroy(config_custom_changed); } END_TEST @@ -2257,6 +2342,10 @@ START_TEST(pointer_accel_profile_invalid) status = libinput_device_config_accel_set_profile(device, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE |LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); + + status = libinput_device_config_accel_set_profile(device, + LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM |LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID); } END_TEST @@ -3633,6 +3722,8 @@ TEST_COLLECTION(pointer) litest_add(pointer_accel_direction_change, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add(pointer_accel_profile_defaults, LITEST_RELATIVE, LITEST_TOUCHPAD); litest_add(pointer_accel_profile_defaults, LITEST_TOUCHPAD, LITEST_ANY); + litest_add(pointer_accel_config_reset_to_defaults, LITEST_RELATIVE, LITEST_ANY); + litest_add(pointer_accel_config, LITEST_RELATIVE, LITEST_ANY); litest_add(pointer_accel_profile_invalid, LITEST_RELATIVE, LITEST_ANY); litest_add(pointer_accel_profile_noaccel, LITEST_ANY, LITEST_TOUCHPAD|LITEST_RELATIVE|LITEST_TABLET); litest_add(pointer_accel_profile_flat_motion_relative, LITEST_RELATIVE, LITEST_TOUCHPAD); diff --git a/test/test-utils.c b/test/test-utils.c index a5248147..fa307031 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -1119,6 +1119,50 @@ START_TEST(strsplit_test) } END_TEST +START_TEST(double_array_from_string_test) +{ + struct double_array_from_string_test { + const char *string; + const char *delim; + const double array[10]; + const size_t len; + const bool result; + } tests[] = { + { "1 2 3", " ", { 1, 2, 3 }, 3 }, + { "1", " ", { 1 }, 1 }, + { "1,2.5,", ",", { 1, 2.5 }, 2 }, + { "1.0 2", " ", { 1, 2.0 }, 2 }, + { " 1 2", " ", { 1, 2 }, 2 }, + { " ; 1;2 3.5 ;;4.1", "; ", { 1, 2, 3.5, 4.1 }, 4 }, + /* special cases */ + { "1 two", " ", { 0 }, 0 }, + { "one two", " ", { 0 }, 0 }, + { "one 2", " ", { 0 }, 0 }, + { "", " ", { 0 }, 0 }, + { " ", " ", { 0 }, 0 }, + { " ", " ", { 0 }, 0 }, + { "", " ", { 0 }, 0 }, + { "oneoneone", "one", { 0 }, 0 }, + { NULL, NULL, { 0 }, 0 } + }; + struct double_array_from_string_test *t = tests; + + while (t->string) { + size_t len; + double *array = double_array_from_string(t->string, + t->delim, + &len); + ck_assert_int_eq(len, t->len); + + for (size_t idx = 0; idx < len; idx++) + ck_assert_double_eq(array[idx], t->array[idx]); + + free(array); + t++; + } +} +END_TEST + START_TEST(strargv_test) { struct argv_test { @@ -1571,6 +1615,7 @@ litest_utils_suite(void) tcase_add_test(tc, safe_atou_base_8_test); tcase_add_test(tc, safe_atod_test); tcase_add_test(tc, strsplit_test); + tcase_add_test(tc, double_array_from_string_test); tcase_add_test(tc, strargv_test); tcase_add_test(tc, kvsplit_double_test); tcase_add_test(tc, strjoin_test); diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man index 7b0c3860..628d8716 100644 --- a/tools/libinput-debug-events.man +++ b/tools/libinput-debug-events.man @@ -94,11 +94,28 @@ 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] +.B \-\-set\-profile=[adaptive|flat|custom] Set pointer acceleration profile .TP 8 .B \-\-set\-speed= Set pointer acceleration speed. The allowed range is [-1, 1]. +This only applies to the flat or adaptive profile. +.TP 8 +.B \-\-set\-custom\-points=";...;" +Sets the n points defining a custom acceleration function +The points are defined in a semicolon-separated list of floating point +non-negative numbers. Defaults to "0.0;1.0". +This only applies to the custom profile. +.TP 8 +.B \-\-set\-custom\-step= +Sets the distance along the x-axis between each point, starting from 0. +Defaults to 1.0. +This only applies to the custom profile. +.TP 8 +.B \-\-set\-custom\-type=[fallback|motion] +Sets the type of the custom acceleration function. +Defaults to fallback. +This only applies to the custom profile. .TP 8 .B \-\-set\-tap\-map=[lrm|lmr] Set button mapping for tapping diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c index afed6464..0f738f6d 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_CUSTOM) ? "*" : "", + (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM) ? "custom" : ""); return str; } diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c index 3fbdf876..f763e3ba 100644 --- a/tools/ptraccel-debug.c +++ b/tools/ptraccel-debug.c @@ -191,6 +191,10 @@ usage(void) " touchpad ... the touchpad motion filter\n" " x230 ... custom filter for the Lenovo x230 touchpad\n" " trackpoint... trackpoint motion filter\n" + " custom ... custom motion filter, use --custom-points and --custom-step with this argument\n" + "--custom-points=\";...;\" ... n points defining a custom acceleration function\n" + "--custom-step= ... distance along the x-axis between each point, \n" + " starting from 0. defaults to 1.0\n" "\n" "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n" "and the arguments are interpreted as sequence of delta x coordinates\n" @@ -226,6 +230,13 @@ main(int argc, char **argv) const char *filter_type = "linear"; accel_profile_func_t profile = NULL; double tp_multiplier = 1.0; + struct libinput_config_accel_custom_func custom_func = { + .step = 1.0, + .npoints = 2, + .points = {0.0, 1.0}, + }; + struct libinput_config_accel *accel_config = + libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); enum { OPT_HELP = 1, @@ -236,6 +247,8 @@ main(int argc, char **argv) OPT_SPEED, OPT_DPI, OPT_FILTER, + OPT_CUSTOM_POINTS, + OPT_CUSTOM_STEP, }; while (1) { @@ -250,6 +263,8 @@ main(int argc, char **argv) {"speed", 1, 0, OPT_SPEED }, {"dpi", 1, 0, OPT_DPI }, {"filter", 1, 0, OPT_FILTER }, + {"custom-points", 1, 0, OPT_CUSTOM_POINTS }, + {"custom-step", 1, 0, OPT_CUSTOM_STEP }, {0, 0, 0, 0} }; @@ -307,6 +322,31 @@ main(int argc, char **argv) case OPT_FILTER: filter_type = optarg; break; + case OPT_CUSTOM_POINTS: { + size_t npoints; + double *points = double_array_from_string(optarg, + ";", + &npoints); + if (!points || + npoints < LIBINPUT_ACCEL_NPOINTS_MIN || + npoints > LIBINPUT_ACCEL_NPOINTS_MAX) { + fprintf(stderr, + "Invalid --custom-points\n" + "Please provide at least 2 points separated by a semicolon\n" + " e.g. --custom-points=\"1.0;1.5\"\n"); + free(points); + return 1; + } + custom_func.npoints = npoints; + memcpy(custom_func.points, + points, + sizeof(*points) * npoints); + free(points); + break; + } + case OPT_CUSTOM_STEP: + custom_func.step = strtod(optarg, NULL); + break; default: usage(); exit(1); @@ -335,6 +375,15 @@ main(int argc, char **argv) filter = create_pointer_accelerator_filter_trackpoint(tp_multiplier, use_averaging); profile = trackpoint_accel_profile; + } else if (streq(filter_type, "custom")) { + libinput_config_accel_set_points(accel_config, + LIBINPUT_ACCEL_TYPE_MOTION, + custom_func.step, + custom_func.npoints, + custom_func.points); + filter = create_custom_accelerator_filter(); + profile = custom_accel_profile_motion; + filter_set_accel_config(filter, accel_config); } else { fprintf(stderr, "Invalid filter type %s\n", filter_type); return 1; @@ -378,6 +427,7 @@ main(int argc, char **argv) break; } + libinput_config_accel_destroy(accel_config); filter_destroy(filter); return 0; diff --git a/tools/shared.c b/tools/shared.c index d7791838..ee80eb93 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -114,6 +114,12 @@ tools_init_options(struct tools_options *options) options->scroll_button_lock = -1; options->speed = 0.0; options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; + /* initialize accel args */ + static double points[] = {0.0, 1.0}; + options->custom_points = points; + options->custom_npoints = ARRAY_LENGTH(points); + options->custom_type = LIBINPUT_ACCEL_TYPE_FALLBACK; + options->custom_step = 1.0; } int @@ -252,6 +258,8 @@ tools_parse_option(int option, options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; else if (streq(optarg, "flat")) options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; + else if (streq(optarg, "custom")) + options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM; else return 1; break; @@ -273,8 +281,39 @@ tools_parse_option(int option, "%s", optarg); break; + case OPT_CUSTOM_POINTS: + if (!optarg) + return 1; + options->custom_points = double_array_from_string(optarg, + ";", + &options->custom_npoints); + if (!options->custom_points || options->custom_npoints < 2) { + fprintf(stderr, + "Invalid --set-custom-points\n" + "Please provide at least 2 points separated by a semicolon\n" + " e.g. --set-custom-points=\"1.0;1.5\"\n"); + return 1; + } + break; + case OPT_CUSTOM_STEP: + if (!optarg) + return 1; + options->custom_step = strtod(optarg, NULL); + break; + case OPT_CUSTOM_TYPE: + if (!optarg) + return 1; + if (streq(optarg, "fallback")) + options->custom_type = LIBINPUT_ACCEL_TYPE_FALLBACK; + else if (streq(optarg, "motion")) + options->custom_type = LIBINPUT_ACCEL_TYPE_MOTION; + else { + fprintf(stderr, "Invalid --set-custom-type\n" + "Valid custom types: fallback|motion\n"); + return 1; + } + break; } - return 0; } @@ -467,6 +506,18 @@ tools_device_apply_config(struct libinput_device *device, libinput_device_config_accel_set_profile(device, options->profile); } + + if (options->profile == LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM) { + struct libinput_config_accel *config = + libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); + libinput_config_accel_set_points(config, + options->custom_type, + options->custom_step, + options->custom_npoints, + options->custom_points); + libinput_device_config_accel_apply(device, config); + libinput_config_accel_destroy(config); + } } static char* diff --git a/tools/shared.h b/tools/shared.h index fc59ae00..825a51f5 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -59,6 +59,9 @@ enum configuration_options { OPT_PROFILE, OPT_DISABLE_SENDEVENTS, OPT_APPLY_TO, + OPT_CUSTOM_POINTS, + OPT_CUSTOM_STEP, + OPT_CUSTOM_TYPE, }; #define CONFIGURATION_OPTIONS \ @@ -87,7 +90,10 @@ enum configuration_options { { "set-profile", required_argument, 0, OPT_PROFILE }, \ { "set-tap-map", required_argument, 0, OPT_TAP_MAP }, \ { "set-speed", required_argument, 0, OPT_SPEED },\ - { "apply-to", required_argument, 0, OPT_APPLY_TO } + { "apply-to", required_argument, 0, OPT_APPLY_TO },\ + { "set-custom-points", required_argument, 0, OPT_CUSTOM_POINTS },\ + { "set-custom-step", required_argument, 0, OPT_CUSTOM_STEP },\ + { "set-custom-type", required_argument, 0, OPT_CUSTOM_TYPE } enum tools_backend { BACKEND_NONE, @@ -114,6 +120,10 @@ struct tools_options { int dwtp; enum libinput_config_accel_profile profile; char disable_pattern[64]; + enum libinput_config_accel_type custom_type; + double custom_step; + size_t custom_npoints; + double *custom_points; }; void tools_init_options(struct tools_options *options); -- 2.34.1