[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