Introduce custom acceleration profile
authorYinon Burgansky <yinonburgansky@gmail.com>
Mon, 12 Dec 2022 22:23:59 +0000 (00:23 +0200)
committerPeter Hutterer <peter.hutterer@who-t.net>
Tue, 17 Jan 2023 01:46:17 +0000 (01:46 +0000)
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 <peter.hutterer@who-t.net>
21 files changed:
doc/user/pointer-acceleration.rst
meson.build
src/evdev-mt-touchpad.c
src/evdev.c
src/filter-custom.c [new file with mode: 0644]
src/filter-private.h
src/filter.c
src/filter.h
src/libinput-private.h
src/libinput.c
src/libinput.h
src/libinput.sym
src/util-strings.h
src/util-time.h
test/test-pointer.c
test/test-utils.c
tools/libinput-debug-events.man
tools/libinput-list-devices.c
tools/ptraccel-debug.c
tools/shared.c
tools/shared.h

index f533970083f621506130beed881bd96df84fc5fd..1d60702a27c6911da2ce77a13cec91ed2d8310e8 100644 (file)
@@ -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.
index 9e6b2c270580b81fd70fac082057fddc9857d27d..ef45428337cfe6fe4f1da89da5c73335e1037d3f 100644 (file)
@@ -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',
index 1ccf80370695cfbb67c1a2967de5df9aed6f4d01..40c5696ef8e568a3050ff18423a8efac06d55a21 100644 (file)
@@ -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;
index 64290335e5d92fa73f1e34fa49e89748d6b0fc28..524ae9a112c47caf14c6c84848dd52109365ccd9 100644 (file)
@@ -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 (file)
index 0000000..57cdea0
--- /dev/null
@@ -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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#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;
+}
index cee65e0b473291d89a8f3f9eb5f8f9fa48b400d7..ed969f04ee49fe39588af14e8467cfe1aff2fc6e 100644 (file)
@@ -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 {
index e39839a259e7466b1abe77fd2befd26b0343a7d0..aa60540a5ba8671bae3308a402c2b18c84961a21 100644 (file)
@@ -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)
 {
index 7824fe9bcd2900d96dbeb228a8c51848f72e0808..a293e5a64c5e353d8116cb3c4de8aab723413a77 100644 (file)
@@ -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 */
index f20944bb0dbf8981a4de48622dcab270224c7e45..042ef07fd1703c3ce8bc75aff5457f7fe0753544 100644 (file)
@@ -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 {
index 9eebbdec33a85d2b91ae7949d517d5086c4b9a5a..37451fa12475826fd40dd938dbe52a9012732d9c 100644 (file)
@@ -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)
 {
index 9f4aeb31759f5a2b5167b29c45500ed500dcc684..1d477b680f0d901f3377e96b678fda73886c6205 100644 (file)
@@ -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 <b>not</b> refcounted. Calling
+ * libinput_config_accel_destroy() <b>will</b> 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 <b>not</b> refcounted. Calling
+ * libinput_config_accel_destroy() <b>will</b> 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 <b>not</b> refcounted. Calling
+ * libinput_config_accel_destroy() <b>will</b> 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
  *
index 434e9241ef7e87da3513ab9ec73a22faf396617f..b89059c11a2b4e0b8607830c2df5544a345b7c0b 100644 (file)
@@ -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
index 0bbd6f67b5ed5fbd37e5e2675b39e299561c387e..4d11a0f0eac3c0483d02e84861b7cc42ef860cea 100644 (file)
@@ -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;
index a5ac4b5714205ab9c02cce45a755923fd7862016..ec1f9937bbc3dc323cede2ce44d43e69ae763675 100644 (file)
@@ -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)
 {
index ee675f398f2eff93c7b8c553d3927fb037d87ee3..c9f2e344b5c42cf415936c01da45b0f399c68e6f 100644 (file)
@@ -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);
index a5248147c6154ce1a2dc30f4b83adc572faadc22..fa307031543686f2a0e62204772f0fbcb5a9a462 100644 (file)
@@ -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);
index 7b0c3860990c6cb30b83ab59f0fdaa0e6f4e7fae..628d87162c8ec01ead1c7980b93fe938dfea71f6 100644 (file)
@@ -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=<value>
 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="<value>;...;<value>"
+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=<value>
+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
index afed64649427d6eeb3d16c1eaa911a66878739aa..0f738f6d2a6c4abec0114c7779467b016f6cba7e 100644 (file)
@@ -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;
 }
index 3fbdf87662f24b725a32737b81fb63c8db96bacf..f763e3ba44df3d34db0d72d54f9ad07ebd153ba2 100644 (file)
@@ -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=\"<double>;...;<double>\"  ... n points defining a custom acceleration function\n"
+              "--custom-step=<double>  ... 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;
index d779183878dd36ff3982a799cd5ffe6bb2f4905c..ee80eb93e9db41e0b1145ee2759eb5772b116b26 100644 (file)
@@ -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*
index fc59ae00f406e10197a277b5d59393f5a3ad1002..825a51f596d2126b91e49bd4832a940bbcb2330a 100644 (file)
@@ -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);