[PATCH libinput 5/7] fallback: move the fallback code into a separate file

Peter Hutterer peter.hutterer at who-t.net
Thu Sep 21 00:57:14 UTC 2017


Split out the fallback-specific device handling from the more generic
evdev-specific handling (which is supposed to be available for all devices).

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 meson.build          |    1 +
 src/evdev-fallback.c | 1622 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/evdev.c          | 1535 +----------------------------------------------
 src/evdev.h          |   93 +--
 4 files changed, 1653 insertions(+), 1598 deletions(-)
 create mode 100644 src/evdev-fallback.c

diff --git a/meson.build b/meson.build
index dc3ab1b6..362e9a58 100644
--- a/meson.build
+++ b/meson.build
@@ -156,6 +156,7 @@ src_libinput = [
 	'src/libinput-private.h',
 	'src/evdev.c',
 	'src/evdev.h',
+	'src/evdev-fallback.c',
 	'src/evdev-middle-button.c',
 	'src/evdev-mt-touchpad.c',
 	'src/evdev-mt-touchpad.h',
diff --git a/src/evdev-fallback.c b/src/evdev-fallback.c
new file mode 100644
index 00000000..916254e4
--- /dev/null
+++ b/src/evdev-fallback.c
@@ -0,0 +1,1622 @@
+/*
+ * Copyright © 2010 Intel Corporation
+ * Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2017 Red Hat, Inc.
+ * Copyright © 2017 James Ye <jye836 at gmail.com>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <mtdev-plumbing.h>
+
+#include "evdev.h"
+
+#define	DEBOUNCE_TIME ms2us(12)
+
+struct fallback_dispatch {
+	struct evdev_dispatch base;
+	struct evdev_device *device;
+
+	struct libinput_device_config_calibration calibration;
+
+	struct {
+		bool is_enabled;
+		int angle;
+		struct matrix matrix;
+		struct libinput_device_config_rotation config;
+	} rotation;
+
+	struct {
+		struct device_coords point;
+		int32_t seat_slot;
+
+		struct {
+			struct device_coords min, max;
+			struct ratelimit range_warn_limit;
+		} warning_range;
+	} abs;
+
+	struct {
+		int slot;
+		struct mt_slot *slots;
+		size_t slots_len;
+		bool want_hysteresis;
+		struct device_coords hysteresis_margin;
+	} mt;
+
+	struct device_coords rel;
+
+	struct {
+		int state;
+	} tablet_mode;
+
+	/* Bitmask of pressed keys used to ignore initial release events from
+	 * the kernel. */
+	unsigned long hw_key_mask[NLONGS(KEY_CNT)];
+
+	enum evdev_event_type pending_event;
+
+	/* true if we're reading events (i.e. not suspended) but we're
+	   ignoring them */
+	bool ignore_events;
+
+	struct {
+		enum evdev_debounce_state state;
+		unsigned int button_code;
+		uint64_t button_up_time;
+		struct libinput_timer timer;
+	} debounce;
+
+	struct {
+		enum switch_reliability reliability;
+
+		bool is_closed;
+		bool is_closed_client_state;
+		struct evdev_device *keyboard;
+		struct libinput_event_listener listener;
+	} lid;
+};
+
+static inline struct fallback_dispatch*
+fallback_dispatch(struct evdev_dispatch *dispatch)
+{
+	evdev_verify_dispatch_type(dispatch, DISPATCH_FALLBACK);
+
+	return container_of(dispatch, struct fallback_dispatch, base);
+}
+enum key_type {
+	KEY_TYPE_NONE,
+	KEY_TYPE_KEY,
+	KEY_TYPE_BUTTON,
+};
+
+static void
+hw_set_key_down(struct fallback_dispatch *dispatch, int code, int pressed)
+{
+	long_set_bit_state(dispatch->hw_key_mask, code, pressed);
+}
+
+static bool
+hw_is_key_down(struct fallback_dispatch *dispatch, int code)
+{
+	return long_bit_is_set(dispatch->hw_key_mask, code);
+}
+
+static int
+get_key_down_count(struct evdev_device *device, int code)
+{
+	return device->key_count[code];
+}
+
+static void
+fallback_keyboard_notify_key(struct fallback_dispatch *dispatch,
+			     struct evdev_device *device,
+			     uint64_t time,
+			     int key,
+			     enum libinput_key_state state)
+{
+	int down_count;
+
+	down_count = evdev_update_key_down_count(device, key, state);
+
+	if ((state == LIBINPUT_KEY_STATE_PRESSED && down_count == 1) ||
+	    (state == LIBINPUT_KEY_STATE_RELEASED && down_count == 0))
+		keyboard_notify_key(&device->base, time, key, state);
+}
+
+static void
+fallback_lid_notify_toggle(struct fallback_dispatch *dispatch,
+			   struct evdev_device *device,
+			   uint64_t time)
+{
+	if (dispatch->lid.is_closed ^ dispatch->lid.is_closed_client_state) {
+		switch_notify_toggle(&device->base,
+				     time,
+				     LIBINPUT_SWITCH_LID,
+				     dispatch->lid.is_closed);
+		dispatch->lid.is_closed_client_state = dispatch->lid.is_closed;
+	}
+}
+
+static enum libinput_switch_state
+fallback_get_switch_state(struct evdev_dispatch *evdev_dispatch,
+			  enum libinput_switch sw)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+
+	switch (sw) {
+	case LIBINPUT_SWITCH_TABLET_MODE:
+		break;
+	default:
+		/* Internal function only, so we can abort here */
+		abort();
+	}
+
+	return dispatch->tablet_mode.state ?
+			LIBINPUT_SWITCH_STATE_ON :
+			LIBINPUT_SWITCH_STATE_OFF;
+}
+
+static inline void
+normalize_delta(struct evdev_device *device,
+		const struct device_coords *delta,
+		struct normalized_coords *normalized)
+{
+	normalized->x = delta->x * DEFAULT_MOUSE_DPI / (double)device->dpi;
+	normalized->y = delta->y * DEFAULT_MOUSE_DPI / (double)device->dpi;
+}
+
+static inline bool
+post_trackpoint_scroll(struct evdev_device *device,
+		       struct normalized_coords unaccel,
+		       uint64_t time)
+{
+	if (device->scroll.method != LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)
+		return false;
+
+	switch(device->scroll.button_scroll_state) {
+	case BUTTONSCROLL_IDLE:
+		return false;
+	case BUTTONSCROLL_BUTTON_DOWN:
+		/* if the button is down but scroll is not active, we're within the
+		   timeout where swallow motion events but don't post scroll buttons */
+		evdev_log_debug(device, "btnscroll: discarding\n");
+		return true;
+	case BUTTONSCROLL_READY:
+		device->scroll.button_scroll_state = BUTTONSCROLL_SCROLLING;
+		/* fallthrough */
+	case BUTTONSCROLL_SCROLLING:
+		evdev_post_scroll(device, time,
+				  LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
+				  &unaccel);
+		return true;
+	}
+
+	assert(!"invalid scroll button state");
+}
+
+static inline bool
+fallback_filter_defuzz_touch(struct fallback_dispatch *dispatch,
+			     struct evdev_device *device,
+			     struct mt_slot *slot)
+{
+	struct device_coords point;
+
+	if (!dispatch->mt.want_hysteresis)
+		return false;
+
+	point.x = evdev_hysteresis(slot->point.x,
+				   slot->hysteresis_center.x,
+				   dispatch->mt.hysteresis_margin.x);
+	point.y = evdev_hysteresis(slot->point.y,
+				   slot->hysteresis_center.y,
+				   dispatch->mt.hysteresis_margin.y);
+
+	slot->hysteresis_center = slot->point;
+	if (point.x == slot->point.x && point.y == slot->point.y)
+		return true;
+
+	slot->point = point;
+
+	return false;
+}
+
+static inline void
+fallback_rotate_relative(struct fallback_dispatch *dispatch,
+			 struct evdev_device *device)
+{
+	struct device_coords rel = dispatch->rel;
+
+	if (!device->base.config.rotation)
+		return;
+
+	/* loss of precision for non-90 degrees, but we only support 90 deg
+	 * right now anyway */
+	matrix_mult_vec(&dispatch->rotation.matrix, &rel.x, &rel.y);
+
+	dispatch->rel = rel;
+}
+
+static void
+fallback_flush_relative_motion(struct fallback_dispatch *dispatch,
+			       struct evdev_device *device,
+			       uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct normalized_coords accel, unaccel;
+	struct device_float_coords raw;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
+		return;
+
+	fallback_rotate_relative(dispatch, device);
+
+	normalize_delta(device, &dispatch->rel, &unaccel);
+	raw.x = dispatch->rel.x;
+	raw.y = dispatch->rel.y;
+	dispatch->rel.x = 0;
+	dispatch->rel.y = 0;
+
+	/* Use unaccelerated deltas for pointing stick scroll */
+	if (post_trackpoint_scroll(device, unaccel, time))
+		return;
+
+	if (device->pointer.filter) {
+		/* Apply pointer acceleration. */
+		accel = filter_dispatch(device->pointer.filter,
+					&raw,
+					device,
+					time);
+	} else {
+		evdev_log_bug_libinput(device,
+				       "accel filter missing\n");
+		accel = unaccel;
+	}
+
+	if (normalized_is_zero(accel) && normalized_is_zero(unaccel))
+		return;
+
+	pointer_notify_motion(base, time, &accel, &raw);
+}
+
+static void
+fallback_flush_absolute_motion(struct fallback_dispatch *dispatch,
+			       struct evdev_device *device,
+			       uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct device_coords point;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
+		return;
+
+	point = dispatch->abs.point;
+	evdev_transform_absolute(device, &point);
+
+	pointer_notify_motion_absolute(base, time, &point);
+}
+
+static bool
+fallback_flush_mt_down(struct fallback_dispatch *dispatch,
+		       struct evdev_device *device,
+		       int slot_idx,
+		       uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct libinput_seat *seat = base->seat;
+	struct device_coords point;
+	struct mt_slot *slot;
+	int seat_slot;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+		return false;
+
+	slot = &dispatch->mt.slots[slot_idx];
+	if (slot->seat_slot != -1) {
+		evdev_log_bug_kernel(device,
+				     "driver sent multiple touch down for the same slot");
+		return false;
+	}
+
+	seat_slot = ffs(~seat->slot_map) - 1;
+	slot->seat_slot = seat_slot;
+
+	if (seat_slot == -1)
+		return false;
+
+	seat->slot_map |= 1 << seat_slot;
+	point = slot->point;
+	slot->hysteresis_center = point;
+	evdev_transform_absolute(device, &point);
+
+	touch_notify_touch_down(base, time, slot_idx, seat_slot,
+				&point);
+
+	return true;
+}
+
+static bool
+fallback_flush_mt_motion(struct fallback_dispatch *dispatch,
+			 struct evdev_device *device,
+			 int slot_idx,
+			 uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct device_coords point;
+	struct mt_slot *slot;
+	int seat_slot;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+		return false;
+
+	slot = &dispatch->mt.slots[slot_idx];
+	seat_slot = slot->seat_slot;
+	point = slot->point;
+
+	if (seat_slot == -1)
+		return false;
+
+	if (fallback_filter_defuzz_touch(dispatch, device, slot))
+		return false;
+
+	evdev_transform_absolute(device, &point);
+	touch_notify_touch_motion(base, time, slot_idx, seat_slot,
+				  &point);
+
+	return true;
+}
+
+static bool
+fallback_flush_mt_up(struct fallback_dispatch *dispatch,
+		     struct evdev_device *device,
+		     int slot_idx,
+		     uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct libinput_seat *seat = base->seat;
+	struct mt_slot *slot;
+	int seat_slot;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+		return false;
+
+	slot = &dispatch->mt.slots[slot_idx];
+	seat_slot = slot->seat_slot;
+	slot->seat_slot = -1;
+
+	if (seat_slot == -1)
+		return false;
+
+	seat->slot_map &= ~(1 << seat_slot);
+
+	touch_notify_touch_up(base, time, slot_idx, seat_slot);
+
+	return true;
+}
+
+static bool
+fallback_flush_st_down(struct fallback_dispatch *dispatch,
+		       struct evdev_device *device,
+		       uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct libinput_seat *seat = base->seat;
+	struct device_coords point;
+	int seat_slot;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+		return false;
+
+	if (dispatch->abs.seat_slot != -1) {
+		evdev_log_bug_kernel(device,
+				     "driver sent multiple touch down for the same slot");
+		return false;
+	}
+
+	seat_slot = ffs(~seat->slot_map) - 1;
+	dispatch->abs.seat_slot = seat_slot;
+
+	if (seat_slot == -1)
+		return false;
+
+	seat->slot_map |= 1 << seat_slot;
+
+	point = dispatch->abs.point;
+	evdev_transform_absolute(device, &point);
+
+	touch_notify_touch_down(base, time, -1, seat_slot, &point);
+
+	return true;
+}
+
+static bool
+fallback_flush_st_motion(struct fallback_dispatch *dispatch,
+			 struct evdev_device *device,
+			 uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct device_coords point;
+	int seat_slot;
+
+	point = dispatch->abs.point;
+	evdev_transform_absolute(device, &point);
+
+	seat_slot = dispatch->abs.seat_slot;
+
+	if (seat_slot == -1)
+		return false;
+
+	touch_notify_touch_motion(base, time, -1, seat_slot, &point);
+
+	return true;
+}
+
+static bool
+fallback_flush_st_up(struct fallback_dispatch *dispatch,
+		     struct evdev_device *device,
+		     uint64_t time)
+{
+	struct libinput_device *base = &device->base;
+	struct libinput_seat *seat = base->seat;
+	int seat_slot;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+		return false;
+
+	seat_slot = dispatch->abs.seat_slot;
+	dispatch->abs.seat_slot = -1;
+
+	if (seat_slot == -1)
+		return false;
+
+	seat->slot_map &= ~(1 << seat_slot);
+
+	touch_notify_touch_up(base, time, -1, seat_slot);
+
+	return true;
+}
+
+static enum evdev_event_type
+fallback_flush_pending_event(struct fallback_dispatch *dispatch,
+			     struct evdev_device *device,
+			     uint64_t time)
+{
+	enum evdev_event_type sent_event;
+	int slot_idx;
+
+	sent_event = dispatch->pending_event;
+
+	switch (dispatch->pending_event) {
+	case EVDEV_NONE:
+		break;
+	case EVDEV_RELATIVE_MOTION:
+		fallback_flush_relative_motion(dispatch, device, time);
+		break;
+	case EVDEV_ABSOLUTE_MT_DOWN:
+		slot_idx = dispatch->mt.slot;
+		if (!fallback_flush_mt_down(dispatch,
+					    device,
+					    slot_idx,
+					    time))
+			sent_event = EVDEV_NONE;
+		break;
+	case EVDEV_ABSOLUTE_MT_MOTION:
+		slot_idx = dispatch->mt.slot;
+		if (!fallback_flush_mt_motion(dispatch,
+					      device,
+					      slot_idx,
+					      time))
+			sent_event = EVDEV_NONE;
+		break;
+	case EVDEV_ABSOLUTE_MT_UP:
+		slot_idx = dispatch->mt.slot;
+		if (!fallback_flush_mt_up(dispatch,
+					  device,
+					  slot_idx,
+					  time))
+			sent_event = EVDEV_NONE;
+		break;
+	case EVDEV_ABSOLUTE_TOUCH_DOWN:
+		if (!fallback_flush_st_down(dispatch, device, time))
+			sent_event = EVDEV_NONE;
+		break;
+	case EVDEV_ABSOLUTE_MOTION:
+		if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
+			if (fallback_flush_st_motion(dispatch,
+						     device,
+						     time))
+				sent_event = EVDEV_ABSOLUTE_MT_MOTION;
+			else
+				sent_event = EVDEV_NONE;
+		} else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
+			fallback_flush_absolute_motion(dispatch,
+						       device,
+						       time);
+		}
+		break;
+	case EVDEV_ABSOLUTE_TOUCH_UP:
+		if (!fallback_flush_st_up(dispatch, device, time))
+			sent_event = EVDEV_NONE;
+		break;
+	default:
+		assert(0 && "Unknown pending event type");
+		break;
+	}
+
+	dispatch->pending_event = EVDEV_NONE;
+
+	return sent_event;
+}
+
+static enum key_type
+get_key_type(uint16_t code)
+{
+	switch (code) {
+	case BTN_TOOL_PEN:
+	case BTN_TOOL_RUBBER:
+	case BTN_TOOL_BRUSH:
+	case BTN_TOOL_PENCIL:
+	case BTN_TOOL_AIRBRUSH:
+	case BTN_TOOL_MOUSE:
+	case BTN_TOOL_LENS:
+	case BTN_TOOL_QUINTTAP:
+	case BTN_TOOL_DOUBLETAP:
+	case BTN_TOOL_TRIPLETAP:
+	case BTN_TOOL_QUADTAP:
+	case BTN_TOOL_FINGER:
+	case BTN_TOUCH:
+		return KEY_TYPE_NONE;
+	}
+
+	if (code >= KEY_ESC && code <= KEY_MICMUTE)
+		return KEY_TYPE_KEY;
+	if (code >= BTN_MISC && code <= BTN_GEAR_UP)
+		return KEY_TYPE_BUTTON;
+	if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE)
+		return KEY_TYPE_KEY;
+	if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT)
+		return KEY_TYPE_BUTTON;
+	if (code >= KEY_ALS_TOGGLE && code <= KEY_ONSCREEN_KEYBOARD)
+		return KEY_TYPE_KEY;
+	if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40)
+		return KEY_TYPE_BUTTON;
+	return KEY_TYPE_NONE;
+}
+
+static void
+fallback_process_touch_button(struct fallback_dispatch *dispatch,
+			      struct evdev_device *device,
+			      uint64_t time, int value)
+{
+	if (dispatch->pending_event != EVDEV_NONE &&
+	    dispatch->pending_event != EVDEV_ABSOLUTE_MOTION)
+		fallback_flush_pending_event(dispatch, device, time);
+
+	dispatch->pending_event = (value ?
+				 EVDEV_ABSOLUTE_TOUCH_DOWN :
+				 EVDEV_ABSOLUTE_TOUCH_UP);
+}
+
+static inline void
+fallback_flush_debounce(struct fallback_dispatch *dispatch,
+			struct evdev_device *device)
+{
+	int code = dispatch->debounce.button_code;
+	int button;
+
+	if (dispatch->debounce.state != DEBOUNCE_ACTIVE)
+		return;
+
+	if (hw_is_key_down(dispatch, code)) {
+		button = evdev_to_left_handed(device, code);
+		evdev_pointer_notify_physical_button(device,
+						     dispatch->debounce.button_up_time,
+						     button,
+						     LIBINPUT_BUTTON_STATE_RELEASED);
+		hw_set_key_down(dispatch, code, 0);
+	}
+
+	dispatch->debounce.state = DEBOUNCE_ON;
+}
+
+static void
+fallback_debounce_timeout(uint64_t now, void *data)
+{
+	struct evdev_device *device = data;
+	struct fallback_dispatch *dispatch =
+		fallback_dispatch(device->dispatch);
+
+	fallback_flush_debounce(dispatch, device);
+}
+
+static bool
+fallback_filter_debounce_press(struct fallback_dispatch *dispatch,
+			       struct evdev_device *device,
+			       struct input_event *e,
+			       uint64_t time)
+{
+	bool filter = false;
+	uint64_t tdelta;
+
+	/* If other button is pressed while we're holding back the release,
+	 * flush the pending release (if any) and continue. We don't handle
+	 * this situation, if you have a mouse that needs per-button
+	 * debouncing, consider writing to santa for a new mouse.
+	 */
+	if (e->code != dispatch->debounce.button_code) {
+		if (dispatch->debounce.state == DEBOUNCE_ACTIVE) {
+			libinput_timer_cancel(&dispatch->debounce.timer);
+			fallback_flush_debounce(dispatch, device);
+		}
+		return false;
+	}
+
+	tdelta = time - dispatch->debounce.button_up_time;
+	assert((int64_t)tdelta >= 0);
+
+	if (tdelta < DEBOUNCE_TIME) {
+		switch (dispatch->debounce.state) {
+		case DEBOUNCE_INIT:
+			/* This is the first time we debounce, enable proper debouncing
+			   from now on but filter this press event */
+			filter = true;
+			evdev_log_info(device,
+				       "Enabling button debouncing, "
+				       "see %sbutton_debouncing.html for details\n",
+				       HTTP_DOC_LINK);
+			dispatch->debounce.state = DEBOUNCE_NEEDED;
+			break;
+		case DEBOUNCE_NEEDED:
+		case DEBOUNCE_ON:
+			break;
+		/* If a release event is pending and, filter press
+		 * events until we flushed the release */
+		case DEBOUNCE_ACTIVE:
+			filter = true;
+			break;
+		}
+	} else if (dispatch->debounce.state == DEBOUNCE_ACTIVE) {
+		/* call libinput_dispatch() more frequently */
+		evdev_log_bug_client(device,
+				     "Debouncing still active past timeout\n");
+	}
+
+	return filter;
+}
+
+static bool
+fallback_filter_debounce_release(struct fallback_dispatch *dispatch,
+				 struct input_event *e,
+				 uint64_t time)
+{
+	bool filter = false;
+
+	dispatch->debounce.button_code = e->code;
+	dispatch->debounce.button_up_time = time;
+
+	switch (dispatch->debounce.state) {
+	case DEBOUNCE_INIT:
+		break;
+	case DEBOUNCE_NEEDED:
+		filter = true;
+		dispatch->debounce.state = DEBOUNCE_ON;
+		break;
+	case DEBOUNCE_ON:
+		libinput_timer_set(&dispatch->debounce.timer,
+				   time + DEBOUNCE_TIME);
+		filter = true;
+		dispatch->debounce.state = DEBOUNCE_ACTIVE;
+		break;
+	case DEBOUNCE_ACTIVE:
+		filter = true;
+		break;
+	}
+
+	return filter;
+}
+
+static bool
+fallback_filter_debounce(struct fallback_dispatch *dispatch,
+			 struct evdev_device *device,
+			 struct input_event *e, uint64_t time)
+{
+	bool filter = false;
+
+	/* Behavior: we monitor the time deltas between release and press
+	 * events. Proper debouncing is disabled on init, but the first
+	 * time we see a bouncing press event we enable it.
+	 *
+	 * The first bounced event is simply discarded, which ends up in the
+	 * button being released sooner than it should be. Subsequent button
+	 * presses are timer-based and thus released a bit later because we
+	 * then wait for a timeout before we post the release event.
+	 */
+	if (e->value)
+		filter = fallback_filter_debounce_press(dispatch, device, e, time);
+	else
+		filter = fallback_filter_debounce_release(dispatch, e, time);
+
+	return filter;
+}
+
+static inline void
+fallback_process_key(struct fallback_dispatch *dispatch,
+		     struct evdev_device *device,
+		     struct input_event *e, uint64_t time)
+{
+	enum key_type type;
+
+	/* ignore kernel key repeat */
+	if (e->value == 2)
+		return;
+
+	if (e->code == BTN_TOUCH) {
+		if (!device->is_mt)
+			fallback_process_touch_button(dispatch,
+						      device,
+						      time,
+						      e->value);
+		return;
+	}
+
+	fallback_flush_pending_event(dispatch, device, time);
+
+	type = get_key_type(e->code);
+
+	/* Ignore key release events from the kernel for keys that libinput
+	 * never got a pressed event for or key presses for keys that we
+	 * think are still down */
+	switch (type) {
+	case KEY_TYPE_NONE:
+		break;
+	case KEY_TYPE_KEY:
+		if ((e->value && hw_is_key_down(dispatch, e->code)) ||
+		    (e->value == 0 && !hw_is_key_down(dispatch, e->code)))
+			return;
+		break;
+	case KEY_TYPE_BUTTON:
+		if (fallback_filter_debounce(dispatch, device, e, time))
+			return;
+
+		if ((e->value && hw_is_key_down(dispatch, e->code)) ||
+		    (e->value == 0 && !hw_is_key_down(dispatch, e->code)))
+			return;
+		break;
+	}
+
+	hw_set_key_down(dispatch, e->code, e->value);
+
+	switch (type) {
+	case KEY_TYPE_NONE:
+		break;
+	case KEY_TYPE_KEY:
+		fallback_keyboard_notify_key(
+			dispatch,
+			device,
+			time,
+			e->code,
+			e->value ? LIBINPUT_KEY_STATE_PRESSED :
+				   LIBINPUT_KEY_STATE_RELEASED);
+		break;
+	case KEY_TYPE_BUTTON:
+		evdev_pointer_notify_physical_button(
+			device,
+			time,
+			evdev_to_left_handed(device, e->code),
+			e->value ? LIBINPUT_BUTTON_STATE_PRESSED :
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+		break;
+	}
+}
+
+static void
+fallback_process_touch(struct fallback_dispatch *dispatch,
+		       struct evdev_device *device,
+		       struct input_event *e,
+		       uint64_t time)
+{
+	switch (e->code) {
+	case ABS_MT_SLOT:
+		if ((size_t)e->value >= dispatch->mt.slots_len) {
+			evdev_log_bug_libinput(device,
+					 "exceeded slot count (%d of max %zd)\n",
+					 e->value,
+					 dispatch->mt.slots_len);
+			e->value = dispatch->mt.slots_len - 1;
+		}
+		fallback_flush_pending_event(dispatch, device, time);
+		dispatch->mt.slot = e->value;
+		break;
+	case ABS_MT_TRACKING_ID:
+		if (dispatch->pending_event != EVDEV_NONE &&
+		    dispatch->pending_event != EVDEV_ABSOLUTE_MT_MOTION)
+			fallback_flush_pending_event(dispatch, device, time);
+		if (e->value >= 0)
+			dispatch->pending_event = EVDEV_ABSOLUTE_MT_DOWN;
+		else
+			dispatch->pending_event = EVDEV_ABSOLUTE_MT_UP;
+		break;
+	case ABS_MT_POSITION_X:
+		evdev_device_check_abs_axis_range(device, e->code, e->value);
+		dispatch->mt.slots[dispatch->mt.slot].point.x = e->value;
+		if (dispatch->pending_event == EVDEV_NONE)
+			dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
+		break;
+	case ABS_MT_POSITION_Y:
+		evdev_device_check_abs_axis_range(device, e->code, e->value);
+		dispatch->mt.slots[dispatch->mt.slot].point.y = e->value;
+		if (dispatch->pending_event == EVDEV_NONE)
+			dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
+		break;
+	}
+}
+static inline void
+fallback_process_absolute_motion(struct fallback_dispatch *dispatch,
+				 struct evdev_device *device,
+				 struct input_event *e)
+{
+	switch (e->code) {
+	case ABS_X:
+		evdev_device_check_abs_axis_range(device, e->code, e->value);
+		dispatch->abs.point.x = e->value;
+		if (dispatch->pending_event == EVDEV_NONE)
+			dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
+		break;
+	case ABS_Y:
+		evdev_device_check_abs_axis_range(device, e->code, e->value);
+		dispatch->abs.point.y = e->value;
+		if (dispatch->pending_event == EVDEV_NONE)
+			dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
+		break;
+	}
+}
+
+static void
+fallback_lid_keyboard_event(uint64_t time,
+			    struct libinput_event *event,
+			    void *data)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(data);
+
+	if (!dispatch->lid.is_closed)
+		return;
+
+	if (event->type != LIBINPUT_EVENT_KEYBOARD_KEY)
+		return;
+
+	if (dispatch->lid.reliability == RELIABILITY_WRITE_OPEN) {
+		int fd = libevdev_get_fd(dispatch->device->evdev);
+		struct input_event ev[2] = {
+			{{ 0, 0 }, EV_SW, SW_LID, 0 },
+			{{ 0, 0 }, EV_SYN, SYN_REPORT, 0 },
+		};
+
+		(void)write(fd, ev, sizeof(ev));
+		/* In case write() fails, we sync the lid state manually
+		 * regardless. */
+	}
+
+	/* Posting the event here means we preempt the keyboard events that
+	 * caused us to wake up, so the lid event is always passed on before
+	 * the key event.
+	 */
+	dispatch->lid.is_closed = false;
+	fallback_lid_notify_toggle(dispatch, dispatch->device, time);
+}
+
+static void
+fallback_lid_toggle_keyboard_listener(struct fallback_dispatch *dispatch,
+				      bool is_closed)
+{
+	if (!dispatch->lid.keyboard)
+		return;
+
+	if (is_closed) {
+		libinput_device_add_event_listener(
+					&dispatch->lid.keyboard->base,
+					&dispatch->lid.listener,
+					fallback_lid_keyboard_event,
+					dispatch);
+	} else {
+		libinput_device_remove_event_listener(
+					&dispatch->lid.listener);
+		libinput_device_init_event_listener(
+					&dispatch->lid.listener);
+	}
+}
+
+static inline void
+fallback_process_switch(struct fallback_dispatch *dispatch,
+			struct evdev_device *device,
+			struct input_event *e,
+			uint64_t time)
+{
+	enum libinput_switch_state state;
+	bool is_closed;
+
+	switch (e->code) {
+	case SW_LID:
+		is_closed = !!e->value;
+
+		if (dispatch->lid.is_closed == is_closed)
+			return;
+		fallback_lid_toggle_keyboard_listener(dispatch, is_closed);
+
+		dispatch->lid.is_closed = is_closed;
+		fallback_lid_notify_toggle(dispatch, device, time);
+		break;
+	case SW_TABLET_MODE:
+		if (dispatch->tablet_mode.state == e->value)
+			return;
+
+		dispatch->tablet_mode.state = e->value;
+		if (e->value)
+			state = LIBINPUT_SWITCH_STATE_ON;
+		else
+			state = LIBINPUT_SWITCH_STATE_OFF;
+		switch_notify_toggle(&device->base,
+				     time,
+				     LIBINPUT_SWITCH_TABLET_MODE,
+				     state);
+		break;
+	}
+}
+
+static inline bool
+fallback_reject_relative(struct evdev_device *device,
+			 const struct input_event *e,
+			 uint64_t time)
+{
+	if ((e->code == REL_X || e->code == REL_Y) &&
+	    (device->seat_caps & EVDEV_DEVICE_POINTER) == 0) {
+		evdev_log_bug_libinput_ratelimit(device,
+						 &device->nonpointer_rel_limit,
+						 "REL_X/Y from a non-pointer device\n");
+		return true;
+	}
+
+	return false;
+}
+
+static inline void
+fallback_process_relative(struct fallback_dispatch *dispatch,
+			  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 };
+	enum libinput_pointer_axis_source source;
+
+	if (fallback_reject_relative(device, e, time))
+		return;
+
+	switch (e->code) {
+	case REL_X:
+		if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
+			fallback_flush_pending_event(dispatch, device, time);
+		dispatch->rel.x += e->value;
+		dispatch->pending_event = EVDEV_RELATIVE_MOTION;
+		break;
+	case REL_Y:
+		if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
+			fallback_flush_pending_event(dispatch, device, time);
+		dispatch->rel.y += e->value;
+		dispatch->pending_event = EVDEV_RELATIVE_MOTION;
+		break;
+	case REL_WHEEL:
+		fallback_flush_pending_event(dispatch, device, time);
+		wheel_degrees.y = -1 * e->value *
+					device->scroll.wheel_click_angle.x;
+		discrete.y = -1 * e->value;
+
+		source = device->scroll.is_tilt.vertical ?
+				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
+				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
+
+		evdev_notify_axis(
+			device,
+			time,
+			AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
+			source,
+			&wheel_degrees,
+			&discrete);
+		break;
+	case REL_HWHEEL:
+		fallback_flush_pending_event(dispatch, device, time);
+		wheel_degrees.x = e->value *
+					device->scroll.wheel_click_angle.y;
+		discrete.x = e->value;
+
+		source = device->scroll.is_tilt.horizontal ?
+				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
+				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
+
+		evdev_notify_axis(
+			device,
+			time,
+			AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
+			source,
+			&wheel_degrees,
+			&discrete);
+		break;
+	}
+}
+
+static inline void
+fallback_process_absolute(struct fallback_dispatch *dispatch,
+			  struct evdev_device *device,
+			  struct input_event *e,
+			  uint64_t time)
+{
+	if (device->is_mt) {
+		fallback_process_touch(dispatch, device, e, time);
+	} else {
+		fallback_process_absolute_motion(dispatch, device, e);
+	}
+}
+
+static inline bool
+fallback_any_button_down(struct fallback_dispatch *dispatch,
+		      struct evdev_device *device)
+{
+	unsigned int button;
+
+	for (button = BTN_LEFT; button < BTN_JOYSTICK; button++) {
+		if (libevdev_has_event_code(device->evdev, EV_KEY, button) &&
+		    hw_is_key_down(dispatch, button))
+			return true;
+	}
+	return false;
+}
+
+static void
+fallback_process(struct evdev_dispatch *evdev_dispatch,
+		 struct evdev_device *device,
+		 struct input_event *event,
+		 uint64_t time)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+	enum evdev_event_type sent;
+
+	if (dispatch->ignore_events)
+		return;
+
+	switch (event->type) {
+	case EV_REL:
+		fallback_process_relative(dispatch, device, event, time);
+		break;
+	case EV_ABS:
+		fallback_process_absolute(dispatch, device, event, time);
+		break;
+	case EV_KEY:
+		fallback_process_key(dispatch, device, event, time);
+		break;
+	case EV_SW:
+		fallback_process_switch(dispatch, device, event, time);
+		break;
+	case EV_SYN:
+		sent = fallback_flush_pending_event(dispatch, device, time);
+		switch (sent) {
+		case EVDEV_ABSOLUTE_TOUCH_DOWN:
+		case EVDEV_ABSOLUTE_TOUCH_UP:
+		case EVDEV_ABSOLUTE_MT_DOWN:
+		case EVDEV_ABSOLUTE_MT_MOTION:
+		case EVDEV_ABSOLUTE_MT_UP:
+			touch_notify_frame(&device->base, time);
+			break;
+		case EVDEV_ABSOLUTE_MOTION:
+		case EVDEV_RELATIVE_MOTION:
+		case EVDEV_NONE:
+			break;
+		}
+		break;
+	}
+}
+
+static void
+release_touches(struct fallback_dispatch *dispatch,
+		struct evdev_device *device,
+		uint64_t time)
+{
+	unsigned int idx;
+	bool need_frame = false;
+
+	need_frame = fallback_flush_st_up(dispatch, device, time);
+
+	for (idx = 0; idx < dispatch->mt.slots_len; idx++) {
+		struct mt_slot *slot = &dispatch->mt.slots[idx];
+
+		if (slot->seat_slot == -1)
+			continue;
+
+		if (fallback_flush_mt_up(dispatch, device, idx, time))
+			need_frame = true;
+	}
+
+	if (need_frame)
+		touch_notify_frame(&device->base, time);
+}
+
+static void
+release_pressed_keys(struct fallback_dispatch *dispatch,
+		     struct evdev_device *device,
+		     uint64_t time)
+{
+	int code;
+
+	for (code = 0; code < KEY_CNT; code++) {
+		int count = get_key_down_count(device, code);
+
+		if (count == 0)
+			continue;
+
+		if (count > 1) {
+			evdev_log_bug_libinput(device,
+					       "key %d is down %d times.\n",
+					       code,
+					       count);
+		}
+
+		switch (get_key_type(code)) {
+		case KEY_TYPE_NONE:
+			break;
+		case KEY_TYPE_KEY:
+			fallback_keyboard_notify_key(
+				dispatch,
+				device,
+				time,
+				code,
+				LIBINPUT_KEY_STATE_RELEASED);
+			break;
+		case KEY_TYPE_BUTTON:
+			evdev_pointer_notify_physical_button(
+				device,
+				time,
+				evdev_to_left_handed(device, code),
+				LIBINPUT_BUTTON_STATE_RELEASED);
+			break;
+		}
+
+		count = get_key_down_count(device, code);
+		if (count != 0) {
+			evdev_log_bug_libinput(device,
+					       "releasing key %d failed.\n",
+					       code);
+			break;
+		}
+	}
+}
+
+static void
+fallback_return_to_neutral_state(struct fallback_dispatch *dispatch,
+				 struct evdev_device *device)
+{
+	struct libinput *libinput = evdev_libinput_context(device);
+	uint64_t time;
+
+	if ((time = libinput_now(libinput)) == 0)
+		return;
+
+	release_touches(dispatch, device, time);
+	release_pressed_keys(dispatch, device, time);
+	memset(dispatch->hw_key_mask, 0, sizeof(dispatch->hw_key_mask));
+}
+
+static void
+fallback_suspend(struct evdev_dispatch *evdev_dispatch,
+		 struct evdev_device *device)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+
+	fallback_return_to_neutral_state(dispatch, device);
+}
+
+static void
+fallback_remove(struct evdev_dispatch *evdev_dispatch)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+
+	if (!dispatch->lid.keyboard)
+		return;
+
+	libinput_device_remove_event_listener(&dispatch->lid.listener);
+}
+
+static void
+fallback_sync_initial_state(struct evdev_device *device,
+			    struct evdev_dispatch *evdev_dispatch)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+	uint64_t time = libinput_now(evdev_libinput_context(device));
+
+	if (device->tags & EVDEV_TAG_LID_SWITCH) {
+		struct libevdev *evdev = device->evdev;
+
+		dispatch->lid.is_closed = libevdev_get_event_value(evdev,
+								   EV_SW,
+								   SW_LID);
+		dispatch->lid.is_closed_client_state = false;
+
+		/* For the initial state sync, we depend on whether the lid switch
+		 * is reliable. If we know it's reliable, we sync as expected.
+		 * If we're not sure, we ignore the initial state and only sync on
+		 * the first future lid close event. Laptops with a broken switch
+		 * that always have the switch in 'on' state thus don't mess up our
+		 * touchpad.
+		 */
+		if (dispatch->lid.is_closed &&
+		    dispatch->lid.reliability == RELIABILITY_RELIABLE) {
+			fallback_lid_notify_toggle(dispatch, device, time);
+		}
+	}
+
+	if (dispatch->tablet_mode.state) {
+		switch_notify_toggle(&device->base,
+				     time,
+				     LIBINPUT_SWITCH_TABLET_MODE,
+				     LIBINPUT_SWITCH_STATE_ON);
+	}
+}
+
+static void
+fallback_toggle_touch(struct evdev_dispatch *evdev_dispatch,
+		      struct evdev_device *device,
+		      bool enable)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+	bool ignore_events = !enable;
+
+	if (ignore_events == dispatch->ignore_events)
+		return;
+
+	if (ignore_events)
+		fallback_return_to_neutral_state(dispatch, device);
+
+	dispatch->ignore_events = ignore_events;
+}
+
+static void
+fallback_destroy(struct evdev_dispatch *evdev_dispatch)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
+
+	libinput_timer_cancel(&dispatch->debounce.timer);
+	libinput_timer_destroy(&dispatch->debounce.timer);
+	free(dispatch->mt.slots);
+	free(dispatch);
+}
+
+static void
+fallback_lid_pair_keyboard(struct evdev_device *lid_switch,
+			   struct evdev_device *keyboard)
+{
+	struct fallback_dispatch *dispatch =
+		fallback_dispatch(lid_switch->dispatch);
+
+	if ((keyboard->tags & EVDEV_TAG_KEYBOARD) == 0 ||
+	    (lid_switch->tags & EVDEV_TAG_LID_SWITCH) == 0)
+		return;
+
+	if (dispatch->lid.keyboard)
+		return;
+
+	if (keyboard->tags & EVDEV_TAG_INTERNAL_KEYBOARD) {
+		dispatch->lid.keyboard = keyboard;
+		evdev_log_debug(lid_switch,
+				"lid: keyboard paired with %s<->%s\n",
+				lid_switch->devname,
+				keyboard->devname);
+
+		/* We need to init the event listener now only if the reported state
+		 * is closed. */
+		if (dispatch->lid.is_closed)
+			fallback_lid_toggle_keyboard_listener(dispatch,
+						    dispatch->lid.is_closed);
+	}
+}
+
+static void
+fallback_interface_device_added(struct evdev_device *device,
+				struct evdev_device *added_device)
+{
+	fallback_lid_pair_keyboard(device, added_device);
+}
+
+static void
+fallback_interface_device_removed(struct evdev_device *device,
+				  struct evdev_device *removed_device)
+{
+	struct fallback_dispatch *dispatch =
+			fallback_dispatch(device->dispatch);
+
+	if (removed_device == dispatch->lid.keyboard) {
+		libinput_device_remove_event_listener(
+					&dispatch->lid.listener);
+		libinput_device_init_event_listener(
+					&dispatch->lid.listener);
+		dispatch->lid.keyboard = NULL;
+	}
+}
+
+struct evdev_dispatch_interface fallback_interface = {
+	.process = fallback_process,
+	.suspend = fallback_suspend,
+	.remove = fallback_remove,
+	.destroy = fallback_destroy,
+	.device_added = fallback_interface_device_added,
+	.device_removed = fallback_interface_device_removed,
+	.device_suspended = fallback_interface_device_removed, /* treat as remove */
+	.device_resumed = fallback_interface_device_added,   /* treat as add */
+	.post_added = fallback_sync_initial_state,
+	.toggle_touch = fallback_toggle_touch,
+	.get_switch_state = fallback_get_switch_state,
+};
+
+static void
+fallback_change_to_left_handed(struct evdev_device *device)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
+
+	if (device->left_handed.want_enabled == device->left_handed.enabled)
+		return;
+
+	if (fallback_any_button_down(dispatch, device))
+		return;
+
+	device->left_handed.enabled = device->left_handed.want_enabled;
+}
+
+static void
+fallback_change_scroll_method(struct evdev_device *device)
+{
+	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
+
+	if (device->scroll.want_method == device->scroll.method &&
+	    device->scroll.want_button == device->scroll.button)
+		return;
+
+	if (fallback_any_button_down(dispatch, device))
+		return;
+
+	device->scroll.method = device->scroll.want_method;
+	device->scroll.button = device->scroll.want_button;
+}
+
+static int
+fallback_rotation_config_is_available(struct libinput_device *device)
+{
+	/* This function only gets called when we support rotation */
+	return 1;
+}
+
+static enum libinput_config_status
+fallback_rotation_config_set_angle(struct libinput_device *libinput_device,
+				unsigned int degrees_cw)
+{
+	struct evdev_device *device = evdev_device(libinput_device);
+	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
+
+	dispatch->rotation.angle = degrees_cw;
+	matrix_init_rotate(&dispatch->rotation.matrix, degrees_cw);
+
+	return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static unsigned int
+fallback_rotation_config_get_angle(struct libinput_device *libinput_device)
+{
+	struct evdev_device *device = evdev_device(libinput_device);
+	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
+
+	return dispatch->rotation.angle;
+}
+
+static unsigned int
+fallback_rotation_config_get_default_angle(struct libinput_device *device)
+{
+	return 0;
+}
+
+static void
+fallback_init_rotation(struct fallback_dispatch *dispatch,
+		       struct evdev_device *device)
+{
+	if ((device->model_flags & EVDEV_MODEL_TRACKBALL) == 0)
+		return;
+
+	dispatch->rotation.config.is_available = fallback_rotation_config_is_available;
+	dispatch->rotation.config.set_angle = fallback_rotation_config_set_angle;
+	dispatch->rotation.config.get_angle = fallback_rotation_config_get_angle;
+	dispatch->rotation.config.get_default_angle = fallback_rotation_config_get_default_angle;
+	dispatch->rotation.is_enabled = false;
+	matrix_init_identity(&dispatch->rotation.matrix);
+	device->base.config.rotation = &dispatch->rotation.config;
+}
+
+static inline int
+fallback_dispatch_init_slots(struct fallback_dispatch *dispatch,
+			     struct evdev_device *device)
+{
+	struct libevdev *evdev = device->evdev;
+	struct mt_slot *slots;
+	int num_slots;
+	int active_slot;
+	int slot;
+
+	if (evdev_is_fake_mt_device(device) ||
+	    !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
+	    !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
+		 return 0;
+
+	/* 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 = zalloc(num_slots * sizeof(struct mt_slot));
+
+	for (slot = 0; slot < num_slots; ++slot) {
+		slots[slot].seat_slot = -1;
+
+		if (evdev_need_mtdev(device))
+			continue;
+
+		slots[slot].point.x = libevdev_get_slot_value(evdev,
+							      slot,
+							      ABS_MT_POSITION_X);
+		slots[slot].point.y = libevdev_get_slot_value(evdev,
+							      slot,
+							      ABS_MT_POSITION_Y);
+	}
+	dispatch->mt.slots = slots;
+	dispatch->mt.slots_len = num_slots;
+	dispatch->mt.slot = active_slot;
+
+	if (device->abs.absinfo_x->fuzz || device->abs.absinfo_y->fuzz) {
+		dispatch->mt.want_hysteresis = true;
+		dispatch->mt.hysteresis_margin.x = device->abs.absinfo_x->fuzz/2;
+		dispatch->mt.hysteresis_margin.y = device->abs.absinfo_y->fuzz/2;
+	}
+
+	return 0;
+}
+
+static inline void
+fallback_dispatch_init_rel(struct fallback_dispatch *dispatch,
+			   struct evdev_device *device)
+{
+	dispatch->rel.x = 0;
+	dispatch->rel.y = 0;
+}
+
+static inline void
+fallback_dispatch_init_abs(struct fallback_dispatch *dispatch,
+			   struct evdev_device *device)
+{
+	if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_X))
+		return;
+
+	dispatch->abs.point.x = device->abs.absinfo_x->value;
+	dispatch->abs.point.y = device->abs.absinfo_y->value;
+	dispatch->abs.seat_slot = -1;
+
+	evdev_device_init_abs_range_warnings(device);
+}
+
+static inline void
+fallback_dispatch_init_switch(struct fallback_dispatch *dispatch,
+			      struct evdev_device *device)
+{
+	int val;
+
+	if (device->tags & EVDEV_TAG_LID_SWITCH) {
+		libinput_device_init_event_listener(&dispatch->lid.listener);
+		dispatch->lid.reliability = evdev_read_switch_reliability_prop(device);
+		dispatch->lid.is_closed = false;
+	}
+
+	if (device->tags & EVDEV_TAG_TABLET_MODE_SWITCH) {
+		val = libevdev_get_event_value(device->evdev,
+					       EV_SW,
+					       SW_TABLET_MODE);
+		dispatch->tablet_mode.state = val;
+	}
+}
+
+struct evdev_dispatch *
+fallback_dispatch_create(struct libinput_device *libinput_device)
+{
+	struct evdev_device *device = evdev_device(libinput_device);
+	struct fallback_dispatch *dispatch;
+	char timer_name[64];
+
+	dispatch = zalloc(sizeof *dispatch);
+	dispatch->device = evdev_device(libinput_device);
+	dispatch->base.dispatch_type = DISPATCH_FALLBACK;
+	dispatch->base.interface = &fallback_interface;
+	dispatch->pending_event = EVDEV_NONE;
+
+	fallback_dispatch_init_rel(dispatch, device);
+	fallback_dispatch_init_abs(dispatch, device);
+	if (fallback_dispatch_init_slots(dispatch, device) == -1) {
+		free(dispatch);
+		return NULL;
+	}
+
+	fallback_dispatch_init_switch(dispatch, device);
+
+	if (device->left_handed.want_enabled)
+		evdev_init_left_handed(device,
+				       fallback_change_to_left_handed);
+
+	if (device->scroll.want_button)
+		evdev_init_button_scroll(device,
+					 fallback_change_scroll_method);
+
+	if (device->scroll.natural_scrolling_enabled)
+		evdev_init_natural_scroll(device);
+
+	evdev_init_calibration(device, &dispatch->calibration);
+	evdev_init_sendevents(device, &dispatch->base);
+	fallback_init_rotation(dispatch, device);
+
+	/* BTN_MIDDLE is set on mice even when it's not present. So
+	 * we can only use the absence 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(device->evdev, EV_KEY, BTN_LEFT) &&
+	    libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) {
+		bool has_middle = libevdev_has_event_code(device->evdev,
+							  EV_KEY,
+							  BTN_MIDDLE);
+		bool want_config = has_middle;
+		bool enable_by_default = !has_middle;
+
+		evdev_init_middlebutton(device,
+					enable_by_default,
+					want_config);
+	}
+
+	snprintf(timer_name,
+		 sizeof(timer_name),
+		 "%s debounce",
+		 evdev_device_get_sysname(device));
+	libinput_timer_init(&dispatch->debounce.timer,
+			    evdev_libinput_context(device),
+			    timer_name,
+			    fallback_debounce_timeout,
+			    device);
+	return &dispatch->base;
+}
diff --git a/src/evdev.c b/src/evdev.c
index 8875f06a..bd6674e7 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -50,13 +50,6 @@
 
 #define DEFAULT_WHEEL_CLICK_ANGLE 15
 #define DEFAULT_BUTTON_SCROLL_TIMEOUT ms2us(200)
-#define	DEBOUNCE_TIME ms2us(12)
-
-enum key_type {
-	KEY_TYPE_NONE,
-	KEY_TYPE_KEY,
-	KEY_TYPE_BUTTON,
-};
 
 enum evdev_device_udev_tags {
         EVDEV_UDEV_TAG_INPUT = (1 << 0),
@@ -115,26 +108,10 @@ parse_udev_flag(struct evdev_device *device,
 	return false;
 }
 
-static void
-hw_set_key_down(struct fallback_dispatch *dispatch, int code, int pressed)
-{
-	long_set_bit_state(dispatch->hw_key_mask, code, pressed);
-}
-
-static bool
-hw_is_key_down(struct fallback_dispatch *dispatch, int code)
-{
-	return long_bit_is_set(dispatch->hw_key_mask, code);
-}
-
-static int
-get_key_down_count(struct evdev_device *device, int code)
-{
-	return device->key_count[code];
-}
-
-static int
-update_key_down_count(struct evdev_device *device, int code, int pressed)
+int
+evdev_update_key_down_count(struct evdev_device *device,
+			    int code,
+			    int pressed)
 {
 	int key_count;
 	assert(code >= 0 && code < KEY_CNT);
@@ -155,36 +132,6 @@ update_key_down_count(struct evdev_device *device, int code, int pressed)
 	return key_count;
 }
 
-static void
-fallback_keyboard_notify_key(struct fallback_dispatch *dispatch,
-			     struct evdev_device *device,
-			     uint64_t time,
-			     int key,
-			     enum libinput_key_state state)
-{
-	int down_count;
-
-	down_count = update_key_down_count(device, key, state);
-
-	if ((state == LIBINPUT_KEY_STATE_PRESSED && down_count == 1) ||
-	    (state == LIBINPUT_KEY_STATE_RELEASED && down_count == 0))
-		keyboard_notify_key(&device->base, time, key, state);
-}
-
-static void
-fallback_lid_notify_toggle(struct fallback_dispatch *dispatch,
-			   struct evdev_device *device,
-			   uint64_t time)
-{
-	if (dispatch->lid.is_closed ^ dispatch->lid.is_closed_client_state) {
-		switch_notify_toggle(&device->base,
-				     time,
-				     LIBINPUT_SWITCH_LID,
-				     dispatch->lid.is_closed);
-		dispatch->lid.is_closed_client_state = dispatch->lid.is_closed;
-	}
-}
-
 enum libinput_switch_state
 evdev_device_switch_get_state(struct evdev_device *device,
 			      enum libinput_switch sw)
@@ -196,25 +143,6 @@ evdev_device_switch_get_state(struct evdev_device *device,
 	return dispatch->interface->get_switch_state(dispatch, sw);
 }
 
-static enum libinput_switch_state
-fallback_get_switch_state(struct evdev_dispatch *evdev_dispatch,
-			  enum libinput_switch sw)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-
-	switch (sw) {
-	case LIBINPUT_SWITCH_TABLET_MODE:
-		break;
-	default:
-		/* Internal function only, so we can abort here */
-		abort();
-	}
-
-	return dispatch->tablet_mode.state ?
-			LIBINPUT_SWITCH_STATE_ON :
-			LIBINPUT_SWITCH_STATE_OFF;
-}
-
 void
 evdev_pointer_notify_physical_button(struct evdev_device *device,
 				     uint64_t time,
@@ -241,7 +169,7 @@ evdev_pointer_post_button(struct evdev_device *device,
 {
 	int down_count;
 
-	down_count = update_key_down_count(device, button, state);
+	down_count = evdev_update_key_down_count(device, button, state);
 
 	if ((state == LIBINPUT_BUTTON_STATE_PRESSED && down_count == 1) ||
 	    (state == LIBINPUT_BUTTON_STATE_RELEASED && down_count == 0)) {
@@ -417,811 +345,6 @@ evdev_device_transform_y(struct evdev_device *device,
 	return scale_axis(device->abs.absinfo_y, y, height);
 }
 
-static inline void
-normalize_delta(struct evdev_device *device,
-		const struct device_coords *delta,
-		struct normalized_coords *normalized)
-{
-	normalized->x = delta->x * DEFAULT_MOUSE_DPI / (double)device->dpi;
-	normalized->y = delta->y * DEFAULT_MOUSE_DPI / (double)device->dpi;
-}
-
-static inline bool
-evdev_post_trackpoint_scroll(struct evdev_device *device,
-			     struct normalized_coords unaccel,
-			     uint64_t time)
-{
-	if (device->scroll.method != LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)
-		return false;
-
-	switch(device->scroll.button_scroll_state) {
-	case BUTTONSCROLL_IDLE:
-		return false;
-	case BUTTONSCROLL_BUTTON_DOWN:
-		/* if the button is down but scroll is not active, we're within the
-		   timeout where swallow motion events but don't post scroll buttons */
-		evdev_log_debug(device, "btnscroll: discarding\n");
-		return true;
-	case BUTTONSCROLL_READY:
-		device->scroll.button_scroll_state = BUTTONSCROLL_SCROLLING;
-		/* fallthrough */
-	case BUTTONSCROLL_SCROLLING:
-		evdev_post_scroll(device, time,
-				  LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
-				  &unaccel);
-		return true;
-	}
-
-	assert(!"invalid scroll button state");
-}
-
-static inline bool
-fallback_filter_defuzz_touch(struct fallback_dispatch *dispatch,
-			     struct evdev_device *device,
-			     struct mt_slot *slot)
-{
-	struct device_coords point;
-
-	if (!dispatch->mt.want_hysteresis)
-		return false;
-
-	point.x = evdev_hysteresis(slot->point.x,
-				   slot->hysteresis_center.x,
-				   dispatch->mt.hysteresis_margin.x);
-	point.y = evdev_hysteresis(slot->point.y,
-				   slot->hysteresis_center.y,
-				   dispatch->mt.hysteresis_margin.y);
-
-	slot->hysteresis_center = slot->point;
-	if (point.x == slot->point.x && point.y == slot->point.y)
-		return true;
-
-	slot->point = point;
-
-	return false;
-}
-
-static inline void
-fallback_rotate_relative(struct fallback_dispatch *dispatch,
-			 struct evdev_device *device)
-{
-	struct device_coords rel = dispatch->rel;
-
-	if (!device->base.config.rotation)
-		return;
-
-	/* loss of precision for non-90 degrees, but we only support 90 deg
-	 * right now anyway */
-	matrix_mult_vec(&dispatch->rotation.matrix, &rel.x, &rel.y);
-
-	dispatch->rel = rel;
-}
-
-static void
-fallback_flush_relative_motion(struct fallback_dispatch *dispatch,
-			       struct evdev_device *device,
-			       uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct normalized_coords accel, unaccel;
-	struct device_float_coords raw;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
-		return;
-
-	fallback_rotate_relative(dispatch, device);
-
-	normalize_delta(device, &dispatch->rel, &unaccel);
-	raw.x = dispatch->rel.x;
-	raw.y = dispatch->rel.y;
-	dispatch->rel.x = 0;
-	dispatch->rel.y = 0;
-
-	/* Use unaccelerated deltas for pointing stick scroll */
-	if (evdev_post_trackpoint_scroll(device, unaccel, time))
-		return;
-
-	if (device->pointer.filter) {
-		/* Apply pointer acceleration. */
-		accel = filter_dispatch(device->pointer.filter,
-					&raw,
-					device,
-					time);
-	} else {
-		evdev_log_bug_libinput(device,
-				       "accel filter missing\n");
-		accel = unaccel;
-	}
-
-	if (normalized_is_zero(accel) && normalized_is_zero(unaccel))
-		return;
-
-	pointer_notify_motion(base, time, &accel, &raw);
-}
-
-static void
-fallback_flush_absolute_motion(struct fallback_dispatch *dispatch,
-			       struct evdev_device *device,
-			       uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct device_coords point;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
-		return;
-
-	point = dispatch->abs.point;
-	evdev_transform_absolute(device, &point);
-
-	pointer_notify_motion_absolute(base, time, &point);
-}
-
-static bool
-fallback_flush_mt_down(struct fallback_dispatch *dispatch,
-		       struct evdev_device *device,
-		       int slot_idx,
-		       uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct libinput_seat *seat = base->seat;
-	struct device_coords point;
-	struct mt_slot *slot;
-	int seat_slot;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-		return false;
-
-	slot = &dispatch->mt.slots[slot_idx];
-	if (slot->seat_slot != -1) {
-		evdev_log_bug_kernel(device,
-				     "driver sent multiple touch down for the same slot");
-		return false;
-	}
-
-	seat_slot = ffs(~seat->slot_map) - 1;
-	slot->seat_slot = seat_slot;
-
-	if (seat_slot == -1)
-		return false;
-
-	seat->slot_map |= 1 << seat_slot;
-	point = slot->point;
-	slot->hysteresis_center = point;
-	evdev_transform_absolute(device, &point);
-
-	touch_notify_touch_down(base, time, slot_idx, seat_slot,
-				&point);
-
-	return true;
-}
-
-static bool
-fallback_flush_mt_motion(struct fallback_dispatch *dispatch,
-			 struct evdev_device *device,
-			 int slot_idx,
-			 uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct device_coords point;
-	struct mt_slot *slot;
-	int seat_slot;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-		return false;
-
-	slot = &dispatch->mt.slots[slot_idx];
-	seat_slot = slot->seat_slot;
-	point = slot->point;
-
-	if (seat_slot == -1)
-		return false;
-
-	if (fallback_filter_defuzz_touch(dispatch, device, slot))
-		return false;
-
-	evdev_transform_absolute(device, &point);
-	touch_notify_touch_motion(base, time, slot_idx, seat_slot,
-				  &point);
-
-	return true;
-}
-
-static bool
-fallback_flush_mt_up(struct fallback_dispatch *dispatch,
-		     struct evdev_device *device,
-		     int slot_idx,
-		     uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct libinput_seat *seat = base->seat;
-	struct mt_slot *slot;
-	int seat_slot;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-		return false;
-
-	slot = &dispatch->mt.slots[slot_idx];
-	seat_slot = slot->seat_slot;
-	slot->seat_slot = -1;
-
-	if (seat_slot == -1)
-		return false;
-
-	seat->slot_map &= ~(1 << seat_slot);
-
-	touch_notify_touch_up(base, time, slot_idx, seat_slot);
-
-	return true;
-}
-
-static bool
-fallback_flush_st_down(struct fallback_dispatch *dispatch,
-		       struct evdev_device *device,
-		       uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct libinput_seat *seat = base->seat;
-	struct device_coords point;
-	int seat_slot;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-		return false;
-
-	if (dispatch->abs.seat_slot != -1) {
-		evdev_log_bug_kernel(device,
-				     "driver sent multiple touch down for the same slot");
-		return false;
-	}
-
-	seat_slot = ffs(~seat->slot_map) - 1;
-	dispatch->abs.seat_slot = seat_slot;
-
-	if (seat_slot == -1)
-		return false;
-
-	seat->slot_map |= 1 << seat_slot;
-
-	point = dispatch->abs.point;
-	evdev_transform_absolute(device, &point);
-
-	touch_notify_touch_down(base, time, -1, seat_slot, &point);
-
-	return true;
-}
-
-static bool
-fallback_flush_st_motion(struct fallback_dispatch *dispatch,
-			 struct evdev_device *device,
-			 uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct device_coords point;
-	int seat_slot;
-
-	point = dispatch->abs.point;
-	evdev_transform_absolute(device, &point);
-
-	seat_slot = dispatch->abs.seat_slot;
-
-	if (seat_slot == -1)
-		return false;
-
-	touch_notify_touch_motion(base, time, -1, seat_slot, &point);
-
-	return true;
-}
-
-static bool
-fallback_flush_st_up(struct fallback_dispatch *dispatch,
-		     struct evdev_device *device,
-		     uint64_t time)
-{
-	struct libinput_device *base = &device->base;
-	struct libinput_seat *seat = base->seat;
-	int seat_slot;
-
-	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-		return false;
-
-	seat_slot = dispatch->abs.seat_slot;
-	dispatch->abs.seat_slot = -1;
-
-	if (seat_slot == -1)
-		return false;
-
-	seat->slot_map &= ~(1 << seat_slot);
-
-	touch_notify_touch_up(base, time, -1, seat_slot);
-
-	return true;
-}
-
-static enum evdev_event_type
-fallback_flush_pending_event(struct fallback_dispatch *dispatch,
-			     struct evdev_device *device,
-			     uint64_t time)
-{
-	enum evdev_event_type sent_event;
-	int slot_idx;
-
-	sent_event = dispatch->pending_event;
-
-	switch (dispatch->pending_event) {
-	case EVDEV_NONE:
-		break;
-	case EVDEV_RELATIVE_MOTION:
-		fallback_flush_relative_motion(dispatch, device, time);
-		break;
-	case EVDEV_ABSOLUTE_MT_DOWN:
-		slot_idx = dispatch->mt.slot;
-		if (!fallback_flush_mt_down(dispatch,
-					    device,
-					    slot_idx,
-					    time))
-			sent_event = EVDEV_NONE;
-		break;
-	case EVDEV_ABSOLUTE_MT_MOTION:
-		slot_idx = dispatch->mt.slot;
-		if (!fallback_flush_mt_motion(dispatch,
-					      device,
-					      slot_idx,
-					      time))
-			sent_event = EVDEV_NONE;
-		break;
-	case EVDEV_ABSOLUTE_MT_UP:
-		slot_idx = dispatch->mt.slot;
-		if (!fallback_flush_mt_up(dispatch,
-					  device,
-					  slot_idx,
-					  time))
-			sent_event = EVDEV_NONE;
-		break;
-	case EVDEV_ABSOLUTE_TOUCH_DOWN:
-		if (!fallback_flush_st_down(dispatch, device, time))
-			sent_event = EVDEV_NONE;
-		break;
-	case EVDEV_ABSOLUTE_MOTION:
-		if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
-			if (fallback_flush_st_motion(dispatch,
-						     device,
-						     time))
-				sent_event = EVDEV_ABSOLUTE_MT_MOTION;
-			else
-				sent_event = EVDEV_NONE;
-		} else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
-			fallback_flush_absolute_motion(dispatch,
-						       device,
-						       time);
-		}
-		break;
-	case EVDEV_ABSOLUTE_TOUCH_UP:
-		if (!fallback_flush_st_up(dispatch, device, time))
-			sent_event = EVDEV_NONE;
-		break;
-	default:
-		assert(0 && "Unknown pending event type");
-		break;
-	}
-
-	dispatch->pending_event = EVDEV_NONE;
-
-	return sent_event;
-}
-
-static enum key_type
-get_key_type(uint16_t code)
-{
-	switch (code) {
-	case BTN_TOOL_PEN:
-	case BTN_TOOL_RUBBER:
-	case BTN_TOOL_BRUSH:
-	case BTN_TOOL_PENCIL:
-	case BTN_TOOL_AIRBRUSH:
-	case BTN_TOOL_MOUSE:
-	case BTN_TOOL_LENS:
-	case BTN_TOOL_QUINTTAP:
-	case BTN_TOOL_DOUBLETAP:
-	case BTN_TOOL_TRIPLETAP:
-	case BTN_TOOL_QUADTAP:
-	case BTN_TOOL_FINGER:
-	case BTN_TOUCH:
-		return KEY_TYPE_NONE;
-	}
-
-	if (code >= KEY_ESC && code <= KEY_MICMUTE)
-		return KEY_TYPE_KEY;
-	if (code >= BTN_MISC && code <= BTN_GEAR_UP)
-		return KEY_TYPE_BUTTON;
-	if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE)
-		return KEY_TYPE_KEY;
-	if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT)
-		return KEY_TYPE_BUTTON;
-	if (code >= KEY_ALS_TOGGLE && code <= KEY_ONSCREEN_KEYBOARD)
-		return KEY_TYPE_KEY;
-	if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40)
-		return KEY_TYPE_BUTTON;
-	return KEY_TYPE_NONE;
-}
-
-static void
-fallback_process_touch_button(struct fallback_dispatch *dispatch,
-			      struct evdev_device *device,
-			      uint64_t time, int value)
-{
-	if (dispatch->pending_event != EVDEV_NONE &&
-	    dispatch->pending_event != EVDEV_ABSOLUTE_MOTION)
-		fallback_flush_pending_event(dispatch, device, time);
-
-	dispatch->pending_event = (value ?
-				 EVDEV_ABSOLUTE_TOUCH_DOWN :
-				 EVDEV_ABSOLUTE_TOUCH_UP);
-}
-
-static inline void
-fallback_flush_debounce(struct fallback_dispatch *dispatch,
-			struct evdev_device *device)
-{
-	int code = dispatch->debounce.button_code;
-	int button;
-
-	if (dispatch->debounce.state != DEBOUNCE_ACTIVE)
-		return;
-
-	if (hw_is_key_down(dispatch, code)) {
-		button = evdev_to_left_handed(device, code);
-		evdev_pointer_notify_physical_button(device,
-						     dispatch->debounce.button_up_time,
-						     button,
-						     LIBINPUT_BUTTON_STATE_RELEASED);
-		hw_set_key_down(dispatch, code, 0);
-	}
-
-	dispatch->debounce.state = DEBOUNCE_ON;
-}
-
-static void
-fallback_debounce_timeout(uint64_t now, void *data)
-{
-	struct evdev_device *device = data;
-	struct fallback_dispatch *dispatch =
-		fallback_dispatch(device->dispatch);
-
-	fallback_flush_debounce(dispatch, device);
-}
-
-static bool
-fallback_filter_debounce_press(struct fallback_dispatch *dispatch,
-			       struct evdev_device *device,
-			       struct input_event *e,
-			       uint64_t time)
-{
-	bool filter = false;
-	uint64_t tdelta;
-
-	/* If other button is pressed while we're holding back the release,
-	 * flush the pending release (if any) and continue. We don't handle
-	 * this situation, if you have a mouse that needs per-button
-	 * debouncing, consider writing to santa for a new mouse.
-	 */
-	if (e->code != dispatch->debounce.button_code) {
-		if (dispatch->debounce.state == DEBOUNCE_ACTIVE) {
-			libinput_timer_cancel(&dispatch->debounce.timer);
-			fallback_flush_debounce(dispatch, device);
-		}
-		return false;
-	}
-
-	tdelta = time - dispatch->debounce.button_up_time;
-	assert((int64_t)tdelta >= 0);
-
-	if (tdelta < DEBOUNCE_TIME) {
-		switch (dispatch->debounce.state) {
-		case DEBOUNCE_INIT:
-			/* This is the first time we debounce, enable proper debouncing
-			   from now on but filter this press event */
-			filter = true;
-			evdev_log_info(device,
-				       "Enabling button debouncing, "
-				       "see %sbutton_debouncing.html for details\n",
-				       HTTP_DOC_LINK);
-			dispatch->debounce.state = DEBOUNCE_NEEDED;
-			break;
-		case DEBOUNCE_NEEDED:
-		case DEBOUNCE_ON:
-			break;
-		/* If a release event is pending and, filter press
-		 * events until we flushed the release */
-		case DEBOUNCE_ACTIVE:
-			filter = true;
-			break;
-		}
-	} else if (dispatch->debounce.state == DEBOUNCE_ACTIVE) {
-		/* call libinput_dispatch() more frequently */
-		evdev_log_bug_client(device,
-				     "Debouncing still active past timeout\n");
-	}
-
-	return filter;
-}
-
-static bool
-fallback_filter_debounce_release(struct fallback_dispatch *dispatch,
-				 struct input_event *e,
-				 uint64_t time)
-{
-	bool filter = false;
-
-	dispatch->debounce.button_code = e->code;
-	dispatch->debounce.button_up_time = time;
-
-	switch (dispatch->debounce.state) {
-	case DEBOUNCE_INIT:
-		break;
-	case DEBOUNCE_NEEDED:
-		filter = true;
-		dispatch->debounce.state = DEBOUNCE_ON;
-		break;
-	case DEBOUNCE_ON:
-		libinput_timer_set(&dispatch->debounce.timer,
-				   time + DEBOUNCE_TIME);
-		filter = true;
-		dispatch->debounce.state = DEBOUNCE_ACTIVE;
-		break;
-	case DEBOUNCE_ACTIVE:
-		filter = true;
-		break;
-	}
-
-	return filter;
-}
-
-static bool
-fallback_filter_debounce(struct fallback_dispatch *dispatch,
-			 struct evdev_device *device,
-			 struct input_event *e, uint64_t time)
-{
-	bool filter = false;
-
-	/* Behavior: we monitor the time deltas between release and press
-	 * events. Proper debouncing is disabled on init, but the first
-	 * time we see a bouncing press event we enable it.
-	 *
-	 * The first bounced event is simply discarded, which ends up in the
-	 * button being released sooner than it should be. Subsequent button
-	 * presses are timer-based and thus released a bit later because we
-	 * then wait for a timeout before we post the release event.
-	 */
-	if (e->value)
-		filter = fallback_filter_debounce_press(dispatch, device, e, time);
-	else
-		filter = fallback_filter_debounce_release(dispatch, e, time);
-
-	return filter;
-}
-
-static inline void
-fallback_process_key(struct fallback_dispatch *dispatch,
-		     struct evdev_device *device,
-		     struct input_event *e, uint64_t time)
-{
-	enum key_type type;
-
-	/* ignore kernel key repeat */
-	if (e->value == 2)
-		return;
-
-	if (e->code == BTN_TOUCH) {
-		if (!device->is_mt)
-			fallback_process_touch_button(dispatch,
-						      device,
-						      time,
-						      e->value);
-		return;
-	}
-
-	fallback_flush_pending_event(dispatch, device, time);
-
-	type = get_key_type(e->code);
-
-	/* Ignore key release events from the kernel for keys that libinput
-	 * never got a pressed event for or key presses for keys that we
-	 * think are still down */
-	switch (type) {
-	case KEY_TYPE_NONE:
-		break;
-	case KEY_TYPE_KEY:
-		if ((e->value && hw_is_key_down(dispatch, e->code)) ||
-		    (e->value == 0 && !hw_is_key_down(dispatch, e->code)))
-			return;
-		break;
-	case KEY_TYPE_BUTTON:
-		if (fallback_filter_debounce(dispatch, device, e, time))
-			return;
-
-		if ((e->value && hw_is_key_down(dispatch, e->code)) ||
-		    (e->value == 0 && !hw_is_key_down(dispatch, e->code)))
-			return;
-		break;
-	}
-
-	hw_set_key_down(dispatch, e->code, e->value);
-
-	switch (type) {
-	case KEY_TYPE_NONE:
-		break;
-	case KEY_TYPE_KEY:
-		fallback_keyboard_notify_key(
-			dispatch,
-			device,
-			time,
-			e->code,
-			e->value ? LIBINPUT_KEY_STATE_PRESSED :
-				   LIBINPUT_KEY_STATE_RELEASED);
-		break;
-	case KEY_TYPE_BUTTON:
-		evdev_pointer_notify_physical_button(
-			device,
-			time,
-			evdev_to_left_handed(device, e->code),
-			e->value ? LIBINPUT_BUTTON_STATE_PRESSED :
-				   LIBINPUT_BUTTON_STATE_RELEASED);
-		break;
-	}
-}
-
-static void
-fallback_process_touch(struct fallback_dispatch *dispatch,
-		       struct evdev_device *device,
-		       struct input_event *e,
-		       uint64_t time)
-{
-	switch (e->code) {
-	case ABS_MT_SLOT:
-		if ((size_t)e->value >= dispatch->mt.slots_len) {
-			evdev_log_bug_libinput(device,
-					 "exceeded slot count (%d of max %zd)\n",
-					 e->value,
-					 dispatch->mt.slots_len);
-			e->value = dispatch->mt.slots_len - 1;
-		}
-		fallback_flush_pending_event(dispatch, device, time);
-		dispatch->mt.slot = e->value;
-		break;
-	case ABS_MT_TRACKING_ID:
-		if (dispatch->pending_event != EVDEV_NONE &&
-		    dispatch->pending_event != EVDEV_ABSOLUTE_MT_MOTION)
-			fallback_flush_pending_event(dispatch, device, time);
-		if (e->value >= 0)
-			dispatch->pending_event = EVDEV_ABSOLUTE_MT_DOWN;
-		else
-			dispatch->pending_event = EVDEV_ABSOLUTE_MT_UP;
-		break;
-	case ABS_MT_POSITION_X:
-		evdev_device_check_abs_axis_range(device, e->code, e->value);
-		dispatch->mt.slots[dispatch->mt.slot].point.x = e->value;
-		if (dispatch->pending_event == EVDEV_NONE)
-			dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
-		break;
-	case ABS_MT_POSITION_Y:
-		evdev_device_check_abs_axis_range(device, e->code, e->value);
-		dispatch->mt.slots[dispatch->mt.slot].point.y = e->value;
-		if (dispatch->pending_event == EVDEV_NONE)
-			dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
-		break;
-	}
-}
-static inline void
-fallback_process_absolute_motion(struct fallback_dispatch *dispatch,
-				 struct evdev_device *device,
-				 struct input_event *e)
-{
-	switch (e->code) {
-	case ABS_X:
-		evdev_device_check_abs_axis_range(device, e->code, e->value);
-		dispatch->abs.point.x = e->value;
-		if (dispatch->pending_event == EVDEV_NONE)
-			dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
-		break;
-	case ABS_Y:
-		evdev_device_check_abs_axis_range(device, e->code, e->value);
-		dispatch->abs.point.y = e->value;
-		if (dispatch->pending_event == EVDEV_NONE)
-			dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
-		break;
-	}
-}
-
-static void
-fallback_lid_keyboard_event(uint64_t time,
-			    struct libinput_event *event,
-			    void *data)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(data);
-
-	if (!dispatch->lid.is_closed)
-		return;
-
-	if (event->type != LIBINPUT_EVENT_KEYBOARD_KEY)
-		return;
-
-	if (dispatch->lid.reliability == RELIABILITY_WRITE_OPEN) {
-		int fd = libevdev_get_fd(dispatch->device->evdev);
-		struct input_event ev[2] = {
-			{{ 0, 0 }, EV_SW, SW_LID, 0 },
-			{{ 0, 0 }, EV_SYN, SYN_REPORT, 0 },
-		};
-
-		(void)write(fd, ev, sizeof(ev));
-		/* In case write() fails, we sync the lid state manually
-		 * regardless. */
-	}
-
-	/* Posting the event here means we preempt the keyboard events that
-	 * caused us to wake up, so the lid event is always passed on before
-	 * the key event.
-	 */
-	dispatch->lid.is_closed = false;
-	fallback_lid_notify_toggle(dispatch, dispatch->device, time);
-}
-
-static void
-fallback_lid_toggle_keyboard_listener(struct fallback_dispatch *dispatch,
-				      bool is_closed)
-{
-	if (!dispatch->lid.keyboard)
-		return;
-
-	if (is_closed) {
-		libinput_device_add_event_listener(
-					&dispatch->lid.keyboard->base,
-					&dispatch->lid.listener,
-					fallback_lid_keyboard_event,
-					dispatch);
-	} else {
-		libinput_device_remove_event_listener(
-					&dispatch->lid.listener);
-		libinput_device_init_event_listener(
-					&dispatch->lid.listener);
-	}
-}
-
-static inline void
-fallback_process_switch(struct fallback_dispatch *dispatch,
-			struct evdev_device *device,
-			struct input_event *e,
-			uint64_t time)
-{
-	enum libinput_switch_state state;
-	bool is_closed;
-
-	switch (e->code) {
-	case SW_LID:
-		is_closed = !!e->value;
-
-		if (dispatch->lid.is_closed == is_closed)
-			return;
-		fallback_lid_toggle_keyboard_listener(dispatch, is_closed);
-
-		dispatch->lid.is_closed = is_closed;
-		fallback_lid_notify_toggle(dispatch, device, time);
-		break;
-	case SW_TABLET_MODE:
-		if (dispatch->tablet_mode.state == e->value)
-			return;
-
-		dispatch->tablet_mode.state = e->value;
-		if (e->value)
-			state = LIBINPUT_SWITCH_STATE_ON;
-		else
-			state = LIBINPUT_SWITCH_STATE_OFF;
-		switch_notify_toggle(&device->base,
-				     time,
-				     LIBINPUT_SWITCH_TABLET_MODE,
-				     state);
-		break;
-	}
-}
-
 void
 evdev_notify_axis(struct evdev_device *device,
 		  uint64_t time,
@@ -1248,113 +371,6 @@ evdev_notify_axis(struct evdev_device *device,
 			    &discrete);
 }
 
