Compare commits

...

26 commits
main ... 1.9.3

Author SHA1 Message Date
Peter Hutterer
05a2da818b libinput 1.9.3
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2017-11-28 15:06:20 +10:00
Peter Hutterer
6e8beeb280 tools: fix dashes in man page
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 1b54b726f0)
2017-11-28 12:44:41 +10:00
Peter Hutterer
e961c1ff5c debounce: handle a timeout in MAYBE_SPURIOUS state
Sequences to trigger:
- spurious debouncing is enabled
- release a button in IS_DOWN state -> RELEASE_DELAYED
- short timeout triggers RELEASE_WAITING

If a button press now comes before the long timeout expires, we transition to
MAYBE_SPURIOUS where the long timeout may expire. In that case we should
transition to pressed state again.

Reported-by: Vicente Bergas <vicencb@gmail.com>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit ac1748ef4d)
2017-11-28 12:44:41 +10:00
Peter Hutterer
c77b6b4c96 evdev: add new debouncing code
The current debouncing code monitors events and switches on when events are
too close together. From then on, any event can be delayed.

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

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

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

The whole button debouncing moves to a state machine which makes debugging a
lot easier. See the accompanying SVG for the diagram.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit de994d135e)
2017-11-28 12:44:41 +10:00
Peter Hutterer
8c55bc060d fallback: change to handle the state at EV_SYN time
The previous approach was to remember the last event and flush it at the right
time. The new approach is to update the device state during the frame and send
out the events at EV_SYN time.

This gives us two advantages: we are not dependent on the kernel order of how
events come in and we can process events depending on other events in the same
frame. This will come in handy later for button debouncing.

This is also the approach we have in the touchpad and tablet backends.

Two FIXMEs are left in place, the button debouncing code and the lid switch
code. Both need to be handled in future patches.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit db3b6fe5f7)
2017-11-28 12:44:41 +10:00
Peter Hutterer
52cdbc1299 evdev: fix axis mixup for the wheel click angles
vertical wheel is y, not x

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit eb048529b5)
2017-11-28 12:44:41 +10:00
Peter Hutterer
fc78e88870 fallback: drop unused ratelimit struct
This one is present in the parent evdev device

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 8e86f28931)
2017-11-28 12:44:41 +10:00
Peter Hutterer
5d78484891 fallback: create the evdev-fallback.h header file
So we can split up evdev-fallback.c into multiple files where needed.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 87920f4992)
2017-11-28 12:44:41 +10:00
Peter Hutterer
16b290a785 test: replace litest_button_click with a debounced version
This is via a simple search & replace. Later auditing is needed to switch
clicks that should not be debounced (e.g. touchpads) back to a non-debounced
version.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 8cf6893f6d)
2017-11-28 12:44:41 +10:00
Peter Hutterer
01ae8bd6a2 touchpad: post a SYN_REPORT after a faked trackpoint button
This has no real effect at the moment because the fallback interface doesn't
care much about SYN_REPORT, it processes events as they come in. But it's a
bug nonetheless, the process() callback expects correct event frames.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit dc7fb65db5)
2017-11-28 12:44:41 +10:00
Peter Hutterer
c24c2af560 test: add a missing libinput_dispatch() to the debounce_timer test
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 6c8068601a)
2017-11-28 12:44:41 +10:00
Peter Hutterer
bd1ad68630 tools: fix typo in man page
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit b170993b74)
2017-11-16 15:31:14 +10:00
Peter Hutterer
56bcb2999e libinput 1.9.2
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2017-11-15 14:17:51 +10:00
Peter Hutterer
4ec04fa960 man: add --enable-middlebutton to debug-events man page
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 13c9ef07a2)
2017-11-15 13:37:21 +10:00
Peter Hutterer
e35c202df7 tools: handle missing evdev/pyudev modules with a better error message
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 67bfb5cf2e)
2017-11-15 13:26:10 +10:00
Peter Hutterer
1593d7da32 tools: when the command isn't installed, print that
Makes it more user-friendly to be able to split the tools into multiple
packages

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 92aa1d1418)
2017-11-15 13:26:06 +10:00
Peter Hutterer
73c9ed2cd9 tools: fix missing words in man page
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit be344a3afb)
2017-11-15 13:26:03 +10:00
Peter Hutterer
ed17f8b637 doc: add a FAQ regarding "please add a configuration option"
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit c67b74b45e)
2017-11-14 14:29:30 +10:00
Stefan Brüns
15e40a42e7 tools: Handle LIBINPUT_SWITCH_TABLET_MODE
Signed-off-by: Stefan Brüns <stefan.bruens@rwth-aachen.de>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 48fd22def7)
2017-11-14 14:29:17 +10:00
Stefan Brüns
5c989940b6 tools: Show gesture/switch capabilities in list-devices output
Signed-off-by: Stefan Brüns <stefan.bruens@rwth-aachen.de>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 5ea84fa7da)
2017-11-14 14:29:16 +10:00
Peter Hutterer
edd83fe99e test: mkdir -p the udev rules and hwdb directories
Especially /run/udev/rules.d may not exist, causing a test suite failure.

https://bugs.freedesktop.org/show_bug.cgi?id=103527

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit d332a64df8)
2017-11-14 14:29:09 +10:00
Peter Hutterer
e7be909838 doc: update test suite page for ninja and other recent changes
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit b15b66616e)
2017-11-14 14:29:07 +10:00
Peter Hutterer
03cd377e00 test: fix the device name for the magic mouse test device
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 04c6439449)
2017-11-14 14:29:01 +10:00
Peter Hutterer
89660005a8 doc: quote all paths in the doxygen file
https://bugs.freedesktop.org/show_bug.cgi?id=103532

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit fb467dce8f)
2017-11-14 14:28:57 +10:00
Peter Hutterer
5b862de70d circle.yml: add ninja dist to tests
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit e182489ce7)
2017-11-14 14:28:55 +10:00
Peter Hutterer
a83085e4f9 touchpad: allow for multiple paired keyboards
needed for the razer blade keybard which provides multiple event nodes for
one physical device but it's hard/impossible to identify which one is the real
event node we care about.

https://bugs.freedesktop.org/show_bug.cgi?id=103156

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 4d7592066a)
2017-11-14 14:28:29 +10:00
38 changed files with 2573 additions and 831 deletions

View file

@ -41,6 +41,12 @@ libinput_jobs:
name: Build - No docs name: Build - No docs
environment: environment:
MESON_PARAMS: -Ddocumentation=false MESON_PARAMS: -Ddocumentation=false
build_dist: &build_dist
run:
<<: *build_and_test_default
name: Build - ninja dist
environment:
NINJA_ARGS: dist
ninja_scan_build: &ninja_scan_build ninja_scan_build: &ninja_scan_build
run: run:
<<: *build_and_test_default <<: *build_and_test_default
@ -88,6 +94,7 @@ fedora_build_all: &fedora_build_all
- *build_no_debug_gui - *build_no_debug_gui
- *build_no_tests - *build_no_tests
- *build_no_docs - *build_no_docs
- *build_dist
ubuntu_install: &ubuntu_install ubuntu_install: &ubuntu_install
run: run:
@ -112,6 +119,7 @@ ubuntu_build_all: &ubuntu_build_all
- *build_no_debug_gui - *build_no_debug_gui
- *build_no_tests - *build_no_tests
- *build_no_docs - *build_no_docs
- *build_dist
scan_build_run: &scan_build_run scan_build_run: &scan_build_run
<<: *default_settings <<: *default_settings

View file

