diff --git a/doc/Makefile.am b/doc/Makefile.am
index c9dccfda..a8d41822 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -18,6 +18,7 @@ header_files = \
$(srcdir)/normalization-of-relative-motion.dox \
$(srcdir)/palm-detection.dox \
$(srcdir)/page-hierarchy.dox \
+ $(srcdir)/pointer-acceleration.dox \
$(srcdir)/reporting-bugs.dox \
$(srcdir)/scrolling.dox \
$(srcdir)/seats.dox \
@@ -39,6 +40,10 @@ diagram_files = \
$(srcdir)/svg/edge-scrolling.svg \
$(srcdir)/svg/palm-detection.svg \
$(srcdir)/svg/pinch-gestures.svg \
+ $(srcdir)/svg/ptraccel-linear.svg \
+ $(srcdir)/svg/ptraccel-low-dpi.svg \
+ $(srcdir)/svg/ptraccel-touchpad.svg \
+ $(srcdir)/svg/ptraccel-trackpoint.svg \
$(srcdir)/svg/swipe-gestures.svg \
$(srcdir)/svg/tap-n-drag.svg \
$(srcdir)/svg/thumb-detection.svg \
diff --git a/doc/page-hierarchy.dox b/doc/page-hierarchy.dox
index 1e82535f..3fdb1f72 100644
--- a/doc/page-hierarchy.dox
+++ b/doc/page-hierarchy.dox
@@ -31,5 +31,6 @@
- @subpage test-suite
- @subpage tools
+- @subpage pointer-acceleration
*/
diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox
new file mode 100644
index 00000000..a372e154
--- /dev/null
+++ b/doc/pointer-acceleration.dox
@@ -0,0 +1,110 @@
+/**
+@page pointer-acceleration Pointer acceleration
+
+libinput uses device-specific pointer acceleration methods, with the default
+being the @ref ptraccel-linear. The methods share common properties, such as
+@ref ptraccel-velocity.
+
+This page explains the high-level concepts used in the code. It aims to
+provide an overview for developers and is not necessarily useful for
+users.
+
+@section ptraccel-velocity Velocity calculation
+
+The device's speed of movement is measured across multiple input events
+through so-called "trackers". Each event prepends a the tracker item, each
+subsequent tracker contains the delta of that item to the current position,
+the timestamp of the event that created it and the cardinal direction of the
+movement at the time. If a device moves into the same direction, the
+velocity is calculated across multiple trackers. For example, if a device
+moves steadily for 10 events to the left, the velocity is calculated across
+all 10 events.
+
+Whenever the movement changes direction or significantly changes speed, the
+velocity is calculated from the direction/speed change only. For example, if
+a device moves steadily for 8 events to the left and then 2 events to the
+right, the velocity is only that of the last 2 events.
+
+An extra time limit prevents events that are too old to factor into the
+velocity calculation. For example, if a device moves steadily for 5 events
+to the left, then pauses, then moves again for 5 events to the left, only
+the last 5 events are used for velocity calculation.
+
+The velocity is then used to calculate the acceleration factor
+
+@section ptraccel-factor Acceleration factor
+
+The acceleration factor is the final outcome of the pointer acceleration
+calculations. It is a unitless factor that is applied to the current delta,
+a factor of 2 doubles the delta (i.e. speeds up the movement), a factor of
+less than 1 reduces the delta (i.e. slows the movement).
+
+Any factor less than 1 requires the user to move the device further to move
+the visible pointer. This is called deceleration and enables high precision
+target selection through subpixel movements. libinput's current maximum
+deceleration factor is 0.3 (i.e. slow down to 30% of the pointer speed).
+
+A factor higher than 1 moves the pointer further than the physical device
+moves. This is acceleration and allows a user to cross the screen quickly
+but effectively skips pixels. libinput's current maximum acceleration factor
+is 3.5.
+
+@section ptraccel-linear Linear pointer acceleration
+
+The linear pointer acceleration method is the default for most pointer
+devices. It provides deceleration at very slow movements, a 1:1 mapping for
+regular movements and a linear increase to the maximum acceleration factor
+for fast movements.
+
+Linear pointer acceleration applies to devices with above 1000dpi resolution
+and after @ref motion_normalization is applied.
+
+@image html ptraccel-linear.svg "Linear pointer acceleration"
+
+The image above shows the linear pointer acceleration settings at various
+speeds. The line for 0.0 is the default acceleration curve, speed settings
+above 0.0 accelerate sooner, faster and to a higher maximum acceleration.
+Speed settings below 0 delay when acceleration kicks in, how soon the
+maximum acceleration is reached and the maximum acceleration factor.
+
+Extremely low speed settings provide no acceleration and additionally
+decelerate all movement by a constant factor.
+
+@section ptraccel-low-dpi Pointer acceleration for low-dpi devices
+
+Low-dpi devices are those with a physical resolution of less than 1000 dots
+per inch (dpi). The pointer acceleration is adjusted to provide roughly the
+same feel for all devices at normal to high speeds. At slow speeds, the
+pointer acceleration works on device-units rather than normalized
+coordinates (see @ref motion_normalization).
+
+@image html ptraccel-low-dpi.svg "Pointer acceleration for low-dpi devices"
+
+The image above shows the default pointer acceleration curve for a speed of
+0.0 at different DPI settings. A device with low DPI has the acceleration
+applied sooner and with a stronger acceleration factor.
+
+@section ptraccel-touchpad Pointer acceleration on touchpads
+
+Touchpad pointer acceleration uses the @ref ptraccel-linear profile, with a
+constant deceleration factor applied. The user expectation of how much a
+pointer should move in response to finger movement is different to that of a
+mouse device, hence the constant deceleration factor.
+
+@image html ptraccel-touchpad.svg "Pointer acceleration curve for touchpads"
+
+The image above shows the touchpad acceleration profile in comparison to the
+@ref ptraccel-linear. The shape of the curve is identical but vertically squashed.
+
+@section ptraccel-trackpoint Pointer acceleration on trackpoints
+
+Trackpoint pointer acceleration uses the @ref ptraccel-low-dpi profile, with a
+constant deceleration factor taking the place of the DPI settings.
+
+@image html ptraccel-trackpoint.svg "Pointer acceleration curves for trackpoints"
+
+The image above shows the trackpoint acceleration profile in comparison to the
+@ref ptraccel-linear. The constant acceleration factor, usually applied by
+udev, shapes the acceleration profile.
+
+*/
diff --git a/doc/svg/ptraccel-linear.svg b/doc/svg/ptraccel-linear.svg
new file mode 100644
index 00000000..cb575f21
--- /dev/null
+++ b/doc/svg/ptraccel-linear.svg
@@ -0,0 +1,5486 @@
+
+
+
+
diff --git a/doc/svg/ptraccel-low-dpi.svg b/doc/svg/ptraccel-low-dpi.svg
new file mode 100644
index 00000000..e31eaa1a
--- /dev/null
+++ b/doc/svg/ptraccel-low-dpi.svg
@@ -0,0 +1,3748 @@
+
+
+
+
diff --git a/doc/svg/ptraccel-touchpad.svg b/doc/svg/ptraccel-touchpad.svg
new file mode 100644
index 00000000..1befa1e5
--- /dev/null
+++ b/doc/svg/ptraccel-touchpad.svg
@@ -0,0 +1,1723 @@
+
+
+
+
diff --git a/doc/svg/ptraccel-trackpoint.svg b/doc/svg/ptraccel-trackpoint.svg
new file mode 100644
index 00000000..79589c24
--- /dev/null
+++ b/doc/svg/ptraccel-trackpoint.svg
@@ -0,0 +1,3689 @@
+
+
+
+
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 872da989..a32a771c 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -1510,7 +1510,7 @@ static int
tp_init_accel(struct tp_dispatch *tp, double diagonal)
{
int res_x, res_y;
- accel_profile_func_t profile;
+ struct motion_filter *filter;
res_x = tp->device->abs.absinfo_x->resolution;
res_y = tp->device->abs.absinfo_y->resolution;
@@ -1526,14 +1526,14 @@ tp_init_accel(struct tp_dispatch *tp, double diagonal)
tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y;
if (tp->device->model_flags & EVDEV_MODEL_LENOVO_X230)
- profile = touchpad_lenovo_x230_accel_profile;
+ filter = create_pointer_accelerator_filter_lenovo_x230(tp->device->dpi);
else
- profile = touchpad_accel_profile_linear;
+ filter = create_pointer_accelerator_filter_touchpad(tp->device->dpi);
- if (evdev_device_init_pointer_acceleration(tp->device, profile) == -1)
+ if (!filter)
return -1;
- return 0;
+ return evdev_device_init_pointer_acceleration(tp->device, filter);
}
static uint32_t
diff --git a/src/evdev.c b/src/evdev.c
index 225c3acc..9414d9d6 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -1408,12 +1408,9 @@ evdev_accel_config_get_default_speed(struct libinput_device *device)
int
evdev_device_init_pointer_acceleration(struct evdev_device *device,
- accel_profile_func_t profile)
+ struct motion_filter *filter)
{
- device->pointer.filter = create_pointer_accelerator_filter(profile,
- device->dpi);
- if (!device->pointer.filter)
- return -1;
+ device->pointer.filter = filter;
device->pointer.config.available = evdev_accel_config_available;
device->pointer.config.set_speed = evdev_accel_config_set_speed;
@@ -1862,14 +1859,19 @@ evdev_configure_mt_device(struct evdev_device *device)
static inline int
evdev_init_accel(struct evdev_device *device)
{
- accel_profile_func_t profile;
+ struct motion_filter *filter;
- if (device->dpi < DEFAULT_MOUSE_DPI)
- profile = pointer_accel_profile_linear_low_dpi;
+ if (device->tags & EVDEV_TAG_TRACKPOINT)
+ filter = create_pointer_accelerator_filter_trackpoint(device->dpi);
+ else if (device->dpi < DEFAULT_MOUSE_DPI)
+ filter = create_pointer_accelerator_filter_linear_low_dpi(device->dpi);
else
- profile = pointer_accel_profile_linear;
+ filter = create_pointer_accelerator_filter_linear(device->dpi);
- return evdev_device_init_pointer_acceleration(device, profile);
+ if (!filter)
+ return -1;
+
+ return evdev_device_init_pointer_acceleration(device, filter);
}
static int
diff --git a/src/evdev.h b/src/evdev.h
index c951671e..9f026b89 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -281,7 +281,7 @@ evdev_device_create(struct libinput_seat *seat,
int
evdev_device_init_pointer_acceleration(struct evdev_device *device,
- accel_profile_func_t profile);
+ struct motion_filter *filter);
struct evdev_dispatch *
evdev_touchpad_create(struct evdev_device *device);
diff --git a/src/filter-private.h b/src/filter-private.h
index 0e7afa1f..f5e8b7f7 100644
--- a/src/filter-private.h
+++ b/src/filter-private.h
@@ -38,11 +38,11 @@ struct motion_filter_interface {
uint64_t time);
void (*destroy)(struct motion_filter *filter);
bool (*set_speed)(struct motion_filter *filter,
- double speed);
+ double speed_adjustment);
};
struct motion_filter {
- double speed; /* normalized [-1, 1] */
+ double speed_adjustment; /* normalized [-1, 1] */
struct motion_filter_interface *interface;
};
diff --git a/src/filter.c b/src/filter.c
index e11d58a9..7054fafa 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -36,6 +36,20 @@
#include "libinput-util.h"
#include "filter-private.h"
+/* Convert speed/velocity from units/us to units/ms */
+static inline double
+v_us2ms(double units_per_us)
+{
+ return units_per_us * 1000.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;
+}
+
struct normalized_coords
filter_dispatch(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
@@ -62,31 +76,36 @@ filter_destroy(struct motion_filter *filter)
bool
filter_set_speed(struct motion_filter *filter,
- double speed)
+ double speed_adjustment)
{
- return filter->interface->set_speed(filter, speed);
+ return filter->interface->set_speed(filter, speed_adjustment);
}
double
filter_get_speed(struct motion_filter *filter)
{
- return filter->speed;
+ return filter->speed_adjustment;
}
/*
* Default parameters for pointer acceleration profiles.
*/
-#define DEFAULT_THRESHOLD 0.4 /* in units/ms */
-#define MINIMUM_THRESHOLD 0.2 /* in units/ms */
+#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 */
+/* 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 */
+
/*
* Pointer acceleration filter constants
*/
-#define MAX_VELOCITY_DIFF 1 /* units/ms */
+#define MAX_VELOCITY_DIFF v_ms2us(1) /* units/us */
#define MOTION_TIMEOUT ms2us(1000)
#define NUM_POINTER_TRACKERS 16
@@ -96,20 +115,18 @@ struct pointer_tracker {
int dir;
};
-struct pointer_accelerator;
struct pointer_accelerator {
struct motion_filter base;
accel_profile_func_t profile;
- double velocity; /* units/ms */
- double last_velocity; /* units/ms */
- struct normalized_coords last;
+ double velocity; /* units/us */
+ double last_velocity; /* units/us */
struct pointer_tracker *trackers;
int cur_tracker;
- double threshold; /* units/ms */
+ double threshold; /* units/us */
double accel; /* unitless factor */
double incline; /* incline of the function */
@@ -151,7 +168,7 @@ static double
calculate_tracker_velocity(struct pointer_tracker *tracker, uint64_t time)
{
double tdelta = time - tracker->time + 1;
- return normalized_length(tracker->delta) / tdelta * 1000.0; /* units/ms */
+ return normalized_length(tracker->delta) / tdelta; /* units/us */
}
static inline double
@@ -221,7 +238,7 @@ calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
}
}
- return result; /* units/ms */
+ return result; /* units/us */
}
static double
@@ -254,14 +271,55 @@ calculate_acceleration(struct pointer_accelerator *accel,
return factor; /* unitless factor */
}
+static inline double
+calculate_acceleration_factor(struct pointer_accelerator *accel,
+ const struct normalized_coords *unaccelerated,
+ void *data,
+ uint64_t time)
+{
+ double velocity; /* units/us */
+ 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;
+}
+
static struct normalized_coords
accelerator_filter(struct motion_filter *filter,
const struct normalized_coords *unaccelerated,
- void *data, uint64_t time /* in us */)
+ void *data, uint64_t time)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+ double accel_value; /* unitless factor */
+ struct normalized_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_low_dpi(struct motion_filter *filter,
+ const struct normalized_coords *unaccelerated,
+ void *data, uint64_t time)
{
struct pointer_accelerator *accel =
(struct pointer_accelerator *) filter;
- double velocity; /* units/ms */
double accel_value; /* unitless factor */
struct normalized_coords accelerated;
struct normalized_coords unnormalized;
@@ -273,21 +331,70 @@ accelerator_filter(struct motion_filter *filter,
unnormalized.x = unaccelerated->x * dpi_factor;
unnormalized.y = unaccelerated->y * dpi_factor;
- feed_trackers(accel, &unnormalized, time);
- velocity = calculate_velocity(accel, time);
- accel_value = calculate_acceleration(accel,
- data,
- velocity,
- accel->last_velocity,
- time);
+ accel_value = calculate_acceleration_factor(accel,
+ &unnormalized,
+ data,
+ time);
accelerated.x = accel_value * unnormalized.x;
accelerated.y = accel_value * unnormalized.y;
- accel->last = unnormalized;
+ return accelerated;
+}
+static struct normalized_coords
+accelerator_filter_trackpoint(struct motion_filter *filter,
+ const struct normalized_coords *unaccelerated,
+ void *data, uint64_t time)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+ double accel_value; /* unitless factor */
+ struct normalized_coords accelerated;
+ struct normalized_coords unnormalized;
+ double dpi_factor = accel->dpi_factor;
+
+ /* trackpoints with a dpi factor have a const accel set, remove that
+ * and restore device units. The accel profile takes const accel
+ * into account */
+ dpi_factor = min(1.0, dpi_factor);
+ unnormalized.x = unaccelerated->x * dpi_factor;
+ unnormalized.y = unaccelerated->y * dpi_factor;
+
+ accel_value = calculate_acceleration_factor(accel,
+ &unnormalized,
+ data,
+ time);
+
+ accelerated.x = accel_value * unnormalized.x;
+ accelerated.y = accel_value * unnormalized.y;
+
+ return accelerated;
+}
+
+static struct normalized_coords
+accelerator_filter_x230(struct motion_filter *filter,
+ const struct normalized_coords *unaccelerated,
+ void *data, uint64_t time)
+{
+ struct pointer_accelerator *accel =
+ (struct pointer_accelerator *) filter;
+ double accel_factor; /* unitless factor */
+ struct normalized_coords accelerated;
+ double velocity; /* units/us */
+
+ 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;
+ accelerated.x = accel_factor * unaccelerated->x;
+ accelerated.y = accel_factor * unaccelerated->y;
+
return accelerated;
}
@@ -326,65 +433,32 @@ accelerator_destroy(struct motion_filter *filter)
static bool
accelerator_set_speed(struct motion_filter *filter,
- double speed)
+ double speed_adjustment)
{
struct pointer_accelerator *accel_filter =
(struct pointer_accelerator *)filter;
- assert(speed >= -1.0 && speed <= 1.0);
+ 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 - speed / 4.0;
+ 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 * 1.5;
+ accel_filter->accel = DEFAULT_ACCELERATION + speed_adjustment * 1.5;
/* higher speed -> faster to reach max */
- accel_filter->incline = DEFAULT_INCLINE + speed * 0.75;
+ accel_filter->incline = DEFAULT_INCLINE + speed_adjustment * 0.75;
- filter->speed = speed;
+ filter->speed_adjustment = speed_adjustment;
return true;
}
-struct motion_filter_interface accelerator_interface = {
- accelerator_filter,
- accelerator_restart,
- accelerator_destroy,
- accelerator_set_speed,
-};
-
-struct motion_filter *
-create_pointer_accelerator_filter(accel_profile_func_t profile,
- int dpi)
-{
- struct pointer_accelerator *filter;
-
- filter = zalloc(sizeof *filter);
- if (filter == NULL)
- return NULL;
-
- filter->base.interface = &accelerator_interface;
-
- filter->profile = profile;
- filter->last_velocity = 0.0;
- filter->last.x = 0;
- filter->last.y = 0;
-
- filter->trackers =
- calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers);
- filter->cur_tracker = 0;
-
- filter->threshold = DEFAULT_THRESHOLD;
- filter->accel = DEFAULT_ACCELERATION;
- filter->incline = DEFAULT_INCLINE;
-
- filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_DPI;
-
- return &filter->base;
-}
-
/**
* Custom acceleration function for mice < 1000dpi.
* At slow motion, a single device unit causes a one-pixel movement.
@@ -398,25 +472,32 @@ create_pointer_accelerator_filter(accel_profile_func_t profile,
double
pointer_accel_profile_linear_low_dpi(struct motion_filter *filter,
void *data,
- double speed_in, /* in device units (units/ms) */
- uint64_t time /* in us */)
+ double speed_in, /* in device units (units/us) */
+ uint64_t time)
{
struct pointer_accelerator *accel_filter =
(struct pointer_accelerator *)filter;
- double s1, s2;
double max_accel = accel_filter->accel; /* unitless factor */
- const double threshold = accel_filter->threshold; /* units/ms */
+ double threshold = accel_filter->threshold; /* units/us */
const double incline = accel_filter->incline;
- double factor;
+ double factor; /* unitless */
double dpi_factor = accel_filter->dpi_factor;
+ /* dpi_factor is always < 1.0, increase max_accel, reduce
+ the threshold so it kicks in earlier */
max_accel /= dpi_factor;
+ threshold *= dpi_factor;
- s1 = min(1, 0.3 + speed_in * 10.0);
- s2 = 1 + (speed_in - threshold * dpi_factor) * incline;
+ /* 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, s2 > 1 ? s2 : s1);
+ factor = min(max_accel, factor);
return factor;
}
@@ -425,54 +506,104 @@ double
pointer_accel_profile_linear(struct motion_filter *filter,
void *data,
double speed_in, /* 1000-dpi normalized */
- uint64_t time /* in us */)
+ uint64_t time)
{
struct pointer_accelerator *accel_filter =
(struct pointer_accelerator *)filter;
-
- double s1, s2;
const double max_accel = accel_filter->accel; /* unitless factor */
- const double threshold = accel_filter->threshold; /* units/ms */
+ const double threshold = accel_filter->threshold; /* units/us */
const double incline = accel_filter->incline;
- double factor;
+ double factor; /* unitless */
- s1 = min(1, 0.3 + speed_in * 10);
- s2 = 1 + (speed_in - threshold) * incline;
+ /*
+ 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:
- factor = min(max_accel, s2 > 1 ? s2 : s1);
+ 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.3 = a * 0.00 + 0.3 => 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,
- uint64_t time /* in us */)
+ void *data,
+ double speed_in, /* units/us */
+ uint64_t time)
{
/* 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 here and proceed
as normal. */
- const double TP_MAGIC_SLOWDOWN = 0.4;
- double speed_out;
+ const double TP_MAGIC_SLOWDOWN = 0.4; /* unitless */
+ double factor; /* unitless */
speed_in *= TP_MAGIC_SLOWDOWN;
- speed_out = pointer_accel_profile_linear(filter, data, speed_in, time);
+ factor = pointer_accel_profile_linear(filter, data, speed_in, time);
- return speed_out * TP_MAGIC_SLOWDOWN;
+ return factor * TP_MAGIC_SLOWDOWN;
}
double
touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
void *data,
double speed_in,
- uint64_t time /* in us */)
+ uint64_t time)
{
/* Keep the magic factor from touchpad_accel_profile_linear. */
- const double TP_MAGIC_SLOWDOWN = 0.4;
+ const double TP_MAGIC_SLOWDOWN = 0.4; /* unitless */
/* Those touchpads presents an actual lower resolution that what is
* advertised. We see some jumps from the cursor due to the big steps
@@ -480,24 +611,207 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
* 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 */
- const double TP_MAGIC_LOW_RES_FACTOR = 4.0;
- double speed_out;
+ const double TP_MAGIC_LOW_RES_FACTOR = 4.0; /* unitless */
+ double factor; /* unitless */
struct pointer_accelerator *accel_filter =
(struct pointer_accelerator *)filter;
- double s1, s2;
+ double f1, f2; /* unitless */
const double max_accel = accel_filter->accel *
TP_MAGIC_LOW_RES_FACTOR; /* unitless factor */
const double threshold = accel_filter->threshold /
- TP_MAGIC_LOW_RES_FACTOR; /* units/ms */
+ TP_MAGIC_LOW_RES_FACTOR; /* units/us */
const double incline = accel_filter->incline * 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 explaination of what the min/max/etc. does.
+ */
speed_in *= TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR;
- s1 = min(1, speed_in * 5);
- s2 = 1 + (speed_in - threshold) * incline;
+ f1 = min(1, v_us2ms(speed_in) * 5);
+ f2 = 1 + (v_us2ms(speed_in) - v_us2ms(threshold)) * incline;
- speed_out = min(max_accel, s2 > 1 ? s2 : s1);
+ factor = min(max_accel, f2 > 1 ? f2 : f1);
- return speed_out * TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR;
+ return factor * TP_MAGIC_SLOWDOWN / TP_MAGIC_LOW_RES_FACTOR;
+}
+
+double
+trackpoint_accel_profile(struct motion_filter *filter,
+ void *data,
+ double speed_in, /* 1000-dpi normalized */
+ 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/ms */
+ const double incline = accel_filter->incline;
+ double factor;
+ double dpi_factor = accel_filter->dpi_factor;
+
+ /* 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;
+}
+
+struct motion_filter_interface accelerator_interface = {
+ accelerator_filter,
+ accelerator_restart,
+ accelerator_destroy,
+ accelerator_set_speed,
+};
+
+static struct pointer_accelerator *
+create_default_filter(int dpi)
+{
+ struct pointer_accelerator *filter;
+
+ filter = zalloc(sizeof *filter);
+ if (filter == NULL)
+ return NULL;
+
+ filter->last_velocity = 0.0;
+
+ filter->trackers =
+ calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers);
+ filter->cur_tracker = 0;
+
+ filter->threshold = DEFAULT_THRESHOLD;
+ filter->accel = DEFAULT_ACCELERATION;
+ filter->incline = DEFAULT_INCLINE;
+
+ filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_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 = {
+ accelerator_filter_low_dpi,
+ accelerator_restart,
+ accelerator_destroy,
+ 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 *
+create_pointer_accelerator_filter_touchpad(int dpi)
+{
+ struct pointer_accelerator *filter;
+
+ filter = create_default_filter(dpi);
+ if (!filter)
+ return NULL;
+
+ filter->base.interface = &accelerator_interface;
+ filter->profile = touchpad_accel_profile_linear;
+
+ return &filter->base;
+}
+
+struct motion_filter_interface accelerator_interface_x230 = {
+ accelerator_filter_x230,
+ accelerator_restart,
+ accelerator_destroy,
+ 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);
+ if (filter == NULL)
+ return NULL;
+
+ filter->base.interface = &accelerator_interface_x230;
+ filter->profile = touchpad_lenovo_x230_accel_profile;
+ filter->last_velocity = 0.0;
+
+ filter->trackers =
+ calloc(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_factor = 1; /* unused for this accel method */
+
+ return &filter->base;
+}
+
+struct motion_filter_interface accelerator_interface_trackpoint = {
+ accelerator_filter_trackpoint,
+ accelerator_restart,
+ accelerator_destroy,
+ accelerator_set_speed,
+};
+
+struct motion_filter *
+create_pointer_accelerator_filter_trackpoint(int dpi)
+{
+ struct pointer_accelerator *filter;
+
+ filter = create_default_filter(dpi);
+ if (!filter)
+ return NULL;
+
+ filter->base.interface = &accelerator_interface_trackpoint;
+ filter->profile = trackpoint_accel_profile;
+ filter->threshold = DEFAULT_THRESHOLD;
+ filter->accel = DEFAULT_ACCELERATION;
+ filter->incline = DEFAULT_INCLINE;
+
+ return &filter->base;
}
diff --git a/src/filter.h b/src/filter.h
index 617fab1f..fd36da49 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -57,9 +57,22 @@ typedef double (*accel_profile_func_t)(struct motion_filter *filter,
double velocity,
uint64_t time);
+/* Pointer acceleration types */
+
struct motion_filter *
-create_pointer_accelerator_filter(accel_profile_func_t filter,
- int dpi);
+create_pointer_accelerator_filter_linear(int dpi);
+
+struct motion_filter *
+create_pointer_accelerator_filter_linear_low_dpi(int dpi);
+
+struct motion_filter *
+create_pointer_accelerator_filter_touchpad(int dpi);
+
+struct motion_filter *
+create_pointer_accelerator_filter_lenovo_x230(int dpi);
+
+struct motion_filter *
+create_pointer_accelerator_filter_trackpoint(int dpi);
/*
* Pointer acceleration profiles.
@@ -85,4 +98,9 @@ touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
void *data,
double speed_in,
uint64_t time);
+double
+trackpoint_accel_profile(struct motion_filter *filter,
+ void *data,
+ double speed_in,
+ uint64_t time);
#endif /* FILTER_H */
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 68e60cbb..8f72db99 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -39,3 +39,5 @@ event_gui_LDADD = ../src/libinput.la libshared.la $(CAIRO_LIBS) $(GTK_LIBS) $(LI
event_gui_CFLAGS = $(CAIRO_CFLAGS) $(GTK_CFLAGS) $(LIBUDEV_CFLAGS)
event_gui_LDFLAGS = -no-install
endif
+
+EXTRA_DIST = make-ptraccel-graphs.sh
diff --git a/tools/make-ptraccel-graphs.sh b/tools/make-ptraccel-graphs.sh
new file mode 100755
index 00000000..901baf9c
--- /dev/null
+++ b/tools/make-ptraccel-graphs.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+tool=`dirname $0`/ptraccel-debug
+gnuplot=/usr/bin/gnuplot
+
+outfile="ptraccel-linear"
+for speed in -1 -0.75 -0.5 -0.25 0 0.5 1; do
+ $tool --mode=accel --dpi=1000 --filter=linear --speed=$speed > $outfile-$speed.gnuplot
+done
+$gnuplot < $outfile-$dpi.gnuplot
+done
+
+$gnuplot < $outfile-mouse.gnuplot
+$tool --mode=accel --dpi=1000 --filter=touchpad > $outfile-touchpad.gnuplot
+$gnuplot < $outfile-mouse.gnuplot
+for constaccel in 1 2 3; do
+ dpi=$((1000/$constaccel))
+ $tool --mode=accel --dpi=$dpi --filter=trackpoint > $outfile-trackpoint-$constaccel.gnuplot
+done
+$gnuplot < ... in motion and delta modes only. Increase dx by step each round\n"
"--speed= ... accel speed [-1, 1], default 0\n"
"--dpi= ... device resolution in DPI (default: 1000)\n"
+ "--filter= \n"
+ " linear ... the default motion filter\n"
+ " low-dpi ... low-dpi filter, use --dpi with this argument\n"
+ " touchpad ... the touchpad motion filter\n"
+ " x230 ... custom filter for the Lenovo x230 touchpad\n"
+ " trackpoint... trackpoint motion filter\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"
@@ -195,6 +198,8 @@ main(int argc, char **argv)
double custom_deltas[1024];
double speed = 0.0;
int dpi = 1000;
+ const char *filter_type = "linear";
+ accel_profile_func_t profile = NULL;
enum {
OPT_MODE = 1,
@@ -203,6 +208,7 @@ main(int argc, char **argv)
OPT_STEP,
OPT_SPEED,
OPT_DPI,
+ OPT_FILTER,
};
while (1) {
@@ -215,6 +221,7 @@ main(int argc, char **argv)
{"step", 1, 0, OPT_STEP },
{"speed", 1, 0, OPT_SPEED },
{"dpi", 1, 0, OPT_DPI },
+ {"filter", 1, 0, OPT_FILTER},
{0, 0, 0, 0}
};
@@ -265,6 +272,9 @@ main(int argc, char **argv)
case OPT_DPI:
dpi = strtod(optarg, NULL);
break;
+ case OPT_FILTER:
+ filter_type = optarg;
+ break;
default:
usage();
exit(1);
@@ -272,8 +282,26 @@ main(int argc, char **argv)
}
}
- filter = create_pointer_accelerator_filter(pointer_accel_profile_linear,
- dpi);
+ if (streq(filter_type, "linear")) {
+ filter = create_pointer_accelerator_filter_linear(dpi);
+ profile = pointer_accel_profile_linear;
+ } else if (streq(filter_type, "low-dpi")) {
+ filter = create_pointer_accelerator_filter_linear_low_dpi(dpi);
+ profile = pointer_accel_profile_linear_low_dpi;
+ } else if (streq(filter_type, "touchpad")) {
+ filter = create_pointer_accelerator_filter_touchpad(dpi);
+ profile = touchpad_accel_profile_linear;
+ } else if (streq(filter_type, "x230")) {
+ filter = create_pointer_accelerator_filter_lenovo_x230(dpi);
+ profile = touchpad_lenovo_x230_accel_profile;
+ } else if (streq(filter_type, "trackpoint")) {
+ filter = create_pointer_accelerator_filter_trackpoint(dpi);
+ profile = trackpoint_accel_profile;
+ } else {
+ fprintf(stderr, "Invalid filter type %s\n", filter_type);
+ return 1;
+ }
+
assert(filter != NULL);
filter_set_speed(filter, speed);
@@ -297,7 +325,7 @@ main(int argc, char **argv)
}
if (print_accel)
- print_accel_func(filter);
+ print_accel_func(filter, profile);
else if (print_delta)
print_ptraccel_deltas(filter, step);
else if (print_motion)