-static inline bool
-fallback_reject_relative(struct evdev_device *device,
-			 const struct input_event *e,
-			 uint64_t time)
-{
-	if ((e->code == REL_X || e->code == REL_Y) &&
-	    (device->seat_caps & EVDEV_DEVICE_POINTER) == 0) {
-		evdev_log_bug_libinput_ratelimit(device,
-						 &device->nonpointer_rel_limit,
-						 "REL_X/Y from a non-pointer device\n");
-		return true;
-	}
-
-	return false;
-}
-
-static inline void
-fallback_process_relative(struct fallback_dispatch *dispatch,
-			  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 };
-	enum libinput_pointer_axis_source source;
-
-	if (fallback_reject_relative(device, e, time))
-		return;
-
-	switch (e->code) {
-	case REL_X:
-		if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
-			fallback_flush_pending_event(dispatch, device, time);
-		dispatch->rel.x += e->value;
-		dispatch->pending_event = EVDEV_RELATIVE_MOTION;
-		break;
-	case REL_Y:
-		if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
-			fallback_flush_pending_event(dispatch, device, time);
-		dispatch->rel.y += e->value;
-		dispatch->pending_event = EVDEV_RELATIVE_MOTION;
-		break;
-	case REL_WHEEL:
-		fallback_flush_pending_event(dispatch, device, time);
-		wheel_degrees.y = -1 * e->value *
-					device->scroll.wheel_click_angle.x;
-		discrete.y = -1 * e->value;
-
-		source = device->scroll.is_tilt.vertical ?
-				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
-				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
-
-		evdev_notify_axis(
-			device,
-			time,
-			AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
-			source,
-			&wheel_degrees,
-			&discrete);
-		break;
-	case REL_HWHEEL:
-		fallback_flush_pending_event(dispatch, device, time);
-		wheel_degrees.x = e->value *
-					device->scroll.wheel_click_angle.y;
-		discrete.x = e->value;
-
-		source = device->scroll.is_tilt.horizontal ?
-				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
-				LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
-
-		evdev_notify_axis(
-			device,
-			time,
-			AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
-			source,
-			&wheel_degrees,
-			&discrete);
-		break;
-	}
-}
-
-static inline void
-fallback_process_absolute(struct fallback_dispatch *dispatch,
-			  struct evdev_device *device,
-			  struct input_event *e,
-			  uint64_t time)
-{
-	if (device->is_mt) {
-		fallback_process_touch(dispatch, device, e, time);
-	} else {
-		fallback_process_absolute_motion(dispatch, device, e);
-	}
-}
-
-static inline bool
-fallback_any_button_down(struct fallback_dispatch *dispatch,
-		      struct evdev_device *device)
-{
-	unsigned int button;
-
-	for (button = BTN_LEFT; button < BTN_JOYSTICK; button++) {
-		if (libevdev_has_event_code(device->evdev, EV_KEY, button) &&
-		    hw_is_key_down(dispatch, button))
-			return true;
-	}
-	return false;
-}
-
 static void
 evdev_tag_external_mouse(struct evdev_device *device,
 			 struct udev_device *udev_device)
