1// SPDX-License-Identifier: MIT
2/*
3 * Copyright © 2014 Red Hat, Inc.
4 */
5
6#include "config.h"
7
8#include <errno.h>
9#include <fcntl.h>
10#include <libgen.h>
11#include <limits.h>
12#include <math.h>
13#include <poll.h>
14#include <signal.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <unistd.h>
20
21#include "libevdev/libevdev.h"
22
23#define min(a, b) (((a) < (b)) ? (a) : (b))
24#define max(a, b) (((a) > (b)) ? (a) : (b))
25
26static int signalled = 0;
27
28static int
29usage(const char *progname) {
30	printf("Usage: %s 12x34 /dev/input/eventX\n", progname);
31	printf("\n");
32	printf("This tool reads the touchpad events from the kernel and calculates\n "
33	       "the minimum and maximum for the x and y coordinates, respectively.\n"
34	       "The first argument is the physical size of the touchpad in mm (WIDTHxHEIGHT).\n");
35	return 1;
36}
37
38struct dimensions {
39	int top, bottom, left, right;
40};
41
42struct size {
43	int w, h;
44};
45
46static int
47print_current_values(const struct dimensions *d)
48{
49	static int progress;
50	char status = 0;
51
52	switch (progress) {
53		case 0: status = '|'; break;
54		case 1: status = '/'; break;
55		case 2: status = '-'; break;
56		case 3: status = '\\'; break;
57	}
58
59	progress = (progress + 1) % 4;
60
61	printf("\rTouchpad sends:	x [%d..%d], y [%d..%d] %c",
62			d->left, d->right, d->top, d->bottom, status);
63	return 0;
64}
65
66static int
67handle_event(struct dimensions *d, const struct input_event *ev) {
68	if (ev->type == EV_SYN)
69		return print_current_values(d);
70
71	if (ev->type != EV_ABS)
72		return 0;
73
74	switch(ev->code) {
75		case ABS_X:
76		case ABS_MT_POSITION_X:
77			d->left = min(d->left, ev->value);
78			d->right = max(d->right, ev->value);
79			break;
80		case ABS_Y:
81		case ABS_MT_POSITION_Y:
82			d->top = min(d->top, ev->value);
83			d->bottom = max(d->bottom, ev->value);
84			break;
85	}
86
87	return 0;
88}
89
90static void
91signal_handler(__attribute__((__unused__)) int signal)
92{
93	signalled++;
94}
95
96static int
97mainloop(struct libevdev *dev, struct dimensions *dim) {
98	struct pollfd fds;
99
100	fds.fd = libevdev_get_fd(dev);
101	fds.events = POLLIN;
102
103	signal(SIGINT, signal_handler);
104
105	while (poll(&fds, 1, -1)) {
106		struct input_event ev;
107		int rc;
108
109		if (signalled)
110			break;
111
112		do {
113			rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
114			if (rc == LIBEVDEV_READ_STATUS_SYNC) {
115				fprintf(stderr, "Error: cannot keep up\n");
116				return 1;
117			}
118
119			if (rc != -EAGAIN && rc < 0) {
120				fprintf(stderr, "Error: %s\n", strerror(-rc));
121				return 1;
122
123			}
124
125			if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
126				handle_event(dim, &ev);
127		} while (rc != -EAGAIN);
128	}
129
130	return 0;
131}
132
133static inline void
134pid_vid_matchstr(struct libevdev *dev, char *match, size_t sz)
135{
136	snprintf(match, sz, "input:b%04Xv%04Xp%04X",
137		libevdev_get_id_bustype(dev),
138		libevdev_get_id_vendor(dev),
139		libevdev_get_id_product(dev));
140}
141
142static inline void
143dmi_matchstr(struct libevdev *dev, char *match, size_t sz)
144{
145	char modalias[PATH_MAX];
146	FILE *fp;
147
148	fp = fopen("/sys/class/dmi/id/modalias", "r");
149	if (!fp || fgets(modalias, sizeof(modalias), fp) == NULL) {
150		sprintf(match, "ERROR READING DMI MODALIAS");
151		if (fp)
152			fclose(fp);
153		return;
154	}
155
156	fclose(fp);
157
158	modalias[strlen(modalias) - 1] = '\0'; /* drop \n */
159	snprintf(match, sz, "name:%s:%s", libevdev_get_name(dev), modalias);
160}
161
162static void
163print_udev_override_rule(struct libevdev *dev,
164			 const struct dimensions *dim,
165			 const struct size *size) {
166	const struct input_absinfo *x, *y;
167	char match[PATH_MAX];
168	int w, h;
169	int xres, yres;
170
171	x = libevdev_get_abs_info(dev, ABS_X);
172	y = libevdev_get_abs_info(dev, ABS_Y);
173	w = dim->right - dim->left;
174	h = dim->bottom - dim->top;
175	xres = round((double)w/size->w);
176	yres = round((double)h/size->h);
177
178	if (x->resolution && y->resolution) {
179		int width = x->maximum - x->minimum,
180		    height = y->maximum - y->minimum;
181		printf("Touchpad size as listed by the kernel: %dx%dmm\n",
182		       width/x->resolution, height/y->resolution);
183	} else {
184		printf("Touchpad has no resolution, size unknown\n");
185	}
186
187	printf("User-specified touchpad size: %dx%dmm\n", size->w, size->h);
188	printf("Calculated ranges: %d/%d\n", w, h);
189	printf("\n");
190	printf("Suggested udev rule:\n");
191
192	switch(libevdev_get_id_bustype(dev)) {
193	case BUS_USB:
194	case BUS_BLUETOOTH:
195		pid_vid_matchstr(dev, match, sizeof(match));
196		break;
197	default:
198		dmi_matchstr(dev, match, sizeof(match));
199		break;
200	}
201
202	printf("# <Laptop model description goes here>\n"
203	       "evdev:%s*\n"
204	       " EVDEV_ABS_00=%d:%d:%d\n"
205	       " EVDEV_ABS_01=%d:%d:%d\n",
206	       match,
207	       dim->left, dim->right, xres,
208	       dim->top, dim->bottom, yres);
209	if (libevdev_has_event_code(dev, EV_ABS, ABS_MT_POSITION_X))
210		printf(" EVDEV_ABS_35=%d:%d:%d\n"
211		       " EVDEV_ABS_36=%d:%d:%d\n",
212		       dim->left, dim->right, xres,
213		       dim->top, dim->bottom, yres);
214}
215
216int main (int argc, char **argv) {
217	int rc;
218	int fd;
219	const char *path;
220	struct libevdev *dev;
221	struct dimensions dim;
222	struct size size;
223
224	if (argc < 3)
225		return usage(basename(argv[0]));
226
227	if (sscanf(argv[1], "%dx%d", &size.w, &size.h) != 2 ||
228	    size.w <= 0 || size.h <= 0)
229		return usage(basename(argv[0]));
230
231	if (size.w < 30 || size.h < 30) {
232		fprintf(stderr,
233			"%dx%dmm is too small for a touchpad.\n"
234			"Please specify the touchpad size in mm.\n",
235			size.w, size.h);
236		return 1;
237	}
238
239	path = argv[2];
240	if (path[0] == '-')
241		return usage(basename(argv[0]));
242
243	fd = open(path, O_RDONLY|O_NONBLOCK);
244	if (fd < 0) {
245		fprintf(stderr, "Error opening the device: %s\n", strerror(errno));
246		return 1;
247	}
248
249	rc = libevdev_new_from_fd(fd, &dev);
250	if (rc != 0) {
251		fprintf(stderr, "Error fetching the device info: %s\n", strerror(-rc));
252		return 1;
253	}
254
255	if (libevdev_grab(dev, LIBEVDEV_GRAB) != 0) {
256		fprintf(stderr, "Error: cannot grab the device, something else is grabbing it.\n");
257		fprintf(stderr, "Use 'fuser -v %s' to find processes with an open fd\n", path);
258		return 1;
259	}
260	libevdev_grab(dev, LIBEVDEV_UNGRAB);
261
262	if (!libevdev_has_event_code(dev, EV_ABS, ABS_X) ||
263	    !libevdev_has_event_code(dev, EV_ABS, ABS_Y)) {
264		fprintf(stderr, "Error: this device does not have abs axes\n");
265		rc = EXIT_FAILURE;
266		goto out;
267	}
268
269	dim.left = INT_MAX;
270	dim.right = INT_MIN;
271	dim.top = INT_MAX;
272	dim.bottom = INT_MIN;
273
274	printf("Touchpad %s on %s\n", libevdev_get_name(dev), path);
275	printf("Move one finger around the touchpad to detect the actual edges\n");
276	printf("Kernel says:	x [%d..%d], y [%d..%d]\n",
277			libevdev_get_abs_minimum(dev, ABS_X),
278			libevdev_get_abs_maximum(dev, ABS_X),
279			libevdev_get_abs_minimum(dev, ABS_Y),
280			libevdev_get_abs_maximum(dev, ABS_Y));
281
282	setbuf(stdout, NULL);
283
284	rc = mainloop(dev, &dim);
285	printf("\n\n");
286
287	print_udev_override_rule(dev, &dim, &size);
288
289out:
290	libevdev_free(dev);
291	close(fd);
292
293	return rc;
294}
295