2014-11-17 15:52:24 +10:00
|
|
|
/*
|
|
|
|
|
* Copyright © 2014 Red Hat, Inc.
|
|
|
|
|
*
|
|
|
|
|
* Permission to use, copy, modify, distribute, and sell this software
|
|
|
|
|
* and its documentation for any purpose is hereby granted without
|
|
|
|
|
* fee, provided that the above copyright notice appear in all copies
|
|
|
|
|
* and that both that copyright notice and this permission notice
|
|
|
|
|
* appear in supporting documentation, and that the name of Red Hat
|
|
|
|
|
* not be used in advertising or publicity pertaining to distribution
|
|
|
|
|
* of the software without specific, written prior permission. Red
|
|
|
|
|
* Hat makes no representations about the suitability of this software
|
|
|
|
|
* for any purpose. It is provided "as is" without express or implied
|
|
|
|
|
* warranty.
|
|
|
|
|
*
|
|
|
|
|
* THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
|
|
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
|
|
|
|
* NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
|
|
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
|
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
|
|
|
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
|
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <libevdev/libevdev.h>
|
|
|
|
|
#include <sys/signalfd.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
#include <poll.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
|
|
|
|
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
|
|
|
|
|
|
|
|
|
struct measurements {
|
|
|
|
|
int distance;
|
2016-09-19 10:37:14 +10:00
|
|
|
double max_frequency;
|
2015-05-21 11:48:34 +10:00
|
|
|
uint64_t us;
|
2014-11-17 15:52:24 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
usage(void) {
|
|
|
|
|
printf("Usage: %s /dev/input/event0\n", program_invocation_short_name);
|
|
|
|
|
printf("\n");
|
2015-01-07 13:39:55 +10:00
|
|
|
printf("This tool reads relative events from the kernel and calculates\n"
|
|
|
|
|
"the distance covered and maximum frequency of the incoming events.\n"
|
|
|
|
|
"Some mouse devices provide dynamic frequencies, it is\n"
|
|
|
|
|
"recommended to measure multiple times to obtain the highest value.\n");
|
2014-11-17 15:52:24 +10:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-21 11:48:34 +10:00
|
|
|
static inline uint64_t
|
|
|
|
|
tv2us(const struct timeval *tv)
|
2014-11-17 15:52:24 +10:00
|
|
|
{
|
2015-05-21 11:48:34 +10:00
|
|
|
return tv->tv_sec * 1000000 + tv->tv_usec;
|
2014-11-17 15:52:24 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline double
|
2016-09-16 07:26:42 +10:00
|
|
|
get_frequency(uint64_t last, uint64_t current)
|
2014-11-17 15:52:24 +10:00
|
|
|
{
|
2015-05-21 11:48:34 +10:00
|
|
|
return 1000000.0/(current - last);
|
2014-11-17 15:52:24 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
print_current_values(const struct measurements *m)
|
|
|
|
|
{
|
|
|
|
|
static int progress = 0;
|
|
|
|
|
char status = 0;
|
|
|
|
|
|
|
|
|
|
switch (progress) {
|
|
|
|
|
case 0: status = '|'; break;
|
|
|
|
|
case 1: status = '/'; break;
|
|
|
|
|
case 2: status = '-'; break;
|
|
|
|
|
case 3: status = '\\'; break;
|
|
|
|
|
default:
|
|
|
|
|
status = '?';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
progress = (progress + 1) % 4;
|
|
|
|
|
|
|
|
|
|
printf("\rCovered distance in device units: %8d at frequency %3.1fHz %c",
|
2016-09-19 10:37:14 +10:00
|
|
|
abs(m->distance), m->max_frequency, status);
|
2014-11-17 15:52:24 +10:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
handle_event(struct measurements *m, const struct input_event *ev)
|
|
|
|
|
{
|
|
|
|
|
if (ev->type == EV_SYN) {
|
2015-05-21 11:48:34 +10:00
|
|
|
const int idle_reset = 3000000; /* us */
|
|
|
|
|
uint64_t last_us = m->us;
|
2014-11-17 15:52:24 +10:00
|
|
|
|
2015-05-21 11:48:34 +10:00
|
|
|
m->us = tv2us(&ev->time);
|
2014-11-17 15:52:24 +10:00
|
|
|
|
|
|
|
|
/* reset after pause */
|
2015-05-21 11:48:34 +10:00
|
|
|
if (last_us + idle_reset < m->us) {
|
2016-09-19 10:37:14 +10:00
|
|
|
m->max_frequency = 0.0;
|
2014-11-17 15:52:24 +10:00
|
|
|
m->distance = 0;
|
|
|
|
|
} else {
|
2015-05-21 11:48:34 +10:00
|
|
|
double freq = get_frequency(last_us, m->us);
|
2016-09-19 10:37:14 +10:00
|
|
|
if (freq < 1200)
|
|
|
|
|
m->max_frequency = max(freq, m->max_frequency);
|
2014-11-17 15:52:24 +10:00
|
|
|
return print_current_values(m);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
} else if (ev->type != EV_REL)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
switch(ev->code) {
|
|
|
|
|
case REL_X:
|
|
|
|
|
m->distance += ev->value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
mainloop(struct libevdev *dev, struct measurements *m) {
|
|
|
|
|
struct pollfd fds[2];
|
|
|
|
|
sigset_t mask;
|
|
|
|
|
|
|
|
|
|
fds[0].fd = libevdev_get_fd(dev);
|
|
|
|
|
fds[0].events = POLLIN;
|
|
|
|
|
|
|
|
|
|
sigemptyset(&mask);
|
|
|
|
|
sigaddset(&mask, SIGINT);
|
|
|
|
|
fds[1].fd = signalfd(-1, &mask, SFD_NONBLOCK);
|
|
|
|
|
fds[1].events = POLLIN;
|
|
|
|
|
|
|
|
|
|
sigprocmask(SIG_BLOCK, &mask, NULL);
|
|
|
|
|
|
|
|
|
|
while (poll(fds, 2, -1)) {
|
|
|
|
|
struct input_event ev;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
if (fds[1].revents)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
|
|
|
|
|
if (rc == LIBEVDEV_READ_STATUS_SYNC) {
|
|
|
|
|
fprintf(stderr, "Error: cannot keep up\n");
|
|
|
|
|
return 1;
|
|
|
|
|
} else if (rc != -EAGAIN && rc < 0) {
|
|
|
|
|
fprintf(stderr, "Error: %s\n", strerror(-rc));
|
|
|
|
|
return 1;
|
|
|
|
|
} else if (rc == LIBEVDEV_READ_STATUS_SUCCESS) {
|
|
|
|
|
handle_event(m, &ev);
|
|
|
|
|
}
|
|
|
|
|
} while (rc != -EAGAIN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
print_summary(struct measurements *m)
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
|
2016-09-19 10:37:14 +10:00
|
|
|
printf("Estimated sampling frequency: %dHz\n", (int)m->max_frequency);
|
2014-11-17 15:52:24 +10:00
|
|
|
printf("To calculate resolution, measure physical distance covered\n"
|
|
|
|
|
"and look up the matching resolution in the table below\n");
|
|
|
|
|
|
|
|
|
|
m->distance = abs(m->distance);
|
|
|
|
|
|
|
|
|
|
/* If the mouse has more than 2500dpi, the manufacturer usually
|
|
|
|
|
shows off on their website anyway */
|
|
|
|
|
for (res = 400; res <= 2500; res += 200) {
|
|
|
|
|
double inch = m->distance/(double)res;
|
|
|
|
|
printf("%8dmm %8.2fin %8ddpi\n",
|
|
|
|
|
(int)(inch * 25.4), inch, res);
|
|
|
|
|
}
|
2014-12-12 07:47:20 +10:00
|
|
|
printf("If your resolution is not in the list, calculate it with:\n"
|
|
|
|
|
"\tresolution=%d/inches, or\n"
|
2015-04-23 10:36:55 +10:00
|
|
|
"\tresolution=%d * 25.4/mm\n", m->distance, m->distance);
|
2014-11-17 15:52:24 +10:00
|
|
|
}
|
|
|
|
|
|
2014-12-01 14:51:13 +10:00
|
|
|
static inline const char*
|
|
|
|
|
bustype(int bustype)
|
|
|
|
|
{
|
|
|
|
|
const char *bus;
|
|
|
|
|
|
|
|
|
|
switch(bustype) {
|
|
|
|
|
case BUS_PCI: bus = "pci"; break;
|
|
|
|
|
case BUS_ISAPNP: bus = "isapnp"; break;
|
|
|
|
|
case BUS_USB: bus = "usb"; break;
|
|
|
|
|
case BUS_HIL: bus = "hil"; break;
|
|
|
|
|
case BUS_BLUETOOTH: bus = "bluetooth"; break;
|
|
|
|
|
case BUS_VIRTUAL: bus = "virtual"; break;
|
|
|
|
|
default: bus = "unknown bus type"; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bus;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-17 15:52:24 +10:00
|
|
|
int
|
|
|
|
|
main (int argc, char **argv) {
|
|
|
|
|
int rc;
|
|
|
|
|
int fd;
|
|
|
|
|
const char *path;
|
|
|
|
|
struct libevdev *dev;
|
2015-01-06 09:05:25 +10:00
|
|
|
struct measurements measurements = {0, 0, 0};
|
2014-11-17 15:52:24 +10:00
|
|
|
|
|
|
|
|
if (argc < 2)
|
|
|
|
|
return usage();
|
|
|
|
|
|
|
|
|
|
path = argv[1];
|
|
|
|
|
if (path[0] == '-')
|
|
|
|
|
return usage();
|
|
|
|
|
|
|
|
|
|
fd = open(path, O_RDONLY|O_NONBLOCK);
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
fprintf(stderr, "Error opening the device: %s\n", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = libevdev_new_from_fd(fd, &dev);
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
fprintf(stderr, "Error fetching the device info: %s\n", strerror(-rc));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (libevdev_grab(dev, LIBEVDEV_GRAB) != 0) {
|
|
|
|
|
fprintf(stderr, "Error: cannot grab the device, something else is grabbing it.\n");
|
|
|
|
|
fprintf(stderr, "Use 'fuser -v %s' to find processes with an open fd\n", path);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
libevdev_grab(dev, LIBEVDEV_UNGRAB);
|
|
|
|
|
|
|
|
|
|
printf("Mouse %s on %s\n", libevdev_get_name(dev), path);
|
2014-12-12 07:47:20 +10:00
|
|
|
printf("Move the device 250mm/10in or more along the x-axis.\n");
|
2014-11-17 15:52:24 +10:00
|
|
|
printf("Pause 3 seconds before movement to reset, Ctrl+C to exit.\n");
|
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
|
|
|
|
|
|
rc = mainloop(dev, &measurements);
|
|
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
|
|
print_summary(&measurements);
|
|
|
|
|
|
2014-12-01 14:51:13 +10:00
|
|
|
printf("\n");
|
|
|
|
|
printf("Entry for hwdb match (replace XXX with the resolution in DPI):\n"
|
2014-12-05 14:17:27 +10:00
|
|
|
"mouse:%s:v%04xp%04x:name:%s:\n"
|
2014-12-01 14:51:13 +10:00
|
|
|
" MOUSE_DPI=XXX@%d\n",
|
|
|
|
|
bustype(libevdev_get_id_bustype(dev)),
|
|
|
|
|
libevdev_get_id_vendor(dev),
|
|
|
|
|
libevdev_get_id_product(dev),
|
|
|
|
|
libevdev_get_name(dev),
|
2016-09-19 10:37:14 +10:00
|
|
|
(int)measurements.max_frequency);
|
2014-12-01 14:51:13 +10:00
|
|
|
|
2014-11-17 15:52:24 +10:00
|
|
|
libevdev_free(dev);
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
}
|