@@ -1425,223 +441,6 @@ evdev_tag_keyboard(struct evdev_device *device,
 	device->tags |= EVDEV_TAG_KEYBOARD;
 }
 
-static void
-fallback_process(struct evdev_dispatch *evdev_dispatch,
-		 struct evdev_device *device,
-		 struct input_event *event,
-		 uint64_t time)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-	enum evdev_event_type sent;
-
-	if (dispatch->ignore_events)
-		return;
-
-	switch (event->type) {
-	case EV_REL:
-		fallback_process_relative(dispatch, device, event, time);
-		break;
-	case EV_ABS:
-		fallback_process_absolute(dispatch, device, event, time);
-		break;
-	case EV_KEY:
-		fallback_process_key(dispatch, device, event, time);
-		break;
-	case EV_SW:
-		fallback_process_switch(dispatch, device, event, time);
-		break;
-	case EV_SYN:
-		sent = fallback_flush_pending_event(dispatch, device, time);
-		switch (sent) {
-		case EVDEV_ABSOLUTE_TOUCH_DOWN:
-		case EVDEV_ABSOLUTE_TOUCH_UP:
-		case EVDEV_ABSOLUTE_MT_DOWN:
-		case EVDEV_ABSOLUTE_MT_MOTION:
-		case EVDEV_ABSOLUTE_MT_UP:
-			touch_notify_frame(&device->base, time);
-			break;
-		case EVDEV_ABSOLUTE_MOTION:
-		case EVDEV_RELATIVE_MOTION:
-		case EVDEV_NONE:
-			break;
-		}
-		break;
-	}
-}
-
-static void
-release_touches(struct fallback_dispatch *dispatch,
-		struct evdev_device *device,
-		uint64_t time)
-{
-	unsigned int idx;
-	bool need_frame = false;
-
-	need_frame = fallback_flush_st_up(dispatch, device, time);
-
-	for (idx = 0; idx < dispatch->mt.slots_len; idx++) {
-		struct mt_slot *slot = &dispatch->mt.slots[idx];
-
-		if (slot->seat_slot == -1)
-			continue;
-
-		if (fallback_flush_mt_up(dispatch, device, idx, time))
-			need_frame = true;
-	}
-
-	if (need_frame)
-		touch_notify_frame(&device->base, time);
-}
-
-static void
-release_pressed_keys(struct fallback_dispatch *dispatch,
-		     struct evdev_device *device,
-		     uint64_t time)
-{
-	int code;
-
-	for (code = 0; code < KEY_CNT; code++) {
-		int count = get_key_down_count(device, code);
-
-		if (count == 0)
-			continue;
-
-		if (count > 1) {
-			evdev_log_bug_libinput(device,
-					       "key %d is down %d times.\n",
-					       code,
-					       count);
-		}
-
-		switch (get_key_type(code)) {
-		case KEY_TYPE_NONE:
-			break;
-		case KEY_TYPE_KEY:
-			fallback_keyboard_notify_key(
-				dispatch,
-				device,
-				time,
-				code,
-				LIBINPUT_KEY_STATE_RELEASED);
-			break;
-		case KEY_TYPE_BUTTON:
-			evdev_pointer_notify_physical_button(
-				device,
-				time,
-				evdev_to_left_handed(device, code),
-				LIBINPUT_BUTTON_STATE_RELEASED);
-			break;
-		}
-
-		count = get_key_down_count(device, code);
-		if (count != 0) {
-			evdev_log_bug_libinput(device,
-					       "releasing key %d failed.\n",
-					       code);
-			break;
-		}
-	}
-}
-
-static void
-fallback_return_to_neutral_state(struct fallback_dispatch *dispatch,
-				 struct evdev_device *device)
-{
-	struct libinput *libinput = evdev_libinput_context(device);
-	uint64_t time;
-
-	if ((time = libinput_now(libinput)) == 0)
-		return;
-
-	release_touches(dispatch, device, time);
-	release_pressed_keys(dispatch, device, time);
-	memset(dispatch->hw_key_mask, 0, sizeof(dispatch->hw_key_mask));
-}
-
-static void
-fallback_suspend(struct evdev_dispatch *evdev_dispatch,
-		 struct evdev_device *device)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-
-	fallback_return_to_neutral_state(dispatch, device);
-}
-
-static void
-fallback_remove(struct evdev_dispatch *evdev_dispatch)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-
-	if (!dispatch->lid.keyboard)
-		return;
-
-	libinput_device_remove_event_listener(&dispatch->lid.listener);
-}
-
-static void
-fallback_sync_initial_state(struct evdev_device *device,
-			    struct evdev_dispatch *evdev_dispatch)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-	uint64_t time = libinput_now(evdev_libinput_context(device));
-
-	if (device->tags & EVDEV_TAG_LID_SWITCH) {
-		struct libevdev *evdev = device->evdev;
-
-		dispatch->lid.is_closed = libevdev_get_event_value(evdev,
-								   EV_SW,
-								   SW_LID);
-		dispatch->lid.is_closed_client_state = false;
-
-		/* For the initial state sync, we depend on whether the lid switch
-		 * is reliable. If we know it's reliable, we sync as expected.
-		 * If we're not sure, we ignore the initial state and only sync on
-		 * the first future lid close event. Laptops with a broken switch
-		 * that always have the switch in 'on' state thus don't mess up our
-		 * touchpad.
-		 */
-		if (dispatch->lid.is_closed &&
-		    dispatch->lid.reliability == RELIABILITY_RELIABLE) {
-			fallback_lid_notify_toggle(dispatch, device, time);
-		}
-	}
-
-	if (dispatch->tablet_mode.state) {
-		switch_notify_toggle(&device->base,
-				     time,
-				     LIBINPUT_SWITCH_TABLET_MODE,
-				     LIBINPUT_SWITCH_STATE_ON);
-	}
-}
-
-static void
-fallback_toggle_touch(struct evdev_dispatch *evdev_dispatch,
-		      struct evdev_device *device,
-		      bool enable)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-	bool ignore_events = !enable;
-
-	if (ignore_events == dispatch->ignore_events)
-		return;
-
-	if (ignore_events)
-		fallback_return_to_neutral_state(dispatch, device);
-
-	dispatch->ignore_events = ignore_events;
-}
-
-static void
-fallback_destroy(struct evdev_dispatch *evdev_dispatch)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
-
-	libinput_timer_cancel(&dispatch->debounce.timer);
-	libinput_timer_destroy(&dispatch->debounce.timer);
-	free(dispatch->mt.slots);
-	free(dispatch);
-}
-
 static int
 evdev_calibration_has_matrix(struct libinput_device *libinput_device)
 {
@@ -1683,72 +482,6 @@ evdev_calibration_get_default_matrix(struct libinput_device *libinput_device,
 	return !matrix_is_identity(&device->abs.default_calibration);
 }
 
-static void
-fallback_lid_pair_keyboard(struct evdev_device *lid_switch,
-			   struct evdev_device *keyboard)
-{
-	struct fallback_dispatch *dispatch =
-		fallback_dispatch(lid_switch->dispatch);
-
-	if ((keyboard->tags & EVDEV_TAG_KEYBOARD) == 0 ||
-	    (lid_switch->tags & EVDEV_TAG_LID_SWITCH) == 0)
-		return;
-
-	if (dispatch->lid.keyboard)
-		return;
-
-	if (keyboard->tags & EVDEV_TAG_INTERNAL_KEYBOARD) {
-		dispatch->lid.keyboard = keyboard;
-		evdev_log_debug(lid_switch,
-				"lid: keyboard paired with %s<->%s\n",
-				lid_switch->devname,
-				keyboard->devname);
-
-		/* We need to init the event listener now only if the reported state
-		 * is closed. */
-		if (dispatch->lid.is_closed)
-			fallback_lid_toggle_keyboard_listener(dispatch,
-						    dispatch->lid.is_closed);
-	}
-}
-
-static void
-fallback_interface_device_added(struct evdev_device *device,
-				struct evdev_device *added_device)
-{
-	fallback_lid_pair_keyboard(device, added_device);
-}
-
-static void
-fallback_interface_device_removed(struct evdev_device *device,
-				  struct evdev_device *removed_device)
-{
-	struct fallback_dispatch *dispatch =
-			fallback_dispatch(device->dispatch);
-
-	if (removed_device == dispatch->lid.keyboard) {
-		libinput_device_remove_event_listener(
-					&dispatch->lid.listener);
-		libinput_device_init_event_listener(
-					&dispatch->lid.listener);
-		dispatch->lid.keyboard = NULL;
-	}
-}
-
-struct evdev_dispatch_interface fallback_interface = {
-	.process = fallback_process,
-	.suspend = fallback_suspend,
-	.remove = fallback_remove,
-	.destroy = fallback_destroy,
-	.device_added = fallback_interface_device_added,
-	.device_removed = fallback_interface_device_removed,
-	.device_suspended = fallback_interface_device_removed, /* treat as remove */
-	.device_resumed = fallback_interface_device_added,   /* treat as add */
-	.post_added = fallback_sync_initial_state,
-	.toggle_touch = fallback_toggle_touch,
-	.get_switch_state = fallback_get_switch_state,
-};
-
 static uint32_t
 evdev_sendevents_get_modes(struct libinput_device *device)
 {
@@ -1804,20 +537,6 @@ evdev_left_handed_has(struct libinput_device *device)
 	return 1;
 }
 
-static void
-fallback_change_to_left_handed(struct evdev_device *device)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
-
-	if (device->left_handed.want_enabled == device->left_handed.enabled)
-		return;
-
-	if (fallback_any_button_down(dispatch, device))
-		return;
-
-	device->left_handed.enabled = device->left_handed.want_enabled;
-}
-
 static enum libinput_config_status
 evdev_left_handed_set(struct libinput_device *device, int left_handed)
 {
@@ -1866,22 +585,6 @@ evdev_scroll_get_methods(struct libinput_device *device)
 	return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
 }
 
