[PATCH libevdev] tools: add a tool to estimate the resolution of a mouse

Benjamin Tissoires benjamin.tissoires at gmail.com
Thu Nov 20 10:51:45 PST 2014


Hey Peter,

On Mon, Nov 17, 2014 at 1:08 AM, Peter Hutterer
<peter.hutterer at who-t.net> wrote:
> 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 at who-t.net>
> ---
> Example output:
>
> $ sudo ./tools/mouse-dpi-tool /dev/input/event5
> Mouse Lenovo Optical USB Mouse on /dev/input/event5
> Move the device along the x-axis.
> Pause for 3 seconds to reset, Ctrl+C to exit.
> Covered distance in device units:     1157 at frequency 125.0Hz         \^C
> Estimated sampling frequency: 125Hz
> To calculate resolution, measure physical distance covered
> and look up the matching resolution in the table below
>       73mm          2.89in           400dpi
>       48mm          1.93in           600dpi
>       36mm          1.45in           800dpi
>       29mm          1.16in          1000dpi
>       24mm          0.96in          1200dpi
>       20mm          0.83in          1400dpi
>       18mm          0.72in          1600dpi
>       16mm          0.64in          1800dpi
>       14mm          0.58in          2000dpi
>       13mm          0.53in          2200dpi
>       12mm          0.48in          2400dpi
>
>

Thanks for the tool Peter. I don't have much to say, so I nitpicked a
little while trying to find issues :)
Comments inlined.

>
>  tools/.gitignore       |   1 +
>  tools/Makefile.am      |   6 +-
>  tools/mouse-dpi-tool.c | 231 +++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 237 insertions(+), 1 deletion(-)
>  create mode 100644 tools/mouse-dpi-tool.c
>
> diff --git a/tools/.gitignore b/tools/.gitignore
> index 8084fed..292c08e 100644
> --- a/tools/.gitignore
> +++ b/tools/.gitignore
> @@ -1,2 +1,3 @@
>  libevdev-events
>  touchpad-edge-detector
> +mouse-dpi-tool
> diff --git a/tools/Makefile.am b/tools/Makefile.am
> index 5d3600d..8e3950d 100644
> --- a/tools/Makefile.am
> +++ b/tools/Makefile.am
> @@ -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
> index 0000000..de5e09a
> --- /dev/null
> +++ b/tools/mouse-dpi-tool.c
> @@ -0,0 +1,231 @@
> +/*
> + * 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;

I am pretty sure some compiler flags will tell you that progress may
be used without init (but not entirely sure either).

> +       char status = 0;

This works because status is the last char to be put on the line. IMO,
this may strike back at some point.

> +
> +       switch (progress) {
> +               case 0: status = '|'; break;
> +               case 1: status = '/'; break;
> +               case 2: status = '-'; break;
> +               case 3: 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);

I just wonder if taking the max is sufficient and if a glitch in the
data may not corrupt the final frequency. I am not entirely sure such
a glitch would be possible.

> +                       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;
> +

Shouldn't you test the various flags of fds[0].revents? This might
help in case the user disconnects the mouse while the program is
running (at least you can output a more specific error message to the
user and ask not to do it again :) )

> +               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 for 3 seconds to reset, Ctrl+C to exit.\n");

"Pause for 3 seconds to reset" might be ambiguous. Adding "before
moving again along the X axis" explicitly shows that if you take more
than 3 secs to hit Ctrl-C, you will not reset the data.

> +       setbuf(stdout, NULL);
> +
> +       rc = mainloop(dev, &measurements);
> +
> +       printf("\n");
> +
> +       print_summary(&measurements);
> +
> +       libevdev_free(dev);
> +       close(fd);
> +
> +       return rc;
> +}
> +
> --
> 2.1.0
>

Cheers,
Benjamin


More information about the Input-tools mailing list