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