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