Thursday, July 27, 2017

libinput and button debouncing

A few days ago, I pushed code for button debouncing into libinput, scheduled for libinput 1.9. What is button debouncing you ask? Well, I'm glad you asked, because otherwise typing this blog post would've been a waste of time :)

Over in Utopia, when you press the button on a device, you get a press event from the hardware. When you release said button, you get a release event from the hardware. Together, they form the button click interaction we have come to learn and love over the last couple of decades. Life is generally merry and the sunshine to rainbow to lollipop ratio is good. Meanwhile, over here in the real world, buttons can be quite dodgy, don't always work like they're supposed to, lollipops are unhealthy and boy, have you seen that sunburn the sunshine gave me? One way how buttons may not work is that they can lose contact for a fraction of a second and send release events even though the button is being held down. The device usually detects that the button is still being down in the next hardware cycle (~8ms on most devices) and thus sends another button press.

For us, there are not a lot of hints that this is bad hardware besides the timestamps. It's not possible for a user to really release and press a button within 8ms, so we can take this as a signal for dodgy hardware. But at least that's someting. All we need to do is ignore the release event (and subsequent button event) and only release when the button is released properly. This requires timeouts and delays of the event, something we generally want to avoid unless absolutely necessary. So the behaviour libinput has now is enabled but inactive button debouncing on all devices. We monitor button release and button press timestamps, but otherwise leave the events as-is, so no delays are introduced. Only if a device sends release/press combos with unfeasably short timeouts, activate button debouncing. Once active, we filter all button release events and instead set a timer. Once the timer expires, we send the button release event. But if at any time before then another button press is detected, the scheduled release is discarded, the button press is filtered and no event is sent. Thus, we paper over the release/press combo the hardware gives us and to anyone using libinput, it will look like the button was held down for the whole time.

There's one downside with this approach - the very first button debounce to happen on a device will still trigger an erroneous button release event. It remains to be seen whether this is a problem in real-world scenarios. That's the cost of having it as an auto-enabling feature rather than an explicit configuration option.

If you do have a mouse that suffers from button bouncing, I recommend you try libinput's master branch and file any issues if the debouncing doesn't work as it should. Might as well get any issues fixed before we have a release.

12 comments:

Leslie Satenstein said...

Your article is a reminder of what we had to do to debounce contacts

Consider a contact made with a drop of mercury. Mercury conducts.
When mercury joins a metal with a second metal having a microdot or mercury between, the elasticity of mercury does not allow a bounced contact. The contact metal has to retract to at least 1.1 times the diameter of the mercury drop to force the mercury drop to let-go.

A sustained contact was timed for auto-repeat.

Imagine if we could use some conducting non-polluting-non-toxic liquid to make electrical contacts, for example.

Bastian said...

ouuh, perfect timing, my mouse happens to do this sometimes because the button gets somehow stuck occassionally. :-)

Lik Doyaya said...

To start testing and using this, is it enough to just install the libinput from the master branch or do I also need to update the Xorg libinput driver and enable this option?

Peter Hutterer said...

@Lik: just updating libinput is enough (provided you're using the xorg libinput driver of course)

Lik Doyaya said...

Thank you for the clarification, it's working now.

Hi-Angel said...

Just curious, did you happen to test an actual hw with alike problem, where debouncing helped? Or is it a hypothetical problem? I'm asking out of pure curiosity about a possibility to stumble upon such issue.

Hi-Angel said...

By the way, one more thought: I know some mice have a "double click" button, I've had one 1.5 year ago. E.g. here's an example of one https://images-na.ssl-images-amazon.com/images/I/41suGcP%2BinL._SX355_.jpg the orange square you see is the button. Will debouncing be okay with them?

Peter Hutterer said...

Yagamy: I don't have hw that reproduces the issue but it was (like most developments in libinput) driven by bug reports of users affected by bad hardware. Fixing hypothetical problems usually causes more headaches than anything and in most cases we need some access to the hw or a reproducer to really fix the problem at the root rather than just papering over it. For the doubleclick buttons: if it breaks please file a bug.

Hi-Angel said...

FWIW: I just got a mouse with double-click (or, it seems, rather tripple-click), and it does work, i.e. debounce doesn't cause problems. No idea why it works though, output from libinput-debug-events:

-event7 POINTER_BUTTON +14.72s BTN_LEFT (272) pressed, seat count: 1
event7 POINTER_BUTTON +14.78s BTN_LEFT (272) released, seat count: 0
event7 POINTER_BUTTON +14.85s BTN_LEFT (272) pressed, seat count: 1
event7 POINTER_BUTTON +14.91s BTN_LEFT (272) released, seat count: 0
event7 POINTER_BUTTON +14.98s BTN_LEFT (272) pressed, seat count: 1
event7 POINTER_BUTTON +15.04s BTN_LEFT (272) released, seat count: 0

Frederick Barbarossa said...

I'm an RTS (real time strategy game) player, and due to the frequency of clicks required to play these games, button debouncing is constantly being triggered on the newer versions of Libinput and many intentional clicks are being discarded. With the newer version of the library, the games are basically unplayable due to this change. I've had to hold back libinput at version 1.8.3. It's worth noting that prior to this change my mouse has never missed a single click, nor has it ever produced any unintended clicks due to bouncing. I've seen a smattering of threads around with false positives for this issue too. I think the detection threshold for this may be overly aggressive, and with Linux now being pushed as a gaming platform, this feature renders libinput effectively useless for these applications.

Peter Hutterer said...

Frederick: this is not a bug reporting tool, I'll need device recordings, etc to figure out what's going on. See https://wayland.freedesktop.org/libinput/doc/latest/reporting_bugs.html please and file a bug, thanks.

Unknown said...

Hi Peter,

Is the debouncing code nominally enabled for the physical button underneath a modern clickpad? I'm seeing behavior on libinput 1.10.7 that suggests to me that it isn't.

event5 - button state: from BUTTON_STATE_BOTTOM, event BUTTON_EVENT_UP to BUTTON_STATE_NONE
event5 - button state: from BUTTON_STATE_NONE, event BUTTON_EVENT_IN_BOTTOM_L to BUTTON_STATE_BOTTOM
event5 POINTER_BUTTON +3.38s BTN_LEFT (272) pressed, seat count: 1
event5 POINTER_BUTTON +3.39s BTN_LEFT (272) released, seat count: 0
event5 POINTER_BUTTON +3.40s BTN_LEFT (272) pressed, seat count: 1
event5 POINTER_BUTTON +3.49s BTN_LEFT (272) released, seat count: 0
event5 - button state: from BUTTON_STATE_BOTTOM, event BUTTON_EVENT_UP to BUTTON_STATE_NONE
event5 - button state: from BUTTON_STATE_NONE, event BUTTON_EVENT_IN_BOTTOM_L to BUTTON_STATE_BOTTOM
event5 POINTER_BUTTON +4.56s BTN_LEFT (272) pressed, seat count: 1
event5 POINTER_BUTTON +4.62s BTN_LEFT (272) released, seat count: 0
event5 POINTER_BUTTON +4.63s BTN_LEFT (272) pressed, seat count: 1
event5 POINTER_BUTTON +4.63s BTN_LEFT (272) released, seat count: 0


If it's useful, I'd be more than happy to tweak code, test a dev branch and/or record timings & open a proper bug report.

I suspect that this is all some sort of karmic justice, since I'm deep inside a debouncer on another project right now ;)

Thanks!