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
38static void
39print_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
65static void
66print_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
112static void
113print_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 */
145static inline double
146mmps_to_upus(double mmps, int dpi)
147{
148	return mmps * (dpi/25.4) / 1e6;
149}
150
151static void
152print_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
173static void
174usage(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	       "	custom    ... custom motion filter, use --custom-points and --custom-step with this argument\n"
195	       "--custom-points=\"<double>;...;<double>\"  ... n points defining a custom acceleration function\n"
196	       "--custom-step=<double>  ... distance along the x-axis between each point, \n"
197	       "                            starting from 0. defaults to 1.0\n"
198	       "\n"
199	       "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n"
200	       "and the arguments are interpreted as sequence of delta x coordinates\n"
201	       "\n"
202	       "If stdin is a pipe, mode defaults to 'sequence' and the pipe is read \n"
203	       "for delta coordinates\n"
204	       "\n"
205	       "Delta coordinates passed into this tool must be in dpi as\n"
206	       "specified by the --dpi argument\n"
207	       "\n"
208	       "Output best viewed with gnuplot. See output for gnuplot commands\n");
209}
210
211enum mode {
212	ACCEL,
213	MOTION,
214	DELTA,
215	SEQUENCE,
216};
217
218int
219main(int argc, char **argv)
220{
221	struct motion_filter *filter;
222	double step = 0.1,
223	       max_dx = 10;
224	int nevents = 0;
225	enum mode mode = ACCEL;
226	double custom_deltas[1024];
227	double speed = 0.0;
228	int dpi = 1000;
229	bool use_averaging = false;
230	const char *filter_type = "linear";
231	accel_profile_func_t profile = NULL;
232	double tp_multiplier = 1.0;
233	struct libinput_config_accel_custom_func custom_func = {
234		.step = 1.0,
235		.npoints = 2,
236		.points = {0.0, 1.0},
237	};
238	struct libinput_config_accel *accel_config =
239		libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM);
240
241	enum {
242		OPT_HELP = 1,
243		OPT_MODE,
244		OPT_NEVENTS,
245		OPT_MAXDX,
246		OPT_STEP,
247		OPT_SPEED,
248		OPT_DPI,
249		OPT_FILTER,
250		OPT_CUSTOM_POINTS,
251		OPT_CUSTOM_STEP,
252	};
253
254	while (1) {
255		int c;
256		int option_index = 0;
257		static struct option long_options[] = {
258			{"help", 0, 0, OPT_HELP },
259			{"mode", 1, 0, OPT_MODE },
260			{"nevents", 1, 0, OPT_NEVENTS },
261			{"maxdx", 1, 0, OPT_MAXDX },
262			{"step", 1, 0, OPT_STEP },
263			{"speed", 1, 0, OPT_SPEED },
264			{"dpi", 1, 0, OPT_DPI },
265			{"filter", 1, 0, OPT_FILTER },
266			{"custom-points", 1, 0, OPT_CUSTOM_POINTS },
267			{"custom-step", 1, 0, OPT_CUSTOM_STEP },
268			{0, 0, 0, 0}
269		};
270
271		c = getopt_long(argc, argv, "",
272				long_options, &option_index);
273		if (c == -1)
274			break;
275
276		switch (c) {
277		case OPT_HELP:
278			usage();
279			exit(0);
280			break;
281		case OPT_MODE:
282			if (streq(optarg, "accel"))
283				mode = ACCEL;
284			else if (streq(optarg, "motion"))
285				mode = MOTION;
286			else if (streq(optarg, "delta"))
287				mode = DELTA;
288			else if (streq(optarg, "sequence"))
289				mode = SEQUENCE;
290			else {
291				usage();
292				return 1;
293			}
294			break;
295		case OPT_NEVENTS:
296			nevents = atoi(optarg);
297			if (nevents == 0) {
298				usage();
299				return 1;
300			}
301			break;
302		case OPT_MAXDX:
303			max_dx = strtod(optarg, NULL);
304			if (max_dx == 0.0) {
305				usage();
306				return 1;
307			}
308			break;
309		case OPT_STEP:
310			step = strtod(optarg, NULL);
311			if (step == 0.0) {
312				usage();
313				return 1;
314			}
315			break;
316		case OPT_SPEED:
317			speed = strtod(optarg, NULL);
318			break;
319		case OPT_DPI:
320			dpi = strtod(optarg, NULL);
321			break;
322		case OPT_FILTER:
323			filter_type = optarg;
324			break;
325		case OPT_CUSTOM_POINTS: {
326			size_t npoints;
327			double *points = double_array_from_string(optarg,
328								  ";",
329								  &npoints);
330			if (!points ||
331			    npoints < LIBINPUT_ACCEL_NPOINTS_MIN ||
332			    npoints > LIBINPUT_ACCEL_NPOINTS_MAX) {
333				fprintf(stderr,
334					"Invalid --custom-points\n"
335					"Please provide at least 2 points separated by a semicolon\n"
336					" e.g. --custom-points=\"1.0;1.5\"\n");
337				free(points);
338				return 1;
339			}
340			custom_func.npoints = npoints;
341			memcpy(custom_func.points,
342			       points,
343			       sizeof(*points) * npoints);
344			free(points);
345			break;
346		}
347		case OPT_CUSTOM_STEP:
348			custom_func.step = strtod(optarg, NULL);
349			break;
350		default:
351			usage();
352			exit(1);
353			break;
354		}
355	}
356
357	if (streq(filter_type, "linear")) {
358		filter = create_pointer_accelerator_filter_linear(dpi,
359								  use_averaging);
360		profile = pointer_accel_profile_linear;
361	} else if (streq(filter_type, "low-dpi")) {
362		filter = create_pointer_accelerator_filter_linear_low_dpi(dpi,
363									  use_averaging);
364		profile = pointer_accel_profile_linear_low_dpi;
365	} else if (streq(filter_type, "touchpad")) {
366		filter = create_pointer_accelerator_filter_touchpad(dpi,
367								    0, 0,
368								    use_averaging);
369		profile = touchpad_accel_profile_linear;
370	} else if (streq(filter_type, "x230")) {
371		filter = create_pointer_accelerator_filter_lenovo_x230(dpi,
372								       use_averaging);
373		profile = touchpad_lenovo_x230_accel_profile;
374	} else if (streq(filter_type, "trackpoint")) {
375		filter = create_pointer_accelerator_filter_trackpoint(tp_multiplier,
376								      use_averaging);
377		profile = trackpoint_accel_profile;
378	} else if (streq(filter_type, "custom")) {
379		libinput_config_accel_set_points(accel_config,
380						 LIBINPUT_ACCEL_TYPE_MOTION,
381						 custom_func.step,
382						 custom_func.npoints,
383						 custom_func.points);
384		filter = create_custom_accelerator_filter();
385		profile = custom_accel_profile_motion;
386		filter_set_accel_config(filter, accel_config);
387	} else {
388		fprintf(stderr, "Invalid filter type %s\n", filter_type);
389		return 1;
390	}
391
392	assert(filter != NULL);
393	filter_set_speed(filter, speed);
394
395	if (!isatty(STDIN_FILENO)) {
396		char buf[12];
397		mode = SEQUENCE;
398		nevents = 0;
399		memset(custom_deltas, 0, sizeof(custom_deltas));
400
401		while(fgets(buf, sizeof(buf), stdin) && nevents < 1024) {
402			custom_deltas[nevents++] = strtod(buf, NULL);
403		}
404	} else if (optind < argc) {
405		mode = SEQUENCE;
406		nevents = 0;
407		memset(custom_deltas, 0, sizeof(custom_deltas));
408		while (optind < argc)
409			custom_deltas[nevents++] = strtod(argv[optind++], NULL);
410	} else if (mode == SEQUENCE) {
411		usage();
412		return 1;
413	}
414
415	switch (mode) {
416	case ACCEL:
417		print_accel_func(filter, profile, dpi);
418		break;
419	case DELTA:
420		print_ptraccel_deltas(filter, step);
421		break;
422	case MOTION:
423		print_ptraccel_movement(filter, nevents, max_dx, step);
424		break;
425	case SEQUENCE:
426		print_ptraccel_sequence(filter, nevents, custom_deltas);
427		break;
428	}
429
430	libinput_config_accel_destroy(accel_config);
431	filter_destroy(filter);
432
433	return 0;
434}
435