evdev: use a different filter for low resolution touchpad on the Lenovo X230
[platform/upstream/libinput.git] / src / evdev.c
index d1b0504..6a9df49 100644 (file)
@@ -138,6 +138,21 @@ evdev_keyboard_notify_key(struct evdev_device *device,
 }
 
 void
+evdev_pointer_notify_physical_button(struct evdev_device *device,
+                                    uint32_t time,
+                                    int button,
+                                    enum libinput_button_state state)
+{
+       if (evdev_middlebutton_filter_button(device,
+                                            time,
+                                            button,
+                                            state))
+                       return;
+
+       evdev_pointer_notify_button(device, time, button, state);
+}
+
+void
 evdev_pointer_notify_button(struct evdev_device *device,
                            uint32_t time,
                            int button,
@@ -151,13 +166,13 @@ evdev_pointer_notify_button(struct evdev_device *device,
            (state == LIBINPUT_BUTTON_STATE_RELEASED && down_count == 0)) {
                pointer_notify_button(&device->base, time, button, state);
 
-               if (state == LIBINPUT_BUTTON_STATE_RELEASED &&
-                   device->left_handed.change_to_enabled)
-                       device->left_handed.change_to_enabled(device);
+               if (state == LIBINPUT_BUTTON_STATE_RELEASED) {
+                       if (device->left_handed.change_to_enabled)
+                               device->left_handed.change_to_enabled(device);
 
-               if (state == LIBINPUT_BUTTON_STATE_RELEASED &&
-                   device->scroll.change_scroll_method)
-                       device->scroll.change_scroll_method(device);
+                       if (device->scroll.change_scroll_method)
+                               device->scroll.change_scroll_method(device);
+               }
        }
 
 }
@@ -230,16 +245,14 @@ normalize_delta(struct evdev_device *device,
                const struct device_coords *delta,
                struct normalized_coords *normalized)
 {
-       normalized->x = delta->x * (double)device->dpi / DEFAULT_MOUSE_DPI;
-       normalized->y = delta->y * (double)device->dpi / DEFAULT_MOUSE_DPI;
+       normalized->x = delta->x * DEFAULT_MOUSE_DPI / (double)device->dpi;
+       normalized->y = delta->y * DEFAULT_MOUSE_DPI / (double)device->dpi;
 }
 
 static void
 evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
 {
        struct libinput *libinput = device->base.seat->libinput;
-       struct motion_params motion;
-       int32_t x, y;
        int slot;
        int seat_slot;
        struct libinput_device *base = &device->base;
@@ -268,16 +281,10 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
                }
 
                /* Apply pointer acceleration. */
-               motion.dx = unaccel.x;
-               motion.dy = unaccel.y;
-               filter_dispatch(device->pointer.filter, &motion, device, time);
-               accel.x = motion.dx;
-               accel.y = motion.dy;
-
-               if (accel.x == 0.0 && accel.y == 0.0 &&
-                   unaccel.x == 0.0 && unaccel.y == 0.0) {
+               accel = filter_dispatch(device->pointer.filter, &unaccel, device, time);
+
+               if (normalized_is_zero(accel) && normalized_is_zero(unaccel))
                        break;
-               }
 
                pointer_notify_motion(base, time, &accel, &unaccel);
                break;
@@ -304,7 +311,7 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
                transform_absolute(device, &point);
 
                touch_notify_touch_down(base, time, slot, seat_slot,
-                                       point.x, point.y);
+                                       &point);
                break;
        case EVDEV_ABSOLUTE_MT_MOTION:
                if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
@@ -318,7 +325,7 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
 
                transform_absolute(device, &point);
                touch_notify_touch_motion(base, time, slot, seat_slot,
-                                         point.x, point.y);
+                                         &point);
                break;
        case EVDEV_ABSOLUTE_MT_UP:
                if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
@@ -357,14 +364,11 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
                point = device->abs.point;
                transform_absolute(device, &point);
 
