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 <poll.h>
13#include <signal.h>
14#include <stdint.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <unistd.h>
19
20#include "libevdev/libevdev.h"
21
22#define min(a, b) (((a) < (b)) ? (a) : (b))
23#define max(a, b) (((a) > (b)) ? (a) : (b))
24
25static int signalled = 0;
26
27struct measurements {
28	int distance;
29	double max_frequency;
30	double *frequencies;
31	size_t frequencies_sz;
32	size_t nfrequencies;
33	uint64_t us;
34};
35
36static int
37usage(const char *progname) {
38	printf("Usage: %s /dev/input/event0\n", progname);
39	printf("\n");
40	printf("This tool reads relative events from the kernel and calculates\n"
41	       "the distance covered and maximum frequency of the incoming events.\n"
42	       "Some mouse devices provide dynamic frequencies, it is\n"
43	       "recommended to measure multiple times to obtain the highest value.\n");
44	return 1;
45}
46
47static inline double
48get_frequency(uint64_t last, uint64_t current)
49{
50	return 1000000.0/(current - last);
51}
52
53static inline void
54push_frequency(struct measurements *m, double freq)
55{
56	if (m->nfrequencies == m->frequencies_sz) {
57		m->frequencies_sz += 100;
58		m->frequencies = realloc(m->frequencies,
59					 m->frequencies_sz * sizeof *m->frequencies);
60		if (!m->frequencies)
61			abort();
62	}
63
64	m->frequencies[m->nfrequencies] = freq;
65	m->nfrequencies++;
66}
67
68static int
69print_current_values(const struct measurements *m)
70{
71	static int progress = 0;
72	char status = 0;
73
74	switch (progress) {
75		case 0: status = '|'; break;
76		case 1: status = '/'; break;
77		case 2: status = '-'; break;
78		case 3: status = '\\'; break;
79		default:
80			status = '?';
81			break;
82	}
83
84	progress = (progress + 1) % 4;
85
86	printf("\rCovered distance in device units: %8d at frequency %3.1fHz 	%c",
87	       abs(m->distance), m->max_frequency, status);
88
89	return 0;
90}
91
92static int
93handle_event(struct measurements *m, const struct input_event *ev)
94{
95	if (ev->type == EV_SYN) {
96		const int idle_reset = 3000000; /* us */
97		uint64_t last_us = m->us;
98
99		m->us = ev->input_event_sec * 1000000 + ev->input_event_usec;
100
101		/* reset after pause */
102		if (last_us + idle_reset < m->us) {
103			m->max_frequency = 0.0;
104			m->distance = 0;
105		} else {
106			double freq = get_frequency(last_us, m->us);
107			push_frequency(m, freq);
108			m->max_frequency = max(freq, m->max_frequency);
109			return print_current_values(m);
110		}
111
112		return 0;
113	}
114
115	if (ev->type != EV_REL)
116		return 0;
117
118	switch(ev->code) {
119		case REL_X:
120			m->distance += ev->value;
121			break;
122	}
123
124	return 0;
125}
126
127static void
128signal_handler(__attribute__((__unused__)) int signal)
129{
130	signalled++;
131}
132
133static int
134mainloop(struct libevdev *dev, struct measurements *m) {
135	struct pollfd fds;
136
137	fds.fd = libevdev_get_fd(dev);
138	fds.events = POLLIN;
139
140	signal(SIGINT, signal_handler);
141
142	while (poll(&fds, 1, -1)) {
143		struct input_event ev;
144		int rc;
145
146		if (signalled)
147			break;
148
149		do {
150			rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
151			if (rc == LIBEVDEV_READ_STATUS_SYNC) {
152				fprintf(stderr, "Error: cannot keep up\n");
153				return 1;
154			}
155
156			if (rc != -EAGAIN && rc < 0) {
157				fprintf(stderr, "Error: %s\n", strerror(-rc));
158				return 1;
159			}
160
161			if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
162				handle_event(m, &ev);
163		} while (rc != -EAGAIN);
164	}
165
166	return 0;
167}
168
169static inline double
170mean_frequency(struct measurements *m)
171{
172	int idx;
173
174	idx = m->nfrequencies/2;
175	return m->frequencies[idx];
176}
177
178static inline const char*
179bustype(int bustype)
180{
181	const char *bus;
182
183	switch(bustype) {
184		case BUS_PCI: bus = "pci"; break;
185		case BUS_ISAPNP: bus = "isapnp"; break;
186		case BUS_USB: bus = "usb"; break;
187		case BUS_HIL: bus = "hil"; break;
188		case BUS_BLUETOOTH: bus = "bluetooth"; break;
189		case BUS_VIRTUAL: bus = "virtual"; break;
190		default: bus = "unknown bus type"; break;
191	}
192
193	return bus;
194}
195
196static void
197print_summary(struct libevdev *dev, struct measurements *m)
198{
199	int res;
200	int max_freq, mean_freq;
201
202	if (m->nfrequencies == 0) {
203		fprintf(stderr, "Error: no matching events received.\n");
204		return;
205	}
206
207	max_freq = (int)m->max_frequency;
208	mean_freq = (int)mean_frequency(m);
209
210	printf("Estimated sampling frequency: %dHz (mean %dHz)\n",
211	       max_freq, mean_freq);
212
213	if (max_freq > mean_freq * 1.3)
214		printf("WARNING: Max frequency is more than 30%% higher "
215		       "than mean frequency. Manual verification required!\n");
216
217	printf("To calculate resolution, measure physical distance covered\n"
218	       "and look up the matching resolution in the table below\n");
219
220	m->distance = abs(m->distance);
221
222	/* If the mouse has more than 2500dpi, the manufacturer usually
223	   shows off on their website anyway */
224	for (res = 400; res <= 2500; res += 200) {
225		double inch = m->distance/(double)res;
226		printf("%8dmm	%8.2fin	%8ddpi\n",
227		       (int)(inch * 25.4), inch, res);
228	}
229	printf("If your resolution is not in the list, calculate it with:\n"
230	       "\tresolution=%d/inches, or\n"
231	       "\tresolution=%d * 25.4/mm\n", m->distance, m->distance);
232
233	printf("\n");
234	printf("Entry for hwdb match (replace XXX with the resolution in DPI):\n"
235	       "mouse:%s:v%04xp%04x:name:%s:\n"
236	       " MOUSE_DPI=XXX@%d\n",
237	       bustype(libevdev_get_id_bustype(dev)),
238	       libevdev_get_id_vendor(dev),
239	       libevdev_get_id_product(dev),
240	       libevdev_get_name(dev),
241	       (int)m->max_frequency);
242}
243
244int
245main (int argc, char **argv) {
246	int rc;
247	int fd;
248	const char *path;
249	struct libevdev *dev;
250	struct measurements measurements = {0};
251
252	if (argc < 2)
253		return usage(basename(argv[0]));
254
255	path = argv[1];
256	if (path[0] == '-')
257		return usage(basename(argv[0]));
258
259	fd = open(path, O_RDONLY|O_NONBLOCK);
260	if (fd < 0) {
261		fprintf(stderr, "Error opening the device: %s\n", strerror(errno));
262		return 1;
263	}
264
265	rc = libevdev_new_from_fd(fd, &dev);
266	if (rc != 0) {
267		fprintf(stderr, "Error fetching the device info: %s\n", strerror(-rc));
268		return 1;
269	}
270
271	if (libevdev_grab(dev, LIBEVDEV_GRAB) != 0) {
272		fprintf(stderr, "Error: cannot grab the device, something else is grabbing it.\n");
273		fprintf(stderr, "Use 'fuser -v %s' to find processes with an open fd\n", path);
274		return 1;
275	}
276	libevdev_grab(dev, LIBEVDEV_UNGRAB);
277
278	printf("Mouse %s on %s\n", libevdev_get_name(dev), path);
279	printf("Move the device 250mm/10in or more along the x-axis.\n");
280	printf("Pause 3 seconds before movement to reset, Ctrl+C to exit.\n");
281	setbuf(stdout, NULL);
282
283	rc = mainloop(dev, &measurements);
284
285	printf("\n");
286
287	print_summary(dev, &measurements);
288
289	libevdev_free(dev);
290	close(fd);
291
292	return rc;
293}
294