[PATCH] add button debouncing

Matt Whitlock freedesktop at mattwhitlock.name
Wed Aug 1 01:41:20 PDT 2012


This patch adds one configuration option, "DebounceDelay", and one XInput
property, "Evdev Debounce Delay". They refer to the amount of time to wait
after receiving a mouse button release event from the kernel before posting
the event to the X server. If a mouse button pressed event is received before
the delay expires, then the release/press pair is forgotten. This allows to
deal with mice whose button switches are worn out or dirty. Each mouse button
is debounced independently.
---
 include/evdev-properties.h |    3 +
 src/Makefile.am            |    1 +
 src/debounce.c             |  198 ++++++++++++++++++++++++++++++++++++++++++++
 src/evdev.c                |    7 ++
 src/evdev.h                |   17 ++++
 5 files changed, 226 insertions(+), 0 deletions(-)
 create mode 100644 src/debounce.c

diff --git a/include/evdev-properties.h b/include/evdev-properties.h
index 745a1ba..4c169c3 100644
--- a/include/evdev-properties.h
+++ b/include/evdev-properties.h
@@ -87,4 +87,7 @@
 */
 #define EVDEV_PROP_FUNCTION_KEYS "Evdev Function Keys"
 
+/* CARD32 */
+#define EVDEV_PROP_DEBOUNCE_DELAY "Evdev Debounce Delay"
+
 #endif
diff --git a/src/Makefile.am b/src/Makefile.am
index f2d0e03..72beb8f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -39,6 +39,7 @@ AM_CPPFLAGS =-I$(top_srcdir)/include
                                emuThird.c \
                                emuWheel.c \
                                draglock.c \
+                               debounce.c \
                                apple.c \
                                axis_labels.h
 
diff --git a/src/debounce.c b/src/debounce.c
new file mode 100644
index 0000000..cda703d
--- /dev/null
+++ b/src/debounce.c
@@ -0,0 +1,198 @@
+/*
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of the authors
+ * not be used in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.  The authors make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "evdev.h"
+
+#include <X11/Xatom.h>
+#include <xf86.h>
+#include <xf86Xinput.h>
+#include <exevents.h>
+
+#include <evdev-properties.h>
+
+/* how many milliseconds to wait for buttons to settle */
+static Atom prop_dbdelay;
+
+struct ButtonTime {
+    unsigned int button;
+    Time time;
+};
+
+static int
+EvdevDBCompareButtonTimes(const void *bt1, const void *bt2)
+{
+    return ((const struct ButtonTime *) bt1)->time -
+            ((const struct ButtonTime *) bt2)->time;
+}
+
+/**
+ * Timer callback for debouncing buttons.
+ */
+static CARD32
+EvdevDBTimer(OsTimerPtr timer, CARD32 time, pointer arg)
+{
+    InputInfoPtr pInfo = arg;
+    EvdevPtr pEvdev = pInfo->private;
+    struct debounce *db = &pEvdev->debounce;
+    struct ButtonTime release[EVDEV_MAXBUTTONS];
+    size_t nRelease = 0;
+    int sigstate = xf86BlockSIGIO();
+
+    if (db->bouncing) {
+        BOOL bouncing = FALSE;
+        Time next_expiry = ~0;
+        for (size_t button = 0; button < EVDEV_MAXBUTTONS; ++button) {
+            if (db->state[button].bouncing) {
+                Time expiry = db->state[button].expiry;
+                if (expiry <= time) {
+                    db->state[button].pressed = FALSE;
+                    db->state[button].bouncing = FALSE;
+                    release[nRelease].button = button;
+                    release[nRelease].time = expiry;
+                    ++nRelease;
+                }
+                else if (expiry < next_expiry) {
+                    bouncing = TRUE;
+                    next_expiry = expiry;
+                }
+            }
+        }
+        if (nRelease > 0) {
+            if (nRelease > 1) {
+                qsort(release, nRelease, sizeof *release,
+                      EvdevDBCompareButtonTimes);
+            }
+            for (size_t i = 0; i < nRelease; ++i) {
+                EvdevPostButtonEvent(pInfo, release[i].button, BUTTON_RELEASE);
+            }
+        }
+        if (bouncing) {
+            db->timer = TimerSet(db->timer, 0, next_expiry - GetTimeInMillis(),
+                                 EvdevDBTimer, pInfo);
+        }
+        else {
+            db->bouncing = FALSE;
+        }
+    }
+
+    xf86UnblockSIGIO(sigstate);
+    return 0;
+}
+
+/**
+ * Debounce button states.
+ *
+ * @return TRUE iff event was swallowed by debouncing.
+ */
+BOOL
+EvdevDBFilterEvent(InputInfoPtr pInfo, unsigned int button, BOOL pressed)
+{
+    EvdevPtr pEvdev = pInfo->private;
+    struct debounce *db = &pEvdev->debounce;
+
+    if (db->delay == 0) {
+        return FALSE;
+    }
+
+    if (pressed) {
+        if (db->state[button].pressed) {
+            db->state[button].bouncing = FALSE;
+            return TRUE;
+        }
+        db->state[button].pressed = TRUE;
+        return FALSE;
+    }
+
+    if (db->state[button].pressed) {
+        db->state[button].bouncing = TRUE;
+        db->state[button].expiry = GetTimeInMillis() + db->delay;
+        if (!db->bouncing) {
+            db->bouncing = TRUE;
+            db->timer = TimerSet(db->timer, 0, db->delay, EvdevDBTimer, pInfo);
+        }
+    }
+
+    return TRUE;
+}
+
+void
+EvdevDBPreInit(InputInfoPtr pInfo)
+{
+    EvdevPtr pEvdev = pInfo->private;
+    struct debounce *db = &pEvdev->debounce;
+
+    db->delay = xf86SetIntOption(pInfo->options, "DebounceDelay", 0);
+    db->timer = TimerSet(NULL, 0, 0, NULL, NULL);
+}
+
+void
+EvdevDBFinalize(InputInfoPtr pInfo)
+{
+    EvdevPtr pEvdev = pInfo->private;
+    struct debounce *db = &pEvdev->debounce;
+
+    TimerFree(db->timer), db->timer = NULL;
+}
+
+static int
+EvdevDBSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
+                   BOOL checkonly)
+{
+    InputInfoPtr pInfo  = dev->public.devicePrivate;
+    EvdevPtr pEvdev = pInfo->private;
+    struct debounce *db = &pEvdev->debounce;
+
+    if (atom == prop_dbdelay) {
+        if (val->format != 32 || val->size != 1 || val->type != XA_INTEGER) {
+            return BadMatch;
+        }
+        if (!checkonly) {
+            db->delay = *((CARD32 *) val->data);
+        }
+    }
+
+    return Success;
+}
+
+void
+EvdevDBInitProperty(DeviceIntPtr dev)
+{
+    InputInfoPtr pInfo = dev->public.devicePrivate;
+    EvdevPtr pEvdev = pInfo->private;
+    struct debounce *db = &pEvdev->debounce;
+
+    if (dev->button) {
+        prop_dbdelay = MakeAtom(EVDEV_PROP_DEBOUNCE_DELAY,
+                                strlen(EVDEV_PROP_DEBOUNCE_DELAY), TRUE);
+        if (XIChangeDeviceProperty(dev, prop_dbdelay, XA_INTEGER, 32,
+                                   PropModeReplace, 1, &db->delay, FALSE)
+                != Success) {
+            return;
+        }
+        XISetDevicePropertyDeletable(dev, prop_dbdelay, FALSE);
+        XIRegisterPropertyHandler(dev, EvdevDBSetProperty, NULL, NULL);
+    }
+}
diff --git a/src/evdev.c b/src/evdev.c
index f54b66f..7336e08 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -667,6 +667,9 @@ EvdevProcessButtonEvent(InputInfoPtr pInfo, struct input_event *ev)
     if (EvdevMBEmuFilterEvent(pInfo, button, value))
         return;
 
