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