-               touch_notify_touch_down(base, time, -1, seat_slot,
-                                       point.x, point.y);
+               touch_notify_touch_down(base, time, -1, seat_slot, &point);
                break;
        case EVDEV_ABSOLUTE_MOTION:
                point = device->abs.point;
                transform_absolute(device, &point);
-               x = point.x;
-               y = point.y;
 
                if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
                        seat_slot = device->abs.seat_slot;
@@ -372,7 +376,8 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
                        if (seat_slot == -1)
                                break;
 
-                       touch_notify_touch_motion(base, time, -1, seat_slot, x, y);
+                       touch_notify_touch_motion(base, time, -1, seat_slot,
+                                                 &point);
                } else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
                        pointer_notify_motion_absolute(base, time, &point);
                }
@@ -440,10 +445,10 @@ evdev_button_scroll_button(struct evdev_device *device,
                } else {
                        /* If the button is released quickly enough emit the
                         * button press/release events. */
-                       evdev_pointer_notify_button(device, time,
+                       evdev_pointer_notify_physical_button(device, time,
                                        device->scroll.button,
                                        LIBINPUT_BUTTON_STATE_PRESSED);
-                       evdev_pointer_notify_button(device, time,
+                       evdev_pointer_notify_physical_button(device, time,
                                        device->scroll.button,
                                        LIBINPUT_BUTTON_STATE_RELEASED);
                }
@@ -515,7 +520,7 @@ evdev_process_key(struct evdev_device *device,
                        evdev_button_scroll_button(device, time, e->value);
                        break;
                }
-               evdev_pointer_notify_button(
+               evdev_pointer_notify_physical_button(
                        device,
                        time,
                        evdev_to_left_handed(device, e->code),
@@ -589,15 +594,16 @@ evdev_notify_axis(struct evdev_device *device,
                  uint32_t axes,
                  enum libinput_pointer_axis_source source,
                  const struct normalized_coords *delta_in,
-                 double x_discrete, double y_discrete)
+                 const struct discrete_coords *discrete_in)
 {
        struct normalized_coords delta = *delta_in;
+       struct discrete_coords discrete = *discrete_in;
 
        if (device->scroll.natural_scrolling_enabled) {
                delta.x *= -1;
                delta.y *= -1;
-               x_discrete *= -1;
-               y_discrete *= -1;
+               discrete.x *= -1;
+               discrete.y *= -1;
        }
 
        pointer_notify_axis(&device->base,
@@ -605,7 +611,7 @@ evdev_notify_axis(struct evdev_device *device,
                            axes,
                            source,
                            &delta,
-                           x_discrete, y_discrete);
+                           &discrete);
 }
 
 static inline void
@@ -613,6 +619,7 @@ evdev_process_relative(struct evdev_device *device,
                       struct input_event *e, uint64_t time)
 {
        struct normalized_coords wheel_degrees = { 0.0, 0.0 };
+       struct discrete_coords discrete = { 0.0, 0.0 };
 
        switch (e->code) {
        case REL_X:
@@ -631,26 +638,26 @@ evdev_process_relative(struct evdev_device *device,
                evdev_flush_pending_event(device, time);
                wheel_degrees.y = -1 * e->value *
                                        device->scroll.wheel_click_angle;
+               discrete.y = -1 * e->value;
                evdev_notify_axis(
                        device,
                        time,
                        AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
                        LIBINPUT_POINTER_AXIS_SOURCE_WHEEL,
                        &wheel_degrees,
-                       0.0,
-                       -1 * e->value);
+                       &discrete);
                break;
        case REL_HWHEEL:
                evdev_flush_pending_event(device, time);
                wheel_degrees.x = e->value * device->scroll.wheel_click_angle;
+               discrete.x = e->value;
                evdev_notify_axis(
                        device,
                        time,
                        AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
                        LIBINPUT_POINTER_AXIS_SOURCE_WHEEL,
                        &wheel_degrees,
-                       e->value,
-                       0.0);
+                       &discrete);
                break;
        }
 }
@@ -1136,6 +1143,24 @@ fallback_dispatch_create(struct libinput_device *device)
        evdev_init_calibration(evdev_device, dispatch);
        evdev_init_sendevents(evdev_device, dispatch);
 
+       /* BTN_MIDDLE is set on mice even when it's not present. So
+        * we can only use the absense of BTN_MIDDLE to mean something, i.e.
+        * we enable it by default on anything that only has L&R.
+        * If we have L&R and no middle, we don't expose it as config
+        * option */
+       if (libevdev_has_event_code(evdev_device->evdev, EV_KEY, BTN_LEFT) &&
+           libevdev_has_event_code(evdev_device->evdev, EV_KEY, BTN_RIGHT)) {
+               bool has_middle = libevdev_has_event_code(evdev_device->evdev,
+                                                         EV_KEY,
+                                                         BTN_MIDDLE);
+               bool want_config = has_middle;
+               bool enable_by_default = !has_middle;
+
+               evdev_init_middlebutton(evdev_device,
+                                       enable_by_default,
+                                       want_config);
+       }
+
        return dispatch;
 }
 
@@ -1145,6 +1170,19 @@ evdev_process_event(struct evdev_device *device, struct input_event *e)
        struct evdev_dispatch *dispatch = device->dispatch;
        uint64_t time = e->time.tv_sec * 1000ULL + e->time.tv_usec / 1000;
 
+#if 0
+       if (libevdev_event_is_code(e, EV_SYN, SYN_REPORT))
+               log_debug(device->base.seat->libinput,
+                         "-------------- EV_SYN ------------\n");
+       else
+               log_debug(device->base.seat->libinput,
+                         "%-7s %-16s %-20s %4d\n",
+                         evdev_device_get_sysname(device),
+                         libevdev_event_type_get_name(e->type),
+                         libevdev_event_code_get_name(e->type, e->code),
+                         e->value);
+#endif
+
        dispatch->interface->process(dispatch, device, e, time);
 }
 