@ -0,0 +1,714 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1108px" height="2101px" version="1.1" content="&lt;mxfile userAgent=&quot;Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0&quot; version=&quot;7.7.2&quot; editor=&quot;www.draw.io&quot; type=&quot;google&quot;&gt;&lt;diagram id=&quot;27c35eac-9917-780f-69cd-550de3271581&quot; name=&quot;Page-1&quot;&gt;7R1dc5u49tdk7t2HZBCf4tGJnd3OtEmmTqezTx0ZZJu7BDyA62R//RUfwkYShDo2Qknz0NoChHy+z5HOORfGzdPznwnarL/EPg4vdM1/vjCmF7oODOiQ//KRl3JE1yy7HFklgV/dtR+YB//ialCrRreBj9PGjVkch1mwaQ56cRRhL2uMoSSJd83blnHYfOsGrTA3MPdQyI9+D/xsXY5C3d6P/4WD1Zq+GdhueeUJ0ZurX5KukR/vDoaM2YVxk8RxVn56er7BYQ49CpfyuduWq/XCEhxlfR7QFwvPs13HcpcIe8i41MsZfqJwW/3YT/Mf3x6q1WYvFARJvI18nM8CLozr3TrI8HyDvPzqjmCdjK2zp7C6vAzC8CYO46R41vAtDH2TjKdZEv+DD65AfWHYNrlSrQInGX5u/WmgBhghNRw/4Sx5IbdUDxiUWioqu7Sq77s9yoBdja0P0GVWY6iiklU99R6Q5EMFy55wNTm4LrZZFkcXuh2S914vEvJplX/aJDhNOXgTQtnkH9f4Ga3IYw34isC/wUlAlo2T/UMP+6GTANhuAtjk4SsCr3UO8NocwLBPWLb6GifZOiYAQOFsP3q9J2GtCc7/4Sx7qaQO2mYxGdrP8DmON9V95TvzF3VDkKwr3iYe7ma5DCUrnHWRD4+JBIcoC342339SwDoc3U7vv9/9+D759Pjp7k8FxYLuNqnWMGSKBQA4+H6dfZ5N5rMfD7O7qZogBloTxJZAMgwIYoMDcUbEYLzNzi5kGejPJvb1qWBsmqzwtQaTvsA1PIBdB7o2hAa65AEcxVmwfOF1W5vOI3IMoxTzCCnIlNpYehMhFFtPz6vczrxahvHOW6Mku/JRhlowxqDk9nbqngolFiNZoMmjpGaNQ5zYZ8AJb2/8Kk5a7BCVMGIwTEJwJA8jbqeJEsXRmGwSp4dNUgrWAYwSFpICiU6clNwuGVpZEgczQ0FUyH9wIikCJbotHKRNlYiW0sUh1YppRwbR2hzR/rI2VMEHlGeGAEclYhVIWDHJyKBVOGJItjFWt65qIePzg9Ll2P4deB9Wk+ttVx7X15HUMdJqq7/fyfauJFrVeRWlavAXMBQKHFeiGUVDTO9L9ztMgF0QSBssAgE4gI1HCLS5L10ywNDEoD+7DDCs3qSqzEZFvds2AnVldO9UKKiuSoqRQapjtvfb+KrTSnVkQRJyTP8hI7hMuBCKQi9DhQsN3nGgO0Pqbr5ZjFnmygxumf2tMmVUHWuTAU2wCTGUrjN1lQS0+7qqM2VZZeb73MNkgoegPrIjg1jHbJi1RZU67TIoi1hVih5S06cLkvpQcVjbW5jIhJajuR42PLqxrgQgTQFJsr9HVhDW5G2pL5O/r2c/5g/fvn66/zZX0JSCtt0UngAKNreHsqWoRFaDVLUeql5WENbiT4S9g1gh0CBDrjJ1vdVtmI7quGgbfXSq/rcSb/HoJEnQy8ENmziIsvRg5od84ADHADRx7Doag6Vyyj3O6rUdh0bV9aMYtzKEDh/1rf0LVuqkawKls8scRqe6kxvn+vY0ssh1xySKVHI7etGwrHCwrZIJYgnCwZZpunAJoe+6rmZA683mcouY1hzzyrSBZcDyX5YbHIbMyzVWszD4eZsAt8ccIBKj45Wte/OtiRotODOZs8WONhyS+NhTutkmQbxNeeWAI7QIc1zd8hriyJ0A7AVpUGiUHrsBbvF3Gj1hMx6WZffcDgAsck4i3vjDGVE8YvZpI6NOu1WW6uD3v96v+WPrI9oioImxSpBwny0CW1bU1WnNJJtyIB5/dIs9BS83uuWM2U5pE2ZdVOrIcjQdlfYGnB4WnylLZzl8TFv53WubBrfG4JlDlfzJPqTqyAppQ141PXydzecKpzjb1GailGoKKHUw/QRVCoQ6PawoKEs/wf7HXxXafYFM9GAfTpAhV1WKeMIechXKinhClQ7AQkHEczRmKewIAAzr6Z/yfJXNnq+Syva8vfoBKxLYrCS2RXbDYCUJVDJxe4liWdEXV6UEJNgjQgBlOQuuSrEWKLBlLUezNd+DS3ux1FxonGkbDzLJCsBhPYvzbQm5Yy4L0XZ2ehQnPDlI8v4GzfKYzj5P/h4+eHuGkib1jjLNv6OHbmX4yq5K7kefTTP3re5Hi3xhUnUcWtljAPHCFx18wbzhRwgyayKH2IfBv/kGdIW36sQaudu6vrCmZASFwYrY7FOPYKEwv3PCDjwUTqoLT4HvF8f9QrTA4TXy/lkVtHDAKsvir4s1qrqz1Uou6vzTBvaFdNnKQ5faleHAZtCJFvl6I6YNszkrzU+mM8TLZYpPjWOg8UHB97vnarLMJChENliZHE0lC8vtETaoaEmC+gaaSpHXmufGeAYDaO+w8oBlj4nvVbJ8+vG9rMgr0FQKvdasNc7CeBoffC3sDZbrc/Mg4cEehsEmxa+7QijdlL0AlsFzjpNzRl4Nm90e5KN81mB8P+YzVm2F8rv5XlaYDwCVIqY1Z3UeYJEGSt4BUJ3tax+ZVsRyJLI9UMnMp7XDOtleXhFHoJSZ36vIoLziooJKuAXja5WbP7AAOOnZatiMpZgCAeAIBACbcnMaOCtlo4rKuIrDoTJoVueVVZytc7Js27w+s3OalojRrnRoNP4EGg5MrZlzIoeWSR6QWu9ZH7OGO6rgcxv0ByBwpTSc3kfDyRMWfCTrQwoLwGxRy6y7CfQxR7+OqxOtSwt/6UqZFnqP8Je0allA58NfH1JasB2tpOYlAn3MQbNjEhMrOpNB4YZSQTN9TBXheFj+dkRKpmcL/MtMFQPGmD2RY3LFKjqTQuFKeSJGD2BKlBa/PZEyvsYWS5SZCQFG3QPgmGP3QFoPAKBUEwAg6gIwmpP3QNAG4ENKC1cfUYE4IGgE8A5y0yxjTN6eqZKH0svbe3MJ0ONhqVIqFOgDTHn2m1IdFPpUhwTy4mzm75bYF/uavONIQAVU6CtB4KBPs4C3l4A8Hpgq+RU1P541d/J4WPI5SB/SFmbTTCU2eQRKNRbp1YzclNXxre6P+tHp2zCaRzalZmhYKpnOvTI0LFkdswDt8fDRexrqTEc4t28V47PYe0q1bKiFZDeJ69JInDeeRwM5/XXIyXOrLf4EMuX+NaDcP4vK5aQZyjB5+4R8rtqh35Qfp/ff73ipQUfIqvaT0cENHZik6fZpkxWFzSf0Lfma0+JLnBAkErDj5JJKp/ylKMpH7x//mn0l/19/e3y8v8vR9RMXnVk0gqX833CHXvKvmyT2iMdaTLXAy7i4SrR8fo+XbVEOpHL6K/JpTt9K17LJzQGNvr9cGsqvB9EqzG9YBPmx7f9uya+69eNd9Ee1ysN76GTk7fXL6dgT8tZBVC4niXPXurrBxz8Dbw+O7XIZeAH5jVcHwN20wnZOqC7reNkuIIKvRqZWLKxGp7ZAJcCK31xOUYLgPyn9ySUqXlpRUbyjIMEEl0fbC0ARrstnRNnBqsg0u7zLzk3BUdyiPRQVuCumutyWKzsEZYR3h0jsIMYDeDFCgy+k0CwPUknlQyVVDaVEjRFEkwFr/+2xECaXutai7GKiz3KtmNuBge/jiJP4J7HrmmrvUhfovfrIUaN6PzyL4vvtT5YgZwqgCRIjBzO3R90xhjsS2kOjVlQmQ6XaKrkuNTd2dmMfyq5razRzICrKtiqCwih1IxYW9kdnR52Ax4FmsMctAO9VQwGbn0L0cuDUFaLMPk1ShtrZ0/0FdKBl+brrQmjiV9rRjrpr4jAcXzz6q20T2ZQqmgt7qq6JHBq7i/eNG41mnyyqtyb+HIVG6AyMRpU2ewRbPeL8uCHUrXCPqaFua2ewNboy2r4EbG9LR3DQ8Wy1Ftl+32MuEyrsTT6Wrd2Wxb2vI2BcBdszHrMjX5M4zg6lbx6d/xL7OL/j/w==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g transform="translate(0.5,0.5)">
<rect x="271" y="220" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(333.5,233.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="35" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 36px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">IS_UP</div>
</div>
</foreignObject>
<text x="18" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">IS_UP</text>
</switch>
</g>
<path d="M 336 310 L 366 310 L 381 335 L 366 360 L 336 360 L 321 335 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(333.5,321.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 351 260 L 351 303.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 351 308.88 L 347.5 301.88 L 351 303.63 L 354.5 301.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="251" y="600" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(283.5,613.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="95" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 96px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">DOWN_WAITING</div>
</div>
</foreignObject>
<text x="48" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">DOWN_WAITING</text>
</switch>
</g>
<rect x="61" y="810" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(81.5,823.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="118" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 119px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">RELEASE_PENDING</div>
</div>
</foreignObject>
<text x="59" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">RELEASE_PENDING</text>
</switch>
</g>
<path d="M 416 675 L 446 675 L 461 700 L 446 725 L 416 725 L 401 700 Z" fill="#ea6b66" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(410.5,693.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout</div>
</div>
</foreignObject>
<text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout</text>
</switch>
</g>
<path d="M 570.61 1120.1 C 571.78 1116.9 573.67 1115.04 575.67 1115.12 L 645.53 1115.12 C 647.13 1115.08 648.65 1115.63 649.65 1116.61 C 650.64 1117.59 650.99 1118.88 650.6 1120.1 L 631.36 1169.9 C 630.19 1173.1 628.3 1174.96 626.3 1174.88 L 555.42 1174.88 C 554.01 1174.69 552.77 1174.05 551.99 1173.1 C 551.22 1172.15 551 1170.99 551.37 1169.9 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(579.5,1123.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="41" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">notify<br/>button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="27" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 320.61 500.1 C 321.78 496.9 323.67 495.04 325.67 495.12 L 395.53 495.12 C 397.13 495.08 398.65 495.63 399.65 496.61 C 400.64 497.59 400.99 498.88 400.6 500.1 L 381.36 549.9 C 380.19 553.1 378.3 554.96 376.3 554.88 L 305.42 554.88 C 304.01 554.69 302.77 554.05 301.99 553.1 C 301.22 552.15 301 550.99 301.37 549.9 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(333.5,503.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="41" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">notify<br/>button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="27" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 356 640 L 405.76 679.81" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 409.86 683.09 L 402.2 681.45 L 405.76 679.81 L 406.58 675.98 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="541" y="220" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(592.5,233.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="56" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 57px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">IS_DOWN</div>
</div>
</foreignObject>
<text x="28" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">IS_DOWN</text>
</switch>
</g>
<path d="M 441.33 675 L 610.31 265.89" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 612.31 261.03 L 612.87 268.84 L 610.31 265.89 L 606.41 266.17 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 336 675 L 366 675 L 381 700 L 366 725 L 336 725 L 321 700 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(330.5,686.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">button&lt;br&gt;release</text>
</switch>
</g>
<path d="M 336 640 L 343.21 668.82" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 344.48 673.92 L 339.39 667.97 L 343.21 668.82 L 346.18 666.28 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 329.13 713.54 L 178.72 806.65" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 174.26 809.41 L 178.37 802.75 L 178.72 806.65 L 182.05 808.7 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 26 965 L 56 965 L 71 990 L 56 1015 L 26 1015 L 11 990 Z" fill="#ea6b66" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(20.5,983.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout</div>
</div>
</foreignObject>
<text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout</text>
</switch>
</g>
<path d="M 128.5 850 L 59.68 960.11" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 56.9 964.56 L 57.64 956.77 L 59.68 960.11 L 63.58 960.48 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="111" y="2060" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(173.5,2073.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="35" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 36px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">IS_UP</div>
</div>
</foreignObject>
<text x="18" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">IS_UP</text>
</switch>
</g>
<path d="M 686 300 L 716 300 L 731 325 L 716 350 L 686 350 L 671 325 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(680.5,311.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">button&lt;br&gt;release</text>
</switch>
</g>
<path d="M 639.82 260 L 678.31 300.9" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 681.91 304.72 L 674.57 302.02 L 678.31 300.9 L 679.66 297.22 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 196 965 L 226 965 L 241 990 L 226 1015 L 196 1015 L 181 990 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(193.5,976.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 149.75 850 L 197.51 959.17" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 199.61 963.98 L 193.6 958.97 L 197.51 959.17 L 200.02 956.16 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 219.11 965 L 322.55 646.06" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 324.17 641.06 L 325.34 648.8 L 322.55 646.06 L 318.68 646.64 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 20.61 1125.1 C 21.78 1121.9 23.67 1120.04 25.67 1120.12 L 95.53 1120.12 C 97.13 1120.08 98.65 1120.63 99.65 1121.61 C 100.64 1122.59 100.99 1123.88 100.6 1125.1 L 81.36 1174.9 C 80.19 1178.1 78.3 1179.96 76.3 1179.88 L 5.42 1179.88 C 4.01 1179.69 2.77 1179.05 1.99 1178.1 C 1.22 1177.15 1 1175.99 1.37 1174.9 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(29.5,1128.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="41" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">notify<br/>button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="27" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<rect x="511" y="1220" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(534.5,1233.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="113" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 114px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">RELEASE_WAITING</div>
</div>
</foreignObject>
<text x="57" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">RELEASE_WAITING</text>
</switch>
</g>
<path d="M 686 1315 L 716 1315 L 731 1340 L 716 1365 L 686 1365 L 671 1340 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(683.5,1326.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 613 1260 L 676.88 1318.07" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 680.76 1321.6 L 673.23 1319.48 L 676.88 1318.07 L 677.94 1314.3 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 436 1580 L 466 1580 L 481 1605 L 466 1630 L 436 1630 L 421 1605 Z" fill="#ea6b66" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(430.5,1598.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout</div>
</div>
</foreignObject>
<text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout</text>
</switch>
</g>
<path d="M 42.56 1015 L 48.73 1113.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 49.06 1118.88 L 45.13 1112.12 L 48.73 1113.64 L 52.11 1111.68 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 55.52 1180 L 187.04 2053.7" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 187.82 2058.89 L 183.32 2052.49 L 187.04 2053.7 L 190.24 2051.45 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 895.31 1495 L 848.83 1574.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 846.18 1579.03 L 846.69 1571.23 L 848.83 1574.5 L 852.73 1574.76 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="827" y="1455" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(850.5,1468.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="112" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 113px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">MAYBE_SPURIOUS</div>
</div>
</foreignObject>
<text x="56" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">MAYBE_SPURIOUS</text>
</switch>
</g>
<path d="M 722.53 1354.11 L 871.16 1451.51" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 875.55 1454.39 L 867.77 1453.48 L 871.16 1451.51 L 871.61 1447.62 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1062 1580 L 1092 1580 L 1107 1605 L 1092 1630 L 1062 1630 L 1047 1605 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(1056.5,1591.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">button&lt;br&gt;release</text>
</switch>
</g>
<path d="M 1077 1580 L 1077 1240 L 677.37 1240" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 672.12 1240 L 679.12 1236.5 L 677.37 1240 L 679.12 1243.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 933.15 1495 L 1051.38 1585.41" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1055.55 1588.6 L 1047.86 1587.12 L 1051.38 1585.41 L 1052.11 1581.56 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 972 1580 L 1002 1580 L 1017 1605 L 1002 1630 L 972 1630 L 957 1605 Z" fill="#9ac7bf" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(966.5,1591.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout<br/>short</div>
</div>
</foreignObject>
<text x="20" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout&lt;br&gt;short</text>
</switch>
</g>
<path d="M 919.31 1495 L 968.47 1574.89" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 971.22 1579.36 L 964.57 1575.24 L 968.47 1574.89 L 970.54 1571.57 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 997.42 1630 L 1019.17 1682.2" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1021.19 1687.04 L 1015.26 1681.93 L 1019.17 1682.2 L 1021.72 1679.24 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1025.62 1763.35 L 991.94 1873.91" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 990.41 1878.93 L 989.1 1871.21 L 991.94 1873.91 L 995.79 1873.25 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 677 830 L 727 880 L 677 930 L 627 880 Z" fill="#999999" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(650.5,865.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="52" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 53px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">spurious<br/>enabled?</div>
</div>
</foreignObject>
<text x="26" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">spurious&lt;br&gt;enabled?</text>
</switch>
</g>
<path d="M 662.66 930 L 611.36 1108.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 609.91 1113.93 L 608.48 1106.23 L 611.36 1108.88 L 615.21 1108.16 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(629.5,1016.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="12" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">no</div>
</div>
</foreignObject>
<text x="6" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">no</text>
</switch>
</g>
<path d="M 596 1315 L 626 1315 L 641 1340 L 626 1365 L 596 1365 L 581 1340 Z" fill="#9ac7bf" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(590.5,1326.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout<br/>short</div>
</div>
</foreignObject>
<text x="20" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout&lt;br&gt;short</text>
</switch>
</g>
<path d="M 595 1260 L 604.75 1308.76" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 605.78 1313.9 L 600.98 1307.73 L 604.75 1308.76 L 607.84 1306.35 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="541" y="1455" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(588.5,1468.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="65" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 66px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">RELEASED</div>
</div>
</foreignObject>
<text x="33" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">RELEASED</text>
</switch>
</g>
<path d="M 612.85 1365 L 619.05 1448.65" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 619.44 1453.89 L 615.43 1447.16 L 619.05 1448.65 L 622.41 1446.65 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 594.85 1495 L 476.62 1585.41" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 472.45 1588.6 L 475.89 1581.56 L 476.62 1585.41 L 480.14 1587.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 627 1580 L 657 1580 L 672 1605 L 657 1630 L 627 1630 L 612 1605 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(624.5,1591.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 624.23 1495 L 636.95 1573.71" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 637.78 1578.9 L 633.21 1572.54 L 636.95 1573.71 L 640.12 1571.43 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="606" y="1680" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(634.5,1693.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="103" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 104px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PRESS_PENDING</div>
</div>
</foreignObject>
<text x="52" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">PRESS_PENDING</text>
</switch>
</g>
<path d="M 653.58 1630 L 674.06 1674.22" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 676.27 1678.99 L 670.15 1674.1 L 674.06 1674.22 L 676.5 1671.16 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 866 1760 L 896 1760 L 911 1785 L 896 1810 L 866 1810 L 851 1785 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(860.5,1771.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">button&lt;br&gt;release</text>
</switch>
</g>
<path d="M 731.88 1720 L 851.38 1772.09" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 856.19 1774.19 L 848.38 1774.6 L 851.38 1772.09 L 851.18 1768.18 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 863.51 1764.15 L 641.87 1499.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 638.49 1495.86 L 645.67 1498.97 L 641.87 1499.88 L 640.31 1503.47 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 636 1760 L 666 1760 L 681 1785 L 666 1810 L 636 1810 L 621 1785 Z" fill="#ea6b66" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(630.5,1778.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout</div>
</div>
</foreignObject>
<text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout</text>
</switch>
</g>
<path d="M 670.61 1885.1 C 671.78 1881.9 673.67 1880.04 675.67 1880.12 L 745.53 1880.12 C 747.13 1880.08 748.65 1880.63 749.65 1881.61 C 750.64 1882.59 750.99 1883.88 750.6 1885.1 L 731.36 1934.9 C 730.19 1938.1 728.3 1939.96 726.3 1939.88 L 655.42 1939.88 C 654.01 1939.69 652.77 1939.05 651.99 1938.1 C 651.22 1937.15 651 1935.99 651.37 1934.9 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(683.5,1888.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="41" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">notify<br/>button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="27" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 677.76 1720 L 663.72 1754.11" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 661.72 1758.97 L 661.15 1751.16 L 663.72 1754.11 L 667.62 1753.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 661 1810 L 686.63 1874.09" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 688.58 1878.96 L 682.74 1873.76 L 686.63 1874.09 L 689.23 1871.16 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 707.35 1940 L 731.45 2053.77" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 732.53 2058.91 L 727.66 2052.78 L 731.45 2053.77 L 734.51 2051.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 437.32 1630 L 205 2054.41" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 202.48 2059.02 L 202.78 2051.2 L 205 2054.41 L 208.92 2054.56 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="331" y="908" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(350.5,921.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="120" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 121px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">RELEASE_DELAYED</div>
</div>
</foreignObject>
<text x="60" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">RELEASE_DELAYED</text>
</switch>
</g>
<path d="M 627 889.02 L 497.27 912.43" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 492.1 913.37 L 498.37 908.68 L 497.27 912.43 L 499.61 915.57 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(608.5,873.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">yes</div>
</div>
</foreignObject>
<text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">yes</text>
</switch>
</g>
<path d="M 426 995 L 456 995 L 471 1020 L 456 1045 L 426 1045 L 411 1020 Z" fill="#9ac7bf" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(420.5,1006.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout<br/>short</div>
</div>
</foreignObject>
<text x="20" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout&lt;br&gt;short</text>
</switch>
</g>
<path d="M 417.52 948 L 430.87 988.95" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 432.5 993.94 L 427 988.37 L 430.87 988.95 L 433.66 986.2 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 461.43 1035.96 L 557.58 1111.08" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 561.72 1114.31 L 554.05 1112.76 L 557.58 1111.08 L 558.36 1107.24 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 536 995 L 566 995 L 581 1020 L 566 1045 L 536 1045 L 521 1020 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(533.5,1006.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 441.43 948 L 524.16 1002.36" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 528.55 1005.25 L 520.78 1004.33 L 524.16 1002.36 L 524.62 998.48 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 553.24 995 L 618.64 266.34" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 619.11 261.11 L 621.97 268.4 L 618.64 266.34 L 614.99 267.77 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<ellipse cx="351" cy="435" rx="25" ry="25" fill="#ea6b66" stroke="#000000" pointer-events="none"/>
<g transform="translate(337.5,421.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="27" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 28px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">set<br/>timer</div>
</div>
</foreignObject>
<text x="14" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 351 360 L 351 403.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 351 408.88 L 347.5 401.88 L 351 403.63 L 354.5 401.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 351 460 L 351 488.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 351 493.88 L 347.5 486.88 L 351 488.63 L 354.5 486.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<ellipse cx="691" cy="665" rx="25" ry="25" fill="#ea6b66" stroke="#000000" pointer-events="none"/>
<g transform="translate(677.5,651.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="27" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 28px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">set<br/>timer</div>
</div>
</foreignObject>
<text x="14" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 700.26 350 L 691.92 633.65" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 691.77 638.89 L 688.48 631.79 L 691.92 633.65 L 695.47 632 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 688.68 689.89 L 684.14 733.84" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 683.6 739.06 L 680.84 731.74 L 684.14 733.84 L 687.8 732.46 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<ellipse cx="680" cy="775" rx="35" ry="35" fill="#9ac7bf" stroke="#000000" pointer-events="none"/>
<g transform="translate(656.5,761.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="46" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 47px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">set short<br/>timer</div>
</div>
</foreignObject>
<text x="23" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">set short&lt;br&gt;timer</text>
</switch>
</g>
<path d="M 679.36 809.99 L 678.73 823.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 678.48 828.88 L 675.31 821.73 L 678.73 823.64 L 682.3 822.05 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 498 675 L 524 675 L 541 700 L 524 725 L 498 725 L 481 700 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(493.5,686.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 376 640 L 481.5 686.89" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 486.29 689.02 L 478.48 689.37 L 481.5 686.89 L 481.32 682.98 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 516.98 675 L 614.74 266.19" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 615.96 261.09 L 617.73 268.71 L 614.74 266.19 L 610.93 267.08 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 118 965 L 144 965 L 161 990 L 144 1015 L 118 1015 L 101 990 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(113.5,976.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 139.75 850 L 132.96 958.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 132.63 963.88 L 129.58 956.68 L 132.96 958.64 L 136.56 957.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 118.5 1015 L 68.85 1114.3" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 66.5 1119 L 66.5 1111.17 L 68.85 1114.3 L 72.76 1114.3 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 418 1315 L 444 1315 L 461 1340 L 444 1365 L 418 1365 L 401 1340 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(413.5,1326.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 559 1260 L 458.22 1322.99" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 453.77 1325.77 L 457.85 1319.09 L 458.22 1322.99 L 461.56 1325.03 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 422.89 1365 L 199.45 2053.94" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 197.83 2058.94 L 196.66 2051.2 L 199.45 2053.94 L 203.32 2053.36 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 528 1580 L 554 1580 L 571 1605 L 554 1630 L 528 1630 L 511 1605 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(523.5,1591.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 608.69 1495 L 559.53 1574.89" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 556.78 1579.36 L 557.46 1571.57 L 559.53 1574.89 L 563.43 1575.24 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 524.46 1627.44 L 209.51 2054.87" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 206.4 2059.1 L 207.73 2051.39 L 209.51 2054.87 L 213.37 2055.54 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 744 1760 L 770 1760 L 787 1785 L 770 1810 L 744 1810 L 727 1785 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(739.5,1771.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 702.71 1720 L 735.46 1759.21" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 738.82 1763.24 L 731.65 1760.11 L 735.46 1759.21 L 737.02 1755.62 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 745.8 1810 L 717.04 1874.19" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 714.9 1878.98 L 714.56 1871.16 L 717.04 1874.19 L 720.95 1874.02 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 904 1580 L 930 1580 L 947 1605 L 930 1630 L 904 1630 L 887 1605 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(899.5,1591.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 506 1315 L 536 1315 L 551 1340 L 536 1365 L 506 1365 L 491 1340 Z" fill="#ea6b66" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(500.5,1333.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout</div>
</div>
</foreignObject>
<text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout</text>
</switch>
</g>
<path d="M 577 1260 L 540.81 1311.71" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 537.79 1316.01 L 538.94 1308.27 L 540.81 1311.71 L 544.68 1312.28 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 509.85 1365 L 202.51 2054.18" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 200.37 2058.98 L 200.03 2051.16 L 202.51 2054.18 L 206.42 2054.01 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 908.54 1495 L 914.59 1573.65" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 914.99 1578.89 L 910.96 1572.17 L 914.59 1573.65 L 917.94 1571.64 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 950.61 1885.1 C 951.78 1881.9 953.67 1880.04 955.67 1880.12 L 1025.53 1880.12 C 1027.13 1880.08 1028.65 1880.63 1029.65 1881.61 C 1030.64 1882.59 1030.99 1883.88 1030.6 1885.1 L 1011.36 1934.9 C 1010.19 1938.1 1008.3 1939.96 1006.3 1939.88 L 935.42 1939.88 C 934.01 1939.69 932.77 1939.05 931.99 1938.1 C 931.22 1937.15 931 1935.99 931.37 1934.9 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(963.5,1888.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="41" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">notify<br/>button<br/>press</div>
</div>
</foreignObject>
<text x="17" y="27" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 922.25 1630 L 973.4 1873.77" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 974.48 1878.91 L 969.61 1872.77 L 973.4 1873.77 L 976.46 1871.34 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 937.94 1940 L 770.93 2056.36" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 766.62 2059.36 L 770.37 2052.49 L 770.93 2056.36 L 774.37 2058.23 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 818 300 L 844 300 L 861 325 L 844 350 L 818 350 L 801 325 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(813.5,311.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 670.41 260 L 800.96 312.84" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 805.83 314.81 L 798.02 315.43 L 800.96 312.84 L 800.65 308.94 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 313 995 L 339 995 L 356 1020 L 339 1045 L 313 1045 L 296 1020 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(308.5,1006.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 392.52 948 L 348.51 995.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 344.95 999.49 L 347.13 991.98 L 348.51 995.64 L 352.27 996.73 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 190.61 1235.1 C 191.78 1231.9 193.67 1230.04 195.67 1230.12 L 265.53 1230.12 C 267.13 1230.08 268.65 1230.63 269.65 1231.61 C 270.64 1232.59 270.99 1233.88 270.6 1235.1 L 251.36 1284.9 C 250.19 1288.1 248.3 1289.96 246.3 1289.88 L 175.42 1289.88 C 174.01 1289.69 172.77 1289.05 171.99 1288.1 C 171.22 1287.15 171 1285.99 171.37 1284.9 Z" fill="#ffd966" stroke="#000000" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(199.5,1238.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="41" height="41" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">notify<br/>button<br/>release</div>
</div>
</foreignObject>
<text x="21" y="27" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 315.06 1045 L 236.68 1224.17" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 234.57 1228.98 L 234.17 1221.16 L 236.68 1224.17 L 240.59 1223.97 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 219.9 1290 L 191.96 2053.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 191.77 2058.88 L 188.53 2051.76 L 191.96 2053.64 L 195.53 2052.02 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(295.5,-0.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="430" height="169" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; overflow: hidden; max-height: 190px; max-width: 430px; width: 430px; white-space: normal; overflow-wrap: normal;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">
<h1>Entry states: IS_UP, IS_DOWN<br/></h1>
<p>Assumption: state is stored per-button, and OTHER BUTTON events are always processed before the actual button. Stored state per button is a single bit (up/down), a single state for the state machine across the device is sufficient.</p>
<p>Start the state machine with IS_UP or IS_DOWN based on the button's bit, any OTHER BUTTON event will reset it to that state anyway, so the state can be re-used for the new button.<br/></p>
</div>
</div>
</foreignObject>
<text x="215" y="91" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 468 310 L 494 310 L 511 335 L 494 360 L 468 360 L 451 335 Z" fill="#e1d5e7" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(463.5,321.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="34" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">other<br/>button</div>
</div>
</foreignObject>
<text x="17" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 378.37 260 L 455 316" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 459.24 319.1 L 451.52 317.8 L 455 316 L 455.66 312.14 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 460.14 319.76 L 383.51 263.76" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 379.27 260.66 L 386.99 261.96 L 383.51 263.76 L 382.86 267.62 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<ellipse cx="1037" cy="1725" rx="40" ry="40" fill="#ffffff" stroke="#000000" pointer-events="none"/>
<g transform="translate(1013.5,1711.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="47" height="27" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 48px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">enable<br/>spurious</div>
</div>
</foreignObject>
<text x="24" y="20" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">enable&lt;br&gt;spurious</text>
</switch>
</g>
<path d="M 597.84 1175 L 593.77 1213.67" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 593.22 1218.89 L 590.47 1211.56 L 593.77 1213.67 L 597.44 1212.29 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 481 310 L 481 240 L 437.37 240" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 432.12 240 L 439.12 236.5 L 437.37 240 L 439.12 243.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 831 300 L 831 240 L 707.37 240" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 702.12 240 L 709.12 236.5 L 707.37 240 L 709.12 243.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 344.68 555 L 336.52 593.77" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 335.44 598.91 L 333.46 591.34 L 336.52 593.77 L 340.31 592.78 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="657" y="2060" width="160" height="40" rx="6" ry="6" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(708.5,2073.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="56" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 57px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">IS_DOWN</div>
</div>
</foreignObject>
<text x="28" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">IS_DOWN</text>
</switch>
</g>
<path d="M 843.3 1630 L 963.44 1874.29" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 965.75 1879 L 959.52 1874.26 L 963.44 1874.29 L 965.8 1871.17 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 816 1580 L 846 1580 L 861 1605 L 846 1630 L 816 1630 L 801 1605 Z" fill="#ea6b66" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(810.5,1598.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="40" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">timeout</div>
</div>
</foreignObject>
<text x="20" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">timeout</text>
</switch>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -0,0 +1,50 @@
# Source for the button debouncing wave diagram
# Paste into http://wavedrom.com/editor.html
{signal: [
{name:'current mode', wave: '3............', data: ['normal button press and release']},
{name:'physical button', wave: '01......0....'},
{name:'application ', wave: '01......0....'},
{},
['bounce mode',
{name:'current mode', wave: '4............', data: ['debounced button press']},
{name:'physical button', wave: '0101...0.....'},
{name: 'timeouts', wave: '01...0.1...0.'},
{name:'application ', wave: '01.....0.....'},
{},
{name:'current mode', wave: '4............', data: ['debounced button release']},
{name:'physical button', wave: '1...010......'},
{name: 'timeouts', wave: '0...1...0....'},
{name:'application ', wave: '1...0........'},
{},
{name:'current mode', wave: '5............', data: ['delayed button press']},
{name:'physical button', wave: '1...01.......'},
{name: 'timeouts', wave: '0...1...0....'},
{name:'application ', wave: '1...0...1....'},
{},
{name:'current mode', wave: '5............', data: ['delayed button release']},
{name:'physical button', wave: '0...10.......'},
{name: 'timeouts', wave: '0...1...0....'},
{name:'application ', wave: '0...1...0....'},
],
{},
['spurious mode',
{name:'current mode', wave: '3............', data: ['first spurious button release ']},
{name:'physical button', wave: '1.......01...'},
{name:'application ', wave: '1.......01...'},
{},
{name:'current mode', wave: '3............', data: ['later spurious button release ']},
{name:'physical button', wave: '1....01......'},
{name: 'timeouts', wave: '0....1..0....'},
{name:'application ', wave: '1............'},
{},
{name:'current mode', wave: '3............', data: ['delayed release in spurious mode ']},
{name:'physical button', wave: '1....0.......'},
{name: 'timeouts', wave: '0....1..0....'},
{name:'application ', wave: '1.......0....'}
],
],
head:{
text:'Button Debouncing Scenarios',
},
}

View file

@ -9,12 +9,25 @@ though the user only pressed or clicked the button once. This effect can be
counteracted by "debouncing" the buttons, usually by ignoring erroneous counteracted by "debouncing" the buttons, usually by ignoring erroneous
events. events.
libinput has a built-in debouncing for hardware defects. This feature is libinput provides two methods of debouncing buttons, referred to as the
available for all button-base devices but not active by default. When "bounce" and "spurious" methods:
libinput detects a faulty button on a device, debouncing is enabled and a
warning is printed to the log. Subsequent button events are handled - In the "bounce" method, libinput monitors hardware bouncing on button
correctly in that bouncing button events are ignored, a user should thus see state changes, i.e. when a user clicks or releases a button. For example,
the expected behavior. if a user presses a button but the hardware generates a
press-release-press sequence in quick succession, libinput ignores the
release and second press event. This method is always enabled.
- in the "spurious" method, libinput detects spurious releases of a button
while the button is physically held down by the user. These releases are
immediately followed by a press event. libinput monitors for these events
and ignores the release and press event. This method is disabled by
default and enables once libinput detects the first faulty event sequence.
The "bounce" method guarantees that all press events are delivered
immediately and most release events are delivered immediately. The
"spurious" method requires that release events are delayed, libinput thus
does not enable this method unless a faulty event sequence is detected. A
message is printed to the log when spurious deboucing was detected.
Note that libinput's debouncing intended to correct hardware damage or Note that libinput's debouncing intended to correct hardware damage or
substandard hardware. Debouncing is also used as an accessibility feature substandard hardware. Debouncing is also used as an accessibility feature
@ -23,4 +36,12 @@ physical key presses, usually caused by involuntary muscle movement, must be
filtered to only one key press. This feature must be implemented higher in filtered to only one key press. This feature must be implemented higher in
the stack, libinput is limited to hardware debouncing. the stack, libinput is limited to hardware debouncing.
Below is an illustration of the button debouncing modes to show the relation
of the physical button state and the application state. Where applicable, an
extra line is added to show the timeouts used by libinput that
affect the button state handling. The waveform's high and low states
correspond to the buttons 'pressed' and 'released' states, respectively.
@image html button-debouncing-wave-diagram.svg "Diagram illustrating button debouncing"
*/ */

View file

@ -125,6 +125,20 @@ Changes performed by xinput do not persist across device hotplugs. xinput is
considered a debugging and testing tool only and should not be used for considered a debugging and testing tool only and should not be used for
permanent configurations. permanent configurations.
@section faq_configuration Can you add a configuration option for $FEATURE?
No. At least that's going to be the initial answer. Read <a
href="http://who-t.blogspot.com/2016/04/why-libinput-doesnt-have-lot-of-config.html">Why
libinput doesn't have a lot of configuration options</a> first.
Configuration options for most features are a signal that we are incapable
of handling it correctly. To get to that point, we want to be sure we're
truly incapable of doing so. libinput has several features that
are handled automatically (and correctly) that users wanted to have
configuration options for initially.
So the answer to this question will almost always be 'no'. A configuration
option is, in most cases, a cop-out.
@section faq_synclient Why don't synclient and syndaemon work with libinput? @section faq_synclient Why don't synclient and syndaemon work with libinput?
Synclient and syndaemon rely on X input device properties that are specific Synclient and syndaemon rely on X input device properties that are specific

View file

@ -9,8 +9,8 @@ EXTRACT_STATIC = YES
MAX_INITIALIZER_LINES = 0 MAX_INITIALIZER_LINES = 0
QUIET = YES QUIET = YES
INPUT = @INPUT@ INPUT = @INPUT@
IMAGE_PATH = @top_srcdir@/doc/svg \ IMAGE_PATH = "@top_srcdir@/doc/svg" \
@top_srcdir@/doc/dot "@top_srcdir@/doc/dot"
GENERATE_HTML = YES GENERATE_HTML = YES
SEARCHENGINE = NO SEARCHENGINE = NO
USE_MATHJAX = YES USE_MATHJAX = YES
@ -20,11 +20,11 @@ MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES EXPAND_ONLY_PREDEF = YES
PREDEFINED = LIBINPUT_ATTRIBUTE_PRINTF(f, a)= \ PREDEFINED = LIBINPUT_ATTRIBUTE_PRINTF(f, a)= \
LIBINPUT_ATTRIBUTE_DEPRECATED LIBINPUT_ATTRIBUTE_DEPRECATED
DOTFILE_DIRS = @top_srcdir@/doc/dot DOTFILE_DIRS = "@top_srcdir@/doc/dot"
HTML_HEADER = @top_srcdir@/doc/style/header.html HTML_HEADER = "@top_srcdir@/doc/style/header.html"
HTML_FOOTER = @top_srcdir@/doc/style/footer.html HTML_FOOTER = "@top_srcdir@/doc/style/footer.html"
HTML_EXTRA_STYLESHEET = @top_srcdir@/doc/style/bootstrap.css \ HTML_EXTRA_STYLESHEET = "@top_srcdir@/doc/style/bootstrap.css" \
@top_srcdir@/doc/style/customdoxygen.css \ "@top_srcdir@/doc/style/customdoxygen.css" \
@top_srcdir@/doc/style/libinputdoxygen.css "@top_srcdir@/doc/style/libinputdoxygen.css"
USE_MDFILE_AS_MAINPAGE = @top_srcdir@/README.md USE_MDFILE_AS_MAINPAGE = "@top_srcdir@/README.md"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 105 KiB

View file

@ -1,18 +1,21 @@
/** /**
@page test-suite libinput test suite @page test-suite libinput test suite
The libinput test suite is based on libinput ships with a number of tests all run automatically on `ninja test`.
[Check](http://check.sourceforge.net/doc/check_html/) and runs automatically The primary test suite is the `libinput-test-suite-runner`. When testing,
during `make check`. Check itself is wrapped into a libinput-specific test the `libinput-test-suite-runner` should always be invoked to check for
suite called *litest*. Tests are found in `$srcdir/test/`, the main test behavior changes.
suite is `libinput-test-suite-runner`.
The test suite has a make-like job control enabled by the `-j` or `--jobs` The test suite runner uses
flag and will fork off as many parallel processes as given by this flag. The [Check](http://check.sourceforge.net/doc/check_html/) underneath the hood
default if unspecified is 8. When debugging a specific test case failure it but most of the functionality is abstracted into *litest* wrappers.
is recommended to employ test filtures (see @ref test-filtering) and disable
parallel tests. The test suite automatically disables parallel make when run The test suite runner has a make-like job control enabled by the `-j` or
in gdb. `--jobs` flag and will fork off as many parallel processes as given by this
flag. The default if unspecified is 8. When debugging a specific test case
failure it is recommended to employ test filtures (see @ref test-filtering)
and disable parallel tests. The test suite automatically disables parallel
make when run in gdb.
@section test-config X.Org config to avoid interference @section test-config X.Org config to avoid interference
@ -28,35 +31,67 @@ with your desktop.
Most tests require the creation of uinput devices and access to the Most tests require the creation of uinput devices and access to the
resulting `/dev/input/eventX` nodes. Some tests require temporary udev rules. resulting `/dev/input/eventX` nodes. Some tests require temporary udev rules.
<b>This usually requires the tests to be run as root</b>. <b>This usually requires the tests to be run as root</b>. If not run as
root, the test suite runner will exit with status 77, interpreted as
"skipped" by ninja.
@section test-filtering Selective running of tests @section test-filtering Selective running of tests
litest's tests are grouped by test groups and devices. A test group is e.g. litest's tests are grouped into test groups, test names and devices. A test
"touchpad:tap" and incorporates all tapping-related tests for touchpads. group is e.g. "touchpad:tap" and incorporates all tapping-related tests for
Each test function is (usually) run with one or more specific devices. touchpads. Each test function is (usually) run with one or more specific
The `--list` commandline argument shows the list of suites and tests. devices. The `--list` commandline argument shows the list of suites and
tests. This is useful when trying to figure out if a specific test is
run for a device.
@code @code
$ ./test/libinput-test-suite-runner --list $ ./test/libinput-test-suite-runner --list
device:wheel: ...
wheel only pointer:left-handed:
blackwidow pointer_left_handed_during_click_multiple_buttons:
device:invalid devices: trackpoint
no device ms-surface-cover
device:group: mouse-wheelclickcount
no device mouse-wheelclickangle
logitech trackball low-dpi-mouse
MS surface cover mouse-roccat
mouse_roccat mouse-wheel-tilt
wheel only mouse
blackwidow logitech-trackball
cyborg-rat
magicmouse
pointer_left_handed_during_click:
trackpoint
ms-surface-cover
mouse-wheelclickcount
mouse-wheelclickangle
low-dpi-mouse
mouse-roccat
mouse-wheel-tilt
mouse
logitech-trackball
cyborg-rat
litest-magicmouse-device
pointer_left_handed:
trackpoint
ms-surface-cover
mouse-wheelclickcount
mouse-wheelclickangle
low-dpi-mouse
mouse-roccat
mouse-wheel-tilt
mouse
... ...
@endcode @endcode
In the above example, the "device:wheel" suite is run for the "wheel only" and In the above example, the "pointer:left-handed" suite contains multiple
the "blackwidow" device. Both devices are automatically instantiated through tests, e.g. "pointer_left_handed_during_click" (this is also the function
uinput by litest. The "no device" entry signals that litest does not name of the test, making it easy to grep for). This particular test is run
instantiate a uinput device for a specific test (though the test itself may for various devices including the trackpoint device and the magic mouse
device.
The "no device" entry signals that litest does not instantiate a uinput
device for a specific test (though the test itself may
instantiate one). instantiate one).
The `--filter-test` argument enables selective running of tests through The `--filter-test` argument enables selective running of tests through
@ -93,7 +128,7 @@ environment variable, if set, also enables verbose mode.
@code @code
$ ./test/libinput-test-suite-runner --verbose $ ./test/libinput-test-suite-runner --verbose
$ LITEST_VERBOSE=1 make check $ LITEST_VERBOSE=1 ninja test
@endcode @endcode
*/ */

View file

@ -1,5 +1,5 @@
project('libinput', 'c', 'cpp', project('libinput', 'c', 'cpp',
version : '1.9.1', version : '1.9.3',
license : 'MIT/Expat', license : 'MIT/Expat',
default_options : [ 'c_std=gnu99', 'warning_level=2' ], default_options : [ 'c_std=gnu99', 'warning_level=2' ],
meson_version : '>= 0.40.0') meson_version : '>= 0.40.0')
@ -156,7 +156,9 @@ src_libinput = [
'src/libinput-private.h', 'src/libinput-private.h',
'src/evdev.c', 'src/evdev.c',
'src/evdev.h', 'src/evdev.h',
'src/evdev-debounce.c',
'src/evdev-fallback.c', 'src/evdev-fallback.c',
'src/evdev-fallback.h',
'src/evdev-middle-button.c', 'src/evdev-middle-button.c',
'src/evdev-mt-touchpad.c', 'src/evdev-mt-touchpad.c',
'src/evdev-mt-touchpad.h', 'src/evdev-mt-touchpad.h',
@ -298,6 +300,7 @@ if get_option('documentation')
meson.source_root() + '/doc/dot/libinput-stack-gnome.gv', meson.source_root() + '/doc/dot/libinput-stack-gnome.gv',
meson.source_root() + '/doc/dot/evemu.gv', meson.source_root() + '/doc/dot/evemu.gv',
# svgs # svgs
meson.source_root() + '/doc/svg/button-debouncing-wave-diagram.svg',
meson.source_root() + '/doc/svg/button-scrolling.svg', meson.source_root() + '/doc/svg/button-scrolling.svg',
meson.source_root() + '/doc/svg/clickfinger.svg', meson.source_root() + '/doc/svg/clickfinger.svg',
meson.source_root() + '/doc/svg/clickfinger-distance.svg', meson.source_root() + '/doc/svg/clickfinger-distance.svg',

562
src/evdev-debounce.c Normal file
View file

@ -0,0 +1,562 @@
/*
* 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 currently unhandled.
*/
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_DOWN_WAITING);
CASE_RETURN_STRING(DEBOUNCE_STATE_RELEASE_PENDING);
CASE_RETURN_STRING(DEBOUNCE_STATE_RELEASE_DELAYED);
CASE_RETURN_STRING(DEBOUNCE_STATE_RELEASE_WAITING);
CASE_RETURN_STRING(DEBOUNCE_STATE_MAYBE_SPURIOUS);
CASE_RETURN_STRING(DEBOUNCE_STATE_RELEASED);
CASE_RETURN_STRING(DEBOUNCE_STATE_PRESS_PENDING);
}
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_PRESS_PENDING);
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 %sbutton_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;
unsigned int code = fallback->debounce.button_code;
uint64_t time = fallback->debounce.button_time;
code = evdev_to_left_handed(device, code);
evdev_pointer_notify_physical_button(device, time, code, 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_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_RELEASE_DELAYED);
} else {
debounce_set_state(fallback, DEBOUNCE_STATE_RELEASE_WAITING);
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_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_state(fallback, DEBOUNCE_STATE_RELEASE_PENDING);
/* 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_release_pending_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_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_release_delayed_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_RELEASE_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_release_waiting_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
{
switch (event) {
case DEBOUNCE_EVENT_PRESS:
/* Note: in a bouncing PRP case, we use the last press
* event time */
fallback->debounce.button_time = time;
debounce_set_state(fallback, DEBOUNCE_STATE_MAYBE_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_RELEASED);
break;
case DEBOUNCE_EVENT_OTHERBUTTON:
debounce_set_state(fallback, DEBOUNCE_STATE_IS_UP);
break;
}
}
static void
debounce_maybe_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_state(fallback, DEBOUNCE_STATE_RELEASE_WAITING);
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_released_handle_event(struct fallback_dispatch *fallback, enum debounce_event event, uint64_t time)
{
switch (event) {
case DEBOUNCE_EVENT_PRESS:
/* Note: in a debouncing PRP case, we use the last press'
* time */
fallback->debounce.button_time = time;
debounce_set_state(fallback, DEBOUNCE_STATE_PRESS_PENDING);
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_press_pending_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_state(fallback, DEBOUNCE_STATE_RELEASED);
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_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_DOWN_WAITING:
debounce_down_waiting_handle_event(fallback, event, time);
break;
case DEBOUNCE_STATE_RELEASE_PENDING:
debounce_release_pending_handle_event(fallback, event, time);
break;
case DEBOUNCE_STATE_RELEASE_DELAYED:
debounce_release_delayed_handle_event(fallback, event, time);
break;
case DEBOUNCE_STATE_RELEASE_WAITING:
debounce_release_waiting_handle_event(fallback, event, time);
break;
case DEBOUNCE_STATE_MAYBE_SPURIOUS:
debounce_maybe_spurious_handle_event(fallback, event, time);
break;
case DEBOUNCE_STATE_RELEASED:
debounce_released_handle_event(fallback, event, time);
break;
case DEBOUNCE_STATE_PRESS_PENDING:
debounce_press_pending_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)
{
unsigned int changed[16] = {0}; /* event codes of changed buttons */
size_t nchanged = 0;
bool flushed = false;
for (unsigned int code = 0; code <= KEY_MAX; code++) {
if (get_key_type(code) != KEY_TYPE_BUTTON)
continue;
if (hw_key_has_changed(dispatch, code))
changed[nchanged++] = code;
/* 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 ||
changed[0] != dispatch->debounce.button_code) {
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) {
debounce_set_state(dispatch,
!is_down ?
DEBOUNCE_STATE_IS_DOWN :
DEBOUNCE_STATE_IS_UP);
flushed = false;
}
dispatch->debounce.button_code = 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];
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);
}

View file

@ -28,124 +28,10 @@
#include <mtdev-plumbing.h> #include <mtdev-plumbing.h>
#include "evdev.h" #include "evdev-fallback.h"
#define DEBOUNCE_TIME ms2us(12) #define DEBOUNCE_TIME ms2us(12)
struct fallback_dispatch {
struct evdev_dispatch base;
struct evdev_device *device;
struct libinput_device_config_calibration calibration;
struct {
bool is_enabled;
int angle;
struct matrix matrix;
struct libinput_device_config_rotation config;
} rotation;
struct {
struct device_coords point;
int32_t seat_slot;
struct {
struct device_coords min, max;
struct ratelimit range_warn_limit;
} warning_range;
} abs;
struct {
int slot;
struct mt_slot *slots;
size_t slots_len;
bool want_hysteresis;
struct device_coords hysteresis_margin;
} mt;
struct device_coords rel;
struct {
/* The struct for the tablet mode switch device itself */
struct {
int state;
} sw;
/* The struct for other devices listening to the tablet mode
switch */
struct {
struct evdev_device *sw_device;
struct libinput_event_listener listener;
} other;
} tablet_mode;
/* Bitmask of pressed keys used to ignore initial release events from
* the kernel. */
unsigned long hw_key_mask[NLONGS(KEY_CNT)];
enum evdev_event_type pending_event;
/* true if we're reading events (i.e. not suspended) but we're
ignoring them */
bool ignore_events;
struct {
enum evdev_debounce_state state;
unsigned int button_code;
uint64_t button_up_time;
struct libinput_timer timer;
} debounce;
struct {
enum switch_reliability reliability;
bool is_closed;
bool is_closed_client_state;
/* We allow up to 3 paired keyboards for the lid switch
* listener. Only one keyboard should exist, but that can
* have more than one event node.
*
* Note: this is a sparse list, any element may have a
* non-NULL device.
*/
struct paired_keyboard {
struct evdev_device *device;
struct libinput_event_listener listener;
} paired_keyboard[3];
} lid;
};
static inline struct fallback_dispatch*
fallback_dispatch(struct evdev_dispatch *dispatch)
{
evdev_verify_dispatch_type(dispatch, DISPATCH_FALLBACK);
return container_of(dispatch, struct fallback_dispatch, base);
}
enum key_type {
KEY_TYPE_NONE,
KEY_TYPE_KEY,
KEY_TYPE_BUTTON,
};
static void
hw_set_key_down(struct fallback_dispatch *dispatch, int code, int pressed)
{
long_set_bit_state(dispatch->hw_key_mask, code, pressed);
}
static bool
hw_is_key_down(struct fallback_dispatch *dispatch, int code)
{
return long_bit_is_set(dispatch->hw_key_mask, code);
}
static int
get_key_down_count(struct evdev_device *device, int code)
{
return device->key_count[code];
}
static void static void
fallback_keyboard_notify_key(struct fallback_dispatch *dispatch, fallback_keyboard_notify_key(struct fallback_dispatch *dispatch,
struct evdev_device *device, struct evdev_device *device,
@ -317,6 +203,58 @@ fallback_flush_relative_motion(struct fallback_dispatch *dispatch,
pointer_notify_motion(base, time, &accel, &raw); pointer_notify_motion(base, time, &accel, &raw);
} }
static void
fallback_flush_wheels(struct fallback_dispatch *dispatch,
struct evdev_device *device,
uint64_t time)
{
struct normalized_coords wheel_degrees = { 0.0, 0.0 };
struct discrete_coords discrete = { 0.0, 0.0 };
enum libinput_pointer_axis_source source;
if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
return;
if (dispatch->wheel.y != 0) {
wheel_degrees.y = -1 * dispatch->wheel.y *
device->scroll.wheel_click_angle.y;
discrete.y = -1 * dispatch->wheel.y;
source = device->scroll.is_tilt.vertical ?
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
evdev_notify_axis(
device,
time,
AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
source,
&wheel_degrees,
&discrete);
dispatch->wheel.y = 0;
}
if (dispatch->wheel.x != 0) {
wheel_degrees.x = dispatch->wheel.x *
device->scroll.wheel_click_angle.x;
discrete.x = dispatch->wheel.x;
source = device->scroll.is_tilt.horizontal ?
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
evdev_notify_axis(
device,
time,
AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
source,
&wheel_degrees,
&discrete);
dispatch->wheel.x = 0;
}
}
static void static void
fallback_flush_absolute_motion(struct fallback_dispatch *dispatch, fallback_flush_absolute_motion(struct fallback_dispatch *dispatch,
struct evdev_device *device, struct evdev_device *device,
@ -514,267 +452,14 @@ fallback_flush_st_up(struct fallback_dispatch *dispatch,
return true; return true;
} }
static enum evdev_event_type
fallback_flush_pending_event(struct fallback_dispatch *dispatch,
struct evdev_device *device,
uint64_t time)
{
enum evdev_event_type sent_event;
int slot_idx;
sent_event = dispatch->pending_event;
switch (dispatch->pending_event) {
case EVDEV_NONE:
break;
case EVDEV_RELATIVE_MOTION:
fallback_flush_relative_motion(dispatch, device, time);
break;
case EVDEV_ABSOLUTE_MT_DOWN:
slot_idx = dispatch->mt.slot;
if (!fallback_flush_mt_down(dispatch,
device,
slot_idx,
time))
sent_event = EVDEV_NONE;
break;
case EVDEV_ABSOLUTE_MT_MOTION:
slot_idx = dispatch->mt.slot;
if (!fallback_flush_mt_motion(dispatch,
device,
slot_idx,
time))
sent_event = EVDEV_NONE;
break;
case EVDEV_ABSOLUTE_MT_UP:
slot_idx = dispatch->mt.slot;
if (!fallback_flush_mt_up(dispatch,
device,
slot_idx,
time))
sent_event = EVDEV_NONE;
break;
case EVDEV_ABSOLUTE_TOUCH_DOWN:
if (!fallback_flush_st_down(dispatch, device, time))
sent_event = EVDEV_NONE;
break;
case EVDEV_ABSOLUTE_MOTION:
if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
if (fallback_flush_st_motion(dispatch,
device,
time))
sent_event = EVDEV_ABSOLUTE_MT_MOTION;
else
sent_event = EVDEV_NONE;
} else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
fallback_flush_absolute_motion(dispatch,
device,
time);
}
break;
case EVDEV_ABSOLUTE_TOUCH_UP:
if (!fallback_flush_st_up(dispatch, device, time))
sent_event = EVDEV_NONE;
break;
default:
assert(0 && "Unknown pending event type");
break;
}
dispatch->pending_event = EVDEV_NONE;
return sent_event;
}
static enum key_type
get_key_type(uint16_t code)
{
switch (code) {
case BTN_TOOL_PEN:
case BTN_TOOL_RUBBER:
case BTN_TOOL_BRUSH:
case BTN_TOOL_PENCIL:
case BTN_TOOL_AIRBRUSH:
case BTN_TOOL_MOUSE:
case BTN_TOOL_LENS:
case BTN_TOOL_QUINTTAP:
case BTN_TOOL_DOUBLETAP:
case BTN_TOOL_TRIPLETAP:
case BTN_TOOL_QUADTAP:
case BTN_TOOL_FINGER:
case BTN_TOUCH:
return KEY_TYPE_NONE;
}
if (code >= KEY_ESC && code <= KEY_MICMUTE)
return KEY_TYPE_KEY;
if (code >= BTN_MISC && code <= BTN_GEAR_UP)
return KEY_TYPE_BUTTON;
if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE)
return KEY_TYPE_KEY;
if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT)
return KEY_TYPE_BUTTON;
if (code >= KEY_ALS_TOGGLE && code <= KEY_ONSCREEN_KEYBOARD)
return KEY_TYPE_KEY;
if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40)
return KEY_TYPE_BUTTON;
return KEY_TYPE_NONE;
}
static void static void
fallback_process_touch_button(struct fallback_dispatch *dispatch, fallback_process_touch_button(struct fallback_dispatch *dispatch,
struct evdev_device *device, struct evdev_device *device,
uint64_t time, int value) uint64_t time, int value)
{ {
if (dispatch->pending_event != EVDEV_NONE && dispatch->pending_event |= (value) ?
dispatch->pending_event != EVDEV_ABSOLUTE_MOTION)
fallback_flush_pending_event(dispatch, device, time);
dispatch->pending_event = (value ?
EVDEV_ABSOLUTE_TOUCH_DOWN : EVDEV_ABSOLUTE_TOUCH_DOWN :
EVDEV_ABSOLUTE_TOUCH_UP); EVDEV_ABSOLUTE_TOUCH_UP;
}
static inline void
fallback_flush_debounce(struct fallback_dispatch *dispatch,
struct evdev_device *device)
{
int code = dispatch->debounce.button_code;
int button;
if (dispatch->debounce.state != DEBOUNCE_ACTIVE)
return;
if (hw_is_key_down(dispatch, code)) {
button = evdev_to_left_handed(device, code);
evdev_pointer_notify_physical_button(device,
dispatch->debounce.button_up_time,
button,
LIBINPUT_BUTTON_STATE_RELEASED);
hw_set_key_down(dispatch, code, 0);
}
dispatch->debounce.state = DEBOUNCE_ON;
}
static void
fallback_debounce_timeout(uint64_t now, void *data)
{
struct evdev_device *device = data;
struct fallback_dispatch *dispatch =
fallback_dispatch(device->dispatch);
fallback_flush_debounce(dispatch, device);
}
static bool
fallback_filter_debounce_press(struct fallback_dispatch *dispatch,
struct evdev_device *device,
struct input_event *e,
uint64_t time)
{
bool filter = false;
uint64_t tdelta;
/* If other button is pressed while we're holding back the release,
* flush the pending release (if any) and continue. We don't handle
* this situation, if you have a mouse that needs per-button
* debouncing, consider writing to santa for a new mouse.
*/
if (e->code != dispatch->debounce.button_code) {
if (dispatch->debounce.state == DEBOUNCE_ACTIVE) {
libinput_timer_cancel(&dispatch->debounce.timer);
fallback_flush_debounce(dispatch, device);
}
return false;
}
tdelta = time - dispatch->debounce.button_up_time;
assert((int64_t)tdelta >= 0);
if (tdelta < DEBOUNCE_TIME) {
switch (dispatch->debounce.state) {
case DEBOUNCE_INIT:
/* This is the first time we debounce, enable proper debouncing
from now on but filter this press event */
filter = true;
evdev_log_info(device,
"Enabling button debouncing, "
"see %sbutton_debouncing.html for details\n",
HTTP_DOC_LINK);
dispatch->debounce.state = DEBOUNCE_NEEDED;
break;
case DEBOUNCE_NEEDED:
case DEBOUNCE_ON:
break;
/* If a release event is pending and, filter press
* events until we flushed the release */
case DEBOUNCE_ACTIVE:
filter = true;
break;
}
} else if (dispatch->debounce.state == DEBOUNCE_ACTIVE) {
/* call libinput_dispatch() more frequently */
evdev_log_bug_client(device,
"Debouncing still active past timeout\n");
}
return filter;
}
static bool
fallback_filter_debounce_release(struct fallback_dispatch *dispatch,
struct input_event *e,
uint64_t time)
{
bool filter = false;
dispatch->debounce.button_code = e->code;
dispatch->debounce.button_up_time = time;
switch (dispatch->debounce.state) {
case DEBOUNCE_INIT:
break;
case DEBOUNCE_NEEDED:
filter = true;
dispatch->debounce.state = DEBOUNCE_ON;
break;
case DEBOUNCE_ON:
libinput_timer_set(&dispatch->debounce.timer,
time + DEBOUNCE_TIME);
filter = true;
dispatch->debounce.state = DEBOUNCE_ACTIVE;
break;
case DEBOUNCE_ACTIVE:
filter = true;
break;
}
return filter;
}
static bool
fallback_filter_debounce(struct fallback_dispatch *dispatch,
struct evdev_device *device,
struct input_event *e, uint64_t time)
{
bool filter = false;
/* Behavior: we monitor the time deltas between release and press
* events. Proper debouncing is disabled on init, but the first
* time we see a bouncing press event we enable it.
*
* The first bounced event is simply discarded, which ends up in the
* button being released sooner than it should be. Subsequent button
* presses are timer-based and thus released a bit later because we
* then wait for a timeout before we post the release event.
*/
if (e->value)
filter = fallback_filter_debounce_press(dispatch, device, e, time);
else
filter = fallback_filter_debounce_release(dispatch, e, time);
return filter;
} }
static inline void static inline void
@ -797,8 +482,6 @@ fallback_process_key(struct fallback_dispatch *dispatch,
return; return;
} }
fallback_flush_pending_event(dispatch, device, time);
type = get_key_type(e->code); type = get_key_type(e->code);
/* Ignore key release events from the kernel for keys that libinput /* Ignore key release events from the kernel for keys that libinput
@ -808,43 +491,16 @@ fallback_process_key(struct fallback_dispatch *dispatch,
case KEY_TYPE_NONE: case KEY_TYPE_NONE:
break; break;
case KEY_TYPE_KEY: case KEY_TYPE_KEY:
case KEY_TYPE_BUTTON:
if ((e->value && hw_is_key_down(dispatch, e->code)) || if ((e->value && hw_is_key_down(dispatch, e->code)) ||
(e->value == 0 && !hw_is_key_down(dispatch, e->code))) (e->value == 0 && !hw_is_key_down(dispatch, e->code)))
return; return;
break;
case KEY_TYPE_BUTTON:
if (fallback_filter_debounce(dispatch, device, e, time))
return;
if ((e->value && hw_is_key_down(dispatch, e->code)) || dispatch->pending_event |= EVDEV_KEY;
(e->value == 0 && !hw_is_key_down(dispatch, e->code)))
return;
break; break;
} }
hw_set_key_down(dispatch, e->code, e->value); hw_set_key_down(dispatch, e->code, e->value);
switch (type) {
case KEY_TYPE_NONE:
break;
case KEY_TYPE_KEY:
fallback_keyboard_notify_key(
dispatch,
device,
time,
e->code,
e->value ? LIBINPUT_KEY_STATE_PRESSED :
LIBINPUT_KEY_STATE_RELEASED);
break;
case KEY_TYPE_BUTTON:
evdev_pointer_notify_physical_button(
device,
time,
evdev_to_left_handed(device, e->code),
e->value ? LIBINPUT_BUTTON_STATE_PRESSED :
LIBINPUT_BUTTON_STATE_RELEASED);
break;
}
} }
static void static void
@ -853,8 +509,9 @@ fallback_process_touch(struct fallback_dispatch *dispatch,
struct input_event *e, struct input_event *e,
uint64_t time) uint64_t time)
{ {
switch (e->code) { struct mt_slot *slot = &dispatch->mt.slots[dispatch->mt.slot];
case ABS_MT_SLOT:
if (e->code == ABS_MT_SLOT) {
if ((size_t)e->value >= dispatch->mt.slots_len) { if ((size_t)e->value >= dispatch->mt.slots_len) {
evdev_log_bug_libinput(device, evdev_log_bug_libinput(device,
"exceeded slot count (%d of max %zd)\n", "exceeded slot count (%d of max %zd)\n",
@ -862,32 +519,36 @@ fallback_process_touch(struct fallback_dispatch *dispatch,
dispatch->mt.slots_len); dispatch->mt.slots_len);
e->value = dispatch->mt.slots_len - 1; e->value = dispatch->mt.slots_len - 1;
} }
fallback_flush_pending_event(dispatch, device, time);
dispatch->mt.slot = e->value; dispatch->mt.slot = e->value;
break; return;
}
switch (e->code) {
case ABS_MT_TRACKING_ID: case ABS_MT_TRACKING_ID:
if (dispatch->pending_event != EVDEV_NONE && if (e->value >= 0) {
dispatch->pending_event != EVDEV_ABSOLUTE_MT_MOTION) dispatch->pending_event |= EVDEV_ABSOLUTE_MT;
fallback_flush_pending_event(dispatch, device, time); slot->state = SLOT_STATE_BEGIN;
if (e->value >= 0) } else {
dispatch->pending_event = EVDEV_ABSOLUTE_MT_DOWN; dispatch->pending_event |= EVDEV_ABSOLUTE_MT;
else slot->state = SLOT_STATE_END;
dispatch->pending_event = EVDEV_ABSOLUTE_MT_UP; }
slot->dirty = true;
break; break;
case ABS_MT_POSITION_X: case ABS_MT_POSITION_X:
evdev_device_check_abs_axis_range(device, e->code, e->value); evdev_device_check_abs_axis_range(device, e->code, e->value);
dispatch->mt.slots[dispatch->mt.slot].point.x = e->value; dispatch->mt.slots[dispatch->mt.slot].point.x = e->value;
if (dispatch->pending_event == EVDEV_NONE) dispatch->pending_event |= EVDEV_ABSOLUTE_MT;
dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION; slot->dirty = true;
break; break;
case ABS_MT_POSITION_Y: case ABS_MT_POSITION_Y:
evdev_device_check_abs_axis_range(device, e->code, e->value); evdev_device_check_abs_axis_range(device, e->code, e->value);
dispatch->mt.slots[dispatch->mt.slot].point.y = e->value; dispatch->mt.slots[dispatch->mt.slot].point.y = e->value;
if (dispatch->pending_event == EVDEV_NONE) dispatch->pending_event |= EVDEV_ABSOLUTE_MT;
dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION; slot->dirty = true;
break; break;
} }
} }
static inline void static inline void
fallback_process_absolute_motion(struct fallback_dispatch *dispatch, fallback_process_absolute_motion(struct fallback_dispatch *dispatch,
struct evdev_device *device, struct evdev_device *device,
@ -897,14 +558,12 @@ fallback_process_absolute_motion(struct fallback_dispatch *dispatch,
case ABS_X: case ABS_X:
evdev_device_check_abs_axis_range(device, e->code, e->value); evdev_device_check_abs_axis_range(device, e->code, e->value);
dispatch->abs.point.x = e->value; dispatch->abs.point.x = e->value;
if (dispatch->pending_event == EVDEV_NONE) dispatch->pending_event |= EVDEV_ABSOLUTE_MOTION;
dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
break; break;
case ABS_Y: case ABS_Y:
evdev_device_check_abs_axis_range(device, e->code, e->value); evdev_device_check_abs_axis_range(device, e->code, e->value);
dispatch->abs.point.y = e->value; dispatch->abs.point.y = e->value;
if (dispatch->pending_event == EVDEV_NONE) dispatch->pending_event |= EVDEV_ABSOLUTE_MOTION;
dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
break; break;
} }
} }
@ -987,6 +646,8 @@ fallback_process_switch(struct fallback_dispatch *dispatch,
enum libinput_switch_state state; enum libinput_switch_state state;
bool is_closed; bool is_closed;
/* TODO: this should to move to handle_state */
switch (e->code) { switch (e->code) {
case SW_LID: case SW_LID:
is_closed = !!e->value; is_closed = !!e->value;
@ -1037,61 +698,25 @@ fallback_process_relative(struct fallback_dispatch *dispatch,
struct evdev_device *device, struct evdev_device *device,
struct input_event *e, uint64_t time) struct input_event *e, uint64_t time)
{ {
struct normalized_coords wheel_degrees = { 0.0, 0.0 };
struct discrete_coords discrete = { 0.0, 0.0 };
enum libinput_pointer_axis_source source;
if (fallback_reject_relative(device, e, time)) if (fallback_reject_relative(device, e, time))
return; return;
switch (e->code) { switch (e->code) {
case REL_X: case REL_X:
if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
fallback_flush_pending_event(dispatch, device, time);
dispatch->rel.x += e->value; dispatch->rel.x += e->value;
dispatch->pending_event = EVDEV_RELATIVE_MOTION; dispatch->pending_event |= EVDEV_RELATIVE_MOTION;
break; break;
case REL_Y: case REL_Y:
if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
fallback_flush_pending_event(dispatch, device, time);
dispatch->rel.y += e->value; dispatch->rel.y += e->value;
dispatch->pending_event = EVDEV_RELATIVE_MOTION; dispatch->pending_event |= EVDEV_RELATIVE_MOTION;
break; break;
case REL_WHEEL: case REL_WHEEL:
fallback_flush_pending_event(dispatch, device, time); dispatch->wheel.y += e->value;
wheel_degrees.y = -1 * e->value * dispatch->pending_event |= EVDEV_WHEEL;
device->scroll.wheel_click_angle.x;
discrete.y = -1 * e->value;
source = device->scroll.is_tilt.vertical ?
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
evdev_notify_axis(
device,
time,
AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
source,
&wheel_degrees,
&discrete);
break; break;
case REL_HWHEEL: case REL_HWHEEL:
fallback_flush_pending_event(dispatch, device, time); dispatch->wheel.x += e->value;
wheel_degrees.x = e->value * dispatch->pending_event |= EVDEV_WHEEL;
device->scroll.wheel_click_angle.y;
discrete.x = e->value;
source = device->scroll.is_tilt.horizontal ?
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL;
evdev_notify_axis(
device,
time,
AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
source,
&wheel_degrees,
&discrete);
break; break;
} }
} }
@ -1123,6 +748,120 @@ fallback_any_button_down(struct fallback_dispatch *dispatch,
return false; return false;
} }
static void
fallback_handle_state(struct fallback_dispatch *dispatch,
struct evdev_device *device,
uint64_t time)
{
bool need_touch_frame = false;
/* Relative motion */
if (dispatch->pending_event & EVDEV_RELATIVE_MOTION)
fallback_flush_relative_motion(dispatch, device, time);
/* Single touch or absolute pointer devices */
if (dispatch->pending_event & EVDEV_ABSOLUTE_TOUCH_DOWN) {
if (fallback_flush_st_down(dispatch, device, time))
need_touch_frame = true;
}
if (dispatch->pending_event & EVDEV_ABSOLUTE_MOTION) {
if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
if (fallback_flush_st_motion(dispatch,
device,
time))
need_touch_frame = true;
} else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
fallback_flush_absolute_motion(dispatch,
device,
time);
}
}
if (dispatch->pending_event & EVDEV_ABSOLUTE_TOUCH_UP) {
if (fallback_flush_st_up(dispatch, device, time))
need_touch_frame = true;
}
/* Multitouch devices */
if (dispatch->pending_event & EVDEV_ABSOLUTE_MT) {
bool sent = false;
for (size_t i = 0; i < dispatch->mt.slots_len; i++) {
struct mt_slot *slot = &dispatch->mt.slots[i];
if (!slot->dirty)
continue;
if (slot->state == SLOT_STATE_BEGIN) {
sent = fallback_flush_mt_down(dispatch,
device,
i,
time);
slot->state = SLOT_STATE_UPDATE;
} else if (slot->state == SLOT_STATE_UPDATE) {
sent = fallback_flush_mt_motion(dispatch,
device,
i,
time);
} else if (slot->state == SLOT_STATE_END) {
sent = fallback_flush_mt_up(dispatch,
device,
i,
time);
slot->state = SLOT_STATE_NONE;
}
slot->dirty = false;
}
need_touch_frame = sent;
}
if (need_touch_frame)
touch_notify_frame(&device->base, time);
fallback_flush_wheels(dispatch, device, time);
/* Buttons and keys */
if (dispatch->pending_event & EVDEV_KEY) {
bool want_debounce = false;
for (unsigned int code = 0; code <= KEY_MAX; code++) {
bool new_state;
if (!hw_key_has_changed(dispatch, code))
continue;
new_state = hw_is_key_down(dispatch, code);
switch (get_key_type(code)) {
case KEY_TYPE_NONE:
break;
case KEY_TYPE_KEY:
fallback_keyboard_notify_key(
dispatch,
device,
time,
code,
new_state ?
LIBINPUT_KEY_STATE_PRESSED :
LIBINPUT_KEY_STATE_RELEASED);
break;
case KEY_TYPE_BUTTON:
want_debounce = true;
break;
}
}
if (want_debounce)
fallback_debounce_handle_state(dispatch, time);
hw_key_update_last_state(dispatch);
}
dispatch->pending_event = EVDEV_NONE;
}
static void static void
fallback_interface_process(struct evdev_dispatch *evdev_dispatch, fallback_interface_process(struct evdev_dispatch *evdev_dispatch,
struct evdev_device *device, struct evdev_device *device,
@ -1130,7 +869,6 @@ fallback_interface_process(struct evdev_dispatch *evdev_dispatch,
uint64_t time) uint64_t time)
{ {
struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch); struct fallback_dispatch *dispatch = fallback_dispatch(evdev_dispatch);
enum evdev_event_type sent;
if (dispatch->ignore_events) if (dispatch->ignore_events)
return; return;
@ -1149,20 +887,7 @@ fallback_interface_process(struct evdev_dispatch *evdev_dispatch,
fallback_process_switch(dispatch, device, event, time); fallback_process_switch(dispatch, device, event, time);
break; break;
case EV_SYN: case EV_SYN:
sent = fallback_flush_pending_event(dispatch, device, time); fallback_handle_state(dispatch, device, time);
switch (sent) {
case EVDEV_ABSOLUTE_TOUCH_DOWN:
case EVDEV_ABSOLUTE_TOUCH_UP:
case EVDEV_ABSOLUTE_MT_DOWN:
case EVDEV_ABSOLUTE_MT_MOTION:
case EVDEV_ABSOLUTE_MT_UP:
touch_notify_frame(&device->base, time);
break;
case EVDEV_ABSOLUTE_MOTION:
case EVDEV_RELATIVE_MOTION:
case EVDEV_NONE:
break;
}
break; break;
} }
} }
@ -1254,6 +979,7 @@ fallback_return_to_neutral_state(struct fallback_dispatch *dispatch,
release_touches(dispatch, device, time); release_touches(dispatch, device, time);
release_pressed_keys(dispatch, device, time); release_pressed_keys(dispatch, device, time);
memset(dispatch->hw_key_mask, 0, sizeof(dispatch->hw_key_mask)); memset(dispatch->hw_key_mask, 0, sizeof(dispatch->hw_key_mask));
memset(dispatch->hw_key_mask, 0, sizeof(dispatch->last_hw_key_mask));
} }
static void static void
@ -1341,6 +1067,8 @@ fallback_interface_destroy(struct evdev_dispatch *evdev_dispatch)
libinput_timer_cancel(&dispatch->debounce.timer); libinput_timer_cancel(&dispatch->debounce.timer);
libinput_timer_destroy(&dispatch->debounce.timer); libinput_timer_destroy(&dispatch->debounce.timer);
libinput_timer_cancel(&dispatch->debounce.timer_short);
libinput_timer_destroy(&dispatch->debounce.timer_short);
free(dispatch->mt.slots); free(dispatch->mt.slots);
free(dispatch); free(dispatch);
} }
@ -1714,7 +1442,6 @@ fallback_dispatch_create(struct libinput_device *libinput_device)
{ {
struct evdev_device *device = evdev_device(libinput_device); struct evdev_device *device = evdev_device(libinput_device);
struct fallback_dispatch *dispatch; struct fallback_dispatch *dispatch;
char timer_name[64];
dispatch = zalloc(sizeof *dispatch); dispatch = zalloc(sizeof *dispatch);
dispatch->device = evdev_device(libinput_device); dispatch->device = evdev_device(libinput_device);
@ -1764,14 +1491,7 @@ fallback_dispatch_create(struct libinput_device *libinput_device)
want_config); want_config);
} }
snprintf(timer_name, fallback_init_debounce(dispatch);
sizeof(timer_name),
"%s debounce",
evdev_device_get_sysname(device));
libinput_timer_init(&dispatch->debounce.timer,
evdev_libinput_context(device),
timer_name,
fallback_debounce_timeout,
device);
return &dispatch->base; return &dispatch->base;
} }

222
src/evdev-fallback.h Normal file
View file

@ -0,0 +1,222 @@
/*
* Copyright © 2010 Intel Corporation
* Copyright © 2013 Jonas Ådahl
* Copyright © 2013-2017 Red Hat, Inc.
* Copyright © 2017 James Ye <jye836@gmail.com>
*
* 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"
#ifndef EVDEV_FALLBACK_H
#define EVDEV_FALLBACK_H
#include "evdev.h"
enum debounce_state {
DEBOUNCE_STATE_IS_UP = 100,
DEBOUNCE_STATE_IS_DOWN,
DEBOUNCE_STATE_DOWN_WAITING,
DEBOUNCE_STATE_RELEASE_PENDING,
DEBOUNCE_STATE_RELEASE_DELAYED,
DEBOUNCE_STATE_RELEASE_WAITING,
DEBOUNCE_STATE_MAYBE_SPURIOUS,
DEBOUNCE_STATE_RELEASED,
DEBOUNCE_STATE_PRESS_PENDING,
};
struct fallback_dispatch {
struct evdev_dispatch base;
struct evdev_device *device;
struct libinput_device_config_calibration calibration;
struct {
bool is_enabled;
int angle;
struct matrix matrix;
struct libinput_device_config_rotation config;
} rotation;
struct {
struct device_coords point;
int32_t seat_slot;
} abs;
struct {
int slot;
struct mt_slot *slots;
size_t slots_len;
bool want_hysteresis;
struct device_coords hysteresis_margin;
} mt;
struct device_coords rel;
struct device_coords wheel;
struct {
/* The struct for the tablet mode switch device itself */
struct {
int state;
} sw;
/* The struct for other devices listening to the tablet mode
switch */
struct {
struct evdev_device *sw_device;
struct libinput_event_listener listener;
} other;
} tablet_mode;
/* Bitmask of pressed keys used to ignore initial release events from
* the kernel. */
unsigned long hw_key_mask[NLONGS(KEY_CNT)];
unsigned long last_hw_key_mask[NLONGS(KEY_CNT)];
enum evdev_event_type pending_event;
/* true if we're reading events (i.e. not suspended) but we're
ignoring them */
bool ignore_events;
struct {
#if 0
enum evdev_debounce_state state;
uint64_t button_up_time;
#endif
unsigned int button_code;
uint64_t button_time;
struct libinput_timer timer;
struct libinput_timer timer_short;
enum debounce_state state;
bool spurious_enabled;
} debounce;
struct {
enum switch_reliability reliability;
bool is_closed;
bool is_closed_client_state;
/* We allow up to 3 paired keyboards for the lid switch
* listener. Only one keyboard should exist, but that can
* have more than one event node.
*
* Note: this is a sparse list, any element may have a
* non-NULL device.
*/
struct paired_keyboard {
struct evdev_device *device;
struct libinput_event_listener listener;
} paired_keyboard[3];
} lid;
};
static inline struct fallback_dispatch*
fallback_dispatch(struct evdev_dispatch *dispatch)
{
evdev_verify_dispatch_type(dispatch, DISPATCH_FALLBACK);
return container_of(dispatch, struct fallback_dispatch, base);
}
enum key_type {
KEY_TYPE_NONE,
KEY_TYPE_KEY,
KEY_TYPE_BUTTON,
};
static inline enum key_type
get_key_type(uint16_t code)
{
switch (code) {
case BTN_TOOL_PEN:
case BTN_TOOL_RUBBER:
case BTN_TOOL_BRUSH:
case BTN_TOOL_PENCIL:
case BTN_TOOL_AIRBRUSH:
case BTN_TOOL_MOUSE:
case BTN_TOOL_LENS:
case BTN_TOOL_QUINTTAP:
case BTN_TOOL_DOUBLETAP:
case BTN_TOOL_TRIPLETAP:
case BTN_TOOL_QUADTAP:
case BTN_TOOL_FINGER:
case BTN_TOUCH:
return KEY_TYPE_NONE;
}
if (code >= KEY_ESC && code <= KEY_MICMUTE)
return KEY_TYPE_KEY;
if (code >= BTN_MISC && code <= BTN_GEAR_UP)
return KEY_TYPE_BUTTON;
if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE)
return KEY_TYPE_KEY;
if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT)
return KEY_TYPE_BUTTON;
if (code >= KEY_ALS_TOGGLE && code <= KEY_ONSCREEN_KEYBOARD)
return KEY_TYPE_KEY;
if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40)
return KEY_TYPE_BUTTON;
return KEY_TYPE_NONE;
}
static inline void
hw_set_key_down(struct fallback_dispatch *dispatch, int code, int pressed)
{
long_set_bit_state(dispatch->hw_key_mask, code, pressed);
}
static inline bool
hw_key_has_changed(struct fallback_dispatch *dispatch, int code)
{
return long_bit_is_set(dispatch->hw_key_mask, code) !=
long_bit_is_set(dispatch->last_hw_key_mask, code);
}
static inline void
hw_key_update_last_state(struct fallback_dispatch *dispatch)
{
static_assert(sizeof(dispatch->hw_key_mask) ==
sizeof(dispatch->last_hw_key_mask),
"Mismatching key mask size");
memcpy(dispatch->last_hw_key_mask,
dispatch->hw_key_mask,
sizeof(dispatch->hw_key_mask));
}
static inline bool
hw_is_key_down(struct fallback_dispatch *dispatch, int code)
{
return long_bit_is_set(dispatch->hw_key_mask, code);
}
static inline int
get_key_down_count(struct evdev_device *device, int code)
{
return device->key_count[code];
}
void fallback_init_debounce(struct fallback_dispatch *dispatch);
void fallback_debounce_handle_state(struct fallback_dispatch *dispatch,
uint64_t time);
#endif

View file

@ -1055,15 +1055,21 @@ tp_notify_clickpadbutton(struct tp_dispatch *tp,
if (is_topbutton && tp->buttons.trackpoint) { if (is_topbutton && tp->buttons.trackpoint) {
struct evdev_dispatch *dispatch = tp->buttons.trackpoint->dispatch; struct evdev_dispatch *dispatch = tp->buttons.trackpoint->dispatch;
struct input_event event; struct input_event event;
struct input_event syn_report = {{ 0, 0 }, EV_SYN, SYN_REPORT, 0 };
event.time = us2tv(time); event.time = us2tv(time);
event.type = EV_KEY; event.type = EV_KEY;
event.code = button; event.code = button;
event.value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0; event.value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0;
syn_report.time = event.time;
dispatch->interface->process(dispatch, dispatch->interface->process(dispatch,
tp->buttons.trackpoint, tp->buttons.trackpoint,
&event, &event,
time); time);
dispatch->interface->process(dispatch,
tp->buttons.trackpoint,
&syn_report,
time);
return 1; return 1;
} }

View file

@ -502,6 +502,7 @@ tp_process_trackpoint_button(struct tp_dispatch *tp,
{ {
struct evdev_dispatch *dispatch; struct evdev_dispatch *dispatch;
struct input_event event; struct input_event event;
struct input_event syn_report = {{ 0, 0 }, EV_SYN, SYN_REPORT, 0 };
if (!tp->buttons.trackpoint) if (!tp->buttons.trackpoint)
return; return;
@ -509,6 +510,7 @@ tp_process_trackpoint_button(struct tp_dispatch *tp,
dispatch = tp->buttons.trackpoint->dispatch; dispatch = tp->buttons.trackpoint->dispatch;
event = *e; event = *e;
syn_report.time = e->time;
switch (event.code) { switch (event.code) {
case BTN_0: case BTN_0:
@ -527,6 +529,9 @@ tp_process_trackpoint_button(struct tp_dispatch *tp,
dispatch->interface->process(dispatch, dispatch->interface->process(dispatch,
tp->buttons.trackpoint, tp->buttons.trackpoint,
&event, time); &event, time);
dispatch->interface->process(dispatch,
tp->buttons.trackpoint,
&syn_report, time);
} }
static void static void
@ -1588,6 +1593,8 @@ tp_interface_process(struct evdev_dispatch *dispatch,
static void static void
tp_remove_sendevents(struct tp_dispatch *tp) tp_remove_sendevents(struct tp_dispatch *tp)
{ {
struct paired_keyboard *kbd;
libinput_timer_cancel(&tp->palm.trackpoint_timer); libinput_timer_cancel(&tp->palm.trackpoint_timer);
libinput_timer_cancel(&tp->dwt.keyboard_timer); libinput_timer_cancel(&tp->dwt.keyboard_timer);
@ -1596,9 +1603,10 @@ tp_remove_sendevents(struct tp_dispatch *tp)
libinput_device_remove_event_listener( libinput_device_remove_event_listener(
&tp->palm.trackpoint_listener); &tp->palm.trackpoint_listener);
if (tp->dwt.keyboard) ARRAY_FOR_EACH(tp->dwt.paired_keyboard, kbd) {
libinput_device_remove_event_listener( if (kbd->device)
&tp->dwt.keyboard_listener); libinput_device_remove_event_listener(&kbd->listener);
}
if (tp->lid_switch.lid_switch) if (tp->lid_switch.lid_switch)
libinput_device_remove_event_listener( libinput_device_remove_event_listener(
@ -1964,9 +1972,8 @@ tp_dwt_pair_keyboard(struct evdev_device *touchpad,
struct evdev_device *keyboard) struct evdev_device *keyboard)
{ {
struct tp_dispatch *tp = (struct tp_dispatch*)touchpad->dispatch; struct tp_dispatch *tp = (struct tp_dispatch*)touchpad->dispatch;
struct paired_keyboard *kbd;
if (tp->dwt.keyboard) bool found = false;
return;
if ((keyboard->tags & EVDEV_TAG_KEYBOARD) == 0) if ((keyboard->tags & EVDEV_TAG_KEYBOARD) == 0)
return; return;
@ -1974,16 +1981,25 @@ tp_dwt_pair_keyboard(struct evdev_device *touchpad,
if (!tp_want_dwt(touchpad, keyboard)) if (!tp_want_dwt(touchpad, keyboard))
return; return;
libinput_device_add_event_listener(&keyboard->base, ARRAY_FOR_EACH(tp->dwt.paired_keyboard, kbd) {
&tp->dwt.keyboard_listener, if (kbd->device)
tp_keyboard_event, tp); continue;
tp->dwt.keyboard = keyboard;
tp->dwt.keyboard_active = false;
evdev_log_debug(touchpad, found = true;
"palm: dwt activated with %s<->%s\n", libinput_device_add_event_listener(&keyboard->base,
touchpad->devname, &kbd->listener,
keyboard->devname); tp_keyboard_event, tp);
kbd->device = keyboard;
evdev_log_debug(touchpad,
"palm: dwt activated with %s<->%s\n",
touchpad->devname,
keyboard->devname);
break;
}
if (!found)
evdev_log_bug_libinput(touchpad,
"too many internal keyboards for dwt\n");
} }
static void static void
@ -2121,6 +2137,7 @@ tp_interface_device_removed(struct evdev_device *device,
struct evdev_device *removed_device) struct evdev_device *removed_device)
{ {
struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch; struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch;
struct paired_keyboard *kbd;
if (removed_device == tp->buttons.trackpoint) { if (removed_device == tp->buttons.trackpoint) {
/* Clear any pending releases for the trackpoint */ /* Clear any pending releases for the trackpoint */
@ -2134,11 +2151,12 @@ tp_interface_device_removed(struct evdev_device *device,
tp->buttons.trackpoint = NULL; tp->buttons.trackpoint = NULL;
} }
if (removed_device == tp->dwt.keyboard) { ARRAY_FOR_EACH(tp->dwt.paired_keyboard, kbd) {
libinput_device_remove_event_listener( if (kbd->device == removed_device) {
&tp->dwt.keyboard_listener); libinput_device_remove_event_listener(&kbd->listener);
tp->dwt.keyboard = NULL; kbd->device = NULL;
tp->dwt.keyboard_active = false; tp->dwt.keyboard_active = false;
}
} }
if (removed_device == tp->lid_switch.lid_switch) { if (removed_device == tp->lid_switch.lid_switch) {

View file

@ -383,13 +383,20 @@ struct tp_dispatch {
struct libinput_device_config_dwt config; struct libinput_device_config_dwt config;
bool dwt_enabled; bool dwt_enabled;
bool keyboard_active; /* We have to allow for more than one device node to be the
struct libinput_event_listener keyboard_listener; * internal dwt keyboard (Razer Blade). But they're the same
struct libinput_timer keyboard_timer; * physical device, so we don't care about per-keyboard
struct evdev_device *keyboard; * key/modifier masks.
*/
struct paired_keyboard {
struct evdev_device *device;
struct libinput_event_listener listener;
} paired_keyboard[3];
unsigned long key_mask[NLONGS(KEY_CNT)]; unsigned long key_mask[NLONGS(KEY_CNT)];
unsigned long mod_mask[NLONGS(KEY_CNT)]; unsigned long mod_mask[NLONGS(KEY_CNT)];
bool keyboard_active;
struct libinput_timer keyboard_timer;
uint64_t keyboard_last_press_time; uint64_t keyboard_last_press_time;
} dwt; } dwt;

View file

@ -1099,17 +1099,17 @@ evdev_read_wheel_click_props(struct evdev_device *device)
/* CLICK_COUNT overrides CLICK_ANGLE */ /* CLICK_COUNT overrides CLICK_ANGLE */
if (!evdev_read_wheel_click_count_prop(device, if (!evdev_read_wheel_click_count_prop(device,
"MOUSE_WHEEL_CLICK_COUNT", "MOUSE_WHEEL_CLICK_COUNT",
&angles.x)) &angles.y))
evdev_read_wheel_click_prop(device, evdev_read_wheel_click_prop(device,
"MOUSE_WHEEL_CLICK_ANGLE", "MOUSE_WHEEL_CLICK_ANGLE",
&angles.x); &angles.y);
if (!evdev_read_wheel_click_count_prop(device, if (!evdev_read_wheel_click_count_prop(device,
"MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL", "MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL",
&angles.y)) { &angles.x)) {
if (!evdev_read_wheel_click_prop(device, if (!evdev_read_wheel_click_prop(device,
"MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL", "MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL",
&angles.y)) &angles.x))
angles.y = angles.x; angles.x = angles.y;
} }
return angles; return angles;

View file

@ -41,13 +41,14 @@
enum evdev_event_type { enum evdev_event_type {
EVDEV_NONE, EVDEV_NONE,
EVDEV_ABSOLUTE_TOUCH_DOWN, EVDEV_ABSOLUTE_TOUCH_DOWN = (1 << 0),
EVDEV_ABSOLUTE_MOTION, EVDEV_ABSOLUTE_MOTION = (1 << 1),
EVDEV_ABSOLUTE_TOUCH_UP, EVDEV_ABSOLUTE_TOUCH_UP = (1 << 2),
EVDEV_ABSOLUTE_MT_DOWN, EVDEV_ABSOLUTE_MT= (1 << 3),
EVDEV_ABSOLUTE_MT_MOTION, EVDEV_WHEEL = (1 << 4),
EVDEV_ABSOLUTE_MT_UP, EVDEV_KEY = (1 << 5),
EVDEV_RELATIVE_MOTION, EVDEV_RELATIVE_MOTION = (1 << 6),
EVDEV_BUTTON = (1 << 7),
}; };
enum evdev_device_seat_capability { enum evdev_device_seat_capability {
@ -150,7 +151,16 @@ enum evdev_debounce_state {
DEBOUNCE_ACTIVE, DEBOUNCE_ACTIVE,
}; };
enum mt_slot_state {
SLOT_STATE_NONE,
SLOT_STATE_BEGIN,
SLOT_STATE_UPDATE,
SLOT_STATE_END,
};
struct mt_slot { struct mt_slot {
bool dirty;
enum mt_slot_state state;
int32_t seat_slot; int32_t seat_slot;
struct device_coords point; struct device_coords point;
struct device_coords hysteresis_center; struct device_coords hysteresis_center;

View file

@ -100,7 +100,7 @@ static const char udev_rule[] =
"\n" "\n"
"LABEL=\"mouse_end\""; "LABEL=\"mouse_end\"";
TEST_DEVICE("litest-magicmouse-device", TEST_DEVICE("magicmouse",
.type = LITEST_MAGICMOUSE, .type = LITEST_MAGICMOUSE,
.features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL, .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
.interface = &interface, .interface = &interface,

View file

@ -27,6 +27,7 @@
#include <check.h> #include <check.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <libgen.h>
#include <fcntl.h> #include <fcntl.h>
#include <fnmatch.h> #include <fnmatch.h>
#include <getopt.h> #include <getopt.h>
@ -1071,20 +1072,35 @@ litest_install_model_quirks(struct list *created_files_list)
list_insert(created_files_list, &file->link); list_insert(created_files_list, &file->link);
} }
static inline void
mkdir_p(const char *dir)
{
char *path, *parent;
int rc;
if (streq(dir, "/"))
return;
path = strdup(dir);
parent = dirname(path);
mkdir_p(parent);
rc = mkdir(dir, 0755);
if (rc == -1 && errno != EEXIST) {
litest_abort_msg("Failed to create directory %s (%s)\n",
dir,
strerror(errno));
}
free(path);
}
static void static void
litest_init_udev_rules(struct list *created_files) litest_init_udev_rules(struct list *created_files)
{ {
int rc; mkdir_p(UDEV_RULES_D);
mkdir_p(UDEV_HWDB_D);
rc = mkdir(UDEV_RULES_D, 0755);
if (rc == -1 && errno != EEXIST)
litest_abort_msg("Failed to create udev rules directory (%s)\n",
strerror(errno));
rc = mkdir(UDEV_HWDB_D, 0755);
if (rc == -1 && errno != EEXIST)
litest_abort_msg("Failed to create udev hwdb directory (%s)\n",
strerror(errno));
litest_install_model_quirks(created_files); litest_install_model_quirks(created_files);
litest_init_all_device_udev_rules(created_files); litest_init_all_device_udev_rules(created_files);
@ -2002,7 +2018,10 @@ litest_hover_move_two_touches(struct litest_device *d,
} }
void void
litest_button_click(struct litest_device *d, unsigned int button, bool is_press) litest_button_click_debounced(struct litest_device *d,
struct libinput *li,
unsigned int button,
bool is_press)
{ {
struct input_event *ev; struct input_event *ev;
@ -2013,7 +2032,9 @@ litest_button_click(struct litest_device *d, unsigned int button, bool is_press)
ARRAY_FOR_EACH(click, ev) ARRAY_FOR_EACH(click, ev)
litest_event(d, ev->type, ev->code, ev->value); litest_event(d, ev->type, ev->code, ev->value);
libinput_dispatch(li);
litest_timeout_debounce(); litest_timeout_debounce();
libinput_dispatch(li);
} }
void void
@ -2023,7 +2044,7 @@ litest_button_scroll(struct litest_device *dev,
{ {
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
litest_button_click(dev, button, 1); litest_button_click_debounced(dev, li, button, 1);
libinput_dispatch(li); libinput_dispatch(li);
litest_timeout_buttonscroll(); litest_timeout_buttonscroll();
@ -2033,7 +2054,7 @@ litest_button_scroll(struct litest_device *dev,
litest_event(dev, EV_REL, REL_Y, dy); litest_event(dev, EV_REL, REL_Y, dy);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_button_click(dev, button, 0); litest_button_click_debounced(dev, li, button, 0);
libinput_dispatch(li); libinput_dispatch(li);
} }
@ -2041,7 +2062,14 @@ litest_button_scroll(struct litest_device *dev,
void void
litest_keyboard_key(struct litest_device *d, unsigned int key, bool is_press) litest_keyboard_key(struct litest_device *d, unsigned int key, bool is_press)
{ {
litest_button_click(d, key, is_press); struct input_event *ev;
struct input_event click[] = {
{ .type = EV_KEY, .code = key, .value = is_press ? 1 : 0 },
{ .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
};
ARRAY_FOR_EACH(click, ev)
litest_event(d, ev->type, ev->code, ev->value);
} }
void void
@ -3154,7 +3182,7 @@ litest_timeout_tapndrag(void)
void void
litest_timeout_debounce(void) litest_timeout_debounce(void)
{ {
msleep(15); msleep(30);
} }
void void

View file

@ -596,9 +596,10 @@ litest_hover_move_two_touches(struct litest_device *d,
int steps, int sleep_ms); int steps, int sleep_ms);
void void
litest_button_click(struct litest_device *d, litest_button_click_debounced(struct litest_device *d,
unsigned int button, struct libinput *li,
bool is_press); unsigned int button,
bool is_press);
void void
litest_button_scroll(struct litest_device *d, litest_button_scroll(struct litest_device *d,

View file

@ -463,7 +463,7 @@ START_TEST(device_disable_release_buttons)
device = dev->libinput_device; device = dev->libinput_device;
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
litest_drain_events(li); litest_drain_events(li);
status = libinput_device_config_send_events_set_mode(device, status = libinput_device_config_send_events_set_mode(device,
@ -497,7 +497,7 @@ START_TEST(device_disable_release_keys)
device = dev->libinput_device; device = dev->libinput_device;
litest_button_click(dev, KEY_A, true); litest_button_click_debounced(dev, li, KEY_A, true);
litest_drain_events(li); litest_drain_events(li);
status = libinput_device_config_send_events_set_mode(device, status = libinput_device_config_send_events_set_mode(device,
@ -616,7 +616,7 @@ START_TEST(device_disable_release_softbutton)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 90, 90); litest_touch_down(dev, 0, 90, 90);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
/* make sure softbutton works */ /* make sure softbutton works */
litest_assert_button_event(li, litest_assert_button_event(li,
@ -633,7 +633,7 @@ START_TEST(device_disable_release_softbutton)
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
@ -669,8 +669,8 @@ START_TEST(device_disable_topsoftbutton)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 90, 10); litest_touch_down(dev, 0, 90, 10);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_wait_for_event(li); litest_wait_for_event(li);

View file

@ -416,8 +416,8 @@ START_TEST(event_conversion_tablet)
litest_tablet_proximity_in(dev, 50, 50, axes); litest_tablet_proximity_in(dev, 50, 50, axes);
litest_tablet_motion(dev, 60, 50, axes); litest_tablet_motion(dev, 60, 50, axes);
litest_button_click(dev, BTN_STYLUS, true); litest_button_click_debounced(dev, li, BTN_STYLUS, true);
litest_button_click(dev, BTN_STYLUS, false); litest_button_click_debounced(dev, li, BTN_STYLUS, false);
libinput_dispatch(li); libinput_dispatch(li);
@ -458,7 +458,7 @@ START_TEST(event_conversion_tablet_pad)
struct libinput_event *event; struct libinput_event *event;
int events = 0; int events = 0;
litest_button_click(dev, BTN_0, true); litest_button_click_debounced(dev, li, BTN_0, true);
litest_pad_ring_start(dev, 10); litest_pad_ring_start(dev, 10);
litest_pad_ring_end(dev); litest_pad_ring_end(dev);

View file

@ -68,8 +68,8 @@ START_TEST(pad_time)
if (!libevdev_has_event_code(dev->evdev, EV_KEY, code)) if (!libevdev_has_event_code(dev->evdev, EV_KEY, code))
continue; continue;
litest_button_click(dev, code, 1); litest_button_click_debounced(dev, li, code, 1);
litest_button_click(dev, code, 0); litest_button_click_debounced(dev, li, code, 0);
libinput_dispatch(li); libinput_dispatch(li);
switch (code) { switch (code) {
@ -98,8 +98,8 @@ START_TEST(pad_time)
litest_drain_events(li); litest_drain_events(li);
msleep(10); msleep(10);
litest_button_click(dev, code, 1); litest_button_click_debounced(dev, li, code, 1);
litest_button_click(dev, code, 0); litest_button_click_debounced(dev, li, code, 0);
libinput_dispatch(li); libinput_dispatch(li);
ev = libinput_get_event(li); ev = libinput_get_event(li);
@ -156,8 +156,8 @@ START_TEST(pad_button)
if (!libevdev_has_event_code(dev->evdev, EV_KEY, code)) if (!libevdev_has_event_code(dev->evdev, EV_KEY, code))
continue; continue;
litest_button_click(dev, code, 1); litest_button_click_debounced(dev, li, code, 1);
litest_button_click(dev, code, 0); litest_button_click_debounced(dev, li, code, 0);
libinput_dispatch(li); libinput_dispatch(li);
switch (code) { switch (code) {
@ -207,8 +207,8 @@ START_TEST(pad_button_mode_groups)
if (!libevdev_has_event_code(dev->evdev, EV_KEY, code)) if (!libevdev_has_event_code(dev->evdev, EV_KEY, code))
continue; continue;
litest_button_click(dev, code, 1); litest_button_click_debounced(dev, li, code, 1);
litest_button_click(dev, code, 0); litest_button_click_debounced(dev, li, code, 0);
libinput_dispatch(li); libinput_dispatch(li);
switch (code) { switch (code) {

View file

@ -348,7 +348,7 @@ test_button_event(struct litest_device *dev, unsigned int button, int state)
{ {
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
litest_button_click(dev, button, state); litest_button_click_debounced(dev, li, button, state);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_button_event(li, button, litest_assert_button_event(li, button,
@ -500,7 +500,7 @@ START_TEST(pointer_recover_from_lost_button_count)
litest_drain_events(dev->libinput); litest_drain_events(dev->libinput);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
@ -508,15 +508,15 @@ START_TEST(pointer_recover_from_lost_button_count)
/* Grab for the release to make libinput lose count */ /* Grab for the release to make libinput lose count */
libevdev_grab(evdev, LIBEVDEV_GRAB); libevdev_grab(evdev, LIBEVDEV_GRAB);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
libevdev_grab(evdev, LIBEVDEV_UNGRAB); libevdev_grab(evdev, LIBEVDEV_UNGRAB);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -802,7 +802,10 @@ START_TEST(pointer_seat_button_count)
} }
for (i = 0; i < num_devices; ++i) for (i = 0; i < num_devices; ++i)
litest_button_click(devices[i], BTN_LEFT, true); litest_button_click_debounced(devices[i],
libinput,
BTN_LEFT,
true);
libinput_dispatch(libinput); libinput_dispatch(libinput);
while ((ev = libinput_get_event(libinput))) { while ((ev = libinput_get_event(libinput))) {
@ -832,7 +835,10 @@ START_TEST(pointer_seat_button_count)
ck_assert_int_eq(seat_button_count, num_devices); ck_assert_int_eq(seat_button_count, num_devices);
for (i = 0; i < num_devices; ++i) for (i = 0; i < num_devices; ++i)
litest_button_click(devices[i], BTN_LEFT, false); litest_button_click_debounced(devices[i],
libinput,
BTN_LEFT,
false);
libinput_dispatch(libinput); libinput_dispatch(libinput);
while ((ev = libinput_get_event(libinput))) { while ((ev = libinput_get_event(libinput))) {
@ -921,8 +927,8 @@ START_TEST(pointer_left_handed)
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_drain_events(li); litest_drain_events(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_RIGHT, BTN_RIGHT,
@ -931,8 +937,8 @@ START_TEST(pointer_left_handed)
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_button_click(dev, BTN_RIGHT, 1); litest_button_click_debounced(dev, li, BTN_RIGHT, 1);
litest_button_click(dev, BTN_RIGHT, 0); litest_button_click_debounced(dev, li, BTN_RIGHT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -941,8 +947,8 @@ START_TEST(pointer_left_handed)
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
if (libinput_device_pointer_has_button(d, BTN_MIDDLE)) { if (libinput_device_pointer_has_button(d, BTN_MIDDLE)) {
litest_button_click(dev, BTN_MIDDLE, 1); litest_button_click_debounced(dev, li, BTN_MIDDLE, 1);
litest_button_click(dev, BTN_MIDDLE, 0); litest_button_click_debounced(dev, li, BTN_MIDDLE, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -961,14 +967,14 @@ START_TEST(pointer_left_handed_during_click)
enum libinput_config_status status; enum libinput_config_status status;
litest_drain_events(li); litest_drain_events(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
libinput_dispatch(li); libinput_dispatch(li);
/* Change while button is down, expect correct release event */ /* Change while button is down, expect correct release event */
status = libinput_device_config_left_handed_set(d, 1); status = libinput_device_config_left_handed_set(d, 1);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
@ -992,16 +998,16 @@ START_TEST(pointer_left_handed_during_click_multiple_buttons)
litest_disable_middleemu(dev); litest_disable_middleemu(dev);
litest_drain_events(li); litest_drain_events(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
libinput_dispatch(li); libinput_dispatch(li);
status = libinput_device_config_left_handed_set(d, 1); status = libinput_device_config_left_handed_set(d, 1);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
/* No left-handed until all buttons were down */ /* No left-handed until all buttons were down */
litest_button_click(dev, BTN_RIGHT, 1); litest_button_click_debounced(dev, li, BTN_RIGHT, 1);
litest_button_click(dev, BTN_RIGHT, 0); litest_button_click_debounced(dev, li, BTN_RIGHT, 0);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
@ -1102,7 +1108,7 @@ START_TEST(pointer_scroll_button_no_event_before_timeout)
BTN_LEFT); BTN_LEFT);
litest_drain_events(li); litest_drain_events(li);
litest_button_click(device, BTN_LEFT, true); litest_button_click_debounced(device, li, BTN_LEFT, true);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
@ -1113,7 +1119,7 @@ START_TEST(pointer_scroll_button_no_event_before_timeout)
litest_timeout_buttonscroll(); litest_timeout_buttonscroll();
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(device, BTN_LEFT, false); litest_button_click_debounced(device, li, BTN_LEFT, false);
litest_assert_button_event(li, BTN_LEFT, litest_assert_button_event(li, BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -1146,8 +1152,8 @@ START_TEST(pointer_scroll_button_middle_emulation)
litest_drain_events(li); litest_drain_events(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_RIGHT, 1); litest_button_click_debounced(dev, li, BTN_RIGHT, 1);
libinput_dispatch(li); libinput_dispatch(li);
litest_timeout_buttonscroll(); litest_timeout_buttonscroll();
libinput_dispatch(li); libinput_dispatch(li);
@ -1159,8 +1165,8 @@ START_TEST(pointer_scroll_button_middle_emulation)
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_button_click(dev, BTN_RIGHT, 0); litest_button_click_debounced(dev, li, BTN_RIGHT, 0);
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -1); litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -1);
@ -1525,16 +1531,16 @@ START_TEST(middlebutton)
litest_drain_events(li); litest_drain_events(li);
for (i = 0; i < ARRAY_LENGTH(btn); i++) { for (i = 0; i < ARRAY_LENGTH(btn); i++) {
litest_button_click(device, btn[i][0], true); litest_button_click_debounced(device, li, btn[i][0], true);
litest_button_click(device, btn[i][1], true); litest_button_click_debounced(device, li, btn[i][1], true);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, btn[i][2], false); litest_button_click_debounced(device, li, btn[i][2], false);
litest_button_click(device, btn[i][3], false); litest_button_click_debounced(device, li, btn[i][3], false);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -1568,33 +1574,33 @@ START_TEST(middlebutton_nostart_while_down)
if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
return; return;
litest_button_click(device, BTN_MIDDLE, true); litest_button_click_debounced(device, li, BTN_MIDDLE, true);
litest_drain_events(li); litest_drain_events(li);
for (i = 0; i < ARRAY_LENGTH(btn); i++) { for (i = 0; i < ARRAY_LENGTH(btn); i++) {
litest_button_click(device, btn[i][0], true); litest_button_click_debounced(device, li, btn[i][0], true);
litest_assert_button_event(li, litest_assert_button_event(li,
btn[i][0], btn[i][0],
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_button_click(device, btn[i][1], true); litest_button_click_debounced(device, li, btn[i][1], true);
litest_assert_button_event(li, litest_assert_button_event(li,
btn[i][1], btn[i][1],
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, btn[i][2], false); litest_button_click_debounced(device, li, btn[i][2], false);
litest_assert_button_event(li, litest_assert_button_event(li,
btn[i][2], btn[i][2],
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_button_click(device, btn[i][3], false); litest_button_click_debounced(device, li, btn[i][3], false);
litest_assert_button_event(li, litest_assert_button_event(li,
btn[i][3], btn[i][3],
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
} }
litest_button_click(device, BTN_MIDDLE, false); litest_button_click_debounced(device, li, BTN_MIDDLE, false);
litest_drain_events(li); litest_drain_events(li);
} }
END_TEST END_TEST
@ -1616,7 +1622,7 @@ START_TEST(middlebutton_timeout)
for (button = BTN_LEFT; button <= BTN_RIGHT; button++) { for (button = BTN_LEFT; button <= BTN_RIGHT; button++) {
litest_drain_events(li); litest_drain_events(li);
litest_button_click(device, button, true); litest_button_click_debounced(device, li, button, true);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_timeout_middlebutton(); litest_timeout_middlebutton();
@ -1624,7 +1630,7 @@ START_TEST(middlebutton_timeout)
button, button,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_button_click(device, button, false); litest_button_click_debounced(device, li, button, false);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -1657,22 +1663,22 @@ START_TEST(middlebutton_doubleclick)
litest_drain_events(li); litest_drain_events(li);
for (i = 0; i < ARRAY_LENGTH(btn); i++) { for (i = 0; i < ARRAY_LENGTH(btn); i++) {
litest_button_click(device, btn[i][0], true); litest_button_click_debounced(device, li, btn[i][0], true);
litest_button_click(device, btn[i][1], true); litest_button_click_debounced(device, li, btn[i][1], true);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, btn[i][2], false); litest_button_click_debounced(device, li, btn[i][2], false);
litest_button_click(device, btn[i][2], true); litest_button_click_debounced(device, li, btn[i][2], true);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_button_click(device, btn[i][3], false); litest_button_click_debounced(device, li, btn[i][3], false);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
@ -1705,8 +1711,8 @@ START_TEST(middlebutton_middleclick)
for (button = BTN_LEFT; button <= BTN_RIGHT; button++) { for (button = BTN_LEFT; button <= BTN_RIGHT; button++) {
/* release button before middle */ /* release button before middle */
litest_drain_events(li); litest_drain_events(li);
litest_button_click(device, button, true); litest_button_click_debounced(device, li, button, true);
litest_button_click(device, BTN_MIDDLE, true); litest_button_click_debounced(device, li, BTN_MIDDLE, true);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -1714,19 +1720,19 @@ START_TEST(middlebutton_middleclick)
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, button, false); litest_button_click_debounced(device, li, button, false);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_button_click(device, BTN_MIDDLE, false); litest_button_click_debounced(device, li, BTN_MIDDLE, false);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
/* release middle before button */ /* release middle before button */
litest_button_click(device, button, true); litest_button_click_debounced(device, li, button, true);
litest_button_click(device, BTN_MIDDLE, true); litest_button_click_debounced(device, li, BTN_MIDDLE, true);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -1734,11 +1740,11 @@ START_TEST(middlebutton_middleclick)
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, BTN_MIDDLE, false); litest_button_click_debounced(device, li, BTN_MIDDLE, false);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_button_click(device, button, false); litest_button_click_debounced(device, li, button, false);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -1770,14 +1776,14 @@ START_TEST(middlebutton_middleclick_during)
/* trigger emulation, then real middle */ /* trigger emulation, then real middle */
for (button = BTN_LEFT; button <= BTN_RIGHT; button++) { for (button = BTN_LEFT; button <= BTN_RIGHT; button++) {
litest_button_click(device, BTN_LEFT, true); litest_button_click_debounced(device, li, BTN_LEFT, true);
litest_button_click(device, BTN_RIGHT, true); litest_button_click_debounced(device, li, BTN_RIGHT, true);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_button_click(device, BTN_MIDDLE, true); litest_button_click_debounced(device, li, BTN_MIDDLE, true);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -1788,23 +1794,23 @@ START_TEST(middlebutton_middleclick_during)
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
/* middle still down, release left/right */ /* middle still down, release left/right */
litest_button_click(device, button, false); litest_button_click_debounced(device, li, button, false);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, button, true); litest_button_click_debounced(device, li, button, true);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
/* release both */ /* release both */
litest_button_click(device, BTN_LEFT, false); litest_button_click_debounced(device, li, BTN_LEFT, false);
litest_button_click(device, BTN_RIGHT, false); litest_button_click_debounced(device, li, BTN_RIGHT, false);
litest_assert_button_event(li, litest_assert_button_event(li,
button, button,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(device, BTN_MIDDLE, false); litest_button_click_debounced(device, li, BTN_MIDDLE, false);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -2104,91 +2110,176 @@ START_TEST(pointer_time_usec)
} }
END_TEST END_TEST
START_TEST(debounce) START_TEST(debounce_bounce)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
unsigned int button = _i; /* ranged test */
if (!libinput_device_pointer_has_button(dev->libinput_device,
button))
return;
litest_disable_middleemu(dev); litest_disable_middleemu(dev);
disable_button_scrolling(dev);
litest_drain_events(li); litest_drain_events(li);
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, button, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, button, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, button, 1);
/* expect debouncing on now, this event is ignored */
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0); libinput_dispatch(li);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_timeout_debounce();
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, button,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li);
litest_event(dev, EV_KEY, button, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, button, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, button, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, button,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
} }
END_TEST END_TEST
START_TEST(debounce_timer) START_TEST(debounce_bounce_check_immediate)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
litest_disable_middleemu(dev); litest_disable_middleemu(dev);
disable_button_scrolling(dev);
litest_drain_events(li); litest_drain_events(li);
/* Press must be sent without delay */
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_assert_button_event(li,
litest_event(dev, EV_SYN, SYN_REPORT, 0); BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_timeout_debounce();
litest_assert_empty_queue(li);
/* held down & past timeout, we expect releases to be immediate */
/* expect debouncing on now, this event is ignored */
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_timeout_debounce(); litest_timeout_debounce();
litest_assert_empty_queue(li);
}
END_TEST
/* Triggers the event sequence that initializes the spurious
* debouncing behavior */
static inline void
debounce_trigger_spurious(struct litest_device *dev, struct libinput *li)
{
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
/* gets filtered now */
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li);
}
START_TEST(debounce_spurious)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
unsigned int button = _i; /* ranged test */
if (!libinput_device_pointer_has_button(dev->libinput_device,
button))
return;
litest_disable_middleemu(dev);
disable_button_scrolling(dev);
litest_drain_events(li); litest_drain_events(li);
debounce_trigger_spurious(dev, li);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, button, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li); libinput_dispatch(li);
litest_timeout_debounce(); litest_timeout_debounce();
libinput_dispatch(li);
/* Not all devices can disable middle button emulation, time out on /* Not all devices can disable middle button emulation, time out on
* middle button here to make sure the initial button press event * middle button here to make sure the initial button press event
* was flushed. * was flushed.
*/ */
libinput_dispatch(li);
litest_timeout_middlebutton(); litest_timeout_middlebutton();
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, button,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
/* bouncy bouncy bouncy */ /* bouncy bouncy bouncy */
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, button, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, button, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, button, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li); libinput_dispatch(li);
litest_timeout_debounce(); litest_timeout_debounce();
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, button,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
@ -2196,7 +2287,7 @@ START_TEST(debounce_timer)
} }
END_TEST END_TEST
START_TEST(debounce_multibounce) START_TEST(debounce_spurious_multibounce)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
@ -2204,15 +2295,7 @@ START_TEST(debounce_multibounce)
litest_disable_middleemu(dev); litest_disable_middleemu(dev);
litest_drain_events(li); litest_drain_events(li);
/* enable debouncing */ debounce_trigger_spurious(dev, li);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_drain_events(li); litest_drain_events(li);
/* Let's assume our button has ventricular fibrilation and sends a /* Let's assume our button has ventricular fibrilation and sends a
@ -2223,6 +2306,8 @@ START_TEST(debounce_multibounce)
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
/* Not all devices can disable middle button emulation, time out on /* Not all devices can disable middle button emulation, time out on
* middle button here to make sure the initial button press event * middle button here to make sure the initial button press event
@ -2261,20 +2346,34 @@ START_TEST(debounce_multibounce)
} }
END_TEST END_TEST
START_TEST(debounce_no_debounce_for_otherbutton) START_TEST(debounce_spurious_dont_enable_on_otherbutton)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
struct libinput_device *device = dev->libinput_device;
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
if (!libinput_device_config_middle_emulation_is_available(device))
return;
litest_disable_middleemu(dev); litest_disable_middleemu(dev);
disable_button_scrolling(dev);
litest_drain_events(li); litest_drain_events(li);
/* Don't trigger spurious debouncing on otherbutton events */
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_RIGHT, 1); litest_event(dev, EV_KEY, BTN_RIGHT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_RIGHT, 0); litest_event(dev, EV_KEY, BTN_RIGHT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
@ -2290,43 +2389,68 @@ START_TEST(debounce_no_debounce_for_otherbutton)
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
/* Expect release to be immediate */
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
} }
END_TEST END_TEST
START_TEST(debounce_cancel_debounce_otherbutton) START_TEST(debounce_spurious_cancel_debounce_otherbutton)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
struct libinput_device *device = dev->libinput_device;
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
if (!libinput_device_config_middle_emulation_is_available(device))
return;
litest_disable_middleemu(dev); litest_disable_middleemu(dev);
disable_button_scrolling(dev);
litest_drain_events(li); litest_drain_events(li);
litest_event(dev, EV_KEY, BTN_LEFT, 1); debounce_trigger_spurious(dev, li);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_drain_events(li);
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
/* spurious debouncing is on but the release should get flushed by
* the other button */
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
/* release is now held back, press was ignored,
* other button should flush the release */
litest_event(dev, EV_KEY, BTN_RIGHT, 1); litest_event(dev, EV_KEY, BTN_RIGHT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_RIGHT, 0); litest_event(dev, EV_KEY, BTN_RIGHT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
@ -2342,6 +2466,12 @@ START_TEST(debounce_cancel_debounce_otherbutton)
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_button_event(li,
BTN_LEFT,
LIBINPUT_BUTTON_STATE_RELEASED);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -2350,31 +2480,28 @@ START_TEST(debounce_cancel_debounce_otherbutton)
} }
END_TEST END_TEST
START_TEST(debounce_switch_to_otherbutton) START_TEST(debounce_spurious_switch_to_otherbutton)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
struct libinput_device *device = dev->libinput_device;
struct libinput *li = dev->libinput; struct libinput *li = dev->libinput;
litest_drain_events(li); if (!libinput_device_config_middle_emulation_is_available(device))
return;
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_drain_events(li); litest_drain_events(li);
debounce_trigger_spurious(dev, li);
litest_event(dev, EV_KEY, BTN_LEFT, 1); litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
libinput_dispatch(li);
litest_timeout_debounce();
libinput_dispatch(li);
litest_event(dev, EV_KEY, BTN_LEFT, 0); litest_event(dev, EV_KEY, BTN_LEFT, 0);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 1); /* release is now held back,
litest_event(dev, EV_SYN, SYN_REPORT, 0);
/* release is now held back, press was ignored,
* other button should flush the release */ * other button should flush the release */
litest_event(dev, EV_KEY, BTN_RIGHT, 1); litest_event(dev, EV_KEY, BTN_RIGHT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0); litest_event(dev, EV_SYN, SYN_REPORT, 0);
@ -2412,6 +2539,7 @@ litest_setup_tests_pointer(void)
{ {
struct range axis_range = {ABS_X, ABS_Y + 1}; struct range axis_range = {ABS_X, ABS_Y + 1};
struct range compass = {0, 7}; /* cardinal directions */ struct range compass = {0, 7}; /* cardinal directions */
struct range buttons = {BTN_LEFT, BTN_TASK + 1};
litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_POINTINGSTICK); litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_POINTINGSTICK);
litest_add_for_device("pointer:motion", pointer_motion_relative_zero, LITEST_MOUSE); litest_add_for_device("pointer:motion", pointer_motion_relative_zero, LITEST_MOUSE);
@ -2474,10 +2602,11 @@ litest_setup_tests_pointer(void)
litest_add("pointer:time", pointer_time_usec, LITEST_RELATIVE, LITEST_ANY); litest_add("pointer:time", pointer_time_usec, LITEST_RELATIVE, LITEST_ANY);
litest_add("pointer:debounce", debounce, LITEST_BUTTON, LITEST_TOUCHPAD); litest_add_ranged("pointer:debounce", debounce_bounce, LITEST_BUTTON, LITEST_TOUCHPAD, &buttons);
litest_add("pointer:debounce", debounce_timer, LITEST_BUTTON, LITEST_TOUCHPAD); litest_add("pointer:debounce", debounce_bounce_check_immediate, LITEST_BUTTON, LITEST_TOUCHPAD);
litest_add("pointer:debounce", debounce_multibounce, LITEST_BUTTON, LITEST_TOUCHPAD); litest_add_ranged("pointer:debounce", debounce_spurious, LITEST_BUTTON, LITEST_TOUCHPAD, &buttons);
litest_add("pointer:debounce_otherbutton", debounce_no_debounce_for_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD); litest_add("pointer:debounce", debounce_spurious_multibounce, LITEST_BUTTON, LITEST_TOUCHPAD);
litest_add("pointer:debounce_otherbutton", debounce_cancel_debounce_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD); litest_add("pointer:debounce_otherbutton", debounce_spurious_dont_enable_on_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD);
litest_add("pointer:debounce_otherbutton", debounce_switch_to_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD); litest_add("pointer:debounce_otherbutton", debounce_spurious_cancel_debounce_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD);
litest_add("pointer:debounce_otherbutton", debounce_spurious_switch_to_otherbutton, LITEST_BUTTON, LITEST_TOUCHPAD);
} }

View file

@ -1041,7 +1041,7 @@ START_TEST(clickpad_finger_pin)
litest_touch_move_to(dev, 0, 48, 48, 50, 50, 10, 1); litest_touch_move_to(dev, 0, 48, 48, 50, 50, 10, 1);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
litest_drain_events(li); litest_drain_events(li);
litest_touch_move_to(dev, 0, 50, 50, 50 + dist, 50 + dist, 10, 1); litest_touch_move_to(dev, 0, 50, 50, 50 + dist, 50 + dist, 10, 1);
@ -1050,7 +1050,7 @@ START_TEST(clickpad_finger_pin)
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
/* still pinned after release */ /* still pinned after release */
@ -1490,7 +1490,7 @@ START_TEST(clickpad_softbutton_hover_into_buttons)
litest_touch_move_to(dev, 0, 90, 90, 91, 91, 1, 0); litest_touch_move_to(dev, 0, 90, 90, 91, 91, 1, 0);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -1498,7 +1498,7 @@ START_TEST(clickpad_softbutton_hover_into_buttons)
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_button_event(li, litest_assert_button_event(li,

View file

@ -332,8 +332,8 @@ START_TEST(touchpad_1fg_multitap_n_drag_click)
litest_touch_down(dev, 0, 50, 50); litest_touch_down(dev, 0, 50, 50);
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
for (ntaps = 0; ntaps <= range; ntaps++) { for (ntaps = 0; ntaps <= range; ntaps++) {
@ -627,8 +627,8 @@ START_TEST(touchpad_1fg_multitap_n_drag_tap_click)
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_touch_down(dev, 0, 70, 50); litest_touch_down(dev, 0, 70, 50);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -799,8 +799,8 @@ START_TEST(touchpad_1fg_tap_n_drag_draglock_tap_click)
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_touch_down(dev, 0, 50, 50); litest_touch_down(dev, 0, 50, 50);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
litest_assert_button_event(li, BTN_LEFT, litest_assert_button_event(li, BTN_LEFT,

View file

@ -842,7 +842,7 @@ START_TEST(touchpad_edge_scroll_buttonareas_click_stops_scroll)
litest_touch_move_to(dev, 0, 20, 95, 70, 95, 10, 5); litest_touch_move_to(dev, 0, 20, 95, 70, 95, 10, 5);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -865,7 +865,7 @@ START_TEST(touchpad_edge_scroll_buttonareas_click_stops_scroll)
litest_touch_move_to(dev, 0, 70, 95, 90, 95, 10, 0); litest_touch_move_to(dev, 0, 70, 95, 90, 95, 10, 0);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
@ -892,7 +892,7 @@ START_TEST(touchpad_edge_scroll_clickfinger_click_stops_scroll)
litest_touch_move_to(dev, 0, 20, 95, 70, 95, 10, 5); litest_touch_move_to(dev, 0, 20, 95, 70, 95, 10, 5);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -916,7 +916,7 @@ START_TEST(touchpad_edge_scroll_clickfinger_click_stops_scroll)
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
@ -1717,8 +1717,8 @@ START_TEST(touchpad_left_handed)
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_drain_events(li); litest_drain_events(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_RIGHT, BTN_RIGHT,
@ -1727,8 +1727,8 @@ START_TEST(touchpad_left_handed)
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_button_click(dev, BTN_RIGHT, 1); litest_button_click_debounced(dev, li, BTN_RIGHT, 1);
litest_button_click(dev, BTN_RIGHT, 0); litest_button_click_debounced(dev, li, BTN_RIGHT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -1739,8 +1739,8 @@ START_TEST(touchpad_left_handed)
if (libevdev_has_event_code(dev->evdev, if (libevdev_has_event_code(dev->evdev,
EV_KEY, EV_KEY,
BTN_MIDDLE)) { BTN_MIDDLE)) {
litest_button_click(dev, BTN_MIDDLE, 1); litest_button_click_debounced(dev, li, BTN_MIDDLE, 1);
litest_button_click(dev, BTN_MIDDLE, 0); litest_button_click_debounced(dev, li, BTN_MIDDLE, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_MIDDLE, BTN_MIDDLE,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -1779,8 +1779,8 @@ START_TEST(touchpad_left_handed_clickpad)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 10, 90); litest_touch_down(dev, 0, 10, 90);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -1792,8 +1792,8 @@ START_TEST(touchpad_left_handed_clickpad)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 90, 90); litest_touch_down(dev, 0, 90, 90);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -1805,8 +1805,8 @@ START_TEST(touchpad_left_handed_clickpad)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 50, 50); litest_touch_down(dev, 0, 50, 50);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -1833,8 +1833,8 @@ START_TEST(touchpad_left_handed_clickfinger)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 10, 90); litest_touch_down(dev, 0, 10, 90);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
/* Clickfinger is unaffected by left-handed setting */ /* Clickfinger is unaffected by left-handed setting */
@ -1848,8 +1848,8 @@ START_TEST(touchpad_left_handed_clickfinger)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 10, 90); litest_touch_down(dev, 0, 10, 90);
litest_touch_down(dev, 1, 30, 90); litest_touch_down(dev, 1, 30, 90);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_touch_up(dev, 1); litest_touch_up(dev, 1);
@ -1943,13 +1943,13 @@ START_TEST(touchpad_left_handed_delayed)
return; return;
litest_drain_events(li); litest_drain_events(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
libinput_dispatch(li); libinput_dispatch(li);
status = libinput_device_config_left_handed_set(d, 1); status = libinput_device_config_left_handed_set(d, 1);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
@ -1959,18 +1959,18 @@ START_TEST(touchpad_left_handed_delayed)
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
/* left-handed takes effect now */ /* left-handed takes effect now */
litest_button_click(dev, BTN_RIGHT, 1); litest_button_click_debounced(dev, li, BTN_RIGHT, 1);
libinput_dispatch(li); libinput_dispatch(li);
litest_timeout_middlebutton(); litest_timeout_middlebutton();
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
libinput_dispatch(li); libinput_dispatch(li);
status = libinput_device_config_left_handed_set(d, 0); status = libinput_device_config_left_handed_set(d, 0);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_button_click(dev, BTN_RIGHT, 0); litest_button_click_debounced(dev, li, BTN_RIGHT, 0);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
BTN_LEFT, BTN_LEFT,
@ -1999,13 +1999,13 @@ START_TEST(touchpad_left_handed_clickpad_delayed)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 10, 90); litest_touch_down(dev, 0, 10, 90);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
libinput_dispatch(li); libinput_dispatch(li);
status = libinput_device_config_left_handed_set(d, 1); status = libinput_device_config_left_handed_set(d, 1);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -2018,13 +2018,13 @@ START_TEST(touchpad_left_handed_clickpad_delayed)
/* left-handed takes effect now */ /* left-handed takes effect now */
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(dev, 0, 90, 90); litest_touch_down(dev, 0, 90, 90);
litest_button_click(dev, BTN_LEFT, 1); litest_button_click_debounced(dev, li, BTN_LEFT, 1);
libinput_dispatch(li); libinput_dispatch(li);
status = libinput_device_config_left_handed_set(d, 0); status = libinput_device_config_left_handed_set(d, 0);
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_button_click(dev, BTN_LEFT, 0); litest_button_click_debounced(dev, li, BTN_LEFT, 0);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_assert_button_event(li, litest_assert_button_event(li,
@ -2657,12 +2657,12 @@ START_TEST(touchpad_trackpoint_buttons)
litest_drain_events(li); litest_drain_events(li);
ARRAY_FOR_EACH(buttons, b) { ARRAY_FOR_EACH(buttons, b) {
litest_button_click(touchpad, b->device_value, true); litest_button_click_debounced(touchpad, li, b->device_value, true);
assert_btnevent_from_device(trackpoint, assert_btnevent_from_device(trackpoint,
b->real_value, b->real_value,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
litest_button_click(touchpad, b->device_value, false); litest_button_click_debounced(touchpad, li, b->device_value, false);
assert_btnevent_from_device(trackpoint, assert_btnevent_from_device(trackpoint,
b->real_value, b->real_value,
@ -2683,7 +2683,7 @@ START_TEST(touchpad_trackpoint_mb_scroll)
LITEST_TRACKPOINT); LITEST_TRACKPOINT);
litest_drain_events(li); litest_drain_events(li);
litest_button_click(touchpad, BTN_2, true); /* middle */ litest_button_click_debounced(touchpad, li, BTN_2, true); /* middle */
libinput_dispatch(li); libinput_dispatch(li);
litest_timeout_buttonscroll(); litest_timeout_buttonscroll();
libinput_dispatch(li); libinput_dispatch(li);
@ -2695,7 +2695,7 @@ START_TEST(touchpad_trackpoint_mb_scroll)
litest_event(trackpoint, EV_SYN, SYN_REPORT, 0); litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
litest_event(trackpoint, EV_REL, REL_Y, -2); litest_event(trackpoint, EV_REL, REL_Y, -2);
litest_event(trackpoint, EV_SYN, SYN_REPORT, 0); litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
litest_button_click(touchpad, BTN_2, false); litest_button_click_debounced(touchpad, li, BTN_2, false);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
@ -2718,8 +2718,8 @@ START_TEST(touchpad_trackpoint_mb_click)
ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
litest_drain_events(li); litest_drain_events(li);
litest_button_click(touchpad, BTN_2, true); /* middle */ litest_button_click_debounced(touchpad, li, BTN_2, true); /* middle */
litest_button_click(touchpad, BTN_2, false); litest_button_click_debounced(touchpad, li, BTN_2, false);
assert_btnevent_from_device(trackpoint, assert_btnevent_from_device(trackpoint,
BTN_MIDDLE, BTN_MIDDLE,
@ -2743,11 +2743,11 @@ START_TEST(touchpad_trackpoint_buttons_softbuttons)
litest_drain_events(li); litest_drain_events(li);
litest_touch_down(touchpad, 0, 95, 90); litest_touch_down(touchpad, 0, 95, 90);
litest_button_click(touchpad, BTN_LEFT, true); litest_button_click_debounced(touchpad, li, BTN_LEFT, true);
litest_button_click(touchpad, BTN_1, true); litest_button_click_debounced(touchpad, li, BTN_1, true);
litest_button_click(touchpad, BTN_LEFT, false); litest_button_click_debounced(touchpad, li, BTN_LEFT, false);
litest_touch_up(touchpad, 0); litest_touch_up(touchpad, 0);
litest_button_click(touchpad, BTN_1, false); litest_button_click_debounced(touchpad, li, BTN_1, false);
assert_btnevent_from_device(touchpad, assert_btnevent_from_device(touchpad,
BTN_RIGHT, BTN_RIGHT,
@ -2763,10 +2763,10 @@ START_TEST(touchpad_trackpoint_buttons_softbuttons)
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
litest_touch_down(touchpad, 0, 95, 90); litest_touch_down(touchpad, 0, 95, 90);
litest_button_click(touchpad, BTN_LEFT, true); litest_button_click_debounced(touchpad, li, BTN_LEFT, true);
litest_button_click(touchpad, BTN_1, true); litest_button_click_debounced(touchpad, li, BTN_1, true);
litest_button_click(touchpad, BTN_1, false); litest_button_click_debounced(touchpad, li, BTN_1, false);
litest_button_click(touchpad, BTN_LEFT, false); litest_button_click_debounced(touchpad, li, BTN_LEFT, false);
litest_touch_up(touchpad, 0); litest_touch_up(touchpad, 0);
assert_btnevent_from_device(touchpad, assert_btnevent_from_device(touchpad,
@ -2818,7 +2818,7 @@ START_TEST(touchpad_trackpoint_buttons_2fg_scroll)
libinput_event_destroy(e); libinput_event_destroy(e);
} }
litest_button_click(touchpad, BTN_1, true); litest_button_click_debounced(touchpad, li, BTN_1, true);
assert_btnevent_from_device(trackpoint, assert_btnevent_from_device(trackpoint,
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_PRESSED); LIBINPUT_BUTTON_STATE_PRESSED);
@ -2838,7 +2838,7 @@ START_TEST(touchpad_trackpoint_buttons_2fg_scroll)
libinput_event_destroy(e); libinput_event_destroy(e);
} }
litest_button_click(touchpad, BTN_1, false); litest_button_click_debounced(touchpad, li, BTN_1, false);
assert_btnevent_from_device(trackpoint, assert_btnevent_from_device(trackpoint,
BTN_RIGHT, BTN_RIGHT,
LIBINPUT_BUTTON_STATE_RELEASED); LIBINPUT_BUTTON_STATE_RELEASED);
@ -2874,16 +2874,16 @@ START_TEST(touchpad_trackpoint_no_trackpoint)
struct libinput *li = touchpad->libinput; struct libinput *li = touchpad->libinput;
litest_drain_events(li); litest_drain_events(li);
litest_button_click(touchpad, BTN_0, true); /* left */ litest_button_click_debounced(touchpad, li, BTN_0, true); /* left */
litest_button_click(touchpad, BTN_0, false); litest_button_click_debounced(touchpad, li, BTN_0, false);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(touchpad, BTN_1, true); /* right */ litest_button_click_debounced(touchpad, li, BTN_1, true); /* right */
litest_button_click(touchpad, BTN_1, false); litest_button_click_debounced(touchpad, li, BTN_1, false);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(touchpad, BTN_2, true); /* middle */ litest_button_click_debounced(touchpad, li, BTN_2, true); /* middle */
litest_button_click(touchpad, BTN_2, false); litest_button_click_debounced(touchpad, li, BTN_2, false);
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
} }
END_TEST END_TEST
@ -3679,8 +3679,8 @@ START_TEST(touchpad_dwt_click)
litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY); litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
litest_touch_down(touchpad, 0, 50, 50); litest_touch_down(touchpad, 0, 50, 50);
litest_button_click(touchpad, BTN_LEFT, true); litest_button_click_debounced(touchpad, li, BTN_LEFT, true);
litest_button_click(touchpad, BTN_LEFT, false); litest_button_click_debounced(touchpad, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
litest_touch_up(touchpad, 0); litest_touch_up(touchpad, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON); litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
@ -4232,6 +4232,152 @@ START_TEST(touchpad_dwt_acer_hawaii)
} }
END_TEST END_TEST
START_TEST(touchpad_dwt_multiple_keyboards)
{
struct litest_device *touchpad = litest_current_device();
struct litest_device *k1, *k2;
struct libinput *li = touchpad->libinput;
ck_assert(has_disable_while_typing(touchpad));
enable_dwt(touchpad);
k1 = litest_add_device(li, LITEST_KEYBOARD);
k2 = litest_add_device(li, LITEST_KEYBOARD);
litest_keyboard_key(k1, KEY_A, true);
litest_keyboard_key(k1, KEY_A, false);
litest_drain_events(li);
litest_touch_down(touchpad, 0, 50, 50);
litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
litest_touch_up(touchpad, 0);
litest_assert_empty_queue(li);
litest_timeout_dwt_short();
litest_keyboard_key(k2, KEY_A, true);
litest_keyboard_key(k2, KEY_A, false);
litest_drain_events(li);
litest_touch_down(touchpad, 0, 50, 50);
litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
litest_touch_up(touchpad, 0);
litest_assert_empty_queue(li);
litest_timeout_dwt_short();
litest_delete_device(k1);
litest_delete_device(k2);
}
END_TEST
START_TEST(touchpad_dwt_multiple_keyboards_bothkeys)
{
struct litest_device *touchpad = litest_current_device();
struct litest_device *k1, *k2;
struct libinput *li = touchpad->libinput;
ck_assert(has_disable_while_typing(touchpad));
enable_dwt(touchpad);
k1 = litest_add_device(li, LITEST_KEYBOARD);
k2 = litest_add_device(li, LITEST_KEYBOARD);
litest_keyboard_key(k1, KEY_A, true);
litest_keyboard_key(k1, KEY_A, false);
litest_keyboard_key(k2, KEY_B, true);
litest_keyboard_key(k2, KEY_B, false);
litest_drain_events(li);
litest_touch_down(touchpad, 0, 50, 50);
litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
litest_touch_up(touchpad, 0);
litest_assert_empty_queue(li);
litest_delete_device(k1);
litest_delete_device(k2);
}
END_TEST
START_TEST(touchpad_dwt_multiple_keyboards_bothkeys_modifier)
{
struct litest_device *touchpad = litest_current_device();
struct litest_device *k1, *k2;
struct libinput *li = touchpad->libinput;
ck_assert(has_disable_while_typing(touchpad));
enable_dwt(touchpad);
k1 = litest_add_device(li, LITEST_KEYBOARD);
k2 = litest_add_device(li, LITEST_KEYBOARD);
litest_keyboard_key(k1, KEY_RIGHTCTRL, true);
litest_keyboard_key(k1, KEY_RIGHTCTRL, false);
litest_keyboard_key(k2, KEY_B, true);
litest_keyboard_key(k2, KEY_B, false);
litest_drain_events(li);
/* If the keyboard is a single physical device, the above should
* trigger the modifier behavior for dwt. But libinput views it as
* two separate devices and this is such a niche case that it
* doesn't matter. So we test for the easy behavior:
* ctrl+B across two devices is *not* a dwt modifier combo
*/
litest_touch_down(touchpad, 0, 50, 50);
litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
litest_touch_up(touchpad, 0);
litest_assert_empty_queue(li);
litest_delete_device(k1);
litest_delete_device(k2);
}
END_TEST
START_TEST(touchpad_dwt_multiple_keyboards_remove)
{
struct litest_device *touchpad = litest_current_device();
struct litest_device *keyboards[2];
struct libinput *li = touchpad->libinput;
int which = _i; /* ranged test */
struct litest_device *removed, *remained;
ck_assert_int_le(which, 1);
ck_assert(has_disable_while_typing(touchpad));
enable_dwt(touchpad);
keyboards[0] = litest_add_device(li, LITEST_KEYBOARD);
keyboards[1] = litest_add_device(li, LITEST_KEYBOARD);
litest_keyboard_key(keyboards[0], KEY_A, true);
litest_keyboard_key(keyboards[0], KEY_A, false);
litest_keyboard_key(keyboards[1], KEY_B, true);
litest_keyboard_key(keyboards[1], KEY_B, false);
litest_drain_events(li);
litest_timeout_dwt_short();
removed = keyboards[which % 2];
remained = keyboards[(which + 1) % 2];
litest_delete_device(removed);
litest_keyboard_key(remained, KEY_C, true);
litest_keyboard_key(remained, KEY_C, false);
litest_drain_events(li);
litest_touch_down(touchpad, 0, 50, 50);
litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
litest_touch_up(touchpad, 0);
litest_assert_empty_queue(li);
litest_delete_device(remained);
}
END_TEST
static int static int
has_thumb_detect(struct litest_device *dev) has_thumb_detect(struct litest_device *dev)
{ {
@ -4346,7 +4492,7 @@ START_TEST(touchpad_thumb_clickfinger)
litest_touch_down(dev, 0, 50, 99); litest_touch_down(dev, 0, 50, 99);
litest_touch_down(dev, 1, 60, 99); litest_touch_down(dev, 1, 60, 99);
litest_touch_move_extended(dev, 0, 55, 99, axes); litest_touch_move_extended(dev, 0, 55, 99, axes);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -4357,7 +4503,7 @@ START_TEST(touchpad_thumb_clickfinger)
litest_assert_empty_queue(li); litest_assert_empty_queue(li);
litest_button_click(dev, BTN_LEFT, false); litest_button_click_debounced(dev, li, BTN_LEFT, false);
litest_touch_up(dev, 0); litest_touch_up(dev, 0);
litest_touch_up(dev, 1); litest_touch_up(dev, 1);
@ -4366,7 +4512,7 @@ START_TEST(touchpad_thumb_clickfinger)
litest_touch_down(dev, 0, 50, 99); litest_touch_down(dev, 0, 50, 99);
litest_touch_down(dev, 1, 60, 99); litest_touch_down(dev, 1, 60, 99);
litest_touch_move_extended(dev, 1, 65, 99, axes); litest_touch_move_extended(dev, 1, 65, 99, axes);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -4401,7 +4547,7 @@ START_TEST(touchpad_thumb_btnarea)
litest_touch_down(dev, 0, 90, 99); litest_touch_down(dev, 0, 90, 99);
litest_touch_move_extended(dev, 0, 95, 99, axes); litest_touch_move_extended(dev, 0, 95, 99, axes);
litest_button_click(dev, BTN_LEFT, true); litest_button_click_debounced(dev, li, BTN_LEFT, true);
/* button areas work as usual with a thumb */ /* button areas work as usual with a thumb */
@ -5493,6 +5639,7 @@ void
litest_setup_tests_touchpad(void) litest_setup_tests_touchpad(void)
{ {
struct range axis_range = {ABS_X, ABS_Y + 1}; struct range axis_range = {ABS_X, ABS_Y + 1};
struct range twice = {0, 2 };
litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY);
litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
@ -5620,6 +5767,10 @@ litest_setup_tests_touchpad(void)
litest_add("touchpad:dwt", touchpad_dwt_remove_kbd_while_active, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:dwt", touchpad_dwt_remove_kbd_while_active, LITEST_TOUCHPAD, LITEST_ANY);
litest_add_for_device("touchpad:dwt", touchpad_dwt_apple, LITEST_BCM5974); litest_add_for_device("touchpad:dwt", touchpad_dwt_apple, LITEST_BCM5974);
litest_add_for_device("touchpad:dwt", touchpad_dwt_acer_hawaii, LITEST_ACER_HAWAII_TOUCHPAD); litest_add_for_device("touchpad:dwt", touchpad_dwt_acer_hawaii, LITEST_ACER_HAWAII_TOUCHPAD);
litest_add_for_device("touchpad:dwt", touchpad_dwt_multiple_keyboards, LITEST_SYNAPTICS_I2C);
litest_add_for_device("touchpad:dwt", touchpad_dwt_multiple_keyboards_bothkeys, LITEST_SYNAPTICS_I2C);
litest_add_for_device("touchpad:dwt", touchpad_dwt_multiple_keyboards_bothkeys_modifier, LITEST_SYNAPTICS_I2C);
litest_add_ranged_for_device("touchpad:dwt", touchpad_dwt_multiple_keyboards_remove, LITEST_SYNAPTICS_I2C, &twice);
litest_add("touchpad:thumb", touchpad_thumb_begin_no_motion, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:thumb", touchpad_thumb_begin_no_motion, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:thumb", touchpad_thumb_update_no_motion, LITEST_CLICKPAD, LITEST_ANY); litest_add("touchpad:thumb", touchpad_thumb_update_no_motion, LITEST_CLICKPAD, LITEST_ANY);

View file

@ -43,9 +43,9 @@ START_TEST(trackpoint_middlebutton)
litest_drain_events(li); litest_drain_events(li);
/* A quick middle button click should get reported normally */ /* A quick middle button click should get reported normally */
litest_button_click(dev, BTN_MIDDLE, 1); litest_button_click_debounced(dev, li, BTN_MIDDLE, 1);
msleep(2); msleep(2);
litest_button_click(dev, BTN_MIDDLE, 0); litest_button_click_debounced(dev, li, BTN_MIDDLE, 0);
litest_wait_for_event(li); litest_wait_for_event(li);
@ -173,7 +173,7 @@ START_TEST(trackpoint_topsoftbuttons_left_handed_trackpoint)
litest_touch_down(touchpad, 0, 5, 5); litest_touch_down(touchpad, 0, 5, 5);
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(touchpad, BTN_LEFT, true); litest_button_click_debounced(touchpad, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -184,7 +184,7 @@ START_TEST(trackpoint_topsoftbuttons_left_handed_trackpoint)
ck_assert(device == trackpoint->libinput_device); ck_assert(device == trackpoint->libinput_device);
libinput_event_destroy(event); libinput_event_destroy(event);
litest_button_click(touchpad, BTN_LEFT, false); litest_button_click_debounced(touchpad, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
litest_is_button_event(event, litest_is_button_event(event,
@ -216,7 +216,7 @@ START_TEST(trackpoint_topsoftbuttons_left_handed_touchpad)
litest_touch_down(touchpad, 0, 5, 5); litest_touch_down(touchpad, 0, 5, 5);
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(touchpad, BTN_LEFT, true); litest_button_click_debounced(touchpad, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -225,7 +225,7 @@ START_TEST(trackpoint_topsoftbuttons_left_handed_touchpad)
ck_assert(device == trackpoint->libinput_device); ck_assert(device == trackpoint->libinput_device);
libinput_event_destroy(event); libinput_event_destroy(event);
litest_button_click(touchpad, BTN_LEFT, false); litest_button_click_debounced(touchpad, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
litest_is_button_event(event, litest_is_button_event(event,
@ -260,7 +260,7 @@ START_TEST(trackpoint_topsoftbuttons_left_handed_both)
litest_touch_down(touchpad, 0, 5, 5); litest_touch_down(touchpad, 0, 5, 5);
libinput_dispatch(li); libinput_dispatch(li);
litest_button_click(touchpad, BTN_LEFT, true); litest_button_click_debounced(touchpad, li, BTN_LEFT, true);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
@ -271,7 +271,7 @@ START_TEST(trackpoint_topsoftbuttons_left_handed_both)
ck_assert(device == trackpoint->libinput_device); ck_assert(device == trackpoint->libinput_device);
libinput_event_destroy(event); libinput_event_destroy(event);
litest_button_click(touchpad, BTN_LEFT, false); litest_button_click_debounced(touchpad, li, BTN_LEFT, false);
libinput_dispatch(li); libinput_dispatch(li);
event = libinput_get_event(li); event = libinput_get_event(li);
litest_is_button_event(event, litest_is_button_event(event,

View file

@ -747,6 +747,9 @@ print_switch_event(struct libinput_event *ev)
case LIBINPUT_SWITCH_LID: case LIBINPUT_SWITCH_LID:
which = "lid"; which = "lid";
break; break;
case LIBINPUT_SWITCH_TABLET_MODE:
which = "tablet-mode";
break;
default: default:
abort(); abort();
} }

View file

@ -62,6 +62,9 @@ Enable or disable natural scrolling
.B \-\-enable\-left\-handed|\-\-disable\-left\-handed .B \-\-enable\-left\-handed|\-\-disable\-left\-handed
Enable or disable left handed button configuration Enable or disable left handed button configuration
.TP 8 .TP 8
.B \-\-enable\-middlebutton|\-\-disable\-middlebutton
Enable or disable middle button emulation
.TP 8
.B \-\-enable\-dwt|\-\-disable\-dwt .B \-\-enable\-dwt|\-\-disable\-dwt
Enable or disable disable-while-typing Enable or disable disable-while-typing
.TP 8 .TP 8

View file

@ -314,6 +314,12 @@ print_device_notify(struct libinput_event *ev)
if (libinput_device_has_capability(dev, if (libinput_device_has_capability(dev,
LIBINPUT_DEVICE_CAP_TABLET_PAD)) LIBINPUT_DEVICE_CAP_TABLET_PAD))
printf("tablet-pad"); printf("tablet-pad");
if (libinput_device_has_capability(dev,
LIBINPUT_DEVICE_CAP_GESTURE))
printf("gesture");
if (libinput_device_has_capability(dev,
LIBINPUT_DEVICE_CAP_SWITCH))
printf("switch");
printf("\n"); printf("\n");
printf("Tap-to-click: %s\n", tap_default(dev)); printf("Tap-to-click: %s\n", tap_default(dev));

View file

@ -26,9 +26,15 @@
import sys import sys
import argparse import argparse
import evdev try:
import evdev.ecodes import evdev
import pyudev import evdev.ecodes
import pyudev
except ModuleNotFoundError as e:
print('Error: {}'.format(str(e)), file=sys.stderr)
print('One or more python modules are missing. Please install those '
'modules and re-run this tool.')
sys.exit(1)
class Range(object): class Range(object):

View file

@ -9,7 +9,7 @@ libinput\-measure\-touch-size \- measure touch size and orientation of devices
The The
.B "libinput measure touch\-size" .B "libinput measure touch\-size"
tool measures the size and orientation of a touch as provided by the kernel. tool measures the size and orientation of a touch as provided by the kernel.
an interactive tool. When executed, the tool will prompt the user to This is an interactive tool. When executed, the tool will prompt the user to
interact with the touch device. On termination, the tool prints a summary of the interact with the touch device. On termination, the tool prints a summary of the
values seen. This data should be attached to any values seen. This data should be attached to any
touch\-size\-related bug report. touch\-size\-related bug report.

View file

@ -26,9 +26,15 @@
import sys import sys
import argparse import argparse
import evdev try:
import evdev.ecodes import evdev
import pyudev import evdev.ecodes
import pyudev
except ModuleNotFoundError as e:
print('Error: {}'.format(str(e)), file=sys.stderr)
print('One or more python modules are missing. Please install those '
'modules and re-run this tool.')
sys.exit(1)
class Range(object): class Range(object):

View file

@ -1,6 +1,6 @@
.TH libinput-measure-touchpad-tap "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual" .TH libinput-measure-touchpad-tap "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual"
.SH NAME .SH NAME
libinput\-measure\-touchpad\-tap \- measure tap-to-click properities of devices libinput\-measure\-touchpad\-tap \- measure tap-to-click properties of devices
.SH SYNOPSIS .SH SYNOPSIS
.B libinput measure touchpad\-tap [\-\-help] [\-\-format=\fI<format>\fB] \fI[/dev/input/event0]\fR .B libinput measure touchpad\-tap [\-\-help] [\-\-format=\fI<format>\fB] \fI[/dev/input/event0]\fR
.SH DESCRIPTION .SH DESCRIPTION

View file

@ -26,9 +26,15 @@
import sys import sys
import argparse import argparse
import evdev try:
import evdev.ecodes import evdev
import pyudev import evdev.ecodes
import pyudev
except ModuleNotFoundError as e:
print('Error: {}'.format(str(e)), file=sys.stderr)
print('One or more python modules are missing. Please install those '
'modules and re-run this tool.')
sys.exit(1)
MINIMUM_EVENT_COUNT = 1000 MINIMUM_EVENT_COUNT = 1000

View file

@ -54,7 +54,7 @@ Measure tap-to-click time
.B libinput\-measure\-touchpad\-pressure(1) .B libinput\-measure\-touchpad\-pressure(1)
Measure touch pressure Measure touch pressure
.TP 8 .TP 8
.B libinput-measure-trackpoint-range(1) .B libinput\-measure\-trackpoint\-range(1)
Measure the delta range of a trackpoint Measure the delta range of a trackpoint
.SH LIBINPUT .SH LIBINPUT
Part of the Part of the

View file

@ -510,11 +510,20 @@ tools_exec_command(const char *prefix, int real_argc, char **real_argv)
setup_path(); setup_path();
rc = execvp(executable, argv); rc = execvp(executable, argv);
if (rc) if (rc) {
fprintf(stderr, if (errno == ENOENT) {
"Failed to execute '%s' (%s)\n", fprintf(stderr,
command, "libinput: %s is not a libinput command or not installed. "
strerror(errno)); "See 'libinput --help'\n",
command);
} else {
fprintf(stderr,
"Failed to execute '%s' (%s)\n",
command,
strerror(errno));
}
}
return EXIT_FAILURE; return EXIT_FAILURE;
} }