mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-27 13:50:08 +01:00
Leave a narrow gap so the mouse moves excruciatingly slow instead of not moving at all. This allows to recover from overexcited mouse speed slider movements. https://bugs.freedesktop.org/show_bug.cgi?id=102501 Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
1517 lines
43 KiB
C
1517 lines
43 KiB
C
/*
|
|
* Copyright © 2006-2009 Simon Thum
|
|
* Copyright © 2012 Jonas Ådahl
|
|
* Copyright © 2014-2015 Red Hat, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#include "filter.h"
|
|
#include "libinput-util.h"
|
|
#include "filter-private.h"
|
|
|
|
/* Once normalized, touchpads see the same acceleration as mice. that is
|
|
* technically correct but subjectively wrong, we expect a touchpad to be a
|
|
* lot slower than a mouse. Apply a magic factor to slow down all movements
|
|
*/
|
|
#define TP_MAGIC_SLOWDOWN 0.37 /* unitless factor */
|
|
|
|
/* Convert speed/velocity from units/us to units/ms */
|
|
static inline double
|
|
v_us2ms(double units_per_us)
|
|
{
|
|
return units_per_us * 1000.0;
|
|
}
|
|
|
|
static inline double
|
|
v_us2s(double units_per_us)
|
|
{
|
|
return units_per_us * 1000000.0;
|
|
}
|
|
|
|
/* Convert speed/velocity from units/ms to units/us */
|
|
static inline double
|
|
v_ms2us(double units_per_ms)
|
|
{
|
|
return units_per_ms/1000.0;
|
|
}
|
|
|
|
static inline struct normalized_coords
|
|
normalize_for_dpi(const struct device_float_coords *coords, int dpi)
|
|
{
|
|
struct normalized_coords norm;
|
|
|
|
norm.x = coords->x * DEFAULT_MOUSE_DPI/dpi;
|
|
norm.y = coords->y * DEFAULT_MOUSE_DPI/dpi;
|
|
|
|
return norm;
|
|
}
|
|
|
|
struct normalized_coords
|
|
filter_dispatch(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
return filter->interface->filter(filter, unaccelerated, data, time);
|
|
}
|
|
|
|
struct normalized_coords
|
|
filter_dispatch_constant(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
return filter->interface->filter_constant(filter, unaccelerated, data, time);
|
|
}
|
|
|
|
void
|
|
filter_restart(struct motion_filter *filter,
|
|
void *data, uint64_t time)
|
|
{
|
|
if (filter->interface->restart)
|
|
filter->interface->restart(filter, data, time);
|
|
}
|
|
|
|
void
|
|
filter_destroy(struct motion_filter *filter)
|
|
{
|
|
if (!filter || !filter->interface->destroy)
|
|
return;
|
|
|
|
filter->interface->destroy(filter);
|
|
}
|
|
|
|
bool
|
|
filter_set_speed(struct motion_filter *filter,
|
|
double speed_adjustment)
|
|
{
|
|
return filter->interface->set_speed(filter, speed_adjustment);
|
|
}
|
|
|
|
double
|
|
filter_get_speed(struct motion_filter *filter)
|
|
{
|
|
return filter->speed_adjustment;
|
|
}
|
|
|
|
enum libinput_config_accel_profile
|
|
filter_get_type(struct motion_filter *filter)
|
|
{
|
|
return filter->interface->type;
|
|
}
|
|
|
|
/*
|
|
* Default parameters for pointer acceleration profiles.
|
|
*/
|
|
|
|
#define DEFAULT_THRESHOLD v_ms2us(0.4) /* in units/us */
|
|
#define MINIMUM_THRESHOLD v_ms2us(0.2) /* in units/us */
|
|
#define DEFAULT_ACCELERATION 2.0 /* unitless factor */
|
|
#define DEFAULT_INCLINE 1.1 /* unitless factor */
|
|
|
|
/* Touchpad acceleration */
|
|
#define TOUCHPAD_DEFAULT_THRESHOLD 254 /* mm/s */
|
|
#define TOUCHPAD_THRESHOLD_RANGE 184 /* mm/s */
|
|
#define TOUCHPAD_ACCELERATION 9.0 /* unitless factor */
|
|
#define TOUCHPAD_INCLINE 0.011 /* unitless factor */
|
|
|
|
/* for the Lenovo x230 custom accel. do not touch */
|
|
#define X230_THRESHOLD v_ms2us(0.4) /* in units/us */
|
|
#define X230_ACCELERATION 2.0 /* unitless factor */
|
|
#define X230_INCLINE 1.1 /* unitless factor */
|
|
#define X230_MAGIC_SLOWDOWN 0.4 /* unitless */
|
|
#define X230_TP_MAGIC_LOW_RES_FACTOR 4.0 /* unitless */
|
|
|
|
/* Trackpoint acceleration */
|
|
#define TRACKPOINT_DEFAULT_MAX_ACCEL 2.0 /* in units/us */
|
|
#define TRACKPOINT_DEFAULT_MAX_DELTA 60
|
|
/* As measured on a Lenovo T440 at kernel-default sensitivity 128 */
|
|
#define TRACKPOINT_DEFAULT_RANGE 20 /* max value */
|
|
|
|
/*
|
|
* Pointer acceleration filter constants
|
|
*/
|
|
|
|
#define MAX_VELOCITY_DIFF v_ms2us(1) /* units/us */
|
|
#define MOTION_TIMEOUT ms2us(1000)
|
|
#define NUM_POINTER_TRACKERS 16
|
|
|
|
struct pointer_tracker {
|
|
struct device_float_coords delta; /* delta to most recent event */
|
|
uint64_t time; /* us */
|
|
uint32_t dir;
|
|
};
|
|
|
|
struct pointer_accelerator {
|
|
struct motion_filter base;
|
|
|
|
accel_profile_func_t profile;
|
|
|
|
double velocity; /* units/us */
|
|
double last_velocity; /* units/us */
|
|
|
|
struct pointer_tracker *trackers;
|
|
int cur_tracker;
|
|
|
|
double threshold; /* units/us */
|
|
double accel; /* unitless factor */
|
|
double incline; /* incline of the function */
|
|
|
|
/* For smoothing timestamps from devices with unreliable timing */
|
|
uint64_t event_delta_smooth_threshold;
|
|
uint64_t event_delta_smooth_value;
|
|
|
|
int dpi;
|
|
};
|
|
|
|
struct pointer_accelerator_flat {
|
|
struct motion_filter base;
|
|
|
|
double factor;
|
|
int dpi;
|
|
};
|
|
|
|
struct tablet_accelerator_flat {
|
|
struct motion_filter base;
|
|
|
|
double factor;
|
|
int xres, yres;
|
|
double xres_scale, /* 1000dpi : tablet res */
|
|
yres_scale; /* 1000dpi : tablet res */
|
|
};
|
|
|
|
struct trackpoint_accelerator {
|
|
struct motion_filter base;
|
|
|
|
struct device_float_coords history[4];
|
|
size_t history_size;
|
|
|
|
double scale_factor;
|
|
double max_accel;
|
|
double max_delta;
|
|
|
|
double incline; /* incline of the function */
|
|
double offset; /* offset of the function */
|
|
};
|
|
|
|
static void
|
|
feed_trackers(struct pointer_accelerator *accel,
|
|
const struct device_float_coords *delta,
|
|
uint64_t time)
|
|
{
|
|
int i, current;
|
|
struct pointer_tracker *trackers = accel->trackers;
|
|
|
|
for (i = 0; i < NUM_POINTER_TRACKERS; i++) {
|
|
trackers[i].delta.x += delta->x;
|
|
trackers[i].delta.y += delta->y;
|
|
}
|
|
|
|
current = (accel->cur_tracker + 1) % NUM_POINTER_TRACKERS;
|
|
accel->cur_tracker = current;
|
|
|
|
trackers[current].delta.x = 0.0;
|
|
trackers[current].delta.y = 0.0;
|
|
trackers[current].time = time;
|
|
trackers[current].dir = device_float_get_direction(*delta);
|
|
}
|
|
|
|
static struct pointer_tracker *
|
|
tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset)
|
|
{
|
|
unsigned int index =
|
|
(accel->cur_tracker + NUM_POINTER_TRACKERS - offset)
|
|
% NUM_POINTER_TRACKERS;
|
|
return &accel->trackers[index];
|
|
}
|
|
|
|
static double
|
|
calculate_tracker_velocity(struct pointer_accelerator *accel,
|
|
struct pointer_tracker *tracker, uint64_t time)
|
|
{
|
|
uint64_t tdelta = time - tracker->time + 1;
|
|
|
|
if (tdelta < accel->event_delta_smooth_threshold)
|
|
tdelta = accel->event_delta_smooth_value;
|
|
|
|
return hypot(tracker->delta.x, tracker->delta.y) /
|
|
(double)tdelta; /* units/us */
|
|
}
|
|
|
|
static inline double
|
|
calculate_velocity_after_timeout(struct pointer_accelerator *accel,
|
|
struct pointer_tracker *tracker)
|
|
{
|
|
/* First movement after timeout needs special handling.
|
|
*
|
|
* When we trigger the timeout, the last event is too far in the
|
|
* past to use it for velocity calculation across multiple tracker
|
|
* values.
|
|
*
|
|
* Use the motion timeout itself to calculate the speed rather than
|
|
* the last tracker time. This errs on the side of being too fast
|
|
* for really slow movements but provides much more useful initial
|
|
* movement in normal use-cases (pause, move, pause, move)
|
|
*/
|
|
return calculate_tracker_velocity(accel, tracker,
|
|
tracker->time + MOTION_TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* Calculate the velocity based on the tracker data. Velocity is averaged
|
|
* across multiple historical values, provided those values aren't "too
|
|
* different" to our current one. That includes either being too far in the
|
|
* past, moving into a different direction or having too much of a velocity
|
|
* change between events.
|
|
*/
|
|
static double
|
|
calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
|
|
{
|
|
struct pointer_tracker *tracker;
|
|
double velocity;
|
|
double result = 0.0;
|
|
double initial_velocity = 0.0;
|
|
double velocity_diff;
|
|
unsigned int offset;
|
|
|
|
unsigned int dir = tracker_by_offset(accel, 0)->dir;
|
|
|
|
/* Find least recent vector within a timelimit, maximum velocity diff
|
|
* and direction threshold. */
|
|
for (offset = 1; offset < NUM_POINTER_TRACKERS; offset++) {
|
|
tracker = tracker_by_offset(accel, offset);
|
|
|
|
/* Bug: time running backwards */
|
|
if (tracker->time > time)
|
|
break;
|
|
|
|
/* Stop if too far away in time */
|
|
if (time - tracker->time > MOTION_TIMEOUT) {
|
|
if (offset == 1)
|
|
result = calculate_velocity_after_timeout(accel, tracker);
|
|
break;
|
|
}
|
|
|
|
velocity = calculate_tracker_velocity(accel, tracker, time);
|
|
|
|
/* Stop if direction changed */
|
|
dir &= tracker->dir;
|
|
if (dir == 0) {
|
|
/* First movement after dirchange - velocity is that
|
|
* of the last movement */
|
|
if (offset == 1)
|
|
result = velocity;
|
|
break;
|
|
}
|
|
|
|
if (initial_velocity == 0.0) {
|
|
result = initial_velocity = velocity;
|
|
} else {
|
|
/* Stop if velocity differs too much from initial */
|
|
velocity_diff = fabs(initial_velocity - velocity);
|
|
if (velocity_diff > MAX_VELOCITY_DIFF)
|
|
break;
|
|
|
|
result = velocity;
|
|
}
|
|
}
|
|
|
|
return result; /* units/us */
|
|
}
|
|
|
|
/**
|
|
* Apply the acceleration profile to the given velocity.
|
|
*
|
|
* @param accel The acceleration filter
|
|
* @param data Caller-specific data
|
|
* @param velocity Velocity in device-units per µs
|
|
* @param time Current time in µs
|
|
*
|
|
* @return A unitless acceleration factor, to be applied to the delta
|
|
*/
|
|
static double
|
|
acceleration_profile(struct pointer_accelerator *accel,
|
|
void *data, double velocity, uint64_t time)
|
|
{
|
|
return accel->profile(&accel->base, data, velocity, time);
|
|
}
|
|
|
|
/**
|
|
* Calculate the acceleration factor for our current velocity, averaging
|
|
* between our current and the most recent velocity to smoothen out changes.
|
|
*
|
|
* @param accel The acceleration filter
|
|
* @param data Caller-specific data
|
|
* @param velocity Velocity in device-units per µs
|
|
* @param last_velocity Previous velocity in device-units per µs
|
|
* @param time Current time in µs
|
|
*
|
|
* @return A unitless acceleration factor, to be applied to the delta
|
|
*/
|
|
static double
|
|
calculate_acceleration(struct pointer_accelerator *accel,
|
|
void *data,
|
|
double velocity,
|
|
double last_velocity,
|
|
uint64_t time)
|
|
{
|
|
double factor;
|
|
|
|
/* Use Simpson's rule to calculate the avarage acceleration between
|
|
* the previous motion and the most recent. */
|
|
factor = acceleration_profile(accel, data, velocity, time);
|
|
factor += acceleration_profile(accel, data, last_velocity, time);
|
|
factor += 4.0 *
|
|
acceleration_profile(accel, data,
|
|
(last_velocity + velocity) / 2,
|
|
time);
|
|
|
|
factor = factor / 6.0;
|
|
|
|
return factor; /* unitless factor */
|
|
}
|
|
|
|
/**
|
|
* Calculate the acceleration factor for the given delta with the timestamp.
|
|
*
|
|
* @param accel The acceleration filter
|
|
* @param unaccelerated The raw delta in the device's dpi
|
|
* @param data Caller-specific data
|
|
* @param time Current time in µs
|
|
*
|
|
* @return A unitless acceleration factor, to be applied to the delta
|
|
*/
|
|
static inline double
|
|
calculate_acceleration_factor(struct pointer_accelerator *accel,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data,
|
|
uint64_t time)
|
|
{
|
|
double velocity; /* units/us in device-native dpi*/
|
|
double accel_factor;
|
|
|
|
feed_trackers(accel, unaccelerated, time);
|
|
velocity = calculate_velocity(accel, time);
|
|
accel_factor = calculate_acceleration(accel,
|
|
data,
|
|
velocity,
|
|
accel->last_velocity,
|
|
time);
|
|
accel->last_velocity = velocity;
|
|
|
|
return accel_factor;
|
|
}
|
|
|
|
/**
|
|
* Generic filter that calculates the acceleration factor and applies it to
|
|
* the coordinates.
|
|
*
|
|
* @param filter The acceleration filter
|
|
* @param unaccelerated The raw delta in the device's dpi
|
|
* @param data Caller-specific data
|
|
* @param time Current time in µs
|
|
*
|
|
* @return An accelerated tuple of coordinates representing accelerated
|
|
* motion, still in device units.
|
|
*/
|
|
static struct device_float_coords
|
|
accelerator_filter_generic(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
double accel_value; /* unitless factor */
|
|
struct device_float_coords accelerated;
|
|
|
|
accel_value = calculate_acceleration_factor(accel,
|
|
unaccelerated,
|
|
data,
|
|
time);
|
|
|
|
accelerated.x = accel_value * unaccelerated->x;
|
|
accelerated.y = accel_value * unaccelerated->y;
|
|
|
|
return accelerated;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
accelerator_filter_post_normalized(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
struct device_float_coords accelerated;
|
|
|
|
/* Accelerate for device units, normalize afterwards */
|
|
accelerated = accelerator_filter_generic(filter,
|
|
unaccelerated,
|
|
data,
|
|
time);
|
|
return normalize_for_dpi(&accelerated, accel->dpi);
|
|
}
|
|
|
|
static struct normalized_coords
|
|
accelerator_filter_pre_normalized(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
struct normalized_coords normalized;
|
|
struct device_float_coords converted, accelerated;
|
|
|
|
/* Accelerate for normalized units and return normalized units.
|
|
API requires device_floats, so we just copy the bits around */
|
|
normalized = normalize_for_dpi(unaccelerated, accel->dpi);
|
|
converted.x = normalized.x;
|
|
converted.y = normalized.y;
|
|
|
|
accelerated = accelerator_filter_generic(filter,
|
|
&converted,
|
|
data,
|
|
time);
|
|
normalized.x = accelerated.x;
|
|
normalized.y = accelerated.y;
|
|
return normalized;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
accelerator_filter_unnormalized(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct device_float_coords accelerated;
|
|
struct normalized_coords normalized;
|
|
|
|
/* Accelerate for device units and return device units */
|
|
accelerated = accelerator_filter_generic(filter,
|
|
unaccelerated,
|
|
data,
|
|
time);
|
|
normalized.x = accelerated.x;
|
|
normalized.y = accelerated.y;
|
|
return normalized;
|
|
}
|
|
|
|
/**
|
|
* Generic filter that does nothing beyond converting from the device's
|
|
* native dpi into normalized coordinates.
|
|
*
|
|
* @param filter The acceleration filter
|
|
* @param unaccelerated The raw delta in the device's dpi
|
|
* @param data Caller-specific data
|
|
* @param time Current time in µs
|
|
*
|
|
* @return An accelerated tuple of coordinates representing normalized
|
|
* motion
|
|
*/
|
|
static struct normalized_coords
|
|
accelerator_filter_noop(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
|
|
return normalize_for_dpi(unaccelerated, accel->dpi);
|
|
}
|
|
|
|
static struct normalized_coords
|
|
accelerator_filter_x230(struct motion_filter *filter,
|
|
const struct device_float_coords *raw,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
double accel_factor; /* unitless factor */
|
|
struct normalized_coords accelerated;
|
|
struct device_float_coords delta_normalized;
|
|
struct normalized_coords unaccelerated;
|
|
double velocity; /* units/us */
|
|
|
|
/* This filter is a "do not touch me" filter. So the hack here is
|
|
* just to replicate the old behavior before filters switched to
|
|
* device-native dpi:
|
|
* 1) convert from device-native to 1000dpi normalized
|
|
* 2) run all calculation on 1000dpi-normalized data
|
|
* 3) apply accel factor no normalized data
|
|
*/
|
|
unaccelerated = normalize_for_dpi(raw, accel->dpi);
|
|
delta_normalized.x = unaccelerated.x;
|
|
delta_normalized.y = unaccelerated.y;
|
|
|
|
feed_trackers(accel, &delta_normalized, time);
|
|
velocity = calculate_velocity(accel, time);
|
|
accel_factor = calculate_acceleration(accel,
|
|
data,
|
|
velocity,
|
|
accel->last_velocity,
|
|
time);
|
|
accel->last_velocity = velocity;
|
|
|
|
accelerated.x = accel_factor * delta_normalized.x;
|
|
accelerated.y = accel_factor * delta_normalized.y;
|
|
|
|
return accelerated;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
accelerator_filter_constant_x230(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
struct normalized_coords normalized;
|
|
const double factor =
|
|
X230_MAGIC_SLOWDOWN/X230_TP_MAGIC_LOW_RES_FACTOR;
|
|
|
|
normalized = normalize_for_dpi(unaccelerated, accel->dpi);
|
|
normalized.x = factor * normalized.x;
|
|
normalized.y = factor * normalized.y;
|
|
|
|
return normalized;
|
|
}
|
|
|
|
static bool
|
|
touchpad_accelerator_set_speed(struct motion_filter *filter,
|
|
double speed_adjustment)
|
|
{
|
|
struct pointer_accelerator *accel_filter =
|
|
(struct pointer_accelerator *)filter;
|
|
|
|
assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
|
|
|
|
/* Note: the numbers below are nothing but trial-and-error magic,
|
|
don't read more into them other than "they mostly worked ok" */
|
|
|
|
/* adjust when accel kicks in */
|
|
accel_filter->threshold = TOUCHPAD_DEFAULT_THRESHOLD -
|
|
TOUCHPAD_THRESHOLD_RANGE * speed_adjustment;
|
|
accel_filter->accel = TOUCHPAD_ACCELERATION;
|
|
accel_filter->incline = TOUCHPAD_INCLINE;
|
|
filter->speed_adjustment = speed_adjustment;
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
touchpad_constant_filter(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *)filter;
|
|
struct normalized_coords normalized;
|
|
|
|
normalized = normalize_for_dpi(unaccelerated, accel->dpi);
|
|
normalized.x = TP_MAGIC_SLOWDOWN * normalized.x;
|
|
normalized.y = TP_MAGIC_SLOWDOWN * normalized.y;
|
|
|
|
return normalized;
|
|
}
|
|
|
|
static void
|
|
accelerator_restart(struct motion_filter *filter,
|
|
void *data,
|
|
uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
unsigned int offset;
|
|
struct pointer_tracker *tracker;
|
|
|
|
for (offset = 1; offset < NUM_POINTER_TRACKERS; offset++) {
|
|
tracker = tracker_by_offset(accel, offset);
|
|
tracker->time = 0;
|
|
tracker->dir = 0;
|
|
tracker->delta.x = 0;
|
|
tracker->delta.y = 0;
|
|
}
|
|
|
|
tracker = tracker_by_offset(accel, 0);
|
|
tracker->time = time;
|
|
tracker->dir = UNDEFINED_DIRECTION;
|
|
}
|
|
|
|
static void
|
|
accelerator_destroy(struct motion_filter *filter)
|
|
{
|
|
struct pointer_accelerator *accel =
|
|
(struct pointer_accelerator *) filter;
|
|
|
|
free(accel->trackers);
|
|
free(accel);
|
|
}
|
|
|
|
static bool
|
|
accelerator_set_speed(struct motion_filter *filter,
|
|
double speed_adjustment)
|
|
{
|
|
struct pointer_accelerator *accel_filter =
|
|
(struct pointer_accelerator *)filter;
|
|
|
|
assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
|
|
|
|
/* Note: the numbers below are nothing but trial-and-error magic,
|
|
don't read more into them other than "they mostly worked ok" */
|
|
|
|
/* delay when accel kicks in */
|
|
accel_filter->threshold = DEFAULT_THRESHOLD -
|
|
v_ms2us(0.25) * speed_adjustment;
|
|
if (accel_filter->threshold < MINIMUM_THRESHOLD)
|
|
accel_filter->threshold = MINIMUM_THRESHOLD;
|
|
|
|
/* adjust max accel factor */
|
|
accel_filter->accel = DEFAULT_ACCELERATION + speed_adjustment * 1.5;
|
|
|
|
/* higher speed -> faster to reach max */
|
|
accel_filter->incline = DEFAULT_INCLINE + speed_adjustment * 0.75;
|
|
|
|
filter->speed_adjustment = speed_adjustment;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Custom acceleration function for mice < 1000dpi.
|
|
* At slow motion, a single device unit causes a one-pixel movement.
|
|
* The threshold/max accel depends on the DPI, the smaller the DPI the
|
|
* earlier we accelerate and the higher the maximum acceleration is. Result:
|
|
* at low speeds we get pixel-precision, at high speeds we get approx. the
|
|
* same movement as a high-dpi mouse.
|
|
*
|
|
* Note: data fed to this function is in device units, not normalized.
|
|
*/
|
|
double
|
|
pointer_accel_profile_linear_low_dpi(struct motion_filter *filter,
|
|
void *data,
|
|
double speed_in, /* in device units (units/us) */
|
|
uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel_filter =
|
|
(struct pointer_accelerator *)filter;
|
|
|
|
double max_accel = accel_filter->accel; /* unitless factor */
|
|
double threshold = accel_filter->threshold; /* units/us */
|
|
const double incline = accel_filter->incline;
|
|
double dpi_factor = accel_filter->dpi/(double)DEFAULT_MOUSE_DPI;
|
|
double factor; /* unitless */
|
|
|
|
/* dpi_factor is always < 1.0, increase max_accel, reduce
|
|
the threshold so it kicks in earlier */
|
|
max_accel /= dpi_factor;
|
|
threshold *= dpi_factor;
|
|
|
|
/* see pointer_accel_profile_linear for a long description */
|
|
if (v_us2ms(speed_in) < 0.07)
|
|
factor = 10 * v_us2ms(speed_in) + 0.3;
|
|
else if (speed_in < threshold)
|
|
factor = 1;
|
|
else
|
|
factor = incline * v_us2ms(speed_in - threshold) + 1;
|
|
|
|
factor = min(max_accel, factor);
|
|
|
|
return factor;
|
|
}
|
|
|
|
double
|
|
pointer_accel_profile_linear(struct motion_filter *filter,
|
|
void *data,
|
|
double speed_in, /* in device units (units/µs) */
|
|
uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel_filter =
|
|
(struct pointer_accelerator *)filter;
|
|
const double max_accel = accel_filter->accel; /* unitless factor */
|
|
const double threshold = accel_filter->threshold; /* units/us */
|
|
const double incline = accel_filter->incline;
|
|
double factor; /* unitless */
|
|
|
|
/* Normalize to 1000dpi, because the rest below relies on that */
|
|
speed_in = speed_in * DEFAULT_MOUSE_DPI/accel_filter->dpi;
|
|
|
|
/*
|
|
Our acceleration function calculates a factor to accelerate input
|
|
deltas with. The function is a double incline with a plateau,
|
|
with a rough shape like this:
|
|
|
|
accel
|
|
factor
|
|
^
|
|
| /
|
|
| _____/
|
|
| /
|
|
|/
|
|
+-------------> speed in
|
|
|
|
The two inclines are linear functions in the form
|
|
y = ax + b
|
|
where y is speed_out
|
|
x is speed_in
|
|
a is the incline of acceleration
|
|
b is minimum acceleration factor
|
|
|
|
for speeds up to 0.07 u/ms, we decelerate, down to 30% of input
|
|
speed.
|
|
hence 1 = a * 0.07 + 0.3
|
|
0.7 = a * 0.07 => a := 10
|
|
deceleration function is thus:
|
|
y = 10x + 0.3
|
|
|
|
Note:
|
|
* 0.07u/ms as threshold is a result of trial-and-error and
|
|
has no other intrinsic meaning.
|
|
* 0.3 is chosen simply because it is above the Nyquist frequency
|
|
for subpixel motion within a pixel.
|
|
*/
|
|
if (v_us2ms(speed_in) < 0.07) {
|
|
factor = 10 * v_us2ms(speed_in) + 0.3;
|
|
/* up to the threshold, we keep factor 1, i.e. 1:1 movement */
|
|
} else if (speed_in < threshold) {
|
|
factor = 1;
|
|
|
|
} else {
|
|
/* Acceleration function above the threshold:
|
|
y = ax' + b
|
|
where T is threshold
|
|
x is speed_in
|
|
x' is speed
|
|
and
|
|
y(T) == 1
|
|
hence 1 = ax' + 1
|
|
=> x' := (x - T)
|
|
*/
|
|
factor = incline * v_us2ms(speed_in - threshold) + 1;
|
|
}
|
|
|
|
/* Cap at the maximum acceleration factor */
|
|
factor = min(max_accel, factor);
|
|
|
|
return factor;
|
|
}
|
|
|
|
double
|
|
touchpad_accel_profile_linear(struct motion_filter *filter,
|
|
void *data,
|
|
double speed_in, /* in device units/µs */
|
|
uint64_t time)
|
|
{
|
|
struct pointer_accelerator *accel_filter =
|
|
(struct pointer_accelerator *)filter;
|
|
const double max_accel = accel_filter->accel; /* unitless factor */
|
|
const double threshold = accel_filter->threshold; /* units/us */
|
|
const double incline = accel_filter->incline;
|
|
double factor; /* unitless */
|
|
|
|
/* Convert to mm/s because that's something one can understand */
|
|
speed_in = v_us2s(speed_in) * 25.4/accel_filter->dpi;
|
|
|
|
/*
|
|
Our acceleration function calculates a factor to accelerate input
|
|
deltas with. The function is a double incline with a plateau,
|
|
with a rough shape like this:
|
|
|
|
accel
|
|
factor
|
|
^
|
|
| /
|
|
| _____/
|
|
| /
|
|
|/
|
|
+-------------> speed in
|
|
|
|
The two inclines are linear functions in the form
|
|
y = ax + b
|
|
where y is speed_out
|
|
x is speed_in
|
|
a is the incline of acceleration
|
|
b is minimum acceleration factor
|
|
|
|
for speeds up to the lower threshold, we decelerate, down to 30%
|
|
of input speed.
|
|
hence 1 = a * 7 + 0.3
|
|
0.7 = a * 7 => a := 0.1
|
|
deceleration function is thus:
|
|
y = 0.1x + 0.3
|
|
|
|
Note:
|
|
* The minimum threshold is a result of trial-and-error and
|
|
has no other intrinsic meaning.
|
|
* 0.3 is chosen simply because it is above the Nyquist frequency
|
|
for subpixel motion within a pixel.
|
|
*/
|
|
if (speed_in < 7.0) {
|
|
factor = 0.1 * speed_in + 0.3;
|
|
/* up to the threshold, we keep factor 1, i.e. 1:1 movement */
|
|
} else if (speed_in < threshold) {
|
|
factor = 1;
|
|
} else {
|
|
/* Acceleration function above the threshold:
|
|
y = ax' + b
|
|
where T is threshold
|
|
x is speed_in
|
|
x' is speed
|
|
and
|
|
y(T) == 1
|
|
hence 1 = ax' + 1
|
|
=> x' := (x - T)
|
|
*/
|
|
factor = incline * (speed_in - threshold) + 1;
|
|
}
|
|
|
|
/* Cap at the maximum acceleration factor */
|
|
factor = min(max_accel, factor);
|
|
|
|
/* Scale everything depending on the acceleration set */
|
|
factor *= 1 + 0.5 * filter->speed_adjustment;
|
|
|
|
return factor * TP_MAGIC_SLOWDOWN;
|
|
}
|
|
|
|
double
|
|
touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
|
|
void *data,
|
|
double speed_in, /* 1000dpi-units/µs */
|
|
uint64_t time)
|
|
{
|
|
/* Those touchpads presents an actual lower resolution that what is
|
|
* advertised. We see some jumps from the cursor due to the big steps
|
|
* in X and Y when we are receiving data.
|
|
* Apply a factor to minimize those jumps at low speed, and try
|
|
* keeping the same feeling as regular touchpads at high speed.
|
|
* It still feels slower but it is usable at least */
|
|
double factor; /* unitless */
|
|
struct pointer_accelerator *accel_filter =
|
|
(struct pointer_accelerator *)filter;
|
|
|
|
double f1, f2; /* unitless */
|
|
const double max_accel = accel_filter->accel *
|
|
X230_TP_MAGIC_LOW_RES_FACTOR; /* unitless factor */
|
|
const double threshold = accel_filter->threshold /
|
|
X230_TP_MAGIC_LOW_RES_FACTOR; /* units/us */
|
|
const double incline = accel_filter->incline * X230_TP_MAGIC_LOW_RES_FACTOR;
|
|
|
|
/* Note: the magic values in this function are obtained by
|
|
* trial-and-error. No other meaning should be interpreted.
|
|
* The calculation is a compressed form of
|
|
* pointer_accel_profile_linear(), look at the git history of that
|
|
* function for an explanation of what the min/max/etc. does.
|
|
*/
|
|
speed_in *= X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR;
|
|
|
|
f1 = min(1, v_us2ms(speed_in) * 5);
|
|
f2 = 1 + (v_us2ms(speed_in) - v_us2ms(threshold)) * incline;
|
|
|
|
factor = min(max_accel, f2 > 1 ? f2 : f1);
|
|
|
|
return factor * X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR;
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
|
|
.filter = accelerator_filter_pre_normalized,
|
|
.filter_constant = accelerator_filter_noop,
|
|
.restart = accelerator_restart,
|
|
.destroy = accelerator_destroy,
|
|
.set_speed = accelerator_set_speed,
|
|
};
|
|
|
|
static struct pointer_accelerator *
|
|
create_default_filter(int dpi)
|
|
{
|
|
struct pointer_accelerator *filter;
|
|
|
|
filter = zalloc(sizeof *filter);
|
|
filter->last_velocity = 0.0;
|
|
|
|
filter->trackers =
|
|
zalloc(NUM_POINTER_TRACKERS * sizeof *filter->trackers);
|
|
filter->cur_tracker = 0;
|
|
|
|
filter->threshold = DEFAULT_THRESHOLD;
|
|
filter->accel = DEFAULT_ACCELERATION;
|
|
filter->incline = DEFAULT_INCLINE;
|
|
filter->dpi = dpi;
|
|
|
|
return filter;
|
|
}
|
|
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_linear(int dpi)
|
|
{
|
|
struct pointer_accelerator *filter;
|
|
|
|
filter = create_default_filter(dpi);
|
|
if (!filter)
|
|
return NULL;
|
|
|
|
filter->base.interface = &accelerator_interface;
|
|
filter->profile = pointer_accel_profile_linear;
|
|
|
|
return &filter->base;
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface_low_dpi = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
|
|
.filter = accelerator_filter_unnormalized,
|
|
.filter_constant = accelerator_filter_noop,
|
|
.restart = accelerator_restart,
|
|
.destroy = accelerator_destroy,
|
|
.set_speed = accelerator_set_speed,
|
|
};
|
|
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_linear_low_dpi(int dpi)
|
|
{
|
|
struct pointer_accelerator *filter;
|
|
|
|
filter = create_default_filter(dpi);
|
|
if (!filter)
|
|
return NULL;
|
|
|
|
filter->base.interface = &accelerator_interface_low_dpi;
|
|
filter->profile = pointer_accel_profile_linear_low_dpi;
|
|
|
|
return &filter->base;
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface_touchpad = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
|
|
.filter = accelerator_filter_post_normalized,
|
|
.filter_constant = touchpad_constant_filter,
|
|
.restart = accelerator_restart,
|
|
.destroy = accelerator_destroy,
|
|
.set_speed = touchpad_accelerator_set_speed,
|
|
};
|
|
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_touchpad(int dpi,
|
|
uint64_t event_delta_smooth_threshold,
|
|
uint64_t event_delta_smooth_value)
|
|
{
|
|
struct pointer_accelerator *filter;
|
|
|
|
filter = create_default_filter(dpi);
|
|
if (!filter)
|
|
return NULL;
|
|
|
|
filter->base.interface = &accelerator_interface_touchpad;
|
|
filter->profile = touchpad_accel_profile_linear;
|
|
filter->event_delta_smooth_threshold = event_delta_smooth_threshold;
|
|
filter->event_delta_smooth_value = event_delta_smooth_value;
|
|
|
|
return &filter->base;
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface_x230 = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
|
|
.filter = accelerator_filter_x230,
|
|
.filter_constant = accelerator_filter_constant_x230,
|
|
.restart = accelerator_restart,
|
|
.destroy = accelerator_destroy,
|
|
.set_speed = accelerator_set_speed,
|
|
};
|
|
|
|
/* The Lenovo x230 has a bad touchpad. This accel method has been
|
|
* trial-and-error'd, any changes to it will require re-testing everything.
|
|
* Don't touch this.
|
|
*/
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_lenovo_x230(int dpi)
|
|
{
|
|
struct pointer_accelerator *filter;
|
|
|
|
filter = zalloc(sizeof *filter);
|
|
filter->base.interface = &accelerator_interface_x230;
|
|
filter->profile = touchpad_lenovo_x230_accel_profile;
|
|
filter->last_velocity = 0.0;
|
|
|
|
filter->trackers =
|
|
zalloc(NUM_POINTER_TRACKERS * sizeof *filter->trackers);
|
|
filter->cur_tracker = 0;
|
|
|
|
filter->threshold = X230_THRESHOLD;
|
|
filter->accel = X230_ACCELERATION; /* unitless factor */
|
|
filter->incline = X230_INCLINE; /* incline of the acceleration function */
|
|
filter->dpi = dpi;
|
|
|
|
return &filter->base;
|
|
}
|
|
|
|
double
|
|
trackpoint_accel_profile(struct motion_filter *filter,
|
|
void *data,
|
|
double delta)
|
|
{
|
|
struct trackpoint_accelerator *accel_filter =
|
|
(struct trackpoint_accelerator *)filter;
|
|
const double max_accel = accel_filter->max_accel;
|
|
double factor;
|
|
|
|
delta = fabs(delta);
|
|
|
|
/* This is almost the equivalent of the xserver acceleration
|
|
at sensitivity 128 and speed 0.0 */
|
|
factor = delta * accel_filter->incline + accel_filter->offset;
|
|
factor = min(factor, max_accel);
|
|
|
|
return factor;
|
|
}
|
|
|
|
/**
|
|
* Average the deltas, they are messy and can provide sequences like 7, 7,
|
|
* 9, 8, 14, 7, 9, 8 ... The outliers cause unpredictable jumps, so average
|
|
* them out.
|
|
*/
|
|
static inline struct device_float_coords
|
|
trackpoint_average_delta(struct trackpoint_accelerator *filter,
|
|
const struct device_float_coords *unaccelerated)
|
|
{
|
|
size_t i;
|
|
struct device_float_coords avg = {0};
|
|
|
|
memmove(&filter->history[1],
|
|
&filter->history[0],
|
|
sizeof(*filter->history) * (filter->history_size - 1));
|
|
filter->history[0] = *unaccelerated;
|
|
|
|
for (i = 0; i < filter->history_size; i++) {
|
|
avg.x += filter->history[i].x;
|
|
avg.y += filter->history[i].y;
|
|
}
|
|
avg.x /= filter->history_size;
|
|
avg.y /= filter->history_size;
|
|
|
|
return avg;
|
|
}
|
|
|
|
/**
|
|
* Undo any system-wide magic scaling, so we're behaving the same regardless
|
|
* of the trackpoint hardware. This way we can apply our profile independent
|
|
* of any other configuration that messes with things.
|
|
*/
|
|
static inline struct device_float_coords
|
|
trackpoint_normalize_deltas(const struct trackpoint_accelerator *accel_filter,
|
|
const struct device_float_coords *delta)
|
|
{
|
|
struct device_float_coords scaled = *delta;
|
|
|
|
scaled.x *= accel_filter->scale_factor;
|
|
scaled.y *= accel_filter->scale_factor;
|
|
|
|
return scaled;
|
|
}
|
|
|
|
/**
|
|
* We set a max delta per event, to avoid extreme jumps once we exceed the
|
|
* expected pressure. Trackpoint hardware is inconsistent once the pressure
|
|
* gets high, so we can expect sequences like 30, 40, 35, 55, etc. This may
|
|
* be caused by difficulty keeping up high consistent pressures or just
|
|
* measuring errors in the hardware. Either way, we cap to a max delta so
|
|
* once we hit the high pressures, movement is capped and consistent.
|
|
*/
|
|
static inline struct normalized_coords
|
|
trackpoint_clip_to_max_delta(const struct trackpoint_accelerator *accel_filter,
|
|
struct normalized_coords coords)
|
|
{
|
|
const double max_delta = accel_filter->max_delta;
|
|
|
|
if (abs(coords.x) > max_delta)
|
|
coords.x = copysign(max_delta, coords.x);
|
|
if (abs(coords.y) > max_delta)
|
|
coords.y = copysign(max_delta, coords.y);
|
|
|
|
return coords;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
trackpoint_accelerator_filter(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct trackpoint_accelerator *accel_filter =
|
|
(struct trackpoint_accelerator *)filter;
|
|
struct device_float_coords scaled;
|
|
struct device_float_coords avg;
|
|
struct normalized_coords coords;
|
|
double f;
|
|
double delta;
|
|
|
|
scaled = trackpoint_normalize_deltas(accel_filter, unaccelerated);
|
|
avg = trackpoint_average_delta(accel_filter, &scaled);
|
|
|
|
delta = hypot(avg.x, avg.y);
|
|
|
|
f = trackpoint_accel_profile(filter, data, delta);
|
|
|
|
coords.x = avg.x * f;
|
|
coords.y = avg.y * f;
|
|
|
|
coords = trackpoint_clip_to_max_delta(accel_filter, coords);
|
|
|
|
return coords;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
trackpoint_accelerator_filter_noop(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
|
|
struct trackpoint_accelerator *accel_filter =
|
|
(struct trackpoint_accelerator *)filter;
|
|
struct device_float_coords scaled;
|
|
struct device_float_coords avg;
|
|
struct normalized_coords coords;
|
|
|
|
scaled = trackpoint_normalize_deltas(accel_filter, unaccelerated);
|
|
avg = trackpoint_average_delta(accel_filter, &scaled);
|
|
|
|
coords.x = avg.x;
|
|
coords.y = avg.y;
|
|
|
|
coords = trackpoint_clip_to_max_delta(accel_filter, coords);
|
|
|
|
return coords;
|
|
}
|
|
|
|
static bool
|
|
trackpoint_accelerator_set_speed(struct motion_filter *filter,
|
|
double speed_adjustment)
|
|
{
|
|
struct trackpoint_accelerator *accel_filter =
|
|
(struct trackpoint_accelerator*)filter;
|
|
double incline, offset, max;
|
|
|
|
assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
|
|
|
|
/* Helloooo, magic numbers.
|
|
|
|
These numbers were obtained by finding an acceleration curve that
|
|
provides precision at slow speeds but still provides a good
|
|
acceleration at higher pressure - and a quick ramp-up to that
|
|
acceleration.
|
|
|
|
Trackpoints have built-in acceleration curves already, so we
|
|
don't put a new function on top, we merely scale the output from
|
|
those curves (re-calculating the pressure values from the
|
|
firmware-defined curve and applying a new curve is unreliable).
|
|
|
|
For that basic scaling, we assume a constant factor f based on
|
|
the speed setting together with a maximum factor m (for this
|
|
speed setting). Delta acceleration is thus:
|
|
factor = max(m, f)
|
|
accelerated_delta = delta * factor;
|
|
|
|
Trial and error showed a couple of pairs that work well for the
|
|
various speed settings (Lenovo T440, sensitivity 128):
|
|
|
|
-1.0: f = 0.3, m = 1
|
|
-0.5: f = 0.6, m = 2
|
|
0.0: f = 1.0, m = 6
|
|
0.5: f = 1.4, m = 8
|
|
1.0: f = 1.9, m = 15
|
|
|
|
Note: if f >= 2.0, some pixels are unaddressable
|
|
|
|
Those pairs were fed into the linear/exponential regression tool
|
|
at http://www.xuru.org/rt/LR.asp and show two functions that map
|
|
speed settings to the respective f and m.
|
|
Given a speed setting s in [-1.0, 1.0]
|
|
f(s) = 0.8 * s + 1.04
|
|
m(s) = 4.6 * e**(1.2 * s)
|
|
These are close enough to the tested pairs.
|
|
*/
|
|
|
|
max = 4.6 * pow(M_E, 1.2 * speed_adjustment);
|
|
incline = 0.8 * speed_adjustment + 1.04;
|
|
offset = 0;
|
|
|
|
accel_filter->max_accel = max;
|
|
accel_filter->incline = incline;
|
|
accel_filter->offset = offset;
|
|
filter->speed_adjustment = speed_adjustment;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
trackpoint_accelerator_destroy(struct motion_filter *filter)
|
|
{
|
|
struct trackpoint_accelerator *accel_filter =
|
|
(struct trackpoint_accelerator *)filter;
|
|
|
|
free(accel_filter);
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface_trackpoint = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
|
|
.filter = trackpoint_accelerator_filter,
|
|
.filter_constant = trackpoint_accelerator_filter_noop,
|
|
.restart = NULL,
|
|
.destroy = trackpoint_accelerator_destroy,
|
|
.set_speed = trackpoint_accelerator_set_speed,
|
|
};
|
|
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_trackpoint(int max_hw_delta)
|
|
{
|
|
struct trackpoint_accelerator *filter;
|
|
|
|
/* Trackpoints are special. They don't have a movement speed like a
|
|
* mouse or a finger, instead they send a constant stream of events
|
|
* based on the pressure applied.
|
|
*
|
|
* Physical ranges on a trackpoint are the max values for relative
|
|
* deltas, but these are highly device-specific.
|
|
*
|
|
*/
|
|
|
|
filter = zalloc(sizeof *filter);
|
|
if (!filter)
|
|
return NULL;
|
|
|
|
filter->history_size = ARRAY_LENGTH(filter->history);
|
|
filter->scale_factor = 1.0 * TRACKPOINT_DEFAULT_RANGE / max_hw_delta;
|
|
filter->max_accel = TRACKPOINT_DEFAULT_MAX_ACCEL;
|
|
filter->max_delta = TRACKPOINT_DEFAULT_MAX_DELTA;
|
|
|
|
filter->base.interface = &accelerator_interface_trackpoint;
|
|
|
|
return &filter->base;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
accelerator_filter_flat(struct motion_filter *filter,
|
|
const struct device_float_coords *unaccelerated,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct pointer_accelerator_flat *accel_filter =
|
|
(struct pointer_accelerator_flat *)filter;
|
|
double factor; /* unitless factor */
|
|
struct normalized_coords accelerated;
|
|
|
|
/* You want flat acceleration, you get flat acceleration for the
|
|
* device */
|
|
factor = accel_filter->factor;
|
|
accelerated.x = factor * unaccelerated->x;
|
|
accelerated.y = factor * unaccelerated->y;
|
|
|
|
return accelerated;
|
|
}
|
|
|
|
static bool
|
|
accelerator_set_speed_flat(struct motion_filter *filter,
|
|
double speed_adjustment)
|
|
{
|
|
struct pointer_accelerator_flat *accel_filter =
|
|
(struct pointer_accelerator_flat *)filter;
|
|
|
|
assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
|
|
|
|
/* Speed rage is 0-200% of the nominal speed, with 0 mapping to the
|
|
* nominal speed. Anything above 200 is pointless, we're already
|
|
* skipping over ever second pixel at 200% speed.
|
|
*/
|
|
|
|
accel_filter->factor = max(0.005, 1 + speed_adjustment);
|
|
filter->speed_adjustment = speed_adjustment;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
accelerator_destroy_flat(struct motion_filter *filter)
|
|
{
|
|
struct pointer_accelerator_flat *accel =
|
|
(struct pointer_accelerator_flat *) filter;
|
|
|
|
free(accel);
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface_flat = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
|
|
.filter = accelerator_filter_flat,
|
|
.filter_constant = accelerator_filter_noop,
|
|
.restart = NULL,
|
|
.destroy = accelerator_destroy_flat,
|
|
.set_speed = accelerator_set_speed_flat,
|
|
};
|
|
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_flat(int dpi)
|
|
{
|
|
struct pointer_accelerator_flat *filter;
|
|
|
|
filter = zalloc(sizeof *filter);
|
|
filter->base.interface = &accelerator_interface_flat;
|
|
filter->dpi = dpi;
|
|
|
|
return &filter->base;
|
|
}
|
|
|
|
static inline struct normalized_coords
|
|
tablet_accelerator_filter_flat_mouse(struct tablet_accelerator_flat *filter,
|
|
const struct device_float_coords *units)
|
|
{
|
|
struct normalized_coords accelerated;
|
|
|
|
/*
|
|
Tablets are high res (Intuos 4 is 5080 dpi) and unmodified deltas
|
|
are way too high. Slow it down to the equivalent of a 1000dpi
|
|
mouse. The ratio of that is:
|
|
ratio = 1000/(resolution_per_mm * 25.4)
|
|
|
|
i.e. on the Intuos4 it's a ratio of ~1/5.
|
|
*/
|
|
|
|
accelerated.x = units->x * filter->xres_scale;
|
|
accelerated.y = units->y * filter->yres_scale;
|
|
|
|
accelerated.x *= filter->factor;
|
|
accelerated.y *= filter->factor;
|
|
|
|
return accelerated;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
tablet_accelerator_filter_flat_pen(struct tablet_accelerator_flat *filter,
|
|
const struct device_float_coords *units)
|
|
{
|
|
struct normalized_coords accelerated;
|
|
|
|
/* Tablet input is in device units, output is supposed to be in
|
|
* logical pixels roughly equivalent to a mouse/touchpad.
|
|
*
|
|
* This is a magical constant found by trial and error. On a 96dpi
|
|
* screen 0.4mm of movement correspond to 1px logical pixel which
|
|
* is almost identical to the tablet mapped to screen in absolute
|
|
* mode. Tested on a Intuos5, other tablets may vary.
|
|
*/
|
|
const double DPI_CONVERSION = 96.0/25.4 * 2.5; /* unitless factor */
|
|
struct normalized_coords mm;
|
|
|
|
mm.x = 1.0 * units->x/filter->xres;
|
|
mm.y = 1.0 * units->y/filter->yres;
|
|
accelerated.x = mm.x * filter->factor * DPI_CONVERSION;
|
|
accelerated.y = mm.y * filter->factor * DPI_CONVERSION;
|
|
|
|
return accelerated;
|
|
}
|
|
|
|
static struct normalized_coords
|
|
tablet_accelerator_filter_flat(struct motion_filter *filter,
|
|
const struct device_float_coords *units,
|
|
void *data, uint64_t time)
|
|
{
|
|
struct tablet_accelerator_flat *accel_filter =
|
|
(struct tablet_accelerator_flat *)filter;
|
|
struct libinput_tablet_tool *tool = (struct libinput_tablet_tool*)data;
|
|
enum libinput_tablet_tool_type type;
|
|
struct normalized_coords accel;
|
|
|
|
type = libinput_tablet_tool_get_type(tool);
|
|
|
|
switch (type) {
|
|
case LIBINPUT_TABLET_TOOL_TYPE_MOUSE:
|
|
case LIBINPUT_TABLET_TOOL_TYPE_LENS:
|
|
accel = tablet_accelerator_filter_flat_mouse(accel_filter,
|
|
units);
|
|
break;
|
|
default:
|
|
accel = tablet_accelerator_filter_flat_pen(accel_filter,
|
|
units);
|
|
break;
|
|
}
|
|
|
|
return accel;
|
|
}
|
|
|
|
static bool
|
|
tablet_accelerator_set_speed(struct motion_filter *filter,
|
|
double speed_adjustment)
|
|
{
|
|
struct tablet_accelerator_flat *accel_filter =
|
|
(struct tablet_accelerator_flat *)filter;
|
|
|
|
assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
|
|
|
|
accel_filter->factor = speed_adjustment + 1.0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
tablet_accelerator_destroy(struct motion_filter *filter)
|
|
{
|
|
struct tablet_accelerator_flat *accel_filter =
|
|
(struct tablet_accelerator_flat *)filter;
|
|
|
|
free(accel_filter);
|
|
}
|
|
|
|
struct motion_filter_interface accelerator_interface_tablet = {
|
|
.type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
|
|
.filter = tablet_accelerator_filter_flat,
|
|
.filter_constant = NULL,
|
|
.restart = NULL,
|
|
.destroy = tablet_accelerator_destroy,
|
|
.set_speed = tablet_accelerator_set_speed,
|
|
};
|
|
|
|
static struct tablet_accelerator_flat *
|
|
create_tablet_filter_flat(int xres, int yres)
|
|
{
|
|
struct tablet_accelerator_flat *filter;
|
|
|
|
filter = zalloc(sizeof *filter);
|
|
filter->factor = 1.0;
|
|
filter->xres = xres;
|
|
filter->yres = yres;
|
|
filter->xres_scale = DEFAULT_MOUSE_DPI/(25.4 * xres);
|
|
filter->yres_scale = DEFAULT_MOUSE_DPI/(25.4 * yres);
|
|
|
|
return filter;
|
|
}
|
|
|
|
struct motion_filter *
|
|
create_pointer_accelerator_filter_tablet(int xres, int yres)
|
|
{
|
|
struct tablet_accelerator_flat *filter;
|
|
|
|
filter = create_tablet_filter_flat(xres, yres);
|
|
if (!filter)
|
|
return NULL;
|
|
|
|
filter->base.interface = &accelerator_interface_tablet;
|
|
|
|
return &filter->base;
|
|
}
|