-static void
-fallback_change_scroll_method(struct evdev_device *device)
-{
-	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
-
-	if (device->scroll.want_method == device->scroll.method &&
-	    device->scroll.want_button == device->scroll.button)
-		return;
-
-	if (fallback_any_button_down(dispatch, device))
-		return;
-
-	device->scroll.method = device->scroll.want_method;
-	device->scroll.button = device->scroll.want_button;
-}
-
 static enum libinput_config_status
 evdev_scroll_set_method(struct libinput_device *device,
 			enum libinput_config_scroll_method method)
@@ -1964,7 +667,7 @@ evdev_scroll_get_default_button(struct libinput_device *device)
 	return 0;
 }
 
-static void
+void
 evdev_init_button_scroll(struct evdev_device *device,
 			 void (*change_scroll_method)(struct evdev_device *))
 {
@@ -2062,58 +765,7 @@ evdev_init_natural_scroll(struct evdev_device *device)
 	device->base.config.natural_scroll = &device->scroll.config_natural;
 }
 
-static int
-fallback_rotation_config_is_available(struct libinput_device *device)
-{
-	/* This function only gets called when we support rotation */
-	return 1;
-}
-
-static enum libinput_config_status
-fallback_rotation_config_set_angle(struct libinput_device *libinput_device,
-				unsigned int degrees_cw)
-{
-	struct evdev_device *device = evdev_device(libinput_device);
-	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
-
-	dispatch->rotation.angle = degrees_cw;
-	matrix_init_rotate(&dispatch->rotation.matrix, degrees_cw);
-
-	return LIBINPUT_CONFIG_STATUS_SUCCESS;
-}
-
-static unsigned int
-fallback_rotation_config_get_angle(struct libinput_device *libinput_device)
-{
-	struct evdev_device *device = evdev_device(libinput_device);
-	struct fallback_dispatch *dispatch = fallback_dispatch(device->dispatch);
-
-	return dispatch->rotation.angle;
-}
-
-static unsigned int
-fallback_rotation_config_get_default_angle(struct libinput_device *device)
-{
-	return 0;
-}
-
-static void
-fallback_init_rotation(struct fallback_dispatch *dispatch,
-		       struct evdev_device *device)
-{
-	if ((device->model_flags & EVDEV_MODEL_TRACKBALL) == 0)
-		return;
-
-	dispatch->rotation.config.is_available = fallback_rotation_config_is_available;
-	dispatch->rotation.config.set_angle = fallback_rotation_config_set_angle;
-	dispatch->rotation.config.get_angle = fallback_rotation_config_get_angle;
-	dispatch->rotation.config.get_default_angle = fallback_rotation_config_get_default_angle;
-	dispatch->rotation.is_enabled = false;
-	matrix_init_identity(&dispatch->rotation.matrix);
-	device->base.config.rotation = &dispatch->rotation.config;
-}
-
-static inline int
+int
 evdev_need_mtdev(struct evdev_device *device)
 {
 	struct libevdev *evdev = device->evdev;
@@ -2126,7 +778,7 @@ evdev_need_mtdev(struct evdev_device *device)
 /* 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
+bool
 evdev_is_fake_mt_device(struct evdev_device *device)
 {
 	struct libevdev *evdev = device->evdev;
@@ -2135,89 +787,7 @@ evdev_is_fake_mt_device(struct evdev_device *device)
 		libevdev_get_num_slots(evdev) == -1;
 }
 
-static inline int
-fallback_dispatch_init_slots(struct fallback_dispatch *dispatch,
-			     struct evdev_device *device)
-{
-	struct libevdev *evdev = device->evdev;
-	struct mt_slot *slots;
-	int num_slots;
-	int active_slot;
-	int slot;
-
-	if (evdev_is_fake_mt_device(device) ||
-	    !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
-	    !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
-		 return 0;
-
-	/* 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 = zalloc(num_slots * sizeof(struct mt_slot));
-
-	for (slot = 0; slot < num_slots; ++slot) {
-		slots[slot].seat_slot = -1;
-
-		if (evdev_need_mtdev(device))
-			continue;
-
-		slots[slot].point.x = libevdev_get_slot_value(evdev,
-							      slot,
-							      ABS_MT_POSITION_X);
-		slots[slot].point.y = libevdev_get_slot_value(evdev,
-							      slot,
-							      ABS_MT_POSITION_Y);
-	}
-	dispatch->mt.slots = slots;
-	dispatch->mt.slots_len = num_slots;
-	dispatch->mt.slot = active_slot;
-
-	if (device->abs.absinfo_x->fuzz || device->abs.absinfo_y->fuzz) {
-		dispatch->mt.want_hysteresis = true;
-		dispatch->mt.hysteresis_margin.x = device->abs.absinfo_x->fuzz/2;
-		dispatch->mt.hysteresis_margin.y = device->abs.absinfo_y->fuzz/2;
-	}
-
-	return 0;
-}
-
-static inline void
-fallback_dispatch_init_rel(struct fallback_dispatch *dispatch,
-			   struct evdev_device *device)
-{
-	dispatch->rel.x = 0;
-	dispatch->rel.y = 0;
-}
-
-static inline void
-fallback_dispatch_init_abs(struct fallback_dispatch *dispatch,
-			   struct evdev_device *device)
-{
-	if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_X))
-		return;
-
-	dispatch->abs.point.x = device->abs.absinfo_x->value;
-	dispatch->abs.point.y = device->abs.absinfo_y->value;
-	dispatch->abs.seat_slot = -1;
-
-	evdev_device_init_abs_range_warnings(device);
-}
-
-static inline enum switch_reliability
+enum switch_reliability
 evdev_read_switch_reliability_prop(struct evdev_device *device)
 {
 	const char *prop;
@@ -2239,93 +809,6 @@ evdev_read_switch_reliability_prop(struct evdev_device *device)
 }
 
 static inline void
-fallback_dispatch_init_switch(struct fallback_dispatch *dispatch,
-			      struct evdev_device *device)
-{
-	int val;
-
-	if (device->tags & EVDEV_TAG_LID_SWITCH) {
-		libinput_device_init_event_listener(&dispatch->lid.listener);
-		dispatch->lid.reliability = evdev_read_switch_reliability_prop(device);
-		dispatch->lid.is_closed = false;
-	}
-
-	if (device->tags & EVDEV_TAG_TABLET_MODE_SWITCH) {
-		val = libevdev_get_event_value(device->evdev,
-					       EV_SW,
-					       SW_TABLET_MODE);
-		dispatch->tablet_mode.state = val;
-	}
-}
-
-static struct evdev_dispatch *
-fallback_dispatch_create(struct libinput_device *libinput_device)
-{
-	struct evdev_device *device = evdev_device(libinput_device);
-	struct fallback_dispatch *dispatch;
-	char timer_name[64];
-
-	dispatch = zalloc(sizeof *dispatch);
-	dispatch->device = evdev_device(libinput_device);
-	dispatch->base.dispatch_type = DISPATCH_FALLBACK;
-	dispatch->base.interface = &fallback_interface;
-	dispatch->pending_event = EVDEV_NONE;
-
-	fallback_dispatch_init_rel(dispatch, device);
-	fallback_dispatch_init_abs(dispatch, device);
-	if (fallback_dispatch_init_slots(dispatch, device) == -1) {
-		free(dispatch);
-		return NULL;
-	}
-
-	fallback_dispatch_init_switch(dispatch, device);
-
-	if (device->left_handed.want_enabled)
-		evdev_init_left_handed(device,
-				       fallback_change_to_left_handed);
-
-	if (device->scroll.want_button)
-		evdev_init_button_scroll(device,
-					 fallback_change_scroll_method);
-
-	if (device->scroll.natural_scrolling_enabled)
-		evdev_init_natural_scroll(device);
-
-	evdev_init_calibration(device, &dispatch->calibration);
-	evdev_init_sendevents(device, &dispatch->base);
-	fallback_init_rotation(dispatch, device);
-
-	/* BTN_MIDDLE is set on mice even when it's not present. So
-	 * we can only use the absence 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(device->evdev, EV_KEY, BTN_LEFT) &&
-	    libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) {
-		bool has_middle = libevdev_has_event_code(device->evdev,
-							  EV_KEY,
-							  BTN_MIDDLE);
-		bool want_config = has_middle;
-		bool enable_by_default = !has_middle;
-
-		evdev_init_middlebutton(device,
-					enable_by_default,
-					want_config);
-	}
-
-	snprintf(timer_name,
-		 sizeof(timer_name),
-		 "%s debounce",
-		 evdev_device_get_sysname(device));
-	libinput_timer_init(&dispatch->debounce.timer,
-			    evdev_libinput_context(device),
-			    timer_name,
-			    fallback_debounce_timeout,
-			    device);
-	return &dispatch->base;
-}
-
-static inline void
 evdev_process_event(struct evdev_device *device, struct input_event *e)
 {
 	struct evdev_dispatch *dispatch = device->dispatch;
diff --git a/src/evdev.h b/src/evdev.h
index d2aa85bd..4c42f6eb 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -345,78 +345,6 @@ evdev_verify_dispatch_type(struct evdev_dispatch *dispatch,
 		abort();
 }
 
-struct fallback_dispatch {
-	struct evdev_dispatch base;
-	struct evdev_device *device;
-
-	struct libinput_device_config_calibration calibration;
-
-	struct {
-		bool is_enabled;
-		int angle;
-		struct matrix matrix;
-		struct libinput_device_config_rotation config;
-	} rotation;
-
-	struct {
-		struct device_coords point;
-		int32_t seat_slot;
-
-		struct {
-			struct device_coords min, max;
-			struct ratelimit range_warn_limit;
-		} warning_range;
-	} abs;
-
-	struct {
-		int slot;
-		struct mt_slot *slots;
-		size_t slots_len;
-		bool want_hysteresis;
-		struct device_coords hysteresis_margin;
-	} mt;
-
-	struct device_coords rel;
-
-	struct {
-		int state;
-	} tablet_mode;
-
-	/* Bitmask of pressed keys used to ignore initial release events from
-	 * the kernel. */
-	unsigned long hw_key_mask[NLONGS(KEY_CNT)];
-
-	enum evdev_event_type pending_event;
-
-	/* true if we're reading events (i.e. not suspended) but we're
-	   ignoring them */
-	bool ignore_events;
-
-	struct {
-		enum evdev_debounce_state state;
-		unsigned int button_code;
-		uint64_t button_up_time;
-		struct libinput_timer timer;
-	} debounce;
-
-	struct {
-		enum switch_reliability reliability;
-
-		bool is_closed;
-		bool is_closed_client_state;
-		struct evdev_device *keyboard;
-		struct libinput_event_listener listener;
-	} lid;
-};
-
-static inline struct fallback_dispatch*
-fallback_dispatch(struct evdev_dispatch *dispatch)
-{
-	evdev_verify_dispatch_type(dispatch, DISPATCH_FALLBACK);
-
-	return container_of(dispatch, struct fallback_dispatch, base);
-}
-
 struct evdev_device *
 evdev_device_create(struct libinput_seat *seat,
 		    struct udev_device *device);
