[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