@@ -1267,11 +1305,10 @@ evdev_accel_config_get_default_speed(struct libinput_device *device)
 }
 
 int
-evdev_device_init_pointer_acceleration(struct evdev_device *device)
+evdev_device_init_pointer_acceleration(struct evdev_device *device,
+                                      accel_profile_func_t profile)
 {
-       device->pointer.filter =
-               create_pointer_accelerator_filter(
-                       pointer_accel_profile_linear);
+       device->pointer.filter = create_pointer_accelerator_filter(profile);
        if (!device->pointer.filter)
                return -1;
 
@@ -1328,6 +1365,31 @@ evdev_read_wheel_click_prop(struct evdev_device *device)
 
        return angle;
 }
+
+static inline int
+evdev_get_trackpoint_dpi(struct evdev_device *device)
+{
+       struct libinput *libinput = device->base.seat->libinput;
+       const char *trackpoint_accel;
+       double accel = DEFAULT_TRACKPOINT_ACCEL;
+
+       trackpoint_accel = udev_device_get_property_value(
+                               device->udev_device, "POINTINGSTICK_CONST_ACCEL");
+       if (trackpoint_accel) {
+               accel = parse_trackpoint_accel_property(trackpoint_accel);
+               if (accel == 0.0) {
+                       log_error(libinput, "Trackpoint accel property for "
+                                           "'%s' is present but invalid, "
+                                           "using %.2f instead\n",
+                                           device->devname,
+                                           DEFAULT_TRACKPOINT_ACCEL);
+                       accel = DEFAULT_TRACKPOINT_ACCEL;
+               }
+       }
+
+       return DEFAULT_MOUSE_DPI / accel;
+}
+
 static inline int
 evdev_read_dpi_prop(struct evdev_device *device)
 {
@@ -1335,6 +1397,14 @@ evdev_read_dpi_prop(struct evdev_device *device)
        const char *mouse_dpi;
        int dpi = DEFAULT_MOUSE_DPI;
 
+       /*
+        * Trackpoints do not have dpi, instead hwdb may contain a
+        * POINTINGSTICK_CONST_ACCEL value to compensate for sensitivity
+        * differences between models, we translate this to a fake dpi.
+        */
+       if (libevdev_has_property(device->evdev, INPUT_PROP_POINTING_STICK))
+               return evdev_get_trackpoint_dpi(device);
+
        mouse_dpi = udev_device_get_property_value(device->udev_device,
                                                   "MOUSE_DPI");
        if (mouse_dpi) {
@@ -1352,23 +1422,81 @@ evdev_read_dpi_prop(struct evdev_device *device)
        return dpi;
 }
 
-static inline int
-evdev_fix_abs_resolution(struct libevdev *evdev,
-                        unsigned int code,
-                        const struct input_absinfo *absinfo)
+static inline enum evdev_device_model
+evdev_read_model(struct evdev_device *device)
+{
+       const struct model_map {
+               const char *property;
+               enum evdev_device_model model;
+       } model_map[] = {
+               { "LIBINPUT_MODEL_LENOVO_X230", EVDEV_MODEL_LENOVO_X230 },
+               { NULL, EVDEV_MODEL_DEFAULT },
+       };
+       const struct model_map *m = model_map;
+
+       while (m->property) {
+               if (!!udev_device_get_property_value(device->udev_device,
+                                                    m->property))
+                       break;
+               m++;
+       }
+
+       return m->model;
+}
+
+/* Return 1 if the given resolutions have been set, or 0 otherwise */
+inline int
+evdev_fix_abs_resolution(struct evdev_device *device,
+                        unsigned int xcode,
+                        unsigned int ycode,
+                        int xresolution,
+                        int yresolution)
 {
+       struct libinput *libinput = device->base.seat->libinput;
+       struct libevdev *evdev = device->evdev;
+       const struct input_absinfo *absx, *absy;
        struct input_absinfo fixed;
+       int rc = 0;
+
+       if (!(xcode == ABS_X && ycode == ABS_Y)  &&
+           !(xcode == ABS_MT_POSITION_X && ycode == ABS_MT_POSITION_Y)) {
+               log_bug_libinput(libinput,
+                                "Invalid x/y code combination %d/%d\n",
+                                xcode, ycode);
+               return 0;
+       }
+
+       if (xresolution == 0 || yresolution == 0 ||
+           (xresolution == EVDEV_FAKE_RESOLUTION && xresolution != yresolution) ||
+           (yresolution == EVDEV_FAKE_RESOLUTION && xresolution != yresolution)) {
+               log_bug_libinput(libinput,
+                                "Invalid x/y resolutions %d/%d\n",
+                                xresolution, yresolution);
+               return 0;
+       }
+
+       absx = libevdev_get_abs_info(evdev, xcode);
+       absy = libevdev_get_abs_info(evdev, ycode);
 
-       if (absinfo->resolution == 0) {
-               fixed = *absinfo;
-               fixed.resolution = 1;
+       if (absx->resolution == 0 || absx->resolution == EVDEV_FAKE_RESOLUTION) {
+               fixed = *absx;
+               fixed.resolution = xresolution;
                /* libevdev_set_abs_info() changes the absinfo we already
                   have a pointer to, no need to fetch it again */
-               libevdev_set_abs_info(evdev, code, &fixed);
-               return 1;
-       } else {
-               return 0;
+               libevdev_set_abs_info(evdev, xcode, &fixed);
+               rc = 1;
        }
+
+       if (absy->resolution == 0 || absy->resolution == EVDEV_FAKE_RESOLUTION) {
+               fixed = *absy;
+               fixed.resolution = yresolution;
+               /* libevdev_set_abs_info() changes the absinfo we already
+                  have a pointer to, no need to fetch it again */
+               libevdev_set_abs_info(evdev, ycode, &fixed);
+               rc = 1;
+       }
+
+       return rc;
 }
 
 static enum evdev_device_udev_tags
@@ -1397,16 +1525,191 @@ evdev_device_get_udev_tags(struct evdev_device *device,
        return tags;
 }
 
+/* Fake MT devices have the ABS_MT_SLOT bit set because of
+   the limited ABS_* range - they aren't MT devices, they
+   just have too many ABS_ axes */
+static inline bool
+evdev_is_fake_mt_device(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+
+       return libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) &&
+               libevdev_get_num_slots(evdev) == -1;
+}
+
+static inline void
+evdev_fix_android_mt(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) ||
+           libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
+               return;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) ||
+           evdev_is_fake_mt_device(device))
+               return;
+
+       libevdev_enable_event_code(evdev, EV_ABS, ABS_X,
+                     libevdev_get_abs_info(evdev, ABS_MT_POSITION_X));
+       libevdev_enable_event_code(evdev, EV_ABS, ABS_Y,
+                     libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y));
+}
+
+static inline int
+evdev_check_min_max(struct evdev_device *device, unsigned int code)
+{
+       struct libevdev *evdev = device->evdev;
+       const struct input_absinfo *absinfo;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, code))
+               return 0;
+
+       absinfo = libevdev_get_abs_info(evdev, code);
+       if (absinfo->minimum == absinfo->maximum) {
+               /* Some devices have a sort-of legitimate min/max of 0 for
+                * ABS_MISC and above (e.g. Roccat Kone XTD). Don't ignore
+                * them, simply disable the axes so we won't get events,
+                * we don't know what to do with them anyway.
+                */
+               if (absinfo->minimum == 0 &&
+                   code >= ABS_MISC && code < ABS_MT_SLOT) {
+                       log_info(device->base.seat->libinput,
+                                "Disabling EV_ABS %#x on device '%s' (min == max == 0)\n",
+                                code,
+                                device->devname);
+                       libevdev_disable_event_code(device->evdev,
+                                                   EV_ABS,
+                                                   code);
+               } else {
+                       log_bug_kernel(device->base.seat->libinput,
+                                      "Device '%s' has min == max on %s\n",
+                                      device->devname,
+                                      libevdev_event_code_get_name(EV_ABS, code));
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
 static int
-evdev_configure_device(struct evdev_device *device)
+evdev_reject_device(struct evdev_device *device)
 {
        struct libinput *libinput = device->base.seat->libinput;
        struct libevdev *evdev = device->evdev;
-       const struct input_absinfo *absinfo;
+       unsigned int code;
+       const struct input_absinfo *absx, *absy;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) ^
+           libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
+               return -1;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ^
+           libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
+               return -1;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) {
+               absx = libevdev_get_abs_info(evdev, ABS_X);
+               absy = libevdev_get_abs_info(evdev, ABS_Y);
+               if ((absx->resolution == 0 && absy->resolution != 0) ||
+                   (absx->resolution != 0 && absy->resolution == 0)) {
+                       log_bug_kernel(libinput,
+                                      "Kernel has only x or y resolution, not both.\n");
+                       return -1;
+               }
+       }
+
+       if (!evdev_is_fake_mt_device(device) &&
+           libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X)) {
+               absx = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
+               absy = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
+               if ((absx->resolution == 0 && absy->resolution != 0) ||
+                   (absx->resolution != 0 && absy->resolution == 0)) {
+                       log_bug_kernel(libinput,
+                                      "Kernel has only x or y MT resolution, not both.\n");
+                       return -1;
+               }
+       }
+
+       for (code = 0; code < ABS_CNT; code++) {
+               switch (code) {
+               case ABS_MISC:
+               case ABS_MT_SLOT:
+               case ABS_MT_TOOL_TYPE:
+                       break;
+               default:
+                       if (evdev_check_min_max(device, code) == -1)
+                               return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int
+evdev_configure_mt_device(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
        struct mt_slot *slots;
        int num_slots;
        int active_slot;
        int slot;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
+                return 0;
+
+       if (evdev_fix_abs_resolution(device,
+                                    ABS_MT_POSITION_X,
+                                    ABS_MT_POSITION_Y,
+                                    EVDEV_FAKE_RESOLUTION,
+                                    EVDEV_FAKE_RESOLUTION))
+               device->abs.fake_resolution = 1;
+
+       device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
+       device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
+       device->is_mt = 1;
+
+       /* We only handle the slotted Protocol B in libinput.
+          Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT
+          require mtdev for conversion. */
+       if (evdev_need_mtdev(device)) {
+               device->mtdev = mtdev_new_open(device->fd);
+               if (!device->mtdev)
+                       return -1;
+
+               /* pick 10 slots as default for type A
+                  devices. */
+               num_slots = 10;
+               active_slot = device->mtdev->caps.slot.value;
+       } else {
+               num_slots = libevdev_get_num_slots(device->evdev);
+               active_slot = libevdev_get_current_slot(evdev);
+       }
+
+       slots = calloc(num_slots, sizeof(struct mt_slot));
+       if (!slots)
+               return -1;
+
+       for (slot = 0; slot < num_slots; ++slot) {
+               slots[slot].seat_slot = -1;
+               slots[slot].point.x = 0;
+               slots[slot].point.y = 0;
+       }
+       device->mt.slots = slots;
+       device->mt.slots_len = num_slots;
+       device->mt.slot = active_slot;
+
+       return 0;
+}
+
+static int
+evdev_configure_device(struct evdev_device *device)
+{
+       struct libinput *libinput = device->base.seat->libinput;
+       struct libevdev *evdev = device->evdev;
        const char *devnode = udev_device_get_devnode(device->udev_device);
        enum evdev_device_udev_tags udev_tags;
 
@@ -1449,75 +1752,30 @@ evdev_configure_device(struct evdev_device *device)
                return -1;
        }
 
-       if (libevdev_has_event_type(evdev, EV_ABS)) {
-
-               if ((absinfo = libevdev_get_abs_info(evdev, ABS_X))) {
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_X,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_x = absinfo;
-               }
-               if ((absinfo = libevdev_get_abs_info(evdev, ABS_Y))) {
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_Y,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_y = absinfo;
-               }
+       if (evdev_reject_device(device) == -1) {
+               log_info(libinput,
+                        "input device '%s', %s was rejected.\n",
+                        device->devname, devnode);
+               return -1;
+       }
 
-               /* Fake MT devices have the ABS_MT_SLOT bit set because of
-                  the limited ABS_* range - they aren't MT devices, they
-                  just have too many ABS_ axes */
-               if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) &&
-                   libevdev_get_num_slots(evdev) == -1) {
-                       udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN;
-               } else if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) &&
-                          libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) {
-                       absinfo = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_MT_POSITION_X,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_x = absinfo;
-
-                       absinfo = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_MT_POSITION_Y,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_y = absinfo;
-                       device->is_mt = 1;
-
-                       /* We only handle the slotted Protocol B in libinput.
-                          Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT
-                          require mtdev for conversion. */
-                       if (evdev_need_mtdev(device)) {
-                               device->mtdev = mtdev_new_open(device->fd);
-                               if (!device->mtdev)
-                                       return -1;
-
-                               /* pick 10 slots as default for type A
-                                  devices. */
-                               num_slots = 10;
-                               active_slot = device->mtdev->caps.slot.value;
-                       } else {
-                               num_slots = libevdev_get_num_slots(device->evdev);
-                               active_slot = libevdev_get_current_slot(evdev);
-                       }
+       if (!evdev_is_fake_mt_device(device))
+               evdev_fix_android_mt(device);
 
-                       slots = calloc(num_slots, sizeof(struct mt_slot));
-                       if (!slots)
-                               return -1;
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) {
+               if (evdev_fix_abs_resolution(device,
+                                            ABS_X,
+                                            ABS_Y,
+                                            EVDEV_FAKE_RESOLUTION,
+                                            EVDEV_FAKE_RESOLUTION))
+                       device->abs.fake_resolution = 1;
+               device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_X);
+               device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_Y);
 