+    if (EvdevDBFilterEvent(pInfo, button, value))
+        return;
+
     if (button)
         EvdevQueueButtonEvent(pInfo, button, value);
     else
@@ -1116,6 +1119,7 @@ EvdevReadInput(InputInfoPtr pInfo)
             {
                 EvdevMBEmuFinalize(pInfo);
                 Evdev3BEmuFinalize(pInfo);
+                EvdevDBFinalize(pInfo);
                 xf86RemoveEnabledDevice(pInfo);
                 EvdevCloseDevice(pInfo);
             } else if (errno != EAGAIN)
@@ -1814,6 +1818,7 @@ EvdevInit(DeviceIntPtr device)
     Evdev3BEmuInitProperty(device);
     EvdevWheelEmuInitProperty(device);
     EvdevDragLockInitProperty(device);
+    EvdevDBInitProperty(device);
     EvdevAppleInitProperty(device);
 
     return Success;
@@ -1871,6 +1876,7 @@ EvdevProc(DeviceIntPtr device, int what)
         {
             EvdevMBEmuFinalize(pInfo);
             Evdev3BEmuFinalize(pInfo);
+            EvdevDBFinalize(pInfo);
         }
         if (pInfo->fd != -1)
         {
@@ -2537,6 +2543,7 @@ EvdevPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
         Evdev3BEmuPreInit(pInfo);
         EvdevWheelEmuPreInit(pInfo);
         EvdevDragLockPreInit(pInfo);
+        EvdevDBPreInit(pInfo);
     }
 
     return Success;
diff --git a/src/evdev.h b/src/evdev.h
index c2f9246..5806f0c 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -218,6 +218,17 @@ typedef struct {
         Time                expires;     /* time of expiry */
         Time                timeout;
     } emulateWheel;
+    /* Button debouncing */
+    struct debounce {
+        Time                delay;
+        OsTimerPtr          timer;
+        BOOL                bouncing;
+        struct {
+            BOOL            pressed;
+            BOOL            bouncing;
+            Time            expiry;
+        } state[EVDEV_MAXBUTTONS];
+    } debounce;
     /* run-time calibration */
     struct {
         int                 min_x;
@@ -294,6 +305,12 @@ BOOL EvdevWheelEmuFilterMotion(InputInfoPtr pInfo, struct input_event *pEv);
 void EvdevDragLockPreInit(InputInfoPtr pInfo);
 BOOL EvdevDragLockFilterEvent(InputInfoPtr pInfo, unsigned int button, int value);
 
+/* Button debouncing */
+BOOL EvdevDBFilterEvent(InputInfoPtr pInfo, unsigned int button, BOOL pressed);
+void EvdevDBPreInit(InputInfoPtr pInfo);
+void EvdevDBFinalize(InputInfoPtr pInfo);
+void EvdevDBInitProperty(DeviceIntPtr dev);
+
 void EvdevMBEmuInitProperty(DeviceIntPtr);
 void Evdev3BEmuInitProperty(DeviceIntPtr);
 void EvdevWheelEmuInitProperty(DeviceIntPtr);
-- 
1.7.8.6



More information about the xorg-devel mailing list