mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-20 15:00:05 +01:00
evdev: replace the debounce handler with a plugin
Same functionality but run as a plugin on the evdev stream instead. Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1230>
This commit is contained in:
parent
7d8fac2868
commit
2c6fa261a3
6 changed files with 867 additions and 613 deletions
|
|
@ -375,13 +375,13 @@ install_headers('src/libinput.h')
|
||||||
src_libinput = src_libfilter + [
|
src_libinput = src_libfilter + [
|
||||||
'src/libinput.c',
|
'src/libinput.c',
|
||||||
'src/libinput-plugin.c',
|
'src/libinput-plugin.c',
|
||||||
|
'src/libinput-plugin-button-debounce.c',
|
||||||
'src/libinput-plugin-tablet-double-tool.c',
|
'src/libinput-plugin-tablet-double-tool.c',
|
||||||
'src/libinput-plugin-tablet-eraser-button.c',
|
'src/libinput-plugin-tablet-eraser-button.c',
|
||||||
'src/libinput-plugin-tablet-forced-tool.c',
|
'src/libinput-plugin-tablet-forced-tool.c',
|
||||||
'src/libinput-plugin-tablet-proximity-timer.c',
|
'src/libinput-plugin-tablet-proximity-timer.c',
|
||||||
'src/libinput-private-config.c',
|
'src/libinput-private-config.c',
|
||||||
'src/evdev.c',
|
'src/evdev.c',
|
||||||
'src/evdev-debounce.c',
|
|
||||||
'src/evdev-fallback.c',
|
'src/evdev-fallback.c',
|
||||||
'src/evdev-plugin.c',
|
'src/evdev-plugin.c',
|
||||||
'src/evdev-totem.c',
|
'src/evdev-totem.c',
|
||||||
|
|
|
||||||
|
|
@ -1,605 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2017 Red Hat, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice (including the next
|
|
||||||
* paragraph) shall be included in all copies or substantial portions of the
|
|
||||||
* Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include "evdev-fallback.h"
|
|
||||||
|
|
||||||
/* Debounce cases to handle
|
|
||||||
P ... button press
|
|
||||||
R ... button release
|
|
||||||
---| timeout duration
|
|
||||||
|
|
||||||
'normal' .... event sent when it happens
|
|
||||||
'filtered' .. event is not sent (but may be sent later)
|
|
||||||
'delayed' ... event is sent with wall-clock delay
|
|
||||||
|
|
||||||
1) P---| R P normal, R normal
|
|
||||||
2) R---| P R normal, P normal
|
|
||||||
3) P---R--| P P normal, R filtered, delayed, P normal
|
|
||||||
4) R---P--| R R normal, P filtered, delayed, R normal
|
|
||||||
4.1) P---| R--P--| P normal, R filtered
|
|
||||||
5) P--R-P-| R P normal, R filtered, P filtered, R normal
|
|
||||||
6) R--P-R-| P R normal, P filtered, R filtered, P normal
|
|
||||||
7) P--R--|
|
|
||||||
---P-| P normal, R filtered, P filtered
|
|
||||||
8) R--P--|
|
|
||||||
---R-| R normal, P filtered, R filtered
|
|
||||||
|
|
||||||
1, 2 are the normal click cases without debouncing taking effect
|
|
||||||
3, 4 are fast clicks where the second event is delivered with a delay
|
|
||||||
5, 6 are contact bounces, fast
|
|
||||||
7, 8 are contact bounces, slow
|
|
||||||
|
|
||||||
4.1 is a special case with the same event sequence as 4 but we want to
|
|
||||||
filter the *release* event out, it's a button losing contact while being
|
|
||||||
held down.
|
|
||||||
|
|
||||||
7 and 8 are cases where the first event happens within the first timeout
|
|
||||||
but the second event is outside that timeout (but within the timeout of
|
|
||||||
the second event). These cases are handled by restarting the timer on every
|
|
||||||
event that could be part of a bouncing sequence, which makes these cases
|
|
||||||
indistinguishable from 5 and 6.
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum debounce_event {
|
|
||||||
DEBOUNCE_EVENT_PRESS = 50,
|
|
||||||
DEBOUNCE_EVENT_RELEASE,
|
|
||||||
DEBOUNCE_EVENT_TIMEOUT,
|
|
||||||
DEBOUNCE_EVENT_TIMEOUT_SHORT,
|
|
||||||
DEBOUNCE_EVENT_OTHERBUTTON,
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline const char *
|
|
||||||
debounce_state_to_str(enum debounce_state state)
|
|
||||||
{
|
|
||||||
switch(state) {
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN_WAITING);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_DELAYING);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_WAITING);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN_DELAYING);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_STATE_DISABLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline const char*
|
|
||||||
debounce_event_to_str(enum debounce_event event)
|
|
||||||
{
|
|
||||||
switch(event) {
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_EVENT_PRESS);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_EVENT_RELEASE);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_EVENT_TIMEOUT);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_EVENT_TIMEOUT_SHORT);
|
|
||||||
CASE_RETURN_STRING(DEBOUNCE_EVENT_OTHERBUTTON);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
log_debounce_bug(struct fallback_dispatch *fallback, enum debounce_event event)
|
|
||||||
{
|
|
||||||
evdev_log_bug_libinput(fallback->device,
|
|
||||||
"invalid debounce event %s in state %s\n",
|
|
||||||
debounce_event_to_str(event),
|
|
||||||
debounce_state_to_str(fallback->debounce.state));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
debounce_set_state(struct fallback_dispatch *fallback,
|
|
||||||
enum debounce_state new_state)
|
|
||||||
{
|
|
||||||
assert(new_state >= DEBOUNCE_STATE_IS_UP &&
|
|
||||||
new_state <= DEBOUNCE_STATE_IS_DOWN_DELAYING);
|
|
||||||
|
|
||||||
fallback->debounce.state = new_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
debounce_set_timer(struct fallback_dispatch *fallback,
|
|
||||||
uint64_t time)
|
|
||||||
{
|
|
||||||
const int DEBOUNCE_TIMEOUT_BOUNCE = ms2us(25);
|
|
||||||
|
|
||||||
libinput_timer_set(&fallback->debounce.timer,
|
|
||||||
time + DEBOUNCE_TIMEOUT_BOUNCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
debounce_set_timer_short(struct fallback_dispatch *fallback,
|
|
||||||
uint64_t time)
|
|
||||||
{
|
|
||||||
const int DEBOUNCE_TIMEOUT_SPURIOUS = ms2us(12);
|
|
||||||
|
|
||||||
libinput_timer_set(&fallback->debounce.timer_short,
|
|
||||||
time + DEBOUNCE_TIMEOUT_SPURIOUS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
debounce_cancel_timer(struct fallback_dispatch *fallback)
|
|
||||||
{
|
|
||||||
libinput_timer_cancel(&fallback->debounce.timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
debounce_cancel_timer_short(struct fallback_dispatch *fallback)
|
|
||||||
{
|
|
||||||
libinput_timer_cancel(&fallback->debounce.timer_short);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
debounce_enable_spurious(struct fallback_dispatch *fallback)
|
|
||||||
{
|
|
||||||
if (fallback->debounce.spurious_enabled)
|
|
||||||
evdev_log_bug_libinput(fallback->device,
|
|
||||||
"tried to enable spurious debouncing twice\n");
|
|
||||||
|
|
||||||
fallback->debounce.spurious_enabled = true;
|
|
||||||
evdev_log_info(fallback->device,
|
|
||||||
"Enabling spurious button debouncing, "
|
|
||||||
"see %s/button-debouncing.html for details\n",
|
|
||||||
HTTP_DOC_LINK);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_notify_button(struct fallback_dispatch *fallback,
|
|
||||||
enum libinput_button_state state)
|
|
||||||
{
|
|
||||||
struct evdev_device *device = fallback->device;
|
|
||||||
evdev_usage_t usage = fallback->debounce.button_usage;
|
|
||||||
uint64_t time = fallback->debounce.button_time;
|
|
||||||
|
|
||||||
usage = evdev_to_left_handed(device, usage);
|
|
||||||
|
|
||||||
fallback_notify_physical_button(fallback, device, time, usage, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_up_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN_WAITING);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_PRESSED);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_down_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_timer_short(fallback, time);
|
|
||||||
if (fallback->debounce.spurious_enabled) {
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS);
|
|
||||||
} else {
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_down_waiting_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_DELAYING);
|
|
||||||
/* Note: In the debouncing RPR case, we use the last
|
|
||||||
* release's time stamp */
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_up_delaying_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN_WAITING);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_up_delaying_spurious_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
debounce_cancel_timer(fallback);
|
|
||||||
debounce_cancel_timer_short(fallback);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_WAITING);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_up_detecting_spurious_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_timer_short(fallback, time);
|
|
||||||
/* Note: in a bouncing PRP case, we use the last press
|
|
||||||
* event time */
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_WAITING);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_down_detecting_spurious_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_timer_short(fallback, time);
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
debounce_cancel_timer(fallback);
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
debounce_enable_spurious(fallback);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_PRESSED);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_PRESSED);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_up_waiting_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
/* Note: in a debouncing PRP case, we use the last press'
|
|
||||||
* time */
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN_DELAYING);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_is_down_delaying_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
debounce_set_timer(fallback, time);
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP_WAITING);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
debounce_set_state(fallback, DEBOUNCE_STATE_IS_DOWN);
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_PRESSED);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_disabled_handle_event(struct fallback_dispatch *fallback,
|
|
||||||
enum debounce_event event,
|
|
||||||
uint64_t time)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case DEBOUNCE_EVENT_PRESS:
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_PRESSED);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_RELEASE:
|
|
||||||
fallback->debounce.button_time = time;
|
|
||||||
debounce_notify_button(fallback,
|
|
||||||
LIBINPUT_BUTTON_STATE_RELEASED);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
|
||||||
case DEBOUNCE_EVENT_TIMEOUT:
|
|
||||||
log_debounce_bug(fallback, event);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_EVENT_OTHERBUTTON:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_handle_event(struct fallback_dispatch *fallback,
|
|
||||||
enum debounce_event event,
|
|
||||||
uint64_t time)
|
|
||||||
{
|
|
||||||
enum debounce_state current = fallback->debounce.state;
|
|
||||||
|
|
||||||
if (event == DEBOUNCE_EVENT_OTHERBUTTON) {
|
|
||||||
debounce_cancel_timer(fallback);
|
|
||||||
debounce_cancel_timer_short(fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(current) {
|
|
||||||
case DEBOUNCE_STATE_IS_UP:
|
|
||||||
debounce_is_up_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_DOWN:
|
|
||||||
debounce_is_down_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_DOWN_WAITING:
|
|
||||||
debounce_is_down_waiting_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_UP_DELAYING:
|
|
||||||
debounce_is_up_delaying_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS:
|
|
||||||
debounce_is_up_delaying_spurious_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS:
|
|
||||||
debounce_is_up_detecting_spurious_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS:
|
|
||||||
debounce_is_down_detecting_spurious_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_UP_WAITING:
|
|
||||||
debounce_is_up_waiting_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_IS_DOWN_DELAYING:
|
|
||||||
debounce_is_down_delaying_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
case DEBOUNCE_STATE_DISABLED:
|
|
||||||
debounce_disabled_handle_event(fallback, event, time);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
evdev_log_debug(fallback->device,
|
|
||||||
"debounce state: %s → %s → %s\n",
|
|
||||||
debounce_state_to_str(current),
|
|
||||||
debounce_event_to_str(event),
|
|
||||||
debounce_state_to_str(fallback->debounce.state));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
fallback_debounce_handle_state(struct fallback_dispatch *dispatch,
|
|
||||||
uint64_t time)
|
|
||||||
{
|
|
||||||
evdev_usage_t changed[16] = {0}; /* usage of changed buttons */
|
|
||||||
size_t nchanged = 0;
|
|
||||||
bool flushed = false;
|
|
||||||
|
|
||||||
for (evdev_usage_t usage = evdev_usage_from(EVDEV_KEY_RESERVED);
|
|
||||||
evdev_usage_le(usage, EVDEV_KEY_MAX);
|
|
||||||
usage = evdev_usage_next(usage)) {
|
|
||||||
if (!evdev_usage_is_button(usage))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (hw_key_has_changed(dispatch, usage))
|
|
||||||
changed[nchanged++] = usage;
|
|
||||||
|
|
||||||
/* If you manage to press more than 16 buttons in the same
|
|
||||||
* frame, we just quietly ignore the rest of them */
|
|
||||||
if (nchanged == ARRAY_LENGTH(changed))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we have more than one button this frame or a different button,
|
|
||||||
* flush the state machine with otherbutton */
|
|
||||||
if (nchanged > 1 ||
|
|
||||||
evdev_usage_cmp(changed[0], dispatch->debounce.button_usage) != 0) {
|
|
||||||
debounce_handle_event(dispatch,
|
|
||||||
DEBOUNCE_EVENT_OTHERBUTTON,
|
|
||||||
time);
|
|
||||||
flushed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The state machine has some pre-conditions:
|
|
||||||
* - the IS_DOWN and IS_UP states are neutral entry states without
|
|
||||||
* any timeouts
|
|
||||||
* - a OTHERBUTTON event always flushes the state to IS_DOWN or
|
|
||||||
* IS_UP
|
|
||||||
*/
|
|
||||||
|
|
||||||
for (size_t i = 0; i < nchanged; i++) {
|
|
||||||
bool is_down = hw_is_key_down(dispatch, changed[i]);
|
|
||||||
|
|
||||||
if (flushed &&
|
|
||||||
dispatch->debounce.state != DEBOUNCE_STATE_DISABLED) {
|
|
||||||
debounce_set_state(dispatch,
|
|
||||||
!is_down ?
|
|
||||||
DEBOUNCE_STATE_IS_DOWN :
|
|
||||||
DEBOUNCE_STATE_IS_UP);
|
|
||||||
flushed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch->debounce.button_usage = changed[i];
|
|
||||||
debounce_handle_event(dispatch,
|
|
||||||
is_down ?
|
|
||||||
DEBOUNCE_EVENT_PRESS :
|
|
||||||
DEBOUNCE_EVENT_RELEASE,
|
|
||||||
time);
|
|
||||||
|
|
||||||
/* if we have more than one event, we flush the state
|
|
||||||
* machine immediately after the event itself */
|
|
||||||
if (nchanged > 1) {
|
|
||||||
debounce_handle_event(dispatch,
|
|
||||||
DEBOUNCE_EVENT_OTHERBUTTON,
|
|
||||||
time);
|
|
||||||
flushed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_timeout(uint64_t now, void *data)
|
|
||||||
{
|
|
||||||
struct evdev_device *device = data;
|
|
||||||
struct fallback_dispatch *dispatch =
|
|
||||||
fallback_dispatch(device->dispatch);
|
|
||||||
|
|
||||||
debounce_handle_event(dispatch, DEBOUNCE_EVENT_TIMEOUT, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
debounce_timeout_short(uint64_t now, void *data)
|
|
||||||
{
|
|
||||||
struct evdev_device *device = data;
|
|
||||||
struct fallback_dispatch *dispatch =
|
|
||||||
fallback_dispatch(device->dispatch);
|
|
||||||
|
|
||||||
debounce_handle_event(dispatch, DEBOUNCE_EVENT_TIMEOUT_SHORT, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
fallback_init_debounce(struct fallback_dispatch *dispatch)
|
|
||||||
{
|
|
||||||
struct evdev_device *device = dispatch->device;
|
|
||||||
char timer_name[64];
|
|
||||||
|
|
||||||
if (evdev_device_has_model_quirk(device, QUIRK_MODEL_BOUNCING_KEYS)) {
|
|
||||||
dispatch->debounce.state = DEBOUNCE_STATE_DISABLED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch->debounce.state = DEBOUNCE_STATE_IS_UP;
|
|
||||||
|
|
||||||
snprintf(timer_name,
|
|
||||||
sizeof(timer_name),
|
|
||||||
"%s debounce short",
|
|
||||||
evdev_device_get_sysname(device));
|
|
||||||
libinput_timer_init(&dispatch->debounce.timer_short,
|
|
||||||
evdev_libinput_context(device),
|
|
||||||
timer_name,
|
|
||||||
debounce_timeout_short,
|
|
||||||
device);
|
|
||||||
|
|
||||||
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,
|
|
||||||
debounce_timeout,
|
|
||||||
device);
|
|
||||||
}
|
|
||||||
|
|
@ -945,7 +945,6 @@ fallback_handle_state(struct fallback_dispatch *dispatch,
|
||||||
|
|
||||||
/* Buttons and keys */
|
/* Buttons and keys */
|
||||||
if (dispatch->pending_event & EVDEV_KEY) {
|
if (dispatch->pending_event & EVDEV_KEY) {
|
||||||
bool want_debounce = false;
|
|
||||||
for (evdev_usage_t usage = evdev_usage_from(EVDEV_KEY_RESERVED);
|
for (evdev_usage_t usage = evdev_usage_from(EVDEV_KEY_RESERVED);
|
||||||
evdev_usage_le(usage, EVDEV_KEY_MAX);
|
evdev_usage_le(usage, EVDEV_KEY_MAX);
|
||||||
usage = evdev_usage_next(usage)) {
|
usage = evdev_usage_next(usage)) {
|
||||||
|
|
@ -953,14 +952,20 @@ fallback_handle_state(struct fallback_dispatch *dispatch,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (evdev_usage_is_button(usage)) {
|
if (evdev_usage_is_button(usage)) {
|
||||||
want_debounce = true;
|
enum libinput_button_state state =
|
||||||
break;
|
hw_is_key_down(dispatch, usage) ?
|
||||||
|
LIBINPUT_BUTTON_STATE_PRESSED :
|
||||||
|
LIBINPUT_BUTTON_STATE_RELEASED;
|
||||||
|
evdev_usage_t button = evdev_to_left_handed(device, usage);
|
||||||
|
fallback_notify_physical_button(dispatch,
|
||||||
|
device,
|
||||||
|
time,
|
||||||
|
button,
|
||||||
|
state);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (want_debounce)
|
|
||||||
fallback_debounce_handle_state(dispatch, time);
|
|
||||||
|
|
||||||
hw_key_update_last_state(dispatch);
|
hw_key_update_last_state(dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1714,7 +1719,6 @@ fallback_dispatch_create(struct libinput_device *libinput_device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fallback_init_wheel(dispatch, device);
|
fallback_init_wheel(dispatch, device);
|
||||||
fallback_init_debounce(dispatch);
|
|
||||||
fallback_init_arbitration(dispatch, device);
|
fallback_init_arbitration(dispatch, device);
|
||||||
|
|
||||||
return &dispatch->base;
|
return &dispatch->base;
|
||||||
|
|
|
||||||
823
src/libinput-plugin-button-debounce.c
Normal file
823
src/libinput-plugin-button-debounce.c
Normal file
|
|
@ -0,0 +1,823 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2017-2025 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice (including the next
|
||||||
|
* paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
* Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <libevdev/libevdev.h>
|
||||||
|
|
||||||
|
#include "util-mem.h"
|
||||||
|
#include "util-strings.h"
|
||||||
|
|
||||||
|
#include "evdev-frame.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "quirks.h"
|
||||||
|
|
||||||
|
#include "libinput-log.h"
|
||||||
|
#include "libinput-util.h"
|
||||||
|
#include "libinput-plugin.h"
|
||||||
|
#include "libinput-plugin-button-debounce.h"
|
||||||
|
|
||||||
|
/* Debounce cases to handle
|
||||||
|
P ... button press
|
||||||
|
R ... button release
|
||||||
|
---| timeout duration
|
||||||
|
|
||||||
|
'normal' .... event sent when it happens
|
||||||
|
'filtered' .. event is not sent (but may be sent later)
|
||||||
|
'delayed' ... event is sent with wall-clock delay
|
||||||
|
|
||||||
|
1) P---| R P normal, R normal
|
||||||
|
2) R---| P R normal, P normal
|
||||||
|
3) P---R--| P P normal, R filtered, delayed, P normal
|
||||||
|
4) R---P--| R R normal, P filtered, delayed, R normal
|
||||||
|
4.1) P---| R--P--| P normal, R filtered
|
||||||
|
5) P--R-P-| R P normal, R filtered, P filtered, R normal
|
||||||
|
6) R--P-R-| P R normal, P filtered, R filtered, P normal
|
||||||
|
7) P--R--|
|
||||||
|
---P-| P normal, R filtered, P filtered
|
||||||
|
8) R--P--|
|
||||||
|
---R-| R normal, P filtered, R filtered
|
||||||
|
|
||||||
|
1, 2 are the normal click cases without debouncing taking effect
|
||||||
|
3, 4 are fast clicks where the second event is delivered with a delay
|
||||||
|
5, 6 are contact bounces, fast
|
||||||
|
7, 8 are contact bounces, slow
|
||||||
|
|
||||||
|
4.1 is a special case with the same event sequence as 4 but we want to
|
||||||
|
filter the *release* event out, it's a button losing contact while being
|
||||||
|
held down.
|
||||||
|
|
||||||
|
7 and 8 are cases where the first event happens within the first timeout
|
||||||
|
but the second event is outside that timeout (but within the timeout of
|
||||||
|
the second event). These cases are handled by restarting the timer on every
|
||||||
|
event that could be part of a bouncing sequence, which makes these cases
|
||||||
|
indistinguishable from 5 and 6.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum debounce_event {
|
||||||
|
DEBOUNCE_EVENT_PRESS = 50,
|
||||||
|
DEBOUNCE_EVENT_RELEASE,
|
||||||
|
DEBOUNCE_EVENT_TIMEOUT,
|
||||||
|
DEBOUNCE_EVENT_TIMEOUT_SHORT,
|
||||||
|
DEBOUNCE_EVENT_OTHERBUTTON,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum debounce_state {
|
||||||
|
DEBOUNCE_STATE_IS_UP = 100,
|
||||||
|
DEBOUNCE_STATE_IS_DOWN,
|
||||||
|
DEBOUNCE_STATE_IS_DOWN_WAITING,
|
||||||
|
DEBOUNCE_STATE_IS_UP_DELAYING,
|
||||||
|
DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS,
|
||||||
|
DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS,
|
||||||
|
DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS,
|
||||||
|
DEBOUNCE_STATE_IS_UP_WAITING,
|
||||||
|
DEBOUNCE_STATE_IS_DOWN_DELAYING,
|
||||||
|
|
||||||
|
DEBOUNCE_STATE_DISABLED = 999,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline const char *
|
||||||
|
debounce_state_to_str(enum debounce_state state)
|
||||||
|
{
|
||||||
|
switch(state) {
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN_WAITING);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_DELAYING);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_UP_WAITING);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_IS_DOWN_DELAYING);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_STATE_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const char*
|
||||||
|
debounce_event_to_str(enum debounce_event event)
|
||||||
|
{
|
||||||
|
switch(event) {
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_EVENT_PRESS);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_EVENT_RELEASE);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_EVENT_TIMEOUT);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_EVENT_TIMEOUT_SHORT);
|
||||||
|
CASE_RETURN_STRING(DEBOUNCE_EVENT_OTHERBUTTON);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct plugin_device {
|
||||||
|
struct list link;
|
||||||
|
struct libinput_device *device;
|
||||||
|
struct plugin_data *parent;
|
||||||
|
|
||||||
|
evdev_usage_t button_usage;
|
||||||
|
uint64_t button_time;
|
||||||
|
enum debounce_state state;
|
||||||
|
bool spurious_enabled;
|
||||||
|
|
||||||
|
struct libinput_plugin_timer *timer;
|
||||||
|
struct libinput_plugin_timer *timer_short;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
plugin_device_destroy(void *d)
|
||||||
|
{
|
||||||
|
struct plugin_device *device = d;
|
||||||
|
|
||||||
|
list_remove(&device->link);
|
||||||
|
libinput_plugin_timer_cancel(device->timer);
|
||||||
|
libinput_plugin_timer_unref(device->timer);
|
||||||
|
libinput_plugin_timer_cancel(device->timer_short);
|
||||||
|
libinput_plugin_timer_unref(device->timer_short);
|
||||||
|
libinput_device_unref(device->device);
|
||||||
|
|
||||||
|
free(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct plugin_data {
|
||||||
|
struct list devices;
|
||||||
|
struct libinput_plugin *plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
plugin_data_destroy(void *d)
|
||||||
|
{
|
||||||
|
struct plugin_data *data = d;
|
||||||
|
|
||||||
|
struct plugin_device *device;
|
||||||
|
list_for_each_safe(device, &data->devices, link) {
|
||||||
|
plugin_device_destroy(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_DESTROY_CLEANUP_FUNC(plugin_data);
|
||||||
|
static void
|
||||||
|
plugin_destroy(struct libinput_plugin *libinput_plugin)
|
||||||
|
{
|
||||||
|
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
|
||||||
|
plugin_data_destroy(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
log_debounce_bug(struct plugin_device *device, enum debounce_event event)
|
||||||
|
{
|
||||||
|
plugin_log_bug_libinput(device->parent->plugin,
|
||||||
|
"invalid debounce event %s in state %s\n",
|
||||||
|
debounce_event_to_str(event),
|
||||||
|
debounce_state_to_str(device->state));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
debounce_set_state(struct plugin_device *device,
|
||||||
|
enum debounce_state new_state)
|
||||||
|
{
|
||||||
|
assert(new_state >= DEBOUNCE_STATE_IS_UP &&
|
||||||
|
new_state <= DEBOUNCE_STATE_IS_DOWN_DELAYING);
|
||||||
|
|
||||||
|
device->state = new_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
debounce_set_timer(struct plugin_device *device,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
const int DEBOUNCE_TIMEOUT_BOUNCE = ms2us(25);
|
||||||
|
|
||||||
|
libinput_plugin_timer_set(device->timer,
|
||||||
|
time + DEBOUNCE_TIMEOUT_BOUNCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
debounce_set_timer_short(struct plugin_device *device,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
const int DEBOUNCE_TIMEOUT_SPURIOUS = ms2us(12);
|
||||||
|
|
||||||
|
libinput_plugin_timer_set(device->timer_short,
|
||||||
|
time + DEBOUNCE_TIMEOUT_SPURIOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
debounce_cancel_timer(struct plugin_device *device)
|
||||||
|
{
|
||||||
|
libinput_plugin_timer_cancel(device->timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
debounce_cancel_timer_short(struct plugin_device *device)
|
||||||
|
{
|
||||||
|
libinput_plugin_timer_cancel(device->timer_short);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
debounce_enable_spurious(struct plugin_device *device)
|
||||||
|
{
|
||||||
|
if (device->spurious_enabled)
|
||||||
|
plugin_log_bug(device->parent->plugin,
|
||||||
|
"tried to enable spurious debouncing twice\n");
|
||||||
|
|
||||||
|
device->spurious_enabled = true;
|
||||||
|
plugin_log_info(device->parent->plugin,
|
||||||
|
"%s: enabling spurious button debouncing, "
|
||||||
|
"see %s/button-debouncing.html for details\n",
|
||||||
|
libinput_device_get_name(device->device),
|
||||||
|
HTTP_DOC_LINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_notify_button(struct plugin_device *device,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
enum libinput_button_state state)
|
||||||
|
{
|
||||||
|
const struct evdev_event button = {
|
||||||
|
.usage = device->button_usage,
|
||||||
|
.value = state == LIBINPUT_BUTTON_STATE_PRESSED ? 1 : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
_unref_(evdev_frame) *button_frame = NULL;
|
||||||
|
if (frame == NULL) {
|
||||||
|
button_frame = evdev_frame_new(2);
|
||||||
|
frame = button_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
evdev_frame_append(frame, &button, 1);
|
||||||
|
evdev_frame_set_time(frame, device->button_time);
|
||||||
|
|
||||||
|
libinput_plugin_prepend_evdev_frame(device->parent->plugin,
|
||||||
|
device->device,
|
||||||
|
frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_up_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
device->button_time = time;
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN_WAITING);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_PRESSED);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_down_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
/* If we lost the kernel button release event (e.g. something
|
||||||
|
* grabbed the device for a short while) we quietly ignore
|
||||||
|
* the next down event */
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
device->button_time = time;
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_timer_short(device, time);
|
||||||
|
if (device->spurious_enabled) {
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS);
|
||||||
|
} else {
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_down_waiting_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_DELAYING);
|
||||||
|
/* Note: In the debouncing RPR case, we use the last
|
||||||
|
* release's time stamp */
|
||||||
|
device->button_time = time;
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_up_delaying_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN_WAITING);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_up_delaying_spurious_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
debounce_cancel_timer(device);
|
||||||
|
debounce_cancel_timer_short(device);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_WAITING);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_up_detecting_spurious_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_timer_short(device, time);
|
||||||
|
/* Note: in a bouncing PRP case, we use the last press
|
||||||
|
* event time */
|
||||||
|
device->button_time = time;
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_WAITING);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_down_detecting_spurious_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_timer_short(device, time);
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
debounce_cancel_timer(device);
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
debounce_enable_spurious(device);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_PRESSED);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_PRESSED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_up_waiting_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
/* Note: in a debouncing PRP case, we use the last press'
|
||||||
|
* time */
|
||||||
|
device->button_time = time;
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN_DELAYING);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_is_down_delaying_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
debounce_set_timer(device, time);
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_UP_WAITING);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
debounce_set_state(device, DEBOUNCE_STATE_IS_DOWN);
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_PRESSED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_disabled_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
switch (event) {
|
||||||
|
case DEBOUNCE_EVENT_PRESS:
|
||||||
|
device->button_time = time;
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_PRESSED);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_RELEASE:
|
||||||
|
device->button_time = time;
|
||||||
|
debounce_notify_button(device,
|
||||||
|
frame,
|
||||||
|
LIBINPUT_BUTTON_STATE_RELEASED);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT_SHORT:
|
||||||
|
case DEBOUNCE_EVENT_TIMEOUT:
|
||||||
|
log_debounce_bug(device, event);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_EVENT_OTHERBUTTON:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_handle_event(struct plugin_device *device,
|
||||||
|
enum debounce_event event,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
enum debounce_state current = device->state;
|
||||||
|
|
||||||
|
if (event == DEBOUNCE_EVENT_OTHERBUTTON) {
|
||||||
|
debounce_cancel_timer(device);
|
||||||
|
debounce_cancel_timer_short(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(current) {
|
||||||
|
case DEBOUNCE_STATE_IS_UP:
|
||||||
|
debounce_is_up_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_DOWN:
|
||||||
|
debounce_is_down_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_DOWN_WAITING:
|
||||||
|
debounce_is_down_waiting_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_UP_DELAYING:
|
||||||
|
debounce_is_up_delaying_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_UP_DELAYING_SPURIOUS:
|
||||||
|
debounce_is_up_delaying_spurious_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_UP_DETECTING_SPURIOUS:
|
||||||
|
debounce_is_up_detecting_spurious_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_DOWN_DETECTING_SPURIOUS:
|
||||||
|
debounce_is_down_detecting_spurious_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_UP_WAITING:
|
||||||
|
debounce_is_up_waiting_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_IS_DOWN_DELAYING:
|
||||||
|
debounce_is_down_delaying_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
case DEBOUNCE_STATE_DISABLED:
|
||||||
|
debounce_disabled_handle_event(device, event, frame, time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin_log_debug(device->parent->plugin,
|
||||||
|
"debounce state: %s → %s → %s\n",
|
||||||
|
debounce_state_to_str(current),
|
||||||
|
debounce_event_to_str(event),
|
||||||
|
debounce_state_to_str(device->state));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_plugin_handle_frame(struct plugin_device *device,
|
||||||
|
struct evdev_frame *frame,
|
||||||
|
uint64_t time)
|
||||||
|
{
|
||||||
|
size_t nchanged = 0;
|
||||||
|
bool flushed = false;
|
||||||
|
|
||||||
|
size_t nevents;
|
||||||
|
struct evdev_event *events = evdev_frame_get_events(frame, &nevents);
|
||||||
|
|
||||||
|
/* Strip out all button events from this frame (if any). Then
|
||||||
|
* append the button events to that stripped frame according
|
||||||
|
* to our state machine.
|
||||||
|
*
|
||||||
|
* We allow for a max of 16 buttons to be appended, if you press more
|
||||||
|
* than 16 buttons within the same frame good luck to you.
|
||||||
|
*/
|
||||||
|
_unref_(evdev_frame) *filtered_frame = evdev_frame_new(nevents + 16);
|
||||||
|
for (size_t i = 0; i < nevents; i++) {
|
||||||
|
struct evdev_event *e = &events[i];
|
||||||
|
if (!evdev_usage_is_button(e->usage)) {
|
||||||
|
evdev_frame_append(filtered_frame, e, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nchanged++;
|
||||||
|
|
||||||
|
/* If we have more than one button this frame or a different button,
|
||||||
|
* flush the state machine with otherbutton */
|
||||||
|
if (!flushed &&
|
||||||
|
(nchanged > 1 ||
|
||||||
|
evdev_usage_cmp(e->usage, device->button_usage) != 0)) {
|
||||||
|
debounce_handle_event(device,
|
||||||
|
DEBOUNCE_EVENT_OTHERBUTTON,
|
||||||
|
NULL,
|
||||||
|
time);
|
||||||
|
flushed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nchanged == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* The state machine has some pre-conditions:
|
||||||
|
* - the IS_DOWN and IS_UP states are neutral entry states without
|
||||||
|
* any timeouts
|
||||||
|
* - a OTHERBUTTON event always flushes the state to IS_DOWN or
|
||||||
|
* IS_UP
|
||||||
|
*/
|
||||||
|
for (size_t i = 0; i < nevents; i++) {
|
||||||
|
struct evdev_event *e = &events[i];
|
||||||
|
bool is_down = !!e->value;
|
||||||
|
|
||||||
|
if (!evdev_usage_is_button(e->usage))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (flushed &&
|
||||||
|
device->state != DEBOUNCE_STATE_DISABLED) {
|
||||||
|
debounce_set_state(device,
|
||||||
|
!is_down ?
|
||||||
|
DEBOUNCE_STATE_IS_DOWN :
|
||||||
|
DEBOUNCE_STATE_IS_UP);
|
||||||
|
flushed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device->button_usage = e->usage;
|
||||||
|
debounce_handle_event(device,
|
||||||
|
is_down ?
|
||||||
|
DEBOUNCE_EVENT_PRESS :
|
||||||
|
DEBOUNCE_EVENT_RELEASE,
|
||||||
|
filtered_frame,
|
||||||
|
time);
|
||||||
|
|
||||||
|
/* if we have more than one event, we flush the state
|
||||||
|
* machine immediately after the event itself */
|
||||||
|
if (nchanged > 1) {
|
||||||
|
debounce_handle_event(device,
|
||||||
|
DEBOUNCE_EVENT_OTHERBUTTON,
|
||||||
|
filtered_frame,
|
||||||
|
time);
|
||||||
|
flushed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evdev_frame_set(frame,
|
||||||
|
evdev_frame_get_events(filtered_frame, NULL),
|
||||||
|
evdev_frame_get_count(filtered_frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_plugin_evdev_frame(struct libinput_plugin *libinput_plugin,
|
||||||
|
struct libinput_device *device,
|
||||||
|
struct evdev_frame *frame)
|
||||||
|
{
|
||||||
|
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
|
||||||
|
struct plugin_device *pd;
|
||||||
|
|
||||||
|
list_for_each(pd, &plugin->devices, link) {
|
||||||
|
if (pd->device == device) {
|
||||||
|
debounce_plugin_handle_frame(pd, frame, frame->time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_timeout(struct libinput_plugin *plugin, uint64_t now, void *data)
|
||||||
|
{
|
||||||
|
struct plugin_device *device = data;
|
||||||
|
|
||||||
|
debounce_handle_event(device, DEBOUNCE_EVENT_TIMEOUT, NULL, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_timeout_short(struct libinput_plugin *plugin, uint64_t now, void *data)
|
||||||
|
{
|
||||||
|
struct plugin_device *device = data;
|
||||||
|
|
||||||
|
debounce_handle_event(device, DEBOUNCE_EVENT_TIMEOUT_SHORT, NULL, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_plugin_device_added(struct libinput_plugin *libinput_plugin,
|
||||||
|
struct libinput_device *device)
|
||||||
|
{
|
||||||
|
if (!libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_unref_(udev_device) *udev_device = libinput_device_get_udev_device(device);
|
||||||
|
if (udev_device) {
|
||||||
|
const char *prop = udev_device_get_property_value(udev_device,
|
||||||
|
"ID_INPUT_TOUCHPAD");
|
||||||
|
bool val;
|
||||||
|
if (parse_boolean_property(prop, &val) && val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_unref_(quirks) *q = libinput_device_get_quirks(device);
|
||||||
|
bool result = false;
|
||||||
|
if (q &&
|
||||||
|
quirks_get_bool(q, QUIRK_MODEL_BOUNCING_KEYS, &result) &&
|
||||||
|
result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
libinput_plugin_enable_device_event_frame(libinput_plugin, device, true);
|
||||||
|
|
||||||
|
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
|
||||||
|
struct plugin_device *pd = zalloc(sizeof(*pd));
|
||||||
|
pd->device = libinput_device_ref(device);
|
||||||
|
pd->parent = plugin;
|
||||||
|
pd->state = DEBOUNCE_STATE_IS_UP;
|
||||||
|
|
||||||
|
_autofree_ char *timer1_name = strdup_printf("debounce-%s",
|
||||||
|
libinput_device_get_sysname(device));
|
||||||
|
_autofree_ char *timer2_name = strdup_printf("debounce-short-%s",
|
||||||
|
libinput_device_get_sysname(device));
|
||||||
|
pd->timer = libinput_plugin_timer_new(libinput_plugin,
|
||||||
|
timer1_name,
|
||||||
|
debounce_timeout,
|
||||||
|
pd);
|
||||||
|
pd->timer_short = libinput_plugin_timer_new(libinput_plugin,
|
||||||
|
timer2_name,
|
||||||
|
debounce_timeout_short,
|
||||||
|
pd);
|
||||||
|
|
||||||
|
list_take_append(&plugin->devices, pd, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
debounce_plugin_device_removed(struct libinput_plugin *libinput_plugin,
|
||||||
|
struct libinput_device *device)
|
||||||
|
{
|
||||||
|
struct plugin_data *plugin = libinput_plugin_get_user_data(libinput_plugin);
|
||||||
|
struct plugin_device *dev;
|
||||||
|
list_for_each_safe(dev, &plugin->devices, link) {
|
||||||
|
if (dev->device == device) {
|
||||||
|
plugin_device_destroy(dev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct libinput_plugin_interface interface = {
|
||||||
|
.run = NULL,
|
||||||
|
.destroy = plugin_destroy,
|
||||||
|
.device_new = NULL,
|
||||||
|
.device_ignored = NULL,
|
||||||
|
.device_added = debounce_plugin_device_added,
|
||||||
|
.device_removed = debounce_plugin_device_removed,
|
||||||
|
.evdev_frame = debounce_plugin_evdev_frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
libinput_debounce_plugin(struct libinput *libinput)
|
||||||
|
{
|
||||||
|
struct plugin_data *plugin = zalloc(sizeof(*plugin));
|
||||||
|
list_init(&plugin->devices);
|
||||||
|
|
||||||
|
_unref_(libinput_plugin) *p = libinput_plugin_new(libinput,
|
||||||
|
"button-debounce",
|
||||||
|
&interface,
|
||||||
|
plugin);
|
||||||
|
plugin->plugin = p;
|
||||||
|
}
|
||||||
30
src/libinput-plugin-button-debounce.h
Normal file
30
src/libinput-plugin-button-debounce.h
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2025 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the "Software"),
|
||||||
|
* to deal in the Software without restriction, including without limitation
|
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
* Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice (including the next
|
||||||
|
* paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
* Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
* DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "libinput.h"
|
||||||
|
#include "libinput-plugin.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
libinput_debounce_plugin(struct libinput *libinput);
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
#include "libinput-util.h"
|
#include "libinput-util.h"
|
||||||
#include "libinput-private.h"
|
#include "libinput-private.h"
|
||||||
|
#include "libinput-plugin-button-debounce.h"
|
||||||
#include "libinput-plugin-tablet-double-tool.h"
|
#include "libinput-plugin-tablet-double-tool.h"
|
||||||
#include "libinput-plugin-tablet-eraser-button.h"
|
#include "libinput-plugin-tablet-eraser-button.h"
|
||||||
#include "libinput-plugin-tablet-forced-tool.h"
|
#include "libinput-plugin-tablet-forced-tool.h"
|
||||||
|
|
@ -392,6 +393,7 @@ libinput_plugin_system_load_internal_plugins(struct libinput *libinput,
|
||||||
libinput_tablet_plugin_double_tool(libinput);
|
libinput_tablet_plugin_double_tool(libinput);
|
||||||
libinput_tablet_plugin_proximity_timer(libinput);
|
libinput_tablet_plugin_proximity_timer(libinput);
|
||||||
libinput_tablet_plugin_eraser_button(libinput);
|
libinput_tablet_plugin_eraser_button(libinput);
|
||||||
|
libinput_debounce_plugin(libinput);
|
||||||
|
|
||||||
/* Our own event dispatch is implemented as mini-plugin,
|
/* Our own event dispatch is implemented as mini-plugin,
|
||||||
* guarantee this one to always be last (and after any
|
* guarantee this one to always be last (and after any
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue