1 /*
2  * Copyright © 2015 Red Hat, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23 
24 #include "config.h"
25 
26 #include <assert.h>
27 #include <errno.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <getopt.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "filter.h"
36 #include "libinput-util.h"
37 
38 static void
print_ptraccel_deltas(struct motion_filter * filter,double step)39 print_ptraccel_deltas(struct motion_filter *filter, double step)
40 {
41 	struct device_float_coords motion;
42 	struct normalized_coords accel;
43 	uint64_t time = 0;
44 	double i;
45 
46 	printf("# gnuplot:\n");
47 	printf("# set xlabel dx unaccelerated\n");
48 	printf("# set ylabel dx accelerated\n");
49 	printf("# set style data lines\n");
50 	printf("# plot \"gnuplot.data\" using 1:2 title \"step %.2f\"\n", step);
51 	printf("#\n");
52 
53 	/* Accel flattens out after 15 and becomes linear */
54 	for (i = 0.0; i < 15.0; i += step) {
55 		motion.x = i;
56 		motion.y = 0;
57 		time += us(12500); /* pretend 80Hz data */
58 
59 		accel = filter_dispatch(filter, &motion, NULL, time);
60 
61 		printf("%.2f	%.3f\n", i, accel.x);
62 	}
63 }
64 
65 static void
print_ptraccel_movement(struct motion_filter * filter,int nevents,double max_dx,double step)66 print_ptraccel_movement(struct motion_filter *filter,
67 			int nevents,
68 			double max_dx,
69 			double step)
70 {
71 	struct device_float_coords motion;
72 	struct normalized_coords accel;
73 	uint64_t time = 0;
74 	double dx;
75 	int i;
76 
77 	printf("# gnuplot:\n");
78 	printf("# set xlabel \"event number\"\n");
79 	printf("# set ylabel \"delta motion\"\n");
80 	printf("# set style data lines\n");
81 	printf("# plot \"gnuplot.data\" using 1:2 title \"dx out\", \\\n");
82 	printf("#      \"gnuplot.data\" using 1:3 title \"dx in\"\n");
83 	printf("#\n");
84 
85 	if (nevents == 0) {
86 		if (step > 1.0)
87 			nevents = max_dx;
88 		else
89 			nevents = 1.0 * max_dx/step + 0.5;
90 
91 		/* Print more events than needed so we see the curve
92 		 * flattening out */
93 		nevents *= 1.5;
94 	}
95 
96 	dx = 0;
97 
98 	for (i = 0; i < nevents; i++) {
99 		motion.x = dx;
100 		motion.y = 0;
101 		time += us(12500); /* pretend 80Hz data */
102 
103 		accel = filter_dispatch(filter, &motion, NULL, time);
104 
105 		printf("%d	%.3f	%.3f\n", i, accel.x, dx);
106 
107 		if (dx < max_dx)
108 			dx += step;
109 	}
110 }
111 
112 static void
print_ptraccel_sequence(struct motion_filter * filter,int nevents,double * deltas)113 print_ptraccel_sequence(struct motion_filter *filter,
114 			int nevents,
115 			double *deltas)
116 {
117 	struct device_float_coords motion;
118 	struct normalized_coords accel;
119 	uint64_t time = 0;
120 	double *dx;
121 	int i;
122 
123 	printf("# gnuplot:\n");
124 	printf("# set xlabel \"event number\"\n");
125 	printf("# set ylabel \"delta motion\"\n");
126 	printf("# set style data lines\n");
127 	printf("# plot \"gnuplot.data\" using 1:2 title \"dx out\", \\\n");
128 	printf("#      \"gnuplot.data\" using 1:3 title \"dx in\"\n");
129 	printf("#\n");
130 
131 	dx = deltas;
132 
133 	for (i = 0; i < nevents; i++, dx++) {
134 		motion.x = *dx;
135 		motion.y = 0;
136 		time += us(12500); /* pretend 80Hz data */
137 
138 		accel = filter_dispatch(filter, &motion, NULL, time);
139 
140 		printf("%d	%.3f	%.3f\n", i, accel.x, *dx);
141 	}
142 }
143 
144 /* mm/s → units/µs */
145 static inline double
mmps_to_upus(double mmps,int dpi)146 mmps_to_upus(double mmps, int dpi)
147 {
148 	return mmps * (dpi/25.4) / 1e6;
149 }
150 
151 static void
print_accel_func(struct motion_filter * filter,accel_profile_func_t profile,int dpi)152 print_accel_func(struct motion_filter *filter,
153 		 accel_profile_func_t profile,
154 		 int dpi)
155 {
156 	double mmps;
157 
158 	printf("# gnuplot:\n");
159 	printf("# set xlabel \"speed (mm/s)\"\n");
160 	printf("# set ylabel \"raw accel factor\"\n");
161 	printf("# set style data lines\n");
162 	printf("# plot \"gnuplot.data\" using 1:2 title 'accel factor'\n");
163 	printf("#\n");
164 	printf("# data: velocity(mm/s) factor velocity(units/us) velocity(units/ms)\n");
165 	for (mmps = 0.0; mmps < 1000.0; mmps += 1) {
166 		double units_per_us = mmps_to_upus(mmps, dpi);
167 		double units_per_ms = units_per_us * 1000.0;
168 		double result = profile(filter, NULL, units_per_us, 0 /* time */);
169 		printf("%.8f\t%.4f\t%.8f\t%.8f\n", mmps, result, units_per_us, units_per_ms);
170 	}
171 }
172 
173 static void
usage(void)174 usage(void)
175 {
176 	printf("Usage: %s [options] [dx1] [dx2] [...] > gnuplot.data\n", program_invocation_short_name);
177 	printf("\n"
178 	       "Options:\n"
179 	       "--mode=<accel|motion|delta|sequence> \n"
180 	       "	accel    ... print accel factor (default)\n"
181 	       "	motion   ... print motion to accelerated motion\n"
182 	       "	delta    ... print delta to accelerated delta\n"
183 	       "	sequence ... print motion for custom delta sequence\n"
184 	       "--maxdx=<double>  ... in motion mode only. Stop increasing dx at maxdx\n"
185 	       "--steps=<double>  ... in motion and delta modes only. Increase dx by step each round\n"
186 	       "--speed=<double>  ... accel speed [-1, 1], default 0\n"
187 	       "--dpi=<int>	... device resolution in DPI (default: 1000)\n"
188 	       "--filter=<linear|low-dpi|touchpad|x230|trackpoint> \n"
189 	       "	linear	  ... the default motion filter\n"
190 	       "	low-dpi	  ... low-dpi filter, use --dpi with this argument\n"
191 	       "	touchpad  ... the touchpad motion filter\n"
192 	       "	x230  	  ... custom filter for the Lenovo x230 touchpad\n"
193 	       "	trackpoint... trackpoint motion filter\n"
194 	       "\n"
195 	       "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n"
196 	       "and the arguments are interpreted as sequence of delta x coordinates\n"
197 	       "\n"
198 	       "If stdin is a pipe, mode defaults to 'sequence' and the pipe is read \n"
199 	       "for delta coordinates\n"
200 	       "\n"
201 	       "Delta coordinates passed into this tool must be in dpi as\n"
202 	       "specified by the --dpi argument\n"
203 	       "\n"
204 	       "Output best viewed with gnuplot. See output for gnuplot commands\n");
205 }
206 
207 enum mode {
208 	ACCEL,
209 	MOTION,
210 	DELTA,
211 	SEQUENCE,
212 };
213 
214 int
main(int argc,char ** argv)215 main(int argc, char **argv)
216 {
217 	struct motion_filter *filter;
218 	double step = 0.1,
219 	       max_dx = 10;
220 	int nevents = 0;
221 	enum mode mode = ACCEL;
222 	double custom_deltas[1024];
223 	double speed = 0.0;
224 	int dpi = 1000;
225 	bool use_averaging = false;
226 	const char *filter_type = "linear";
227 	accel_profile_func_t profile = NULL;
228 	double tp_multiplier = 1.0;
229 
230 	enum {
231 		OPT_HELP = 1,
232 		OPT_MODE,
233 		OPT_NEVENTS,
234 		OPT_MAXDX,
235 		OPT_STEP,
236 		OPT_SPEED,
237 		OPT_DPI,
238 		OPT_FILTER,
239 	};
240 
241 	while (1) {
242 		int c;
243 		int option_index = 0;
244 		static struct option long_options[] = {
245 			{"help", 0, 0, OPT_HELP },
246 			{"mode", 1, 0, OPT_MODE },
247 			{"nevents", 1, 0, OPT_NEVENTS },
248 			{"maxdx", 1, 0, OPT_MAXDX },
249 			{"step", 1, 0, OPT_STEP },
250 			{"speed", 1, 0, OPT_SPEED },
251 			{"dpi", 1, 0, OPT_DPI },
252 			{"filter", 1, 0, OPT_FILTER },
253 			{0, 0, 0, 0}
254 		};
255 
256 		c = getopt_long(argc, argv, "",
257 				long_options, &option_index);
258 		if (c == -1)
259 			break;
260 
261 		switch (c) {
262 		case OPT_HELP:
263 			usage();
264 			exit(0);
265 			break;
266 		case OPT_MODE:
267 			if (streq(optarg, "accel"))
268 				mode = ACCEL;
269 			else if (streq(optarg, "motion"))
270 				mode = MOTION;
271 			else if (streq(optarg, "delta"))
272 				mode = DELTA;
273 			else if (streq(optarg, "sequence"))
274 				mode = SEQUENCE;
275 			else {
276 				usage();
277 				return 1;
278 			}
279 			break;
280 		case OPT_NEVENTS:
281 			nevents = atoi(optarg);
282 			if (nevents == 0) {
283 				usage();
284 				return 1;
285 			}
286 			break;
287 		case OPT_MAXDX:
288 			max_dx = strtod(optarg, NULL);
289 			if (max_dx == 0.0) {
290 				usage();
291 				return 1;
292 			}
293 			break;
294 		case OPT_STEP:
295 			step = strtod(optarg, NULL);
296 			if (step == 0.0) {
297 				usage();
298 				return 1;
299 			}
300 			break;
301 		case OPT_SPEED:
302 			speed = strtod(optarg, NULL);
303 			break;
304 		case OPT_DPI:
305 			dpi = strtod(optarg, NULL);
306 			break;
307 		case OPT_FILTER:
308 			filter_type = optarg;
309 			break;
310 		default:
311 			usage();
312 			exit(1);
313 			break;
314 		}
315 	}
316 
317 	if (streq(filter_type, "linear")) {
318 		filter = create_pointer_accelerator_filter_linear(dpi,
319 								  use_averaging);
320 		profile = pointer_accel_profile_linear;
321 	} else if (streq(filter_type, "low-dpi")) {
322 		filter = create_pointer_accelerator_filter_linear_low_dpi(dpi,
323 									  use_averaging);
324 		profile = pointer_accel_profile_linear_low_dpi;
325 	} else if (streq(filter_type, "touchpad")) {
326 		filter = create_pointer_accelerator_filter_touchpad(dpi,
327 								    0, 0,
328 								    use_averaging);
329 		profile = touchpad_accel_profile_linear;
330 	} else if (streq(filter_type, "x230")) {
331 		filter = create_pointer_accelerator_filter_lenovo_x230(dpi,
332 								       use_averaging);
333 		profile = touchpad_lenovo_x230_accel_profile;
334 	} else if (streq(filter_type, "trackpoint")) {
335 		filter = create_pointer_accelerator_filter_trackpoint(tp_multiplier,
336 								      use_averaging);
337 		profile = trackpoint_accel_profile;
338 	} else {
339 		fprintf(stderr, "Invalid filter type %s\n", filter_type);
340 		return 1;
341 	}
342 
343 	assert(filter != NULL);
344 	filter_set_speed(filter, speed);
345 
346 	if (!isatty(STDIN_FILENO)) {
347 		char buf[12];
348 		mode = SEQUENCE;
349 		nevents = 0;
350 		memset(custom_deltas, 0, sizeof(custom_deltas));
351 
352 		while(fgets(buf, sizeof(buf), stdin) && nevents < 1024) {
353 			custom_deltas[nevents++] = strtod(buf, NULL);
354 		}
355 	} else if (optind < argc) {
356 		mode = SEQUENCE;
357 		nevents = 0;
358 		memset(custom_deltas, 0, sizeof(custom_deltas));
359 		while (optind < argc)
360 			custom_deltas[nevents++] = strtod(argv[optind++], NULL);
361 	} else if (mode == SEQUENCE) {
362 		usage();
363 		return 1;
364 	}
365 
366 	switch (mode) {
367 	case ACCEL:
368 		print_accel_func(filter, profile, dpi);
369 		break;
370 	case DELTA:
371 		print_ptraccel_deltas(filter, step);
372 		break;
373 	case MOTION:
374 		print_ptraccel_movement(filter, nevents, max_dx, step);
375 		break;
376 	case SEQUENCE:
377 		print_ptraccel_sequence(filter, nevents, custom_deltas);
378 		break;
379 	}
380 
381 	filter_destroy(filter);
382 
383 	return 0;
384 }
385