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

Peter Hutterer peter.hutterer at who-t.net
Thu Nov 20 15:05:41 PST 2014


On Thu, Nov 20, 2014 at 01:51:45PM -0500, Benjamin Tissoires wrote:
> 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).

fwiw, statics are initialized to 0, and I even found the right reference to
it: C99 standard, section 6.7.8 paragraph 10.  been that way for a while,
but the c99 standard was the only one I had lying around on my disk.

either way, I agree though, added = 0 for expressiveness.

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

fixed with default: status = '?'; case in the switch statement now

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

I had that glitch and it was caused by uninitialized data. If anything, if a
device has 143Hz (like in my case) that'd be a good hint that something is
wrong, we shouldn't paper over it.

> 
> > +                       return print_current_values(m);
> > +               }
> 
> return 0?

added, thanks.
> 
> > +       } 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 :) )

shouldn't matter, if we disconnect libevdev_next_event will get a ENODEV and
we print that in the strerror couple of lines below here.
and given this is a helper tool only, I think a bit of a rough interface is
acceptable. might even be a learning experience for mouse-disconnecting
users :)

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

How about "Pause 3 seconds before movement to reset"? Again, here too I'm
inclined to make this a bit of a learning experience :)

thanks for the review though

Cheers,
   Peter

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