@@ -436,6 +364,9 @@ evdev_init_calibration(struct evdev_device *device,
 void
 evdev_read_calibration_prop(struct evdev_device *device);
 
+enum switch_reliability
+evdev_read_switch_reliability_prop(struct evdev_device *device);
+
 void
 evdev_init_sendevents(struct evdev_device *device,
 		      struct evdev_dispatch *dispatch);
@@ -459,6 +390,15 @@ evdev_tablet_pad_create(struct evdev_device *device);
 struct evdev_dispatch *
 evdev_lid_switch_dispatch_create(struct evdev_device *device);
 
+struct evdev_dispatch *
+fallback_dispatch_create(struct libinput_device *libinput_device);
+
+bool
+evdev_is_fake_mt_device(struct evdev_device *device);
+
+int
+evdev_need_mtdev(struct evdev_device *device);
+
 void
 evdev_device_led_update(struct evdev_device *device, enum libinput_led leds);
 
@@ -565,6 +505,15 @@ void
 evdev_init_natural_scroll(struct evdev_device *device);
 
 void
+evdev_init_button_scroll(struct evdev_device *device,
+			 void (*change_scroll_method)(struct evdev_device *));
+
+int
+evdev_update_key_down_count(struct evdev_device *device,
+			    int code,
+			    int pressed);
+
+void
 evdev_notify_axis(struct evdev_device *device,
 		  uint64_t time,
 		  uint32_t axes,
-- 
2.13.5



More information about the wayland-devel mailing list