diff --git a/doc/normalization-of-relative-motion.dox b/doc/normalization-of-relative-motion.dox index aaa1735f..dd5d39b3 100644 --- a/doc/normalization-of-relative-motion.dox +++ b/doc/normalization-of-relative-motion.dox @@ -12,10 +12,33 @@ physical movement or 10 millimeters, depending on the sensor. This affects pointer acceleration in libinput and interpretation of relative coordinates in callers. -libinput normalizes all relative input to a physical resolution of -1000dpi, the same delta from two different devices thus represents the -same physical movement of those two devices (within sensor error -margins). +libinput does partial normalization of relative input. For devices with a +resolution of 1000dpi and higher, motion events are normalized to a default +of 1000dpi before pointer acceleration is applied. As a result, devices with +1000dpi and above feel the same. + +Devices below 1000dpi are not normalized (normalization of a 1-device unit +movement on a 400dpi mouse would cause a 2.5 pixel movement). Instead, +libinput applies a dpi-dependent acceleration function. At low speeds, a +1-device unit movement usually translates into a 1-pixel movements. As the +movement speed increases, acceleration is applied - at high speeds a low-dpi +device will roughly feel the same as a higher-dpi mouse. + +This normalization only applies to accelerated coordinates, unaccelerated +coordiantes are left in device-units. It is up to the caller to interpret +those coordinates correctly. + +@section Normalization of touchpad coordinates + +Touchpads may have a different resolution for the horizontal and vertical +axis. Interpreting coordinates from the touchpad without taking resolutino +into account results in uneven motion. + +libinput scales unaccelerated touchpad motion do the resolution of the +touchpad's x axis, i.e. the unaccelerated value for the y axis is: + y = (x / resolution_x) * resolution_y + +@section Setting custom DPI settings Devices usually do not advertise their resolution and libinput relies on the udev property MOUSE_DPI for this information. This property is usually diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c index 328a744b..e85a9d77 100644 --- a/src/evdev-mt-touchpad-gestures.c +++ b/src/evdev-mt-touchpad-gestures.c @@ -91,6 +91,7 @@ static void tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time) { struct normalized_coords delta, unaccel; + struct device_float_coords raw; /* When a clickpad is clicked, combine motion of all active touches */ if (tp->buttons.is_clickpad && tp->buttons.state) @@ -101,8 +102,11 @@ tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time) delta = tp_filter_motion(tp, &unaccel, time); if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) { - pointer_notify_motion(&tp->device->base, time, - &delta, &unaccel); + raw = tp_unnormalize_for_xaxis(tp, unaccel); + pointer_notify_motion(&tp->device->base, + time, + &delta, + &raw); } } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 2466dab5..edad6110 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -321,6 +321,21 @@ tp_normalize_delta(struct tp_dispatch *tp, struct device_float_coords delta) return normalized; } +/** + * Takes a dpi-normalized set of coordinates, returns a set of coordinates + * in the x-axis' coordinate space. + */ +static inline struct device_float_coords +tp_unnormalize_for_xaxis(struct tp_dispatch *tp, struct normalized_coords delta) +{ + struct device_float_coords raw; + + raw.x = delta.x / tp->accel.x_scale_coeff; + raw.y = delta.y / tp->accel.x_scale_coeff; /* <--- not a typo */ + + return raw; +} + struct normalized_coords tp_get_delta(struct tp_touch *t); diff --git a/src/evdev.c b/src/evdev.c index 905b5cc7..346f11ab 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -281,6 +281,7 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) struct libinput_seat *seat = base->seat; struct normalized_coords accel, unaccel; struct device_coords point; + struct device_float_coords raw; slot = device->mt.slot; @@ -289,6 +290,8 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) return; case EVDEV_RELATIVE_MOTION: normalize_delta(device, &device->rel, &unaccel); + raw.x = device->rel.x; + raw.y = device->rel.y; device->rel.x = 0; device->rel.y = 0; @@ -305,7 +308,7 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) if (normalized_is_zero(accel) && normalized_is_zero(unaccel)) break; - pointer_notify_motion(base, time, &accel, &unaccel); + pointer_notify_motion(base, time, &accel, &raw); break; case EVDEV_ABSOLUTE_MT_DOWN: if (!(device->seat_caps & EVDEV_DEVICE_TOUCH)) @@ -1407,7 +1410,8 @@ int evdev_device_init_pointer_acceleration(struct evdev_device *device, accel_profile_func_t profile) { - device->pointer.filter = create_pointer_accelerator_filter(profile); + device->pointer.filter = create_pointer_accelerator_filter(profile, + device->dpi); if (!device->pointer.filter) return -1; @@ -1824,6 +1828,19 @@ evdev_configure_mt_device(struct evdev_device *device) return 0; } +static inline int +evdev_init_accel(struct evdev_device *device) +{ + accel_profile_func_t profile; + + if (device->dpi < DEFAULT_MOUSE_DPI) + profile = pointer_accel_profile_linear_low_dpi; + else + profile = pointer_accel_profile_linear; + + return evdev_device_init_pointer_acceleration(device, profile); +} + static int evdev_configure_device(struct evdev_device *device) { @@ -1919,9 +1936,7 @@ evdev_configure_device(struct evdev_device *device) udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK) { if (libevdev_has_event_code(evdev, EV_REL, REL_X) && libevdev_has_event_code(evdev, EV_REL, REL_Y) && - evdev_device_init_pointer_acceleration( - device, - pointer_accel_profile_linear) == -1) + evdev_init_accel(device) == -1) return -1; device->seat_caps |= EVDEV_DEVICE_POINTER; diff --git a/src/evdev.h b/src/evdev.h index 9c30b402..aa548d2d 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -36,9 +36,6 @@ #include "timer.h" #include "filter.h" -/* The HW DPI rate we normalize to before calculating pointer acceleration */ -#define DEFAULT_MOUSE_DPI 1000 - /* * The constant (linear) acceleration factor we use to normalize trackpoint * deltas before calculating pointer acceleration. diff --git a/src/filter.c b/src/filter.c index b37ca766..35449f56 100644 --- a/src/filter.c +++ b/src/filter.c @@ -111,6 +111,8 @@ struct pointer_accelerator { double threshold; /* units/ms */ double accel; /* unitless factor */ double incline; /* incline of the function */ + + double dpi_factor; }; static void @@ -262,8 +264,16 @@ accelerator_filter(struct motion_filter *filter, double velocity; /* units/ms */ double accel_value; /* unitless factor */ struct normalized_coords accelerated; + struct normalized_coords unnormalized; + double dpi_factor = accel->dpi_factor; - feed_trackers(accel, unaccelerated, time); + /* For low-dpi mice, use device units, everything else uses + 1000dpi normalized */ + dpi_factor = min(1.0, dpi_factor); + unnormalized.x = unaccelerated->x * dpi_factor; + unnormalized.y = unaccelerated->y * dpi_factor; + + feed_trackers(accel, &unnormalized, time); velocity = calculate_velocity(accel, time); accel_value = calculate_acceleration(accel, data, @@ -271,10 +281,10 @@ accelerator_filter(struct motion_filter *filter, accel->last_velocity, time); - accelerated.x = accel_value * unaccelerated->x; - accelerated.y = accel_value * unaccelerated->y; + accelerated.x = accel_value * unnormalized.x; + accelerated.y = accel_value * unnormalized.y; - accel->last = *unaccelerated; + accel->last = unnormalized; accel->last_velocity = velocity; @@ -346,7 +356,8 @@ struct motion_filter_interface accelerator_interface = { }; struct motion_filter * -create_pointer_accelerator_filter(accel_profile_func_t profile) +create_pointer_accelerator_filter(accel_profile_func_t profile, + int dpi) { struct pointer_accelerator *filter; @@ -369,13 +380,51 @@ create_pointer_accelerator_filter(accel_profile_func_t profile) filter->accel = DEFAULT_ACCELERATION; filter->incline = DEFAULT_INCLINE; + filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_DPI; + return &filter->base; } +/** + * Custom acceleration function for mice < 1000dpi. + * At slow motion, a single device unit causes a one-pixel movement. + * The threshold/max accel depends on the DPI, the smaller the DPI the + * earlier we accelerate and the higher the maximum acceleration is. Result: + * at low speeds we get pixel-precision, at high speeds we get approx. the + * same movement as a high-dpi mouse. + * + * Note: data fed to this function is in device units, not normalized. + */ +double +pointer_accel_profile_linear_low_dpi(struct motion_filter *filter, + void *data, + double speed_in, /* in device units */ + uint64_t time) +{ + struct pointer_accelerator *accel_filter = + (struct pointer_accelerator *)filter; + + double s1, s2; + double max_accel = accel_filter->accel; /* unitless factor */ + const double threshold = accel_filter->threshold; /* units/ms */ + const double incline = accel_filter->incline; + double factor; + double dpi_factor = accel_filter->dpi_factor; + + max_accel /= dpi_factor; + + s1 = min(1, 0.3 + speed_in * 10); + s2 = 1 + (speed_in - threshold * dpi_factor) * incline; + + factor = min(max_accel, s2 > 1 ? s2 : s1); + + return factor; +} + double pointer_accel_profile_linear(struct motion_filter *filter, void *data, - double speed_in, + double speed_in, /* 1000-dpi normalized */ uint64_t time) { struct pointer_accelerator *accel_filter = @@ -387,7 +436,7 @@ pointer_accel_profile_linear(struct motion_filter *filter, const double incline = accel_filter->incline; double factor; - s1 = min(1, 0.3 + speed_in * 4); + s1 = min(1, 0.3 + speed_in * 10); s2 = 1 + (speed_in - threshold) * incline; factor = min(max_accel, s2 > 1 ? s2 : s1); diff --git a/src/filter.h b/src/filter.h index de949979..617fab1f 100644 --- a/src/filter.h +++ b/src/filter.h @@ -58,13 +58,19 @@ typedef double (*accel_profile_func_t)(struct motion_filter *filter, uint64_t time); struct motion_filter * -create_pointer_accelerator_filter(accel_profile_func_t filter); +create_pointer_accelerator_filter(accel_profile_func_t filter, + int dpi); /* * Pointer acceleration profiles. */ double +pointer_accel_profile_linear_low_dpi(struct motion_filter *filter, + void *data, + double speed_in, + uint64_t time); +double pointer_accel_profile_linear(struct motion_filter *filter, void *data, double speed_in, diff --git a/src/libinput-private.h b/src/libinput-private.h index 5192b651..d11f000f 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -324,7 +324,7 @@ void pointer_notify_motion(struct libinput_device *device, uint64_t time, const struct normalized_coords *delta, - const struct normalized_coords *unaccel); + const struct device_float_coords *raw); void pointer_notify_motion_absolute(struct libinput_device *device, diff --git a/src/libinput-util.h b/src/libinput-util.h index 0c56b765..00c93fe0 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -37,6 +37,9 @@ #define VENDOR_ID_APPLE 0x5ac #define VENDOR_ID_WACOM 0x56a +/* The HW DPI rate we normalize to before calculating pointer acceleration */ +#define DEFAULT_MOUSE_DPI 1000 + void set_logging_enabled(int enabled); diff --git a/src/libinput.c b/src/libinput.c index 319934ac..d164604c 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -96,7 +96,7 @@ struct libinput_event_pointer { struct libinput_event base; uint32_t time; struct normalized_coords delta; - struct normalized_coords delta_unaccel; + struct device_float_coords delta_raw; struct device_coords absolute; struct discrete_coords discrete; uint32_t button; @@ -339,7 +339,7 @@ libinput_event_pointer_get_dx_unaccelerated( 0, LIBINPUT_EVENT_POINTER_MOTION); - return event->delta_unaccel.x; + return event->delta_raw.x; } LIBINPUT_EXPORT double @@ -351,7 +351,7 @@ libinput_event_pointer_get_dy_unaccelerated( 0, LIBINPUT_EVENT_POINTER_MOTION); - return event->delta_unaccel.y; + return event->delta_raw.y; } LIBINPUT_EXPORT double @@ -1130,7 +1130,7 @@ void pointer_notify_motion(struct libinput_device *device, uint64_t time, const struct normalized_coords *delta, - const struct normalized_coords *unaccel) + const struct device_float_coords *raw) { struct libinput_event_pointer *motion_event; @@ -1144,7 +1144,7 @@ pointer_notify_motion(struct libinput_device *device, *motion_event = (struct libinput_event_pointer) { .time = time, .delta = *delta, - .delta_unaccel = *unaccel, + .delta_raw = *raw, }; post_device_event(device, time, diff --git a/src/libinput.h b/src/libinput.h index 7d907f16..5df71836 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -462,8 +462,8 @@ libinput_event_pointer_get_time(struct libinput_event_pointer *event); * If a device employs pointer acceleration, the delta returned by this * function is the accelerated delta. * - * Relative motion deltas are normalized to represent those of a device with - * 1000dpi resolution. See @ref motion_normalization for more details. + * Relative motion deltas are to be interpreted as pixel movement of a + * standardized mouse. See @ref motion_normalization for more details. * * @note It is an application bug to call this function for events other than * @ref LIBINPUT_EVENT_POINTER_MOTION. @@ -483,8 +483,8 @@ libinput_event_pointer_get_dx(struct libinput_event_pointer *event); * If a device employs pointer acceleration, the delta returned by this * function is the accelerated delta. * - * Relative motion deltas are normalized to represent those of a device with - * 1000dpi resolution. See @ref motion_normalization for more details. + * Relative motion deltas are to be interpreted as pixel movement of a + * standardized mouse. See @ref motion_normalization for more details. * * @note It is an application bug to call this function for events other than * @ref LIBINPUT_EVENT_POINTER_MOTION. @@ -501,10 +501,11 @@ libinput_event_pointer_get_dy(struct libinput_event_pointer *event); * current event. For pointer events that are not of type @ref * LIBINPUT_EVENT_POINTER_MOTION, this function returns 0. * - * Relative unaccelerated motion deltas are normalized to represent those of a - * device with 1000dpi resolution. See @ref motion_normalization for more - * details. Note that unaccelerated events are not equivalent to 'raw' events - * as read from the device. + * Relative unaccelerated motion deltas are raw device coordinates. + * Note that these coordinates are subject to the device's native + * resolution. Touchpad coordinates represent raw device coordinates in the + * X resolution of the touchpad. See @ref motion_normalization for more + * details. * * @note It is an application bug to call this function for events other than * @ref LIBINPUT_EVENT_POINTER_MOTION. @@ -522,10 +523,11 @@ libinput_event_pointer_get_dx_unaccelerated( * current event. For pointer events that are not of type @ref * LIBINPUT_EVENT_POINTER_MOTION, this function returns 0. * - * Relative unaccelerated motion deltas are normalized to represent those of a - * device with 1000dpi resolution. See @ref motion_normalization for more - * details. Note that unaccelerated events are not equivalent to 'raw' events - * as read from the device. + * Relative unaccelerated motion deltas are raw device coordinates. + * Note that these coordinates are subject to the device's native + * resolution. Touchpad coordinates represent raw device coordinates in the + * X resolution of the touchpad. See @ref motion_normalization for more + * details. * * @note It is an application bug to call this function for events other than * @ref LIBINPUT_EVENT_POINTER_MOTION. diff --git a/test/Makefile.am b/test/Makefile.am index 6a55c7f0..e8843c27 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -23,6 +23,7 @@ liblitest_la_SOURCES = \ litest-logitech-trackball.c \ litest-mouse.c \ litest-mouse-roccat.c \ + litest-mouse-low-dpi.c \ litest-ms-surface-cover.c \ litest-protocol-a-touch-screen.c \ litest-qemu-usb-tablet.c \ diff --git a/test/litest-mouse-low-dpi.c b/test/litest-mouse-low-dpi.c new file mode 100644 index 00000000..dccf40fa --- /dev/null +++ b/test/litest-mouse-low-dpi.c @@ -0,0 +1,75 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_mouse_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_MOUSE_LOW_DPI); + litest_set_current_device(d); +} + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x1, + .product = 0x1, +}; + +static int events[] = { + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + EV_REL, REL_X, + EV_REL, REL_Y, + EV_REL, REL_WHEEL, + -1 , -1, +}; + +static const char udev_rule[] = +"ACTION==\"remove\", GOTO=\"touchpad_end\"\n" +"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n" +"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n" +"\n" +"ATTRS{name}==\"litest Low DPI Mouse*\",\\\n" +" ENV{MOUSE_DPI}=\"400@125\"\n" +"\n" +"LABEL=\"touchpad_end\""; + +struct litest_test_device litest_mouse_low_dpi_device = { + .type = LITEST_MOUSE_LOW_DPI, + .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL, + .shortname = "low-dpi mouse", + .setup = litest_mouse_setup, + .interface = NULL, + + .name = "Low DPI Mouse", + .id = &input_id, + .absinfo = NULL, + .events = events, + .udev_rule = udev_rule, +}; diff --git a/test/litest.c b/test/litest.c index 456b8a96..0c5eedb9 100644 --- a/test/litest.c +++ b/test/litest.c @@ -352,6 +352,7 @@ extern struct litest_test_device litest_ms_surface_cover_device; extern struct litest_test_device litest_logitech_trackball_device; extern struct litest_test_device litest_atmel_hover_device; extern struct litest_test_device litest_alps_dualpoint_device; +extern struct litest_test_device litest_mouse_low_dpi_device; struct litest_test_device* devices[] = { &litest_synaptics_clickpad_device, @@ -378,6 +379,7 @@ struct litest_test_device* devices[] = { &litest_logitech_trackball_device, &litest_atmel_hover_device, &litest_alps_dualpoint_device, + &litest_mouse_low_dpi_device, NULL, }; diff --git a/test/litest.h b/test/litest.h index 4990fdd1..9e782966 100644 --- a/test/litest.h +++ b/test/litest.h @@ -137,6 +137,7 @@ enum litest_device_type { LITEST_LOGITECH_TRACKBALL = -23, LITEST_ATMEL_HOVER = -24, LITEST_ALPS_DUALPOINT = -25, + LITEST_MOUSE_LOW_DPI = -26, }; enum litest_device_feature { diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c index c774e3bf..b2dd1f90 100644 --- a/tools/ptraccel-debug.c +++ b/tools/ptraccel-debug.c @@ -168,6 +168,7 @@ usage(void) "--maxdx= ... in motion mode only. Stop increasing dx at maxdx\n" "--steps= ... in motion and delta modes only. Increase dx by step each round\n" "--speed= ... accel speed [-1, 1], default 0\n" + "--dpi= ... device resolution in DPI (default: 1000)\n" "\n" "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n" "and the arguments are interpreted as sequence of delta x coordinates\n" @@ -191,17 +192,17 @@ main(int argc, char **argv) print_sequence = false; double custom_deltas[1024]; double speed = 0.0; + int dpi = 1000; + enum { OPT_MODE = 1, OPT_NEVENTS, OPT_MAXDX, OPT_STEP, OPT_SPEED, + OPT_DPI, }; - filter = create_pointer_accelerator_filter(pointer_accel_profile_linear); - assert(filter != NULL); - while (1) { int c; int option_index = 0; @@ -211,6 +212,7 @@ main(int argc, char **argv) {"maxdx", 1, 0, OPT_MAXDX }, {"step", 1, 0, OPT_STEP }, {"speed", 1, 0, OPT_SPEED }, + {"dpi", 1, 0, OPT_DPI }, {0, 0, 0, 0} }; @@ -258,6 +260,9 @@ main(int argc, char **argv) case OPT_SPEED: speed = strtod(optarg, NULL); break; + case OPT_DPI: + dpi = strtod(optarg, NULL); + break; default: usage(); exit(1); @@ -265,6 +270,9 @@ main(int argc, char **argv) } } + filter = create_pointer_accelerator_filter(pointer_accel_profile_linear, + dpi); + assert(filter != NULL); filter_set_speed(filter, speed); if (!isatty(STDIN_FILENO)) {