-                       for (slot = 0; slot < num_slots; ++slot) {
-                               slots[slot].seat_slot = -1;
-                               slots[slot].point.x = 0;
-                               slots[slot].point.y = 0;
-                       }
-                       device->mt.slots = slots;
-                       device->mt.slots_len = num_slots;
-                       device->mt.slot = active_slot;
+               if (evdev_is_fake_mt_device(device)) {
+                       udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN;
+               } else if (evdev_configure_mt_device(device) == -1) {
+                       return -1;
                }
        }
 
@@ -1532,7 +1790,9 @@ evdev_configure_device(struct evdev_device *device)
        if (udev_tags & EVDEV_UDEV_TAG_MOUSE) {
                if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) &&
                    !libevdev_has_event_code(evdev, EV_ABS, ABS_Y) &&
-                   evdev_device_init_pointer_acceleration(device) == -1)
+                   evdev_device_init_pointer_acceleration(
+                                       device,
+                                       pointer_accel_profile_linear) == -1)
                        return -1;
 
                device->seat_caps |= EVDEV_DEVICE_POINTER;
@@ -1554,6 +1814,13 @@ evdev_configure_device(struct evdev_device *device)
                log_info(libinput,
                         "input device '%s', %s is a keyboard\n",
                         device->devname, devnode);
