[PATCH libinput] pointer: add button debouncing

Peter Hutterer peter.hutterer at who-t.net
Mon Jul 17 08:35:33 UTC 2017


Some devices have worn-out switches or just cheap switches that trigger
multiple button events for each press. These can be identified by unfeasably
short time deltas between the release and the next press event. In the
recordings I've seen so far, that timeout is 8ms.

This approach here is the simplest one to ignore these events.
We record the time stamp of the release event and if a subsequent button
press for the same button follows within the hardcoded timeout of 12ms, filter
the button press event. The release will be filtered automatically after that
by the existing code.

https://bugs.freedesktop.org/show_bug.cgi?id=100057

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 doc/button_debouncing.dox |  26 ++++
 doc/page-hierarchy.dox    |   1 +
 meson.build               |   1 +
 src/evdev.c               | 174 ++++++++++++++++++++++++--
 src/evdev.h               |  26 ++++
 test/litest.c             |   7 ++
 test/litest.h             |   3 +
 test/test-pointer.c       | 309 +++++++++++++++++++++++++++++++++++++++++++++-
 8 files changed, 537 insertions(+), 10 deletions(-)
 create mode 100644 doc/button_debouncing.dox

diff --git a/doc/button_debouncing.dox b/doc/button_debouncing.dox
new file mode 100644
index 00000000..fd52986e
--- /dev/null
+++ b/doc/button_debouncing.dox
@@ -0,0 +1,26 @@
+/**
+
+ at page button_debouncing Button debouncing
+
+Physical buttons experience wear-and-tear with usage. On some devices this
+can result in an effect called "contact bouncing" or "chatter". This effect
+can cause the button to send multiple events within a short time frame, even
+though the user only pressed or clicked the button once. This effect can be
+counteracted by "debouncing" the buttons, usually by ignoring erroneous
+events.
+
+libinput has a built-in debouncing for hardware defects. This feature is
+available for all button-base devices but not active by default. When
+libinput detects a faulty button on a device, debouncing is enabled and a
+warning is printed to the log. Subsequent button events are handled
+correctly in that bouncing button events are ignored, a user should thus see
+the expected behavior.
+
+Note that libinput's debouncing intended to correct hardware damage or
+substandard hardware. Debouncing is also used as an accessibility feature
+but the requirements are different. In the accessibility feature, multiple
+physical key presses, usually caused by involuntary muscle movement, must be
+filtered to only one key press. This feature must be implemented higher in
+the stack, libinput is limited to hardware debouncing.
+
+*/
diff --git a/doc/page-hierarchy.dox b/doc/page-hierarchy.dox
index 1f0b735a..edff4d43 100644
--- a/doc/page-hierarchy.dox
+++ b/doc/page-hierarchy.dox
@@ -19,6 +19,7 @@
 
 - @subpage motion_normalization
 - @subpage middle_button_emulation
+- @subpage button_debouncing
 
 @page tablets Graphics Tablets
 
diff --git a/meson.build b/meson.build
index 0a8d6a26..153c66cd 100644
--- a/meson.build
+++ b/meson.build
@@ -260,6 +260,7 @@ if get_option('documentation')
 		meson.source_root() + '/doc/absolute-axes.dox',
 		meson.source_root() + '/doc/absolute-coordinate-ranges.dox',
 		meson.source_root() + '/doc/building.dox',
+		meson.source_root() + '/doc/button_debouncing.dox',
 		meson.source_root() + '/doc/clickpad-softbuttons.dox',
 		meson.source_root() + '/doc/contributing.dox',
 		meson.source_root() + '/doc/device-configuration-via-udev.dox',
diff --git a/src/evdev.c b/src/evdev.c
index 24bfad07..7b7ffd48 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -49,6 +49,7 @@
 
 #define DEFAULT_WHEEL_CLICK_ANGLE 15
 #define DEFAULT_BUTTON_SCROLL_TIMEOUT ms2us(200)
