mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-25 15:10:13 +01:00
This avoids mixing up milliseconds and usec, both by failing if we're providing just a number somewhere we expect usecs and also by making the API blindingly obvious that we're in usecs now. Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1373>
864 lines
24 KiB
C
864 lines
24 KiB
C
/*
|
|
* 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 "libinput-feature.h"
|
|
#include "libinput-log.h"
|
|
#include "libinput-plugin-button-debounce.h"
|
|
#include "libinput-plugin.h"
|
|
#include "libinput-private.h"
|
|
#include "libinput-util.h"
|
|
#include "quirks.h"
|
|
#include "timer.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;
|
|
usec_t button_time;
|
|
enum debounce_state state;
|
|
bool spurious_enabled;
|
|
|
|
bool want_feature_disabled;
|
|
|
|
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);
|
|
}
|
|
|
|
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_maybe_disable(struct plugin_device *device)
|
|
{
|
|
if (device->state != DEBOUNCE_STATE_IS_UP)
|
|
return;
|
|
|
|
if (device->want_feature_disabled) {
|
|
plugin_log_debug(device->parent->plugin,
|
|
"%s: disabled button debouncing on request\n",
|
|
libinput_device_get_name(device->device));
|
|
device->state = DEBOUNCE_STATE_DISABLED;
|
|
libinput_plugin_enable_device_event_frame(device->parent->plugin,
|
|
device->device,
|
|
false);
|
|
}
|
|
}
|
|
|
|
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, usec_t time)
|
|
{
|
|
const usec_t DEBOUNCE_TIMEOUT_BOUNCE = usec_from_millis(25);
|
|
|
|
libinput_plugin_timer_set(device->timer,
|
|
usec_add(time, DEBOUNCE_TIMEOUT_BOUNCE));
|
|
}
|
|
|
|
static inline void
|
|
debounce_set_timer_short(struct plugin_device *device, usec_t time)
|
|
{
|
|
const usec_t DEBOUNCE_TIMEOUT_SPURIOUS = usec_from_millis(12);
|
|
|
|
libinput_plugin_timer_set(device->timer_short,
|
|
usec_add(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)
|
|
{
|
|
_unref_(evdev_frame) *button_frame = NULL;
|
|
if (frame == NULL) {
|
|
button_frame = evdev_frame_new(2);
|
|
frame = button_frame;
|
|
}
|
|
|
|
evdev_frame_append_one(frame,
|
|
device->button_usage,
|
|
state == LIBINPUT_BUTTON_STATE_PRESSED ? 1 : 0);
|
|
evdev_frame_set_time(frame, device->button_time);
|
|
|
|
libinput_plugin_prepend_evdev_frame(device->parent->plugin,
|
|
device->device,
|
|
frame);
|
|
|
|
/* If we used the original frame, reset it to avoid re-sending any
|
|
* non-button events that may be present in this frame */
|
|
if (button_frame == NULL)
|
|
evdev_frame_reset(frame);
|
|
}
|
|
|
|
static void
|
|
debounce_is_up_handle_event(struct plugin_device *device,
|
|
enum debounce_event event,
|
|
struct evdev_frame *frame,
|
|
usec_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,
|
|
usec_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,
|
|
usec_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,
|
|
usec_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_maybe_disable(device);
|
|
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,
|
|
usec_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_maybe_disable(device);
|
|
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,
|
|
usec_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);
|
|
debounce_maybe_disable(device);
|
|
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);
|
|
debounce_maybe_disable(device);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
debounce_is_down_detecting_spurious_handle_event(struct plugin_device *device,
|
|
enum debounce_event event,
|
|
struct evdev_frame *frame,
|
|
usec_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,
|
|
usec_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);
|
|
debounce_maybe_disable(device);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
debounce_is_down_delaying_handle_event(struct plugin_device *device,
|
|
enum debounce_event event,
|
|
struct evdev_frame *frame,
|
|
usec_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,
|
|
usec_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,
|
|
usec_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,
|
|
usec_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;
|
|
debounce_maybe_disable(device);
|
|
}
|
|
|
|
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, usec_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, usec_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;
|
|
|
|
if (libinput_device_is_virtual(device))
|
|
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);
|
|
|
|
/* We don't care about BTN_TRIGGER_HAPPY_* */
|
|
for (unsigned int code = BTN_0; code <= KEY_OK; code++) {
|
|
evdev_usage_t usage = evdev_usage_from_code(EV_KEY, code);
|
|
if (evdev_usage_is_button(usage)) {
|
|
libinput_plugin_enable_evdev_usage(libinput_plugin,
|
|
evdev_usage_enum(usage));
|
|
}
|
|
}
|
|
|
|
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 void
|
|
debounce_plugin_feature_disabled(struct libinput_plugin *libinput_plugin,
|
|
struct libinput_device *device,
|
|
enum libinput_feature feature)
|
|
{
|
|
if (feature != LIBINPUT_FEATURE_BUTTON_DEBOUNCING)
|
|
return;
|
|
|
|
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) {
|
|
pd->want_feature_disabled = true;
|
|
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,
|
|
.feature_disabled = debounce_plugin_feature_disabled,
|
|
};
|
|
|
|
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;
|
|
}
|