+
+               /* want natural-scroll config option */
+               if (libevdev_has_event_code(evdev, EV_REL, REL_WHEEL) ||
+                   libevdev_has_event_code(evdev, EV_REL, REL_HWHEEL)) {
+                       device->scroll.natural_scrolling_enabled = true;
+                       device->seat_caps |= EVDEV_DEVICE_POINTER;
+               }
        }
 
        if (udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN) {
@@ -1705,6 +1972,7 @@ evdev_device_create(struct libinput_seat *seat,
        device->scroll.wheel_click_angle =
                evdev_read_wheel_click_prop(device);
        device->dpi = evdev_read_dpi_prop(device);
+       device->model = evdev_read_model(device);
        /* at most 5 SYN_DROPPED log-messages per 30s */
        ratelimit_init(&device->syn_drop_limit, 30ULL * 1000, 5);
 
@@ -1903,6 +2171,15 @@ evdev_device_has_button(struct evdev_device *device, uint32_t code)
        return libevdev_has_event_code(device->evdev, EV_KEY, code);
 }
 
+int
+evdev_device_has_key(struct evdev_device *device, uint32_t code)
+{
+       if (!(device->seat_caps & EVDEV_DEVICE_KEYBOARD))
+               return -1;
+
+       return libevdev_has_event_code(device->evdev, EV_KEY, code);
+}
+
 static inline bool
 evdev_is_scrolling(const struct evdev_device *device,
                   enum libinput_pointer_axis axis)
@@ -1980,13 +2257,15 @@ evdev_post_scroll(struct evdev_device *device,
                               LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
                event.x = 0.0;
 
-       if (event.x != 0.0 || event.y != 0.0)
+       if (!normalized_is_zero(event)) {
+               const struct discrete_coords zero_discrete = { 0.0, 0.0 };
                evdev_notify_axis(device,
                                  time,
                                  device->scroll.direction,
                                  source,
                                  &event,
-                                 0.0, 0.0);
+                                 &zero_discrete);
+       }
 }
 
 void
@@ -1995,6 +2274,7 @@ evdev_stop_scroll(struct evdev_device *device,
                  enum libinput_pointer_axis_source source)
 {
        const struct normalized_coords zero = { 0.0, 0.0 };
+       const struct discrete_coords zero_discrete = { 0.0, 0.0 };
 
        /* terminate scrolling with a zero scroll event */
        if (device->scroll.direction != 0)
@@ -2003,7 +2283,7 @@ evdev_stop_scroll(struct evdev_device *device,
                                    device->scroll.direction,
                                    source,
                                    &zero,
-                                   0.0, 0.0);
+                                   &zero_discrete);
 
        device->scroll.buildup.x = 0;
        device->scroll.buildup.y = 0;
@@ -2042,7 +2322,7 @@ release_pressed_keys(struct evdev_device *device)
                                        LIBINPUT_KEY_STATE_RELEASED);
                                break;
                        case EVDEV_KEY_TYPE_BUTTON:
-                               evdev_pointer_notify_button(
+                               evdev_pointer_notify_physical_button(
                                        device,
                                        time,
                                        evdev_to_left_handed(device, code),