+#define	DEBOUNCE_TIME ms2us(12)
 
 enum evdev_key_type {
 	EVDEV_KEY_TYPE_NONE,
@@ -812,6 +813,144 @@ fallback_process_touch_button(struct fallback_dispatch *dispatch,
 }
 
 static inline void
+fallback_flush_debounce(struct fallback_dispatch *dispatch,
+			struct evdev_device *device)
+{
+	int button;
+
+	if (dispatch->debounce.state != DEBOUNCE_ACTIVE)
+		return;
+
+	button = evdev_to_left_handed(device,
+				      dispatch->debounce.button_code);
+	evdev_pointer_notify_physical_button(device,
+					     dispatch->debounce.button_up_time,
+					     button,
+					     LIBINPUT_BUTTON_STATE_RELEASED);
+	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)
@@ -837,15 +976,20 @@ fallback_process_key(struct fallback_dispatch *dispatch,
 
 	/* Ignore key release events from the kernel for keys that libinput
 	 * never got a pressed event for. */
-	if (e->value == 0) {
-		switch (type) {
-		case EVDEV_KEY_TYPE_NONE:
-			break;
-		case EVDEV_KEY_TYPE_KEY:
-		case EVDEV_KEY_TYPE_BUTTON:
-			if (!hw_is_key_down(dispatch, e->code))
-				return;
-		}
+	switch (type) {
+	case EVDEV_KEY_TYPE_NONE:
+		break;
+	case EVDEV_KEY_TYPE_KEY:
+		if (e->value == 0 && !hw_is_key_down(dispatch, e->code))
+			return;
+		break;
+	case EVDEV_KEY_TYPE_BUTTON:
+		if (fallback_filter_debounce(dispatch, device, e, time))
+			return;
+
+		if (e->value == 0 && !hw_is_key_down(dispatch, e->code))
+			return;
+		break;
 	}
 
 	hw_set_key_down(dispatch, e->code, e->value);
@@ -1306,6 +1450,8 @@ 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);
 }
@@ -1837,6 +1983,7 @@ 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->base.dispatch_type = DISPATCH_FALLBACK;
@@ -1883,6 +2030,15 @@ fallback_dispatch_create(struct libinput_device *libinput_device)
 					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.h b/src/evdev.h
index b891f906..103b9adf 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -135,6 +135,25 @@ enum evdev_button_scroll_state {
 	BUTTONSCROLL_SCROLLING,		/* have sent scroll events */
 };
 
+enum evdev_debounce_state {
+	/**
+	 * Initial state, no debounce but monitoring events
+	 */
+	DEBOUNCE_INIT,
+	/**
+	 * Bounce detected, future events need debouncing
+	 */
+	DEBOUNCE_NEEDED,
+	/**
+	 * Debounce is enabled, but no event is currently being filtered
+	 */
+	DEBOUNCE_ON,
+	/**
+	 * Debounce is enabled and we are currently filtering an event
+	 */
+	DEBOUNCE_ACTIVE,
+};
+
 struct mt_slot {
 	int32_t seat_slot;
 	struct device_coords point;
@@ -364,6 +383,13 @@ struct fallback_dispatch {
 	/* 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;
 };
 
 static inline struct fallback_dispatch*
diff --git a/test/litest.c b/test/litest.c
index f3f71448..ec4683a5 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -2127,6 +2127,7 @@ litest_button_click(struct litest_device *d, unsigned int button, bool is_press)
 
 	ARRAY_FOR_EACH(click, ev)
 		litest_event(d, ev->type, ev->code, ev->value);
+	litest_timeout_debounce();
 }
 
 void
@@ -3237,6 +3238,12 @@ litest_timeout_tapndrag(void)
 }
 
 void
+litest_timeout_debounce(void)
+{
+	msleep(15);
+}
+
+void
 litest_timeout_softbuttons(void)
 {
 	msleep(300);
diff --git a/test/litest.h b/test/litest.h
index 7086804e..abe6ca0b 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -684,6 +684,9 @@ void
 litest_timeout_tapndrag(void);
 
 void
+litest_timeout_debounce(void);
+
+void
 litest_timeout_softbuttons(void);
 
 void
diff --git a/test/test-pointer.c b/test/test-pointer.c
index e09f8f8a..9c0f2c14 100644
--- a/test/test-pointer.c
+++ b/test/test-pointer.c
@@ -348,7 +348,7 @@ test_button_event(struct litest_device *dev, unsigned int button, int state)
 {
 	struct libinput *li = dev->libinput;
 
-	litest_event(dev, EV_KEY, button, state);
+	litest_button_click(dev, button, state);
 	litest_event(dev, EV_SYN, SYN_REPORT, 0);
 
 	litest_assert_button_event(li, button,
@@ -2073,6 +2073,306 @@ START_TEST(pointer_time_usec)
 }
 END_TEST
 
+START_TEST(debounce)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+
+	litest_disable_middleemu(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	/* expect debouncing on now, this event is ignored */
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(debounce_timer)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+
+	litest_disable_middleemu(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	/* expect debouncing on now, this event is ignored */
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	litest_timeout_debounce();
+	litest_drain_events(li);
+
+	for (int i = 0; i < 3; i++) {
+		litest_event(dev, EV_KEY, BTN_LEFT, 1);
+		litest_event(dev, EV_SYN, SYN_REPORT, 0);
+		litest_timeout_debounce();
+
+		/* Not all devices can disable middle button emulation, time out on
+		 * middle button here to make sure the initial button press event
+		 * was flushed.
+		 */
+		libinput_dispatch(li);
+		litest_timeout_middlebutton();
+		libinput_dispatch(li);
+		litest_assert_button_event(li,
+					   BTN_LEFT,
+					   LIBINPUT_BUTTON_STATE_PRESSED);
+
+		/* bouncy bouncy bouncy */
+		litest_event(dev, EV_KEY, BTN_LEFT, 0);
+		litest_event(dev, EV_SYN, SYN_REPORT, 0);
+		litest_event(dev, EV_KEY, BTN_LEFT, 1);
+		litest_event(dev, EV_SYN, SYN_REPORT, 0);
+		litest_assert_empty_queue(li);
+
+		litest_event(dev, EV_KEY, BTN_LEFT, 0);
+		litest_event(dev, EV_SYN, SYN_REPORT, 0);
+		libinput_dispatch(li);
+		litest_assert_button_event(li,
+					   BTN_LEFT,
+					   LIBINPUT_BUTTON_STATE_RELEASED);
+
+		litest_assert_empty_queue(li);
+	}
+}
+END_TEST
+
+START_TEST(debounce_multibounce)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+
+	litest_disable_middleemu(dev);
+	litest_drain_events(li);
+
+	/* enable debouncing */
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_drain_events(li);
+
+	/* Let's assume our button has ventricular fibrilation and sends a
+	 * lot of clicks. Debouncing is now enabled, ventricular
+	 * fibrillation should cause one button down for the first press and
+	 * one release for the last release.
+	 */
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	/* Not all devices can disable middle button emulation, time out on
+	 * middle button here to make sure the initial button press event
+	 * was flushed.
+	 */
+	libinput_dispatch(li);
+	litest_timeout_middlebutton();
+	libinput_dispatch(li);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	litest_assert_empty_queue(li);
+	litest_timeout_debounce();
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(debounce_no_debounce_for_otherbutton)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+
+	litest_disable_middleemu(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_RIGHT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_RIGHT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(debounce_cancel_debounce_otherbutton)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+
+	litest_disable_middleemu(dev);
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	/* release is now held back, press was ignored,
+	 * other button should flush the release */
+	litest_event(dev, EV_KEY, BTN_RIGHT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_RIGHT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(debounce_switch_to_otherbutton)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_LEFT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	/* release is now held back, press was ignored,
+	 * other button should flush the release */
+	litest_event(dev, EV_KEY, BTN_RIGHT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_RIGHT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	/* bouncing right button triggers debounce */
+	litest_event(dev, EV_KEY, BTN_RIGHT, 1);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+	litest_event(dev, EV_KEY, BTN_RIGHT, 0);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	libinput_dispatch(li);
+
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_LEFT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_PRESSED);
+	litest_assert_button_event(li,
+				   BTN_RIGHT,
+				   LIBINPUT_BUTTON_STATE_RELEASED);
+
+	litest_assert_empty_queue(li);
+}
+END_TEST
+
 void
 litest_setup_tests_pointer(void)
 {
@@ -2138,4 +2438,11 @@ litest_setup_tests_pointer(void)
 	litest_add_ranged("pointer:state", pointer_absolute_initial_state, LITEST_ABSOLUTE, LITEST_ANY, &axis_range);
 
 	litest_add("pointer:time", pointer_time_usec, LITEST_RELATIVE, LITEST_ANY);
+
+	litest_add("pointer:debounce", debounce, LITEST_BUTTON, LITEST_TOUCHPAD);
+	litest_add("pointer:debounce", debounce_timer, LITEST_BUTTON, LITEST_TOUCHPAD);
+	litest_add("pointer:debounce", debounce_multibounce, LITEST_BUTTON, LITEST_TOUCHPAD);
+	litest_add("pointer:debounce_otherbutton", debounce_no_debounce_for_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD);
+	litest_add("pointer:debounce_otherbutton", debounce_cancel_debounce_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD);
+	litest_add("pointer:debounce_otherbutton", debounce_switch_to_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD);
 }
-- 
2.13.0



More information about the wayland-devel mailing list