tools: add a tool to estimate the resolution of a mouse
authorPeter Hutterer <peter.hutterer@who-t.net>
Mon, 17 Nov 2014 05:52:24 +0000 (15:52 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Thu, 20 Nov 2014 23:42:12 +0000 (09:42 +1000)
Relative devices don't provide a physical resolution to the host. For things
like pointer acceleration, the physical amount of movement is better as
baseline than the movement in device units.

Alas, many devices don't come with any information at all, so the users have
to guess. Help that guesswork by providing a tool that does the calculations
for them.

This tool measures the device units covered, then prints the frequency and an
lookup table for various resolutions (in dpi) to match to the physical
movement of the device.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Reviewed-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
tools/.gitignore
tools/Makefile.am
tools/mouse-dpi-tool.c [new file with mode: 0644]

index 8084fed..292c08e 100644 (file)
@@ -1,2 +1,3 @@
 libevdev-events
 touchpad-edge-detector
+mouse-dpi-tool
index 5d3600d..8e3950d 100644 (file)
@@ -1,5 +1,7 @@
 noinst_PROGRAMS = libevdev-events
-bin_PROGRAMS = touchpad-edge-detector
+bin_PROGRAMS = \
+              touchpad-edge-detector \
+              mouse-dpi-tool
 
 AM_CPPFLAGS = $(GCC_CFLAGS) -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_srcdir)/libevdev
 libevdev_ldadd = $(top_builddir)/libevdev/libevdev.la
@@ -10,3 +12,5 @@ libevdev_events_LDADD = $(libevdev_ldadd)
 touchpad_edge_detector_SOURCES = touchpad-edge-detector.c
 touchpad_edge_detector_LDADD = $(libevdev_ldadd)
 
+mouse_dpi_tool_SOURCES = mouse-dpi-tool.c
+mouse_dpi_tool_LDADD = $(libevdev_ldadd)
diff --git a/tools/mouse-dpi-tool.c b/tools/mouse-dpi-tool.c
new file mode 100644 (file)
index 0000000..f063028
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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;
+       double frequency;
+       uint32_t ms;
+};
+
+static int
+usage(void) {
+       printf("Usage: %s /dev/input/event0\n", program_invocation_short_name);
+       printf("\n");
+       printf("This tool reads relative events from the kernel and calculates\n "
+              "the distance covered and frequency of the incoming events.\n");
+       return 1;
+}
+
+static inline uint32_t
+tv2ms(const struct timeval *tv)
+{
+       return tv->tv_sec * 1000 + tv->tv_usec/1000;
+}
+
+static inline double
+get_frequency(double last, double current)
+{
+       return 1000.0/(current - last);
+}
+
+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",
+              abs(m->distance), m->frequency, status);
+
+       return 0;
+}
+
+static int
+handle_event(struct measurements *m, const struct input_event *ev)
+{
+       if (ev->type == EV_SYN) {
+               const int idle_reset = 3000; /* ms */
+               uint32_t last_millis = m->ms;
+
+               m->ms = tv2ms(&ev->time);
+
+               /* reset after pause */
+               if (last_millis + idle_reset < m->ms) {
+                       m->frequency = 0.0;
+                       m->distance = 0;
+               } else {
+                       double freq = get_frequency(last_millis, m->ms);
+                       m->frequency = max(freq, m->frequency);
+                       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;
+
+       printf("Estimated sampling frequency: %dHz\n", (int)m->frequency);
+       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);
+       }
+}
+
+int
+main (int argc, char **argv) {
+       int rc;
+       int fd;
+       const char *path;
+       struct libevdev *dev;
+       struct measurements measurements = {0};
+
+       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);
+       printf("Move the device along the x-axis.\n");
+       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);
+
+       libevdev_free(dev);
+       close(fd);
+
+       return rc;
+}