libinput/src/filter-custom.c
José Expósito f6a1f264df sparse: make some variables static
Fix warnings about variables that should be made static when compiling
with Sparse enabled:

        $ CC=cgcc meson builddir

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/987>
2024-03-18 23:35:34 +00:00

439 lines
13 KiB
C

/*
* 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;
for (size_t idx = 0; idx < npoints; idx++) {
if (points[idx] < LIBINPUT_ACCEL_POINT_MIN_VALUE ||
points[idx] > LIBINPUT_ACCEL_POINT_MAX_VALUE)
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;
struct custom_accel_function *scroll;
} 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;
case LIBINPUT_ACCEL_TYPE_SCROLL:
return f->funcs.scroll ? f->funcs.scroll : 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);
custom_accel_function_destroy(f->funcs.scroll);
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,
*scroll = 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;
}
if (config->custom.scroll) {
scroll = create_custom_accel_function(config->custom.scroll->step,
config->custom.scroll->points,
config->custom.scroll->npoints);
if (!scroll)
goto out;
}
custom_accel_function_destroy(f->funcs.fallback);
custom_accel_function_destroy(f->funcs.motion);
custom_accel_function_destroy(f->funcs.scroll);
f->funcs.fallback = fallback;
f->funcs.motion = motion;
f->funcs.scroll = scroll;
return true;
out:
custom_accel_function_destroy(fallback);
custom_accel_function_destroy(motion);
custom_accel_function_destroy(scroll);
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);
}
double
custom_accel_profile_scroll(struct motion_filter *filter,
void *data,
double speed_in,
uint64_t time)
{
return custom_accelerator_profile(LIBINPUT_ACCEL_TYPE_SCROLL,
filter,
speed_in);
}
static struct normalized_coords
custom_accelerator_filter_scroll(struct motion_filter *filter,
const struct device_float_coords *unaccelerated,
void *data,
uint64_t time)
{
return custom_accelerator_filter(LIBINPUT_ACCEL_TYPE_SCROLL,
filter,
unaccelerated,
time);
}
static struct motion_filter_interface custom_accelerator_interface = {
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM,
.filter = custom_accelerator_filter_motion,
.filter_constant = custom_accelerator_filter_fallback,
.filter_scroll = custom_accelerator_filter_scroll,
.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;
}