Commit de994d13 authored by Peter Hutterer's avatar Peter Hutterer

evdev: add new debouncing code

The current debouncing code monitors events and switches on when events are
too close together. From then on, any event can be delayed.

Vicente Bergas provided an algorithm that avoids most of these delays:
on a button state change we now forward the change without delay but start a
timer. If the button changes state during that timer, the changes are
ignored. On timer expiry, events are sent to match the hardware state
with the client's view of the device. This is only done if needed.

Thus, a press-release sequence of: PRP sends a single press event, a sequence of
PRPR sends press and then the release at the end of the timeout. The timeout
is short enough that the delay should not be noticeable.

This new mode is called the 'bounce' mode. The old mode is now referred to as
'spurious' mode and only covers the case of a button held down that loses
contact. It works as before, monitoring a button for these spurious contact
losses and switching on. When on, button release events are delayed as before.

The whole button debouncing moves to a state machine which makes debugging a
lot easier. See the accompanying SVG for the diagram.
Signed-off-by: Peter Hutterer's avatarPeter Hutterer <peter.hutterer@who-t.net>
parent db3b6fe5
This diff is collapsed.
# Source for the button debouncing wave diagram
# Paste into http://wavedrom.com/editor.html
{signal: [
{name:'current mode', wave: '3............', data: ['normal button press and release']},
{name:'physical button', wave: '01......0....'},
{name:'application ', wave: '01......0....'},
{},
['bounce mode',
{name:'current mode', wave: '4............', data: ['debounced button press']},
{name:'physical button', wave: '0101...0.....'},
{name: 'timeouts', wave: '01...0.1...0.'},
{name:'application ', wave: '01.....0.....'},
{},
{name:'current mode', wave: '4............', data: ['debounced button release']},
{name:'physical button', wave: '1...010......'},
{name: 'timeouts', wave: '0...1...0....'},
{name:'application ', wave: '1...0........'},
{},
{name:'current mode', wave: '5............', data: ['delayed button press']},
{name:'physical button', wave: '1...01.......'},
{name: 'timeouts', wave: '0...1...0....'},
{name:'application ', wave: '1...0...1....'},
{},
{name:'current mode', wave: '5............', data: ['delayed button release']},
{name:'physical button', wave: '0...10.......'},
{name: 'timeouts', wave: '0...1...0....'},
{name:'application ', wave: '0...1...0....'},
],
{},
['spurious mode',
{name:'current mode', wave: '3............', data: ['first spurious button release ']},
{name:'physical button', wave: '1.......01...'},
{name:'application ', wave: '1.......01...'},
{},
{name:'current mode', wave: '3............', data: ['later spurious button release ']},
{name:'physical button', wave: '1....01......'},
{name: 'timeouts', wave: '0....1..0....'},
{name:'application ', wave: '1............'},
{},
{name:'current mode', wave: '3............', data: ['delayed release in spurious mode ']},
{name:'physical button', wave: '1....0.......'},
{name: 'timeouts', wave: '0....1..0....'},
{name:'application ', wave: '1.......0....'}
],
],
head:{
text:'Button Debouncing Scenarios',
},
}
......@@ -9,12 +9,25 @@ 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.
libinput provides two methods of debouncing buttons, referred to as the
"bounce" and "spurious" methods:
- In the "bounce" method, libinput monitors hardware bouncing on button
state changes, i.e. when a user clicks or releases a button. For example,
if a user presses a button but the hardware generates a
press-release-press sequence in quick succession, libinput ignores the
release and second press event. This method is always enabled.
- in the "spurious" method, libinput detects spurious releases of a button
while the button is physically held down by the user. These releases are
immediately followed by a press event. libinput monitors for these events
and ignores the release and press event. This method is disabled by
default and enables once libinput detects the first faulty event sequence.
The "bounce" method guarantees that all press events are delivered
immediately and most release events are delivered immediately. The
"spurious" method requires that release events are delayed, libinput thus
does not enable this method unless a faulty event sequence is detected. A
message is printed to the log when spurious deboucing was detected.
Note that libinput's debouncing intended to correct hardware damage or
substandard hardware. Debouncing is also used as an accessibility feature
......@@ -23,4 +36,12 @@ 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.
Below is an illustration of the button debouncing modes to show the relation
of the physical button state and the application state. Where applicable, an
extra line is added to show the timeouts used by libinput that
affect the button state handling. The waveform's high and low states
correspond to the buttons 'pressed' and 'released' states, respectively.
@image html button-debouncing-wave-diagram.svg "Diagram illustrating button debouncing"
*/
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -156,6 +156,7 @@ src_libinput = [
'src/libinput-private.h',
'src/evdev.c',
'src/evdev.h',
'src/evdev-debounce.c',
'src/evdev-fallback.c',
'src/evdev-fallback.h',
'src/evdev-middle-button.c',
......@@ -299,6 +300,7 @@ if get_option('documentation')
meson.source_root() + '/doc/dot/libinput-stack-gnome.gv',
meson.source_root() + '/doc/dot/evemu.gv',
# svgs
meson.source_root() + '/doc/svg/button-debouncing-wave-diagram.svg',
meson.source_root() + '/doc/svg/button-scrolling.svg',
meson.source_root() + '/doc/svg/clickfinger.svg',
meson.source_root() + '/doc/svg/clickfinger-distance.svg',
......
This diff is collapsed.
......@@ -32,49 +32,6 @@
#define DEBOUNCE_TIME ms2us(12)
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_key_has_changed(struct fallback_dispatch *dispatch, int code)
{
return long_bit_is_set(dispatch->hw_key_mask, code) !=
long_bit_is_set(dispatch->last_hw_key_mask, code);
}
static void
hw_key_update_last_state(struct fallback_dispatch *dispatch)
{
static_assert(sizeof(dispatch->hw_key_mask) ==
sizeof(dispatch->last_hw_key_mask),
"Mismatching key mask size");
memcpy(dispatch->last_hw_key_mask,
dispatch->hw_key_mask,
sizeof(dispatch->hw_key_mask));
}
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,
......@@ -495,41 +452,6 @@ fallback_flush_st_up(struct fallback_dispatch *dispatch,
return true;
}
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,
......@@ -540,149 +462,6 @@ fallback_process_touch_button(struct fallback_dispatch *dispatch,
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);
hw_key_update_last_state(dispatch);
}
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,
......@@ -712,20 +491,11 @@ fallback_process_key(struct fallback_dispatch *dispatch,
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;
dispatch->pending_event |= EVDEV_KEY;
break;
case KEY_TYPE_BUTTON:
/* FIXME: should move to handle_state */
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;
dispatch->pending_event |= EVDEV_KEY;
break;
}
......@@ -1055,6 +825,7 @@ fallback_handle_state(struct fallback_dispatch *dispatch,
/* Buttons and keys */
if (dispatch->pending_event & EVDEV_KEY) {
bool want_debounce = false;
for (unsigned int code = 0; code <= KEY_MAX; code++) {
bool new_state;
......@@ -1077,17 +848,14 @@ fallback_handle_state(struct fallback_dispatch *dispatch,
LIBINPUT_KEY_STATE_RELEASED);
break;
case KEY_TYPE_BUTTON:
evdev_pointer_notify_physical_button(
device,
time,
evdev_to_left_handed(device, code),
new_state ?
LIBINPUT_BUTTON_STATE_PRESSED :
LIBINPUT_BUTTON_STATE_RELEASED);
want_debounce = true;
break;
}
}
if (want_debounce)
fallback_debounce_handle_state(dispatch, time);
hw_key_update_last_state(dispatch);
}
......@@ -1299,6 +1067,8 @@ fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch)
libinput_timer_cancel(&dispatch->debounce.timer);
libinput_timer_destroy(&dispatch->debounce.timer);
libinput_timer_cancel(&dispatch->debounce.timer_short);
libinput_timer_destroy(&dispatch->debounce.timer_short);
free(dispatch->mt.slots);
free(dispatch);
}
......@@ -1672,7 +1442,6 @@ 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);
......@@ -1722,14 +1491,7 @@ 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);
fallback_init_debounce(dispatch);
return &dispatch->base;
}
......@@ -31,6 +31,18 @@
#include "evdev.h"
enum debounce_state {
DEBOUNCE_STATE_IS_UP = 100,
DEBOUNCE_STATE_IS_DOWN,
DEBOUNCE_STATE_DOWN_WAITING,
DEBOUNCE_STATE_RELEASE_PENDING,
DEBOUNCE_STATE_RELEASE_DELAYED,
DEBOUNCE_STATE_RELEASE_WAITING,
DEBOUNCE_STATE_MAYBE_SPURIOUS,
DEBOUNCE_STATE_RELEASED,
DEBOUNCE_STATE_PRESS_PENDING,
};
struct fallback_dispatch {
struct evdev_dispatch base;
struct evdev_device *device;
......@@ -85,10 +97,16 @@ struct fallback_dispatch {
bool ignore_events;
struct {
#if 0
enum evdev_debounce_state state;
unsigned int button_code;
uint64_t button_up_time;
#endif
unsigned int button_code;
uint64_t button_time;
struct libinput_timer timer;
struct libinput_timer timer_short;
enum debounce_state state;
bool spurious_enabled;
} debounce;
struct {
......@@ -119,4 +137,86 @@ fallback_dispatch(struct evdev_dispatch *dispatch)
return container_of(dispatch, struct fallback_dispatch, base);
}
enum key_type {
KEY_TYPE_NONE,
KEY_TYPE_KEY,
KEY_TYPE_BUTTON,
};
static inline 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 inline void
hw_set_key_down(struct fallback_dispatch *dispatch, int code, int pressed)
{
long_set_bit_state(dispatch->hw_key_mask, code, pressed);
}
static inline bool
hw_key_has_changed(struct fallback_dispatch *dispatch, int code)
{
return long_bit_is_set(dispatch->hw_key_mask, code) !=
long_bit_is_set(dispatch->last_hw_key_mask, code);
}
static inline void
hw_key_update_last_state(struct fallback_dispatch *dispatch)
{
static_assert(sizeof(dispatch->hw_key_mask) ==
sizeof(dispatch->last_hw_key_mask),
"Mismatching key mask size");
memcpy(dispatch->last_hw_key_mask,
dispatch->hw_key_mask,
sizeof(dispatch->hw_key_mask));
}
static inline bool
hw_is_key_down(struct fallback_dispatch *dispatch, int code)
{
return long_bit_is_set(dispatch->hw_key_mask, code);
}
static inline int
get_key_down_count(struct evdev_device *device, int code)
{
return device->key_count[code];
}
void fallback_init_debounce(struct fallback_dispatch *dispatch);
void fallback_debounce_handle_state(struct fallback_dispatch *dispatch,
uint64_t time);
#endif
......@@ -3182,7 +3182,7 @@ litest_timeout_tapndrag(void)
void
litest_timeout_debounce(void)
{
msleep(15);
msleep(30);
}
void
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment