1 /*
2 * Copyright © 2006-2009 Simon Thum
3 * Copyright © 2012 Jonas Ådahl
4 * Copyright © 2014-2015 Red Hat, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 */
25
26 #include "config.h"
27
28 #include <assert.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31
32 #include "filter.h"
33 #include "libinput-util.h"
34 #include "filter-private.h"
35
36 /* Trackpoint acceleration for the Lenovo x230. DO NOT TOUCH.
37 * This code is only invoked on the X230 and is quite flimsy,
38 * custom-designed to make this touchpad less terrible than the
39 * out-of-the-box experience. The x230 was released in 2013, it's
40 * not worth trying to optimize the code or de-duplicate the various
41 * copy-pastes.
42 */
43
44 /*
45 * Default parameters for pointer acceleration profiles.
46 */
47
48 #define DEFAULT_THRESHOLD v_ms2us(0.4) /* in units/us */
49 #define MINIMUM_THRESHOLD v_ms2us(0.2) /* in units/us */
50 #define DEFAULT_ACCELERATION 2.0 /* unitless factor */
51 #define DEFAULT_INCLINE 1.1 /* unitless factor */
52
53 /* for the Lenovo x230 custom accel. do not touch */
54 #define X230_THRESHOLD v_ms2us(0.4) /* in units/us */
55 #define X230_ACCELERATION 2.0 /* unitless factor */
56 #define X230_INCLINE 1.1 /* unitless factor */
57 #define X230_MAGIC_SLOWDOWN 0.4 /* unitless */
58 #define X230_TP_MAGIC_LOW_RES_FACTOR 4.0 /* unitless */
59
60 struct pointer_accelerator_x230 {
61 struct motion_filter base;
62
63 accel_profile_func_t profile;
64
65 double velocity; /* units/us */
66 double last_velocity; /* units/us */
67
68 struct pointer_trackers trackers;
69
70 double threshold; /* units/us */
71 double accel; /* unitless factor */
72 double incline; /* incline of the function */
73
74 int dpi;
75 };
76
77 /**
78 * Apply the acceleration profile to the given velocity.
79 *
80 * @param accel The acceleration filter
81 * @param data Caller-specific data
82 * @param velocity Velocity in device-units per µs
83 * @param time Current time in µs
84 *
85 * @return A unitless acceleration factor, to be applied to the delta
86 */
87 static double
acceleration_profile(struct pointer_accelerator_x230 * accel,void * data,double velocity,uint64_t time)88 acceleration_profile(struct pointer_accelerator_x230 *accel,
89 void *data, double velocity, uint64_t time)
90 {
91 return accel->profile(&accel->base, data, velocity, time);
92 }
93
94 /**
95 * Calculate the acceleration factor for our current velocity, averaging
96 * between our current and the most recent velocity to smoothen out changes.
97 *
98 * @param accel The acceleration filter
99 * @param data Caller-specific data
100 * @param velocity Velocity in device-units per µs
101 * @param last_velocity Previous velocity in device-units per µs
102 * @param time Current time in µs
103 *
104 * @return A unitless acceleration factor, to be applied to the delta
105 */
106 static double
calculate_acceleration(struct pointer_accelerator_x230 * accel,void * data,double velocity,double last_velocity,uint64_t time)107 calculate_acceleration(struct pointer_accelerator_x230 *accel,
108 void *data,
109 double velocity,
110 double last_velocity,
111 uint64_t time)
112 {
113 double factor;
114
115 /* Use Simpson's rule to calculate the average acceleration between
116 * the previous motion and the most recent. */
117 factor = acceleration_profile(accel, data, velocity, time);
118 factor += acceleration_profile(accel, data, last_velocity, time);
119 factor += 4.0 *
120 acceleration_profile(accel, data,
121 (last_velocity + velocity) / 2,
122 time);
123
124 factor = factor / 6.0;
125
126 return factor; /* unitless factor */
127 }
128
129 static struct normalized_coords
accelerator_filter_x230(struct motion_filter * filter,const struct device_float_coords * raw,void * data,uint64_t time)130 accelerator_filter_x230(struct motion_filter *filter,
131 const struct device_float_coords *raw,
132 void *data, uint64_t time)
133 {
134 struct pointer_accelerator_x230 *accel =
135 (struct pointer_accelerator_x230 *) filter;
136 double accel_factor; /* unitless factor */
137 struct normalized_coords accelerated;
138 struct device_float_coords delta_normalized;
139 struct normalized_coords unaccelerated;
140 double velocity; /* units/us */
141
142 /* This filter is a "do not touch me" filter. So the hack here is
143 * just to replicate the old behavior before filters switched to
144 * device-native dpi:
145 * 1) convert from device-native to 1000dpi normalized
146 * 2) run all calculation on 1000dpi-normalized data
147 * 3) apply accel factor no normalized data
148 */
149 unaccelerated = normalize_for_dpi(raw, accel->dpi);
150 delta_normalized.x = unaccelerated.x;
151 delta_normalized.y = unaccelerated.y;
152
153 trackers_feed(&accel->trackers, &delta_normalized, time);
154 velocity = trackers_velocity(&accel->trackers, time);
155 accel_factor = calculate_acceleration(accel,
156 data,
157 velocity,
158 accel->last_velocity,
159 time);
160 accel->last_velocity = velocity;
161
162 accelerated.x = accel_factor * delta_normalized.x;
163 accelerated.y = accel_factor * delta_normalized.y;
164
165 return accelerated;
166 }
167
168 static struct normalized_coords
accelerator_filter_constant_x230(struct motion_filter * filter,const struct device_float_coords * unaccelerated,void * data,uint64_t time)169 accelerator_filter_constant_x230(struct motion_filter *filter,
170 const struct device_float_coords *unaccelerated,
171 void *data, uint64_t time)
172 {
173 struct pointer_accelerator_x230 *accel =
174 (struct pointer_accelerator_x230 *) filter;
175 struct normalized_coords normalized;
176 const double factor =
177 X230_MAGIC_SLOWDOWN/X230_TP_MAGIC_LOW_RES_FACTOR;
178
179 normalized = normalize_for_dpi(unaccelerated, accel->dpi);
180 normalized.x = factor * normalized.x;
181 normalized.y = factor * normalized.y;
182
183 return normalized;
184 }
185
186 static void
accelerator_restart_x230(struct motion_filter * filter,void * data,uint64_t time)187 accelerator_restart_x230(struct motion_filter *filter,
188 void *data,
189 uint64_t time)
190 {
191 struct pointer_accelerator_x230 *accel =
192 (struct pointer_accelerator_x230 *) filter;
193 unsigned int offset;
194 struct pointer_tracker *tracker;
195
196 for (offset = 1; offset < accel->trackers.ntrackers; offset++) {
197 tracker = trackers_by_offset(&accel->trackers, offset);
198 tracker->time = 0;
199 tracker->dir = 0;
200 tracker->delta.x = 0;
201 tracker->delta.y = 0;
202 }
203
204 tracker = trackers_by_offset(&accel->trackers, 0);
205 tracker->time = time;
206 tracker->dir = UNDEFINED_DIRECTION;
207 }
208
209 static void
accelerator_destroy_x230(struct motion_filter * filter)210 accelerator_destroy_x230(struct motion_filter *filter)
211 {
212 struct pointer_accelerator_x230 *accel =
213 (struct pointer_accelerator_x230 *) filter;
214
215 free(accel->trackers.trackers);
216 free(accel);
217 }
218
219 static bool
accelerator_set_speed_x230(struct motion_filter * filter,double speed_adjustment)220 accelerator_set_speed_x230(struct motion_filter *filter,
221 double speed_adjustment)
222 {
223 struct pointer_accelerator_x230 *accel_filter =
224 (struct pointer_accelerator_x230 *)filter;
225
226 assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
227
228 /* Note: the numbers below are nothing but trial-and-error magic,
229 don't read more into them other than "they mostly worked ok" */
230
231 /* delay when accel kicks in */
232 accel_filter->threshold = DEFAULT_THRESHOLD -
233 v_ms2us(0.25) * speed_adjustment;
234 if (accel_filter->threshold < MINIMUM_THRESHOLD)
235 accel_filter->threshold = MINIMUM_THRESHOLD;
236
237 /* adjust max accel factor */
238 accel_filter->accel = DEFAULT_ACCELERATION + speed_adjustment * 1.5;
239
240 /* higher speed -> faster to reach max */
241 accel_filter->incline = DEFAULT_INCLINE + speed_adjustment * 0.75;
242
243 filter->speed_adjustment = speed_adjustment;
244 return true;
245 }
246
247 double
touchpad_lenovo_x230_accel_profile(struct motion_filter * filter,void * data,double speed_in,uint64_t time)248 touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
249 void *data,
250 double speed_in, /* 1000dpi-units/µs */
251 uint64_t time)
252 {
253 /* Those touchpads presents an actual lower resolution that what is
254 * advertised. We see some jumps from the cursor due to the big steps
255 * in X and Y when we are receiving data.
256 * Apply a factor to minimize those jumps at low speed, and try
257 * keeping the same feeling as regular touchpads at high speed.
258 * It still feels slower but it is usable at least */
259 double factor; /* unitless */
260 struct pointer_accelerator_x230 *accel_filter =
261 (struct pointer_accelerator_x230 *)filter;
262
263 double f1, f2; /* unitless */
264 const double max_accel = accel_filter->accel *
265 X230_TP_MAGIC_LOW_RES_FACTOR; /* unitless factor */
266 const double threshold = accel_filter->threshold /
267 X230_TP_MAGIC_LOW_RES_FACTOR; /* units/us */
268 const double incline = accel_filter->incline * X230_TP_MAGIC_LOW_RES_FACTOR;
269
270 /* Note: the magic values in this function are obtained by
271 * trial-and-error. No other meaning should be interpreted.
272 * The calculation is a compressed form of
273 * pointer_accel_profile_linear(), look at the git history of that
274 * function for an explanation of what the min/max/etc. does.
275 */
276 speed_in *= X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR;
277
278 f1 = min(1, v_us2ms(speed_in) * 5);
279 f2 = 1 + (v_us2ms(speed_in) - v_us2ms(threshold)) * incline;
280
281 factor = min(max_accel, f2 > 1 ? f2 : f1);
282
283 return factor * X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR;
284 }
285
286 struct motion_filter_interface accelerator_interface_x230 = {
287 .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
288 .filter = accelerator_filter_x230,
289 .filter_constant = accelerator_filter_constant_x230,
290 .restart = accelerator_restart_x230,
291 .destroy = accelerator_destroy_x230,
292 .set_speed = accelerator_set_speed_x230,
293 };
294
295 /* The Lenovo x230 has a bad touchpad. This accel method has been
296 * trial-and-error'd, any changes to it will require re-testing everything.
297 * Don't touch this.
298 */
299 struct motion_filter *
create_pointer_accelerator_filter_lenovo_x230(int dpi,bool use_velocity_averaging)300 create_pointer_accelerator_filter_lenovo_x230(int dpi, bool use_velocity_averaging)
301 {
302 struct pointer_accelerator_x230 *filter;
303
304 filter = zalloc(sizeof *filter);
305 filter->base.interface = &accelerator_interface_x230;
306 filter->profile = touchpad_lenovo_x230_accel_profile;
307 filter->last_velocity = 0.0;
308
309 trackers_init(&filter->trackers, use_velocity_averaging ? 16 : 2);
310
311 filter->threshold = X230_THRESHOLD;
312 filter->accel = X230_ACCELERATION; /* unitless factor */
313 filter->incline = X230_INCLINE; /* incline of the acceleration function */
314 filter->dpi = dpi;
315
316 return &filter->base;
317 }
318