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);