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
25 static int signalled = 0;
26
27 struct 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
36 static int
usage(const char * progname)37 usage(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
47 static inline double
get_frequency(uint64_t last,uint64_t current)48 get_frequency(uint64_t last, uint64_t current)
49 {
50 return 1000000.0/(current - last);
51 }
52
53 static inline void
push_frequency(struct measurements * m,double freq)54 push_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
68 static int
print_current_values(const struct measurements * m)69 print_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
92 static int
handle_event(struct measurements * m,const struct input_event * ev)93 handle_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
127 static void
signal_handler(int signal)128 signal_handler(__attribute__((__unused__)) int signal)
129 {
130 signalled++;
131 }
132
133 static int
mainloop(struct libevdev * dev,struct measurements * m)134 mainloop(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
169 static inline double
mean_frequency(struct measurements * m)170 mean_frequency(struct measurements *m)
171 {
172 int idx;
173
174 idx = m->nfrequencies/2;
175 return m->frequencies[idx];
176 }
177
178 static inline const char*
bustype(int bustype)179 bustype(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
196 static void
print_summary(struct libevdev * dev,struct measurements * m)197 print_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
244 int
main(int argc,char ** argv)245 main (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