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