• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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