summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2015-06-04 12:49:11 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2015-06-04 12:49:18 +1000
commit99aa1f5dc3593826e512460a5c914a87a3a7c16a (patch)
treece80466d60aacf32db8be56a11ab88ad2b516b34
parentb2a345e25d379ff83abc16a1bc3d8ad94d9f2e4c (diff)
parent3d2264ef28cbee51b3d7f45fc1304e7e98f19f68 (diff)
Merge branch 'master' into tablet-support
-rw-r--r--CODING_STYLE79
-rw-r--r--COPYING10
-rw-r--r--README.txt64
-rw-r--r--configure.ac4
-rw-r--r--doc/Makefile.am8
-rw-r--r--doc/clickpad-softbuttons.dox26
-rw-r--r--doc/dot/libinput-stack-gnome.gv30
-rw-r--r--doc/dot/libinput-stack-wayland.gv17
-rw-r--r--doc/dot/libinput-stack-xorg.gv19
-rw-r--r--doc/faqs.dox51
-rw-r--r--doc/palm-detection.dox5
-rw-r--r--doc/scrolling.dox19
-rw-r--r--doc/svg/clickfinger-distance.svg106
-rw-r--r--doc/svg/clickfinger.svg207
-rw-r--r--doc/svg/software-buttons.svg175
-rw-r--r--doc/svg/tap-n-drag.svg810
-rw-r--r--doc/svg/top-software-buttons.svg213
-rw-r--r--doc/t440-support.dox92
-rw-r--r--doc/tapping.dox25
-rw-r--r--src/evdev-middle-button.c4
-rw-r--r--src/evdev-mt-touchpad-buttons.c119
-rw-r--r--src/evdev-mt-touchpad-edge-scroll.c23
-rw-r--r--src/evdev-mt-touchpad-tap.c14
-rw-r--r--src/evdev-mt-touchpad.c235
-rw-r--r--src/evdev-mt-touchpad.h16
-rw-r--r--src/evdev-tablet.c1
-rw-r--r--src/evdev.c84
-rw-r--r--src/evdev.h12
-rw-r--r--src/filter.c52
-rw-r--r--src/filter.h1
-rw-r--r--src/libinput-private.h1
-rw-r--r--src/libinput-util.c1
-rw-r--r--src/libinput-util.h38
-rw-r--r--src/libinput.c1
-rw-r--r--src/libinput.h1
-rw-r--r--src/path.c2
-rw-r--r--src/timer.c4
-rw-r--r--src/udev-seat.c1
-rw-r--r--test/device.c2
-rw-r--r--test/litest.c74
-rw-r--r--test/litest.h2
-rw-r--r--test/touch.c3
-rw-r--r--test/touchpad.c504
-rw-r--r--test/trackpoint.c23
-rw-r--r--tools/Makefile.am4
-rw-r--r--tools/event-debug.c11
-rw-r--r--tools/libinput-list-devices.c14
47 files changed, 2978 insertions, 229 deletions
diff --git a/CODING_STYLE b/CODING_STYLE
new file mode 100644
index 0000000..1a64ffe
--- /dev/null
+++ b/CODING_STYLE
@@ -0,0 +1,79 @@
+- Indentation in tabs, 8 characters wide, spaces after the tabs where
+ vertical alignment is required (see below)
+
+- Max line width 80ch, do not break up printed strings though
+
+- Break up long lines at logical groupings, one line for each logical group
+
+ int a = somelongname() +
+ someotherlongname();
+
+ if (a < 0 &&
+ (b > 20 & d < 10) &&
+ d != 0.0)
+
+
+ somelongfunctioncall(arg1,
+ arg2,
+ arg3);
+
+- Function declarations: return type on separate line, {} on separate line,
+ arguments broken up as above.
+
+ static inline int
+ foobar(int a, int b)
+ {
+
+ }
+
+ void
+ somenamethatiswaytoolong(int a,
+ int b,
+ int c)
+ {
+ }
+
+- /* comments only */, no // comments
+
+- variable_name, not VariableName or variableName. same for functions.
+
+- no typedefs of structs, enums, unions
+
+- if it generates a compiler warning, it needs to be fixed
+- if it generates a static checker warning, it needs to be fixed or
+ commented
+
+- declare variables at the top, try to keep them as local as possible.
+ Exception: if the same variable is re-used in multiple blocks, declare it
+ at the top.
+
+ int a;
+ int c;
+
+ if (foo) {
+ int b;
+
+ c = get_value();
+ usevalue(c);
+ }
+
+ if (bar) {
+ c = get_value();
+ useit(c);
+ }
+
+- public functions MUST be doxygen-commented, use doxygen's @foo rather than
+ \foo notation
+
+- include "config.h" comes first, followed by system headers, followed by
+ external library headers, followed by internal headers.
+ sort alphabetically where it makes sense (specifically system headers)
+
+ #include "config.h"
+
+ #include <stdio.h>
+ #include <string.h>
+
+ #include <libevdev/libevdev.h>
+
+ #include "libinput-private.h"
diff --git a/COPYING b/COPYING
index efc1a94..93ac3bc 100644
--- a/COPYING
+++ b/COPYING
@@ -4,7 +4,7 @@ Copyright © 2010-2012 Intel Corporation
Copyright © 2010-2011 Benjamin Franzke
Copyright © 2011-2012 Collabora, Ltd.
Copyright © 2013-2014 Jonas Ådahl
-Copyright © 2013-2014 Red Hat, Inc.
+Copyright © 2013-2015 Red Hat, Inc.
Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
@@ -23,3 +23,11 @@ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.
+
+libinput ships a copy of the GPL-licensed Linux kernel's linux/input.h
+header file. [1] This does not make libinput GPL.
+This copy is provided to provide consistent behavior regardless which kernel
+version libinput is compiled against. The header is used during compilation
+only, libinput does not link against GPL libraries.
+
+[1] http://cgit.freedesktop.org/wayland/libinput/tree/include/linux/input.h
diff --git a/README.txt b/README.txt
index c5dc61e..07e8fa3 100644
--- a/README.txt
+++ b/README.txt
@@ -9,22 +9,78 @@ applications that need to directly deal with input devices.
It provides device detection, device handling, input device event processing
and abstraction so minimize the amount of custom input code the user of
libinput need to provide the common set of functionality that users expect.
-
Input event processing includes scaling touch coordinates, generating
pointer events from touchpads, pointer acceleration, etc.
-libinput originates from weston, the Wayland reference compositor.
+libinput originates from
+[weston](http://cgit.freedesktop.org/wayland/weston/), the Wayland reference
+compositor.
+
+Architecture
+------------
+
+libinput is not used directly by applications, rather it is used by the
+xf86-input-libinput X.Org driver or wayland compositors. The typical
+software stack for a system running Wayland is:
+
+@dotfile libinput-stack-wayland.gv
+
+Where the Wayland compositor may be Weston, mutter, KWin, etc. Note that
+Wayland encourages the use of toolkits, so the Wayland client (your
+application) does not usually talk directly to the compositor but rather
+employs a toolkit (e.g. GTK) to do so.
+
+The simplified software stack for a system running X.Org is:
+
+@dotfile libinput-stack-xorg.gv
+
+Again, on a modern system the application does not usually talk directly to
+the X server using Xlib but rather employs a toolkit to do so.
+
+Source code
+-----------
The source code of libinput can be found at:
http://cgit.freedesktop.org/wayland/libinput
-For more information, visit:
+For a list of current and past releases visit:
http://www.freedesktop.org/wiki/Software/libinput/
+Reporting Bugs
+--------------
+
Bugs can be filed in the libinput component of Wayland:
https://bugs.freedesktop.org/enter_bug.cgi?product=Wayland&component=libinput
-Online API documentation:
+Where possible, please provide an
+[evemu](http://www.freedesktop.org/wiki/Evemu/) recording of the input
+device and/or the event sequence in question.
+
+Documentation
+-------------
+
+Developer API documentation:
http://wayland.freedesktop.org/libinput/doc/latest/modules.html
+High-level documentation about libinput's features:
+http://wayland.freedesktop.org/libinput/doc/latest/pages.html
+
+License
+-------
+
+libinput is licensed under the MIT license.
+
+> Permission to use, copy, modify, distribute, and sell this software and its
+> documentation for any purpose is hereby granted without fee, provided that
+> the above copyright notice appear in all copies and that both that copyright
+> notice and this permission notice appear in supporting documentation, and
+> that the name of the copyright holders not be used in advertising or
+> publicity pertaining to distribution of the software without specific,
+> written prior permission. The copyright holders make no representations
+> about the suitability of this software for any purpose. It is provided "as
+> is" without express or implied warranty.
+
+See the [COPYING](http://cgit.freedesktop.org/wayland/libinput/tree/COPYING)
+file for the full license information.
+
*/
diff --git a/configure.ac b/configure.ac
index d321e87..0265042 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
AC_PREREQ([2.64])
m4_define([libinput_major_version], [0])
-m4_define([libinput_minor_version], [15])
+m4_define([libinput_minor_version], [17])
m4_define([libinput_micro_version], [0])
m4_define([libinput_version],
[libinput_major_version.libinput_minor_version.libinput_micro_version])
@@ -31,7 +31,7 @@ AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz])
# b) If interfaces have been changed or added, but binary compatibility has
# been preserved, change to C+1:0:A+1
# c) If the interface is the same as the previous version, change to C:R+1:A
-LIBINPUT_LT_VERSION=12:0:2
+LIBINPUT_LT_VERSION=12:2:2
AC_SUBST(LIBINPUT_LT_VERSION)
AM_SILENT_RULES([yes])
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 48e68b8..113666c 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -13,6 +13,7 @@ header_files = \
$(srcdir)/absolute-axes.dox \
$(srcdir)/clickpad-softbuttons.dox \
$(srcdir)/device-configuration-via-udev.dox \
+ $(srcdir)/faqs.dox \
$(srcdir)/normalization-of-relative-motion.dox \
$(srcdir)/palm-detection.dox \
$(srcdir)/scrolling.dox \
@@ -25,9 +26,16 @@ header_files = \
diagram_files = \
$(srcdir)/dot/seats-sketch.gv \
$(srcdir)/dot/seats-sketch-libinput.gv \
+ $(srcdir)/dot/libinput-stack-wayland.gv \
+ $(srcdir)/dot/libinput-stack-xorg.gv \
+ $(srcdir)/dot/libinput-stack-gnome.gv \
+ $(srcdir)/svg/software-buttons.svg \
+ $(srcdir)/svg/clickfinger.svg \
$(srcdir)/svg/button-scrolling.svg \
$(srcdir)/svg/edge-scrolling.svg \
$(srcdir)/svg/palm-detection.svg \
+ $(srcdir)/svg/tap-n-drag.svg \
+ $(srcdir)/svg/top-software-buttons.svg \
$(srcdir)/svg/twofinger-scrolling.svg
html/index.html: libinput.doxygen $(header_files) $(diagram_files)
diff --git a/doc/clickpad-softbuttons.dox b/doc/clickpad-softbuttons.dox
index 8d91936..a4f2e44 100644
--- a/doc/clickpad-softbuttons.dox
+++ b/doc/clickpad-softbuttons.dox
@@ -24,15 +24,6 @@ is split in the middle to generate left or right button events on click. The
height of the button area depends on the hardware but is usually around
10mm.
-@dot
-digraph G {
- clickpad [
- shape = "record";
- label = "{\nMain\nArea\n\n|{LEFT|RIGHT}}";
- ]
-}
-@enddot
-
Left, right and middle button events can be triggered as follows:
- if a finger is in the main area or the left button area, a click generates
left button events.
@@ -40,6 +31,8 @@ Left, right and middle button events can be triggered as follows:
- if there is a finger in both the left and right button area, a click
generates middle button events.
+@image html software-buttons.svg "Left, right and middle-button click with software button areas"
+
If fingers are down in the main area in addition to fingers in the
left or right button area, those fingers are are ignored.
A release event always releases the buttons logically down, regardless of
@@ -69,9 +62,18 @@ three fingers are held down on the touchpad when a physical click is
generated. The location of the fingers does not matter and there are no
software-defined button areas.
-The Xorg synaptics driver uses 30% of the touchpad dimensions as threshold,
-libinput does not have this restriction. If two fingers are on the pad
-while clicking, that is a two-finger click.
+@image html clickfinger.svg "One, two and three-finger click with Clickfinger behavior"
+
+On some touchpads, libinput imposes a limit on how the fingers may be placed
+on the touchpad. In the most common use-case this allows for a user to
+trigger a click with the thumb while leaving the pointer-moving finger on
+the touchpad.
+
+@image html clickfinger-distance.svg "Illustration of the distance detection algorithm"
+
+In the illustration above the red area marks the proximity area around the
+first finger. Since the thumb is outside of that area libinput considers the
+click a single-finger click rather than a two-finger click.
Clickfinger configuration can be enabled through the
libinput_device_config_click_set_method() call. If clickfingers are
diff --git a/doc/dot/libinput-stack-gnome.gv b/doc/dot/libinput-stack-gnome.gv
new file mode 100644
index 0000000..4e0ccf4
--- /dev/null
+++ b/doc/dot/libinput-stack-gnome.gv
@@ -0,0 +1,30 @@
+digraph stack
+{
+ compound=true;
+ rankdir="LR";
+ node [
+ shape="box";
+ ]
+
+ gcc -> gsettings
+
+ xf86libinput -> libinput
+
+ subgraph cluster0 {
+ label="X.Org";
+ xf86libinput [label="xf86-input-libinput"];
+ xserver [label="X Server"];
+ xserver -> xf86libinput;
+ }
+
+ gcc [label="gnome-control-center"];
+
+ subgraph cluster3 {
+ gsettings
+ }
+
+ gsd [label="gnome-settings-daemon"];
+
+ gsd -> gsettings
+ gsd -> xserver
+}
diff --git a/doc/dot/libinput-stack-wayland.gv b/doc/dot/libinput-stack-wayland.gv
new file mode 100644
index 0000000..20b334e
--- /dev/null
+++ b/doc/dot/libinput-stack-wayland.gv
@@ -0,0 +1,17 @@
+digraph stack
+{
+ rankdir="LR";
+ node [
+ shape="box";
+ ]
+
+ kernel [label="Kernel"];
+
+ libinput;
+ compositor [label="Wayland Compositor"];
+ client [label="Wayland Client"];
+
+ kernel -> libinput
+ libinput -> compositor
+ compositor -> client
+}
diff --git a/doc/dot/libinput-stack-xorg.gv b/doc/dot/libinput-stack-xorg.gv
new file mode 100644
index 0000000..e50f241
--- /dev/null
+++ b/doc/dot/libinput-stack-xorg.gv
@@ -0,0 +1,19 @@
+digraph stack
+{
+ rankdir="LR";
+ node [
+ shape="box";
+ ]
+
+ kernel [label="Kernel"];
+
+ libinput;
+ xf86libinput [label="xf86-input-libinput"];
+ xserver [label="X Server"];
+ client [label="X11 client"];
+
+ kernel -> libinput
+ libinput -> xf86libinput
+ xf86libinput -> xserver
+ xserver -> client
+}
diff --git a/doc/faqs.dox b/doc/faqs.dox
new file mode 100644
index 0000000..92a4228
--- /dev/null
+++ b/doc/faqs.dox
@@ -0,0 +1,51 @@
+/**
+@page faq FAQs - Frequently Asked Questions
+
+Frequently asked questions about libinput.
+
+@section faq_kinetic_scrolling Kinetic scrolling does not work
+
+The X.Org synaptics driver implemented kinetic scrolling in the driver. It
+measures the scroll speed and once the finger leaves the touchpad the driver
+keeps sending scroll events for a predetermined time. This effectively
+provides for kinetic scrolling without client support but triggers an
+unfixable [bug](https://bugs.freedesktop.org/show_bug.cgi?id=38909): the
+client cannot know that the events are from a kinetic scroll source. Scroll
+events in X are always sent to the current cursor position, a movement of the
+cursor after lifting the finger will send the kinetic scroll events to the
+new client, something the user does not usually expect. A key event during
+the kinetic scroll procedure causes side-effects such as triggering zoom.
+
+libinput does not implement kinetic scrolling for touchpads. Instead it
+provides the libinput_event_pointer_get_axis_source() function that enables
+callers to implement kinetic scrolling on a per-widget basis, see @ref
+scroll_sources.
+
+@section faq_gpl Is libinput GPL-licensed?
+
+No, libinput is MIT licensed. The Linux kernel header file linux/input.h in
+libinput's tree is provded to ensure the same behavior regardless of which
+kernel version libinput is built on. It does not make libinput GPL-licensed.
+
+@section faq_config_options Where is the configuration stored?
+
+libinput does not store configuration options, it is up to the caller to
+manage these and decide which configuration option to apply to each device.
+This must be done at startup, after a resume and whenever a new device is
+detected.
+
+In a GNOME X.Org stack a user would usually toggle an option in
+the gnome-control-center which adjusts a gsettings entry. That change is
+picked up by gnome-settings-daemon and applied to the device by adjusting
+input device properties that the xf86-input-libinput driver provides.
+The input device property changes map to the respective libinput
+configuration options.
+
+@dotfile libinput-stack-gnome.gv
+
+This has an effect on the availability of configuration options: if an
+option is not exposed by the intermediary, it cannot be configured by the
+client. Also some configuration options that are provided by the
+intermediary may not be libinput-specific configuration options.
+
+*/
diff --git a/doc/palm-detection.dox b/doc/palm-detection.dox
index a03f9c1..d787455 100644
--- a/doc/palm-detection.dox
+++ b/doc/palm-detection.dox
@@ -74,8 +74,9 @@ Notable behaviors of libinput's disable-while-typing feature:
- Some keys do not trigger the timeout, specifically some modifier keys
(Ctrl, Alt, Shift, and Fn). Actions such as Ctrl + click thus stay
responsive.
-- Touches started while the touchpad is disabled do not control the cursor,
- it is thus possible to rest the palm on the touchpad while typing.
+- Touches started while typing do not control the cursor even after typing
+ has stopped, it is thus possible to rest the palm on the touchpad while
+ typing.
- Physical buttons work even while the touchpad is disabled. This includes
software-emulated buttons.
diff --git a/doc/scrolling.dox b/doc/scrolling.dox
index b5a01cf..94aa815 100644
--- a/doc/scrolling.dox
+++ b/doc/scrolling.dox
@@ -63,4 +63,23 @@ the motion events. Cross-device scrolling is not supported but
for one exception: libinput's @ref t440_support enables the use of the middle
button for button scrolling (even when the touchpad is disabled).
+@section scroll_sources Scroll sources
+
+libinput provides a pointer axis *source* for each scroll event. The
+source can be obtained with the libinput_event_pointer_get_axis_source()
+function and is one of **wheel**, **finger**, or **continuous**. The source
+information lets a caller decide when to implement kinetic scrolling.
+Usually, a caller will process events of source wheel as they come in.
+For events of source finger a caller should calculate the velocity of the
+scroll motion and upon finger release start a kinetic scrolling motion (i.e.
+continue executing a scroll according to some friction factor).
+libinput expects the caller to be in charge of widget handling, the source
+information is thus enough to provide kinetic scrolling on a per-widget
+basis. A caller should cancel kinetic scrolling when the pointer leaves the
+current widget or when a key is pressed.
+
+See the libinput_event_pointer_get_axis_source() for details on the
+behavior of each scroll source.
+
+See also http://who-t.blogspot.com.au/2015/03/libinput-scroll-sources.html
*/
diff --git a/doc/svg/clickfinger-distance.svg b/doc/svg/clickfinger-distance.svg
new file mode 100644
index 0000000..ac659cf
--- /dev/null
+++ b/doc/svg/clickfinger-distance.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="89.829216mm"
+ height="59.06765mm"
+ viewBox="0 0 318.2925 209.29482"
+ id="svg4184"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="clickfinger-distance.svg">
+ <defs
+ id="defs4186" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4"
+ inkscape:cx="235.68795"
+ inkscape:cy="163.39995"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata4189">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-257.99662,-299.41313)">
+ <rect
+ width="313.09872"
+ height="167.89594"
+ x="260.59351"
+ y="302.01001"
+ id="rect2858-0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.19376326;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="opacity:0.92000002;fill:#7b0000;fill-opacity:0.2983426;stroke:#000000;stroke-width:1.00100005;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4788"
+ width="188.57143"
+ height="79.285713"
+ x="355"
+ y="309.50507" />
+ <g
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,276.6631,-158.96703)"
+ id="g3663-9-5">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path2820-6-6"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path2824-1-1"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path2824-7-1-4"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00100005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-1-1-3"
+ d="m 353.70196,495.15765 c -24.01774,-7.29937 -29.0012,-10.10221 -30.51977,-10.54973 -10.67294,-3.14527 -18.27051,-5.54063 -23.77758,-13.4704 -5.50707,-7.92977 -5.34967,-20.78347 8.87612,-26.31604 14.2258,-5.53257 39.34351,8.79597 60.13061,16.16341 20.7871,7.36744 33.04563,11.44545 39.33422,13.87551 -8.10022,18.05041 -7.22129,21.15857 -10.11054,33.34117 -0.0481,0.20261 -17.87459,-5.12433 -43.93306,-13.04392 z"
+ sodipodi:nodetypes="sszzzcss" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-1-4-3"
+ d="m 324.44991,483.39364 c -10.67294,-1.94747 -17.88441,-5.64478 -21.62691,-8.75386 -8.11652,-9.03765 -6.31775,-15.03428 -3.3272,-13.99784 8.90495,-0.9097 30.20384,9.01528 33.86042,10.17935 -5.80268,11.37909 -1.08919,13.70271 -8.90631,12.57235 z"
+ sodipodi:nodetypes="ccccc" />
+ </g>
+</svg>
diff --git a/doc/svg/clickfinger.svg b/doc/svg/clickfinger.svg
new file mode 100644
index 0000000..b92dcb4
--- /dev/null
+++ b/doc/svg/clickfinger.svg
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="1084.4291"
+ height="218.18733"
+ id="svg2"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="clickfinger.svg">
+ <metadata
+ id="metadata4314">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ id="namedview4312"
+ showgrid="false"
+ inkscape:zoom="0.81739538"
+ inkscape:cx="347.81182"
+ inkscape:cy="125.36322"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-page="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <sodipodi:guide
+ position="563.61797,339.58481"
+ orientation="0,1"
+ id="guide4473" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4" />
+ <g
+ id="g4405"
+ transform="matrix(0.81023703,0,0,0.6422249,-2.3181067e-6,0.47063383)">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:7.20000076;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858-0"
+ y="3.6000037"
+ x="3.6000032"
+ height="261.42856"
+ width="386.42856" />
+ <g
+ id="g3663-9-5"
+ transform="matrix(0.98314313,0.18283763,-0.18283763,0.98314313,-4.7772181,-625.20496)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820-6-6"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-1-1"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-1-4"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ </g>
+ <g
+ id="g4394"
+ transform="matrix(0.81023703,0,0,0.6422249,-2.3181067e-6,0.47063383)">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:7.20000076;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858"
+ y="3.6000218"
+ x="475.52567"
+ height="261.42856"
+ width="386.42856" />
+ <g
+ id="g3663"
+ transform="matrix(0.98196551,0.12493315,-0.14261338,1.1209308,508.26203,-717.12108)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:0.94553083px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00189106;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ <g
+ id="g3663-9"
+ transform="matrix(0.98314313,0.18283763,-0.18283763,0.98314313,458.58466,-619.69966)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820-6"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-1"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-1"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ </g>
+ <rect
+ width="313.09872"
+ height="167.89594"
+ x="768.41345"
+ y="2.3120098"
+ id="rect2858-7"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.19376326;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <g
+ transform="matrix(0.79562482,0.08023518,-0.11555064,0.71988967,731.4982,-458.98163)"
+ id="g3663-3">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path2820-0"
+ style="fill:none;stroke:#000000;stroke-width:0.94553083px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path2824-9"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00189106;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path2824-7-8"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,697.24775,-392.41517)"
+ id="g3663-9-8">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path2820-6-7"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path2824-1-6"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path2824-7-1-7"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="matrix(0.79562482,0.08023518,-0.11555064,0.71988967,793.15605,-458.77793)"
+ id="g3663-3-0">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path2820-0-5"
+ style="fill:none;stroke:#000000;stroke-width:0.94553083px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path2824-9-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00189106;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path2824-7-8-8"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/doc/svg/software-buttons.svg b/doc/svg/software-buttons.svg
new file mode 100644
index 0000000..903535c
--- /dev/null
+++ b/doc/svg/software-buttons.svg
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="1083.7891"
+ height="270.02051"
+ id="svg2"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="software-buttons.svg">
+ <metadata
+ id="metadata4314">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ id="namedview4312"
+ showgrid="false"
+ inkscape:zoom="0.57798581"
+ inkscape:cx="1134.9723"
+ inkscape:cy="-71.183873"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-page="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <sodipodi:guide
+ position="563.298,391.13313"
+ orientation="0,1"
+ id="guide4473" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4" />
+ <g
+ id="g4598"
+ transform="translate(-0.31997204,0.28487182)">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.19376326;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858-0"
+ y="2.7826459"
+ x="2.9168537"
+ height="167.89594"
+ width="313.09872" />
+ <g
+ id="g3663-9-5"
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,-118.87011,-352.11563)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820-6-6"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-1-1"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-1-4"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ </g>
+ <rect
+ width="313.09872"
+ height="167.89594"
+ x="384.96854"
+ y="3.0675292"
+ id="rect2858"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.19376326;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ width="313.09872"
+ height="167.89594"
+ x="768.09351"
+ y="2.5968816"
+ id="rect2858-7"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.19376326;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <g
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,665.11943,-345.64117)"
+ id="g3663-9-8">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path2820-6-7"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path2824-1-6"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path2824-7-1-7"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g4656"
+ transform="translate(-0.31997204,0.28487182)">
+ <g
+ id="g4639">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:0.72135597px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820-0-5"
+ d="m 970.15914,263.93369 32.58666,-97.47985 c 0,0 12.2603,-46.32869 38.5506,-33.925 20.7457,9.78778 17.4301,24.72594 16.4399,28.75425 -10.1846,41.43257 -30.105,105.84033 -30.105,105.84033 l -0.428,-0.37827 z"
+ sodipodi:nodetypes="ccssccc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00144271;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-9-3"
+ d="m 987.16367,214.84092 c 8.97014,-26.84686 16.75933,-50.19953 17.30923,-51.89481 3.8651,-11.91483 10.3862,-22.76212 16.5622,-27.54951 6.8496,-5.30946 13.8243,-5.75615 21.9108,-1.40323 3.7999,2.04543 6.0302,3.61208 8.2265,5.77836 2.5214,2.487 3.6881,4.17002 5.1008,7.35828 1.3655,3.08181 1.9391,7.11725 1.5051,10.58923 -0.597,4.77663 -10.2821,40.41668 -20.9931,77.25236 -7.7256,26.56907 -9.371,31.11182 -9.5644,31.10964 -0.1479,-0.002 -55.70067,-1.83937 -56.08626,-2.18017 -0.16723,-0.1478 6.29681,-19.93218 16.02913,-49.06015 z"
+ sodipodi:nodetypes="scsssscsccs" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-8-8"
+ d="m 1008.3595,164.57667 c 3.8651,-11.91483 7.6606,-19.35039 13.8366,-24.13778 6.8495,-5.30946 12.0833,-5.57765 20.1698,-1.22474 9.3061,4.73331 12.9905,9.62271 11.9094,19.03362 -6.3459,19.3209 -6.9054,22.12042 -9.2168,26.32727 -0.1479,-0.002 -33.6651,-14.70742 -35.0296,-15.23839 -1.4035,-0.54616 -1.8884,-3.70289 -1.6694,-4.75998 z" />
+ </g>
+ </g>
+ <g
+ transform="translate(-386.56163,2.2570367)"
+ id="g4656-3">
+ <g
+ id="g4639-2">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:0.72135597px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820-0-5-6"
+ d="m 970.15914,263.93369 32.58666,-97.47985 c 0,0 12.2603,-46.32869 38.5506,-33.925 20.7457,9.78778 17.4301,24.72594 16.4399,28.75425 -10.1846,41.43257 -30.105,105.84033 -30.105,105.84033 l -0.428,-0.37827 z"
+ sodipodi:nodetypes="ccssccc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00144271;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-9-3-6"
+ d="m 987.16367,214.84092 c 8.97014,-26.84686 16.75933,-50.19953 17.30923,-51.89481 3.8651,-11.91483 10.3862,-22.76212 16.5622,-27.54951 6.8496,-5.30946 13.8243,-5.75615 21.9108,-1.40323 3.7999,2.04543 6.0302,3.61208 8.2265,5.77836 2.5214,2.487 3.6881,4.17002 5.1008,7.35828 1.3655,3.08181 1.9391,7.11725 1.5051,10.58923 -0.597,4.77663 -10.2821,40.41668 -20.9931,77.25236 -7.7256,26.56907 -9.371,31.11182 -9.5644,31.10964 -0.1479,-0.002 -55.70067,-1.83937 -56.08626,-2.18017 -0.16723,-0.1478 6.29681,-19.93218 16.02913,-49.06015 z"
+ sodipodi:nodetypes="scsssscsccs" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-8-8-4"
+ d="m 1008.3595,164.57667 c 3.8651,-11.91483 7.6606,-19.35039 13.8366,-24.13778 6.8495,-5.30946 12.0833,-5.57765 20.1698,-1.22474 9.3061,4.73331 12.9905,9.62271 11.9094,19.03362 -6.3459,19.3209 -6.9054,22.12042 -9.2168,26.32727 -0.1479,-0.002 -33.6651,-14.70742 -35.0296,-15.23839 -1.4035,-0.54616 -1.8884,-3.70289 -1.6694,-4.75998 z" />
+ </g>
+ </g>
+</svg>
diff --git a/doc/svg/tap-n-drag.svg b/doc/svg/tap-n-drag.svg
new file mode 100644
index 0000000..5c84beb
--- /dev/null
+++ b/doc/svg/tap-n-drag.svg
@@ -0,0 +1,810 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="753.05255"
+ height="737.14532"
+ id="svg2"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="tap-n-drag.svg">
+ <metadata
+ id="metadata4314">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ id="namedview4312"
+ showgrid="false"
+ inkscape:zoom="0.57798582"
+ inkscape:cx="926.00645"
+ inkscape:cy="627.63785"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-page="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <sodipodi:guide
+ position="600.67806,858.83692"
+ orientation="0,1"
+ id="guide4473" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6136"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend">
+ <path
+ inkscape:connector-curvature="0"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path6138" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker6132"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path6134"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6111"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend">
+ <path
+ inkscape:connector-curvature="0"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ id="path6113" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5983"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path5985"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5979"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5981"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5429"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5414"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5963"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5965"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5954"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5956"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker5950"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend"
+ inkscape:collect="always">
+ <path
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path5952"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5935"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5937"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker5931"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend">
+ <path
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path5933"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker5873"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend">
+ <path
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path5875"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker5869"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lstart">
+ <path
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path5871"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5772"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path5774"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker5743"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend"
+ inkscape:collect="always">
+ <path
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path5745"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker5739"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lstart">
+ <path
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ id="path5741"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5682"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path5684"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5678"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5680"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5674"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path5676"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5668"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5670"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5411"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5408"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ </defs>
+ <g
+ id="g6226"
+ transform="translate(5.049491e-5,0)">
+ <rect
+ transform="matrix(1,0,-0.3959173,0.91828617,0,0)"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.41992331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858-0"
+ y="2.7099617"
+ x="77.243813"
+ height="182.83618"
+ width="313.09872" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path5690"
+ d="m 187.13717,141.62175 c -7.23616,-20.04776 -13.54188,-37.48106 -14.01271,-38.74063 -3.30915,-8.852639 -3.7924,-18.06951 -1.22734,-23.40863 2.84478,-5.921356 8.47454,-8.88606 17.68557,-9.313448 4.32826,-0.20083 7.07051,-0.09252 10.1144,0.399498 3.49454,0.564856 5.4048,1.155131 8.35172,2.580712 2.84854,1.377989 5.55537,3.650555 7.10286,5.96335 2.12899,3.181858 13.60803,28.938928 24.88164,55.830298 8.13151,19.39637 18.05518,43.64654 17.89104,43.71977 -0.12552,0.056 -56.74536,-0.47345 -57.25799,-0.53541 -0.22232,-0.0269 -5.6782,-14.74433 -13.52919,-36.49551 z" />
+ <g
+ id="g3663-9-5"
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,28.295757,-432.54234)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2820-6-6"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path2824-1-1"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path2824-7-1-4"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path5672"
+ d="m 166.69233,71.697127 0,37.313633"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker5682);marker-end:url(#marker5674)" />
+ </g>
+ <g
+ id="g5747"
+ transform="translate(37.060081,-0.29412562)">
+ <rect
+ width="313.09872"
+ height="182.83618"
+ x="400.31058"
+ y="3.0302601"
+ id="rect5718"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.41992331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ transform="matrix(1,0,-0.3959173,0.91828617,0,0)" />
+ <path
+ d="m 510.07714,141.91588 c -7.23616,-20.04776 -13.54188,-37.48106 -14.01271,-38.74063 -3.30915,-8.852643 -3.7924,-18.069514 -1.22734,-23.408634 2.84478,-5.921356 8.47454,-8.88606 17.68557,-9.313448 4.32826,-0.20083 7.07051,-0.09252 10.1144,0.399498 3.49454,0.564856 5.4048,1.155131 8.35172,2.580712 2.84854,1.377989 5.55537,3.650555 7.10286,5.96335 2.12899,3.181858 13.60803,28.938932 24.88164,55.830302 8.13151,19.39637 18.05518,43.64654 17.89104,43.71977 -0.12552,0.056 -56.74536,-0.47345 -57.25799,-0.53541 -0.22232,-0.0269 -5.6782,-14.74433 -13.52919,-36.49551 z"
+ id="path5720"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(0.79717939,0.09917984,-0.14825348,0.53330368,349.25624,-338.55557)"
+ id="g5722">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path5724"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path5726"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path5728"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5743)"
+ d="m 489.6323,71.991253 0,37.313637"
+ id="path5730"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+ <g
+ id="g6193"
+ transform="translate(-719.99994,240)">
+ <rect
+ width="313.09872"
+ height="182.83618"
+ x="1157.2438"
+ y="2.7099617"
+ id="rect6064"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.41992331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ transform="matrix(1,0,-0.3959173,0.91828617,0,0)" />
+ <path
+ d="m 1204.1322,161.80782 c -7.2362,-20.04776 -13.5419,-37.48106 -14.0127,-38.74063 -3.3092,-8.85264 -3.7925,-18.06951 -1.2274,-23.40863 2.8448,-5.921356 8.4746,-8.88606 17.6856,-9.313448 4.3282,-0.20083 7.0705,-0.09252 10.1144,0.399498 3.4945,0.564856 5.4048,1.155131 8.3517,2.580712 2.8485,1.377989 5.5554,3.650555 7.1029,5.96335 2.129,3.181858 13.608,28.938928 24.8816,55.830298 8.1315,19.39637 18.0552,43.64654 17.891,43.71977 -0.1255,0.056 -56.7453,-0.47345 -57.258,-0.53541 -0.2223,-0.0269 -5.6781,-14.74433 -13.5291,-36.49551 z"
+ id="path6066"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(0.79717939,0.09917984,-0.14825348,0.53330368,1043.3113,-318.66363)"
+ id="g6068">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path6070"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path6072"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path6074"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker6136)"
+ d="m 1360.3559,35.196081 -121.004,62.192402"
+ id="path6076"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path6080"
+ d="m 1204.1322,161.80782 c -7.2362,-20.04776 -13.5419,-37.48106 -14.0127,-38.74063 -3.3092,-8.85264 -3.7925,-18.06951 -1.2274,-23.40863 2.8448,-5.921356 8.4746,-8.88606 17.6856,-9.313448 4.3282,-0.20083 7.0705,-0.09252 10.1144,0.399498 3.4945,0.564856 5.4048,1.155131 8.3517,2.580712 2.8485,1.377989 5.5554,3.650555 7.1029,5.96335 2.129,3.181858 13.608,28.938928 24.8816,55.830298 8.1315,19.39637 18.0552,43.64654 17.891,43.71977 -0.1255,0.056 -56.7453,-0.47345 -57.258,-0.53541 -0.2223,-0.0269 -5.6781,-14.74433 -13.5291,-36.49551 z" />
+ <g
+ id="g6082"
+ transform="matrix(0.79717939,0.09917984,-0.14825348,0.53330368,1043.3113,-318.66363)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path6084"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path6086"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path6088"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path6092"
+ d="M 1238.5753,97.34219 C 1220.4088,41.977511 1322.8368,-6.8249094 1359.1699,33.833528"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker5983)" />
+ <g
+ transform="translate(397.06003,-0.29412562)"
+ id="g5801">
+ <g
+ id="g5776">
+ <rect
+ transform="matrix(1,0,-0.3959173,0.91828617,0,0)"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.41992331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect5758"
+ y="3.0302601"
+ x="400.31058"
+ height="182.83618"
+ width="313.09872" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path5760"
+ d="m 447.07213,162.10195 c -7.23616,-20.04776 -13.54188,-37.48106 -14.01271,-38.74063 -3.30915,-8.85264 -3.7924,-18.06951 -1.22734,-23.408634 2.84478,-5.921356 8.47454,-8.88606 17.68557,-9.313448 4.32826,-0.20083 7.07051,-0.09252 10.1144,0.399498 3.49454,0.564856 5.4048,1.155131 8.35172,2.580712 2.84854,1.377989 5.55537,3.650555 7.10286,5.96335 2.12899,3.181862 13.60803,28.938932 24.88164,55.830302 8.13151,19.39637 18.05518,43.64654 17.89104,43.71977 -0.12552,0.056 -56.74536,-0.47345 -57.25799,-0.53541 -0.22232,-0.0269 -5.6782,-14.74433 -13.52919,-36.49551 z" />
+ <g
+ id="g5762"
+ transform="matrix(0.79717939,0.09917984,-0.14825348,0.53330368,286.25123,-318.3695)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path5764"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path5766"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path5768"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path5770"
+ d="M 534.17392,70.357054 482.29191,97.682609"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5772)" />
+ </g>
+ <g
+ id="g5785">
+ <path
+ d="m 447.07213,162.10195 c -7.23616,-20.04776 -13.54188,-37.48106 -14.01271,-38.74063 -3.30915,-8.85264 -3.7924,-18.06951 -1.22734,-23.408634 2.84478,-5.921356 8.47454,-8.88606 17.68557,-9.313448 4.32826,-0.20083 7.07051,-0.09252 10.1144,0.399498 3.49454,0.564856 5.4048,1.155131 8.35172,2.580712 2.84854,1.377989 5.55537,3.650555 7.10286,5.96335 2.12899,3.181862 13.60803,28.938932 24.88164,55.830302 8.13151,19.39637 18.05518,43.64654 17.89104,43.71977 -0.12552,0.056 -56.74536,-0.47345 -57.25799,-0.53541 -0.22232,-0.0269 -5.6782,-14.74433 -13.52919,-36.49551 z"
+ id="path5789"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(0.79717939,0.09917984,-0.14825348,0.53330368,286.25123,-318.3695)"
+ id="g5791">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path5793"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path5795"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path5797"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker5950)"
+ d="M 534.17392,70.357054 482.29191,97.682609"
+ id="path5799"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+ </g>
+ </g>
+ <g
+ id="g6251"
+ transform="translate(-2.4752545e-5,40)">
+ <g
+ id="g6042"
+ transform="translate(-1042.9399,459.70587)">
+ <rect
+ width="313.09872"
+ height="182.83618"
+ x="1120.3105"
+ y="3.0302601"
+ id="rect6044"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.41992331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ transform="matrix(1,0,-0.3959173,0.91828617,0,0)" />
+ <path
+ d="m 1167.0721,162.10195 c -7.2361,-20.04776 -13.5418,-37.48106 -14.0127,-38.74063 -3.3091,-8.85264 -3.7924,-18.06951 -1.2273,-23.408634 2.8448,-5.921356 8.4745,-8.88606 17.6856,-9.313448 4.3282,-0.20083 7.0705,-0.09252 10.1144,0.399498 3.4945,0.564856 5.4048,1.155131 8.3517,2.580712 2.8485,1.377989 5.5553,3.650555 7.1028,5.96335 2.129,3.181862 13.6081,28.938932 24.8817,55.830302 8.1315,19.39637 18.0552,43.64654 17.891,43.71977 -0.1255,0.056 -56.7453,-0.47345 -57.258,-0.53541 -0.2223,-0.0269 -5.6782,-14.74433 -13.5292,-36.49551 z"
+ id="path6046"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,1011.2357,-412.24821)"
+ id="g6048">
+ <path
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z"
+ id="path6050"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z"
+ id="path6052"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z"
+ id="path6054"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker5935)"
+ d="m 1149.6323,91.991253 0,37.313637"
+ id="path6056"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+ <g
+ transform="translate(360.00008,460)"
+ id="g6235">
+ <rect
+ transform="matrix(1,0,-0.3959173,0.91828617,0,0)"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.41992331;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect6237"
+ y="2.7099617"
+ x="77.243813"
+ height="182.83618"
+ width="313.09872" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0.2983426;fill-rule:nonzero;stroke:none;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path6239"
+ d="m 187.13717,141.62175 c -7.23616,-20.04776 -13.54188,-37.48106 -14.01271,-38.74063 -3.30915,-8.852639 -3.7924,-18.06951 -1.22734,-23.40863 2.84478,-5.921356 8.47454,-8.88606 17.68557,-9.313448 4.32826,-0.20083 7.07051,-0.09252 10.1144,0.399498 3.49454,0.564856 5.4048,1.155131 8.35172,2.580712 2.84854,1.377989 5.55537,3.650555 7.10286,5.96335 2.12899,3.181858 13.60803,28.938928 24.88164,55.830298 8.13151,19.39637 18.05518,43.64654 17.89104,43.71977 -0.12552,0.056 -56.74536,-0.47345 -57.25799,-0.53541 -0.22232,-0.0269 -5.6782,-14.74433 -13.52919,-36.49551 z" />
+ <g
+ id="g6241"
+ transform="matrix(0.79657897,0.11742288,-0.14814182,0.631399,28.295757,-432.54234)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path6243"
+ d="m 388.57143,893.79076 -57.14285,-130 c 0,0 -30.0247,-58.84827 4.28571,-70.00001 27.07438,-8.79984 37.32196,9.59496 40,14.64286 27.54455,51.91936 84.64285,173.21429 84.64285,173.21429 l -0.71428,0 -71.07143,12.14286 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="path6245"
+ d="m 360.32021,827.78041 c -15.74169,-35.7991 -29.44655,-66.92657 -30.45523,-69.17214 -7.08929,-15.78239 -10.8761,-32.88254 -9.6176,-43.43026 1.39575,-11.69796 7.19746,-18.50389 18.22574,-21.38044 5.18218,-1.35169 8.54724,-1.76827 12.41155,-1.53649 4.43642,0.26609 6.95929,0.93715 11.03011,2.93391 3.93491,1.9301 8.0085,5.56248 10.68932,9.53159 3.68818,5.46055 26.56068,50.9623 49.57778,98.62829 16.60192,34.38082 37.06388,77.41994 36.89013,77.59369 -0.13286,0.13286 -69.01932,11.92114 -69.66286,11.92114 -0.27909,0 -12.00972,-26.24842 -29.08894,-65.08929 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ id="path6247"
+ d="m 334.75785,756.75053 c -7.08929,-15.78239 -10.28437,-26.89033 -9.02587,-37.43805 1.39575,-11.69796 5.8085,-16.73613 16.83678,-19.61268 12.44766,-3.59459 20.03902,-1.91353 27.39013,8.75815 11.42622,25.66382 13.40166,29.05484 15.06365,35.48866 -0.13286,0.13286 -42.89663,15.49027 -44.57776,16.18518 -1.72922,0.71479 -4.94789,-2.09377 -5.68693,-3.38126 z" />
+ </g>
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path6249"
+ d="m 166.69233,71.697127 0,37.313633"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker5682);marker-end:url(#marker5674)" />
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="138.85577"
+ y="212.46516"
+ id="text6269"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan6271"
+ x="138.85577"
+ y="212.46516"
+ style="font-size:22.5px">a) Tap</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text6273"
+ y="212.46516"
+ x="462.85577"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="font-size:22.5px"
+ y="212.46516"
+ x="462.85577"
+ id="tspan6275"
+ sodipodi:role="line">b) Hold down</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text6277"
+ y="472.46515"
+ x="136.85577"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="font-size:22.5px"
+ y="472.46515"
+ x="136.85577"
+ id="tspan6279"
+ sodipodi:role="line">c) Move</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="378.85577"
+ y="472.46515"
+ id="text6281"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan6283"
+ x="378.85577"
+ y="472.46515"
+ style="font-size:22.5px">d) Reset finger, move (optional)</tspan></text>
+ <text
+ sodipodi:linespacing="125%"
+ id="text6285"
+ y="732.46515"
+ x="436.85577"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="font-size:22.5px"
+ y="732.46515"
+ x="436.85577"
+ id="tspan6287"
+ sodipodi:role="line">f) Tap to end (optional)</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="122.85577"
+ y="732.46515"
+ id="text6289"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan6291"
+ x="122.85577"
+ y="732.46515"
+ style="font-size:22.5px">e) Release</tspan></text>
+</svg>
diff --git a/doc/svg/top-software-buttons.svg b/doc/svg/top-software-buttons.svg
new file mode 100644
index 0000000..ab31124
--- /dev/null
+++ b/doc/svg/top-software-buttons.svg
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="956.69696"
+ height="207.31395"
+ id="svg2"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="top-software-buttons.svg">
+ <metadata
+ id="metadata4314">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1136"
+ id="namedview4312"
+ showgrid="false"
+ inkscape:zoom="1.1559716"
+ inkscape:cx="527.43768"
+ inkscape:cy="31.469935"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-page="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0">
+ <sodipodi:guide
+ position="126.62171,304.81443"
+ orientation="0,1"
+ id="guide4473" />
+ <sodipodi:guide
+ position="-59.334811,222.84675"
+ orientation="1,0"
+ id="guide4774" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4" />
+ <g
+ id="g4776"
+ transform="translate(0,33.753604)">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#222222;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.26503992;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858-0"
+ y="-28.093496"
+ x="2.63252"
+ height="199.02132"
+ width="271.43192" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path4718"
+ d="m 12.201667,-12.062569 97.876283,0.09937"
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.42133546;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.77900552" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.42133546;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.77900552"
+ d="m 166.20167,-12.062569 97.87628,0.09937"
+ id="path4768"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4772"
+ d="m 117.44622,-11.972768 41.59554,0"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.5999999;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.6, 5.2;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <g
+ id="g4800"
+ transform="translate(340,33.753604)">
+ <rect
+ width="271.43192"
+ height="199.02132"
+ x="2.63252"
+ y="-28.093496"
+ id="rect4802"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#222222;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.26503992;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.42133546;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.77900552"
+ d="m 12.201667,-12.062569 97.876283,0.09937"
+ id="path4804"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path4806"
+ d="m 166.20167,-12.062569 97.87628,0.09937"
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.42133546;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.77900552" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.5999999;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.6, 5.2;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 117.44622,-11.972768 41.59554,0"
+ id="path4808"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g4835"
+ transform="matrix(0.73822306,-0.67455668,0.67455668,0.73822306,537.11787,65.759499)"
+ inkscape:transform-center-x="-34.439004"
+ inkscape:transform-center-y="45.87469">
+ <path
+ d="M 65.88204,110.10383 39.621684,21.31208 c 0,0 -15.199155,-40.682325 13.783835,-43.694695 22.870506,-2.377059 28.308474,10.4407 29.693939,13.9424019 C 97.349439,27.575979 124.86389,110.86612 124.86389,110.86612 l -0.56898,-0.0839 -58.41287,-0.67842 z"
+ id="path2820-6-6"
+ style="fill:none;stroke:#000000;stroke-width:0.72135597px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 53.156605,65.10762 C 45.92045,40.655669 39.614727,19.392552 39.143896,17.856259 35.834749,7.0588289 35.351503,-4.1828468 37.916556,-10.69489 c 2.844782,-7.222187 8.474545,-10.838191 17.685579,-11.35947 4.328257,-0.244949 7.070506,-0.112842 10.114398,0.487262 3.49454,0.688946 5.404793,1.408896 8.351719,3.147655 2.848538,1.680712 5.555366,4.452526 7.10286,7.273407 2.128991,3.8808629 13.608031,35.296377 24.881638,68.09537 8.13151,23.65746 18.05518,53.235016 17.89104,53.324326 -0.12552,0.0683 -56.745362,-0.57745 -57.257993,-0.65302 -0.222317,-0.0328 -5.678201,-17.983442 -13.529192,-44.51302 z"
+ id="path2824-1-1"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00144271;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 43.316663,17.257819 C 40.007515,6.4603886 39.107932,-0.92832907 41.672985,-7.4403722 c 2.844782,-7.2221878 7.10625,-9.8851248 16.317283,-10.4064038 10.448053,-0.80798 16.246136,1.144839 20.520953,8.7461155 5.300002,17.54581 6.371244,19.9188585 6.742033,24.1763215 -0.125516,0.06829 -36.465311,4.743495 -37.907409,4.984858 -1.48335,0.248267 -3.63121,-1.903 -4.029182,-2.8027 z"
+ id="path2824-7-1-4"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="translate(680,33.753604)"
+ id="g4810">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#222222;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.26503992;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4812"
+ y="-28.093496"
+ x="2.63252"
+ height="199.02132"
+ width="271.43192" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path4814"
+ d="m 12.201667,-12.062569 97.876283,0.09937"
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.42133546;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.77900552" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.42133546;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.77900552"
+ d="m 166.20167,-12.062569 97.87628,0.09937"
+ id="path4816"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4818"
+ d="m 117.44622,-11.972768 41.59554,0"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.5999999;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.6, 5.2;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <g
+ id="g4835-3"
+ transform="matrix(0.85372372,-0.52072624,0.52072624,0.85372372,770.03163,57.741757)">
+ <path
+ d="M 65.88204,110.10383 39.621684,21.31208 c 0,0 -15.199155,-40.682325 13.783835,-43.694695 22.870506,-2.377059 28.308474,10.4407 29.693939,13.9424019 C 97.349439,27.575979 124.86389,110.86612 124.86389,110.86612 l -0.56898,-0.0839 -58.41287,-0.67842 z"
+ id="path2820-6-6-7"
+ style="fill:none;stroke:#000000;stroke-width:0.72135597px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 53.156605,65.10762 C 45.92045,40.655669 39.614727,19.392552 39.143896,17.856259 35.834749,7.0588289 35.351503,-4.1828468 37.916556,-10.69489 c 2.844782,-7.222187 8.474545,-10.838191 17.685579,-11.35947 4.328257,-0.244949 7.070506,-0.112842 10.114398,0.487262 3.49454,0.688946 5.404793,1.408896 8.351719,3.147655 2.848538,1.680712 5.555366,4.452526 7.10286,7.273407 2.128991,3.8808629 13.608031,35.296377 24.881638,68.09537 8.13151,23.65746 18.05518,53.235016 17.89104,53.324326 -0.12552,0.0683 -56.745362,-0.57745 -57.257993,-0.65302 -0.222317,-0.0328 -5.678201,-17.983442 -13.529192,-44.51302 z"
+ id="path2824-1-1-7"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00144271;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 43.316663,17.257819 C 40.007515,6.4603886 39.107932,-0.92832907 41.672985,-7.4403722 c 2.844782,-7.2221878 7.10625,-9.8851248 16.317283,-10.4064038 10.448053,-0.80798 16.246136,1.144839 20.520953,8.7461155 5.300002,17.54581 6.371244,19.9188585 6.742033,24.1763215 -0.125516,0.06829 -36.465311,4.743495 -37.907409,4.984858 -1.48335,0.248267 -3.63121,-1.903 -4.029182,-2.8027 z"
+ id="path2824-7-1-4-9"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g4835-0"
+ transform="matrix(0.78868314,-0.61479989,0.61479989,0.78868314,7.9549192,61.170364)"
+ inkscape:transform-center-x="-31.290987"
+ inkscape:transform-center-y="47.742947">
+ <path
+ d="M 65.88204,110.10383 39.621684,21.31208 c 0,0 -15.199155,-40.682325 13.783835,-43.694695 22.870506,-2.377059 28.308474,10.4407 29.693939,13.9424019 C 97.349439,27.575979 124.86389,110.86612 124.86389,110.86612 l -0.56898,-0.0839 -58.41287,-0.67842 z"
+ id="path2820-6-6-1"
+ style="fill:none;stroke:#000000;stroke-width:0.72135597px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 53.156605,65.10762 C 45.92045,40.655669 39.614727,19.392552 39.143896,17.856259 35.834749,7.0588289 35.351503,-4.1828468 37.916556,-10.69489 c 2.844782,-7.222187 8.474545,-10.838191 17.685579,-11.35947 4.328257,-0.244949 7.070506,-0.112842 10.114398,0.487262 3.49454,0.688946 5.404793,1.408896 8.351719,3.147655 2.848538,1.680712 5.555366,4.452526 7.10286,7.273407 2.128991,3.8808629 13.608031,35.296377 24.881638,68.09537 8.13151,23.65746 18.05518,53.235016 17.89104,53.324326 -0.12552,0.0683 -56.745362,-0.57745 -57.257993,-0.65302 -0.222317,-0.0328 -5.678201,-17.983442 -13.529192,-44.51302 z"
+ id="path2824-1-1-0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffccaa;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.00144271;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ <path
+ d="M 43.316663,17.257819 C 40.007515,6.4603886 39.107932,-0.92832907 41.672985,-7.4403722 c 2.844782,-7.2221878 7.10625,-9.8851248 16.317283,-10.4064038 10.448053,-0.80798 16.246136,1.144839 20.520953,8.7461155 5.300002,17.54581 6.371244,19.9188585 6.742033,24.1763215 -0.125516,0.06829 -36.465311,4.743495 -37.907409,4.984858 -1.48335,0.248267 -3.63121,-1.903 -4.029182,-2.8027 z"
+ id="path2824-7-1-4-0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.92000002;fill:#ffe6d5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;enable-background:accumulate"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/doc/t440-support.dox b/doc/t440-support.dox
index 277dbe8..652a6d0 100644
--- a/doc/t440-support.dox
+++ b/doc/t440-support.dox
@@ -11,21 +11,10 @@ laptops had a separate set of physical buttons for the
<a href="http://en.wikipedia.org/wiki/Pointing_stick">trackstick</a>. This
series removed these buttons, relying on a software emulation of the top
section of the touchpad. This is visually marked on the trackpad itself,
-approximately like this:
+and clicks can be triggered by pressing the touchpad down with a finger in
+the respective area:
-@dot
-digraph G {
- subgraph cluster_0 {
- margin="0";
-
- clickpad [
- shape = "record";
- color = "none";
- label = "{{LLLLLLLLLL|MMMMM|RRRRRRRRR}|\n\n\n\n\n\n\n\n|{LLLLLLLL| |RRRRRRRR}}";
- ]
- }
-}
-@enddot
+@image html top-software-buttons.svg "Left, right and middle-button click with top software button areas"
This page only covers the top software buttons, the bottom button behavior
is covered in @ref clickpad_softbuttons "Clickpad software buttons".
@@ -36,36 +25,77 @@ property.
@section t440_support_btn_size Size of the buttons
+The size of the buttons matches the visual markings on this touchpad.
+The width of the left and right buttons is approximately 42% of the
+touchpad's width, the middle button is centered and assigned 16% of the
+touchpad width.
+
The line of the buttons is 5mm from the top edge of the touchpad,
measurements of button presses showed that the size of the buttons needs to
be approximately 10mm high to work reliable (especially when using the
thumb to press the button).
-The width of the left and right buttons is approximately 42% of the
-touchpad's width, the middle button is centered and should be assigned
-approximately 16% of the touchpad width.
-
@section t440_support_btn_behavior Button behavior
Movement in the top button area does not generate pointer movement. These
buttons are not replacement buttons for the bottom button area but have
-their own behavior.
-Semantically attached to the trackstick device, libinput re-routes events
-from these buttons to appear through the trackstick device. The top button
-areas work even if the touchpad is disabled but will be disabled when the
-trackstick device is disabled.
+their own behavior. Semantically attached to the trackstick device, libinput
+re-routes events from these buttons to appear through the trackstick device.
+
+@dot
+digraph top_button_routing
+{
+ rankdir="LR";
+ node [shape="box";]
+
+ trackstick [label="trackstick kernel device"];
+ touchpad [label="touchpad kernel device"];
-If the finger starts inside the top area and moves outside the button area
-the finger is treated as dead and must be lifted to generate future buttons.
-Likewise, movement into the top button area does not trigger button events, a click
-has to start inside this area to take effect.
+ subgraph cluster0 {
+ bgcolor = floralwhite
+ label = "libinput"
+
+ libinput_ts [label="trackstick libinput_device"
+ style=filled
+ fillcolor=white];
+ libinput_tp [label="touchpad libinput_device"
+ style=filled
+ fillcolor=white];
+
+ libinput_tp -> libinput_ts [constraint=false
+ color="red4"];
+ }
+
+ trackstick -> libinput_ts [arrowhead="none"]
+ touchpad -> libinput_tp [color="red4"]
+
+ events_tp [label="other touchpad events"];
+ events_topbutton [label="top sofware button events"];
+
+ libinput_tp -> events_tp [arrowhead="none"]
+ libinput_ts -> events_topbutton [color="red4"]
+}
+@enddot
+
+
+The top button areas work even if the touchpad is disabled but will be
+disabled when the trackstick device is disabled. If the finger starts inside
+the top area and moves outside the button area the finger is treated as dead
+and must be lifted to generate future buttons. Likewise, movement into the
+top button area does not trigger button events, a click has to start inside
+this area to take effect.
@section t440_support_identification Kernel support
-The firmware on touchpads providing top software buttons is buggy and
-announces wrong ranges. <a href="https://lkml.org/lkml/2014/3/7/722">Kernel
-patches</a> are required; these fixes are available in kernels
-3.14.1, 3.15 and later but each touchpad needs a separate fix.
+The firmware on the first generation of touchpads providing top software
+buttons is buggy and announces wrong ranges.
+<a href="https://lkml.org/lkml/2014/3/7/722">Kernel patches</a> are required;
+these fixes are available in kernels 3.14.1, 3.15 and later but each
+touchpad needs a separate fix.
+
+The October 2014 refresh of these laptops do not have this firmware bug
+anymore and should work without per-device patches, though
+<a href="http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=02e07492cdfae9c86e3bd21c0beec88dbcc1e9e8">this kernel commit</a> is required.
For a complete list of supported touchpads check <a
href="http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/input/mouse/synaptics.c">the
diff --git a/doc/tapping.dox b/doc/tapping.dox
index 7eb81e6..1337fc5 100644
--- a/doc/tapping.dox
+++ b/doc/tapping.dox
@@ -27,15 +27,22 @@ libinput_device_config_tap_set_enabled() for details.
libinput also supports "tap-and-drag" where a tap immediately followed by a
finger down and that finger being held down emulates a button press. Moving
-the finger around can thus drag the selected item on the screen. Lifting the
-finger and putting it back down immediately (i.e. within the timeout) will
-continue the dragging process, so that multiple touchpad-widths of distance
-can be covered easily. If two-fingers are supported by the hardware, a
-second finger can be used to drag while the first is held in-place.
-
-An alternative method to end a drag process is to tap immediately after
-lifting the finger. The full sequence is thus: tap, finger down, drag,
-finger up, tap.
+the finger around can thus drag the selected item on the screen.
+
+@image html tap-n-drag.svg "Tap-and-drag process"
+
+The above diagram explains the process, a tap (a) followed by a finger held
+down (b) starts the drag process and logically holds the left mouse button
+down. A movement of the finger (c) will drag the selected item until the
+finger is relased (e). If needed, the finger's position can be reset by
+lifting and quickly setting it down again on the touchpad (d). This will be
+interpreted as continuing move and is especially useful on small touchpads
+or with slow pointer acceleration.
+The release of the mouse buttons after the finger release (e) is triggered
+by a timeout. To release the button immediately, simply tap again (f).
+
+If two fingers are supported by the hardware, a second finger can be used to
+drag while the first is held in-place.
@section tap_constraints Constraints while tapping
diff --git a/src/evdev-middle-button.c b/src/evdev-middle-button.c
index 328cf6c..c989486 100644
--- a/src/evdev-middle-button.c
+++ b/src/evdev-middle-button.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -20,6 +20,8 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include "config.h"
+
#include <stdint.h>
#include "evdev.h"
diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index 43e983b..5786ea8 100644
--- a/src/evdev-mt-touchpad-buttons.c
+++ b/src/evdev-mt-touchpad-buttons.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -20,6 +20,8 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include "config.h"
+
#include <errno.h>
#include <limits.h>
#include <math.h>
@@ -392,7 +394,7 @@ tp_button_handle_event(struct tp_dispatch *tp,
enum button_event event,
uint64_t time)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
enum button_state current = t->button.state;
switch(t->button.state) {
@@ -478,7 +480,7 @@ tp_process_button(struct tp_dispatch *tp,
const struct input_event *e,
uint64_t time)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
uint32_t mask = 1 << (e->code - BTN_LEFT);
/* Ignore other buttons on clickpads */
@@ -680,7 +682,7 @@ int
tp_init_buttons(struct tp_dispatch *tp,
struct evdev_device *device)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
struct tp_touch *t;
int width, height;
double diagonal;
@@ -731,7 +733,7 @@ tp_init_buttons(struct tp_dispatch *tp,
tp_for_each_touch(tp, t) {
t->button.state = BUTTON_STATE_NONE;
libinput_timer_init(&t->button.timer,
- tp->device->base.seat->libinput,
+ tp_libinput_context(tp),
tp_button_handle_timeout, t);
}
@@ -782,6 +784,104 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
return 0;
}
+static inline int
+tp_check_clickfinger_distance(struct tp_dispatch *tp,
+ struct tp_touch *t1,
+ struct tp_touch *t2)
+{
+ double x, y;
+
+ if (!t1 || !t2)
+ return 0;
+
+ x = abs(t1->point.x - t2->point.x);
+ y = abs(t1->point.y - t2->point.y);
+
+ /* no resolution, so let's assume they're close enough together */
+ if (tp->device->abs.fake_resolution) {
+ int w, h;
+
+ /* Use a maximum of 30% of the touchpad width or height if
+ * we dont' have resolution. */
+ w = tp->device->abs.absinfo_x->maximum -
+ tp->device->abs.absinfo_x->minimum;
+ h = tp->device->abs.absinfo_y->maximum -
+ tp->device->abs.absinfo_y->minimum;
+
+ return (x < w * 0.3 && y < h * 0.3) ? 1 : 0;
+ } else {
+ /* maximum spread is 40mm horiz, 20mm vert. Anything wider than that
+ * is probably a gesture. The y spread is small so we ignore clicks
+ * with thumbs at the bottom of the touchpad while the pointer
+ * moving finger is still on the pad */
+
+ x /= tp->device->abs.absinfo_x->resolution;
+ y /= tp->device->abs.absinfo_y->resolution;
+
+ return (x < 40 && y < 20) ? 1 : 0;
+ }
+
+}
+
+static uint32_t
+tp_clickfinger_set_button(struct tp_dispatch *tp)
+{
+ uint32_t button;
+ unsigned int nfingers = tp->nfingers_down;
+ struct tp_touch *t;
+ struct tp_touch *first = NULL,
+ *second = NULL,
+ *third = NULL;
+ uint32_t close_touches = 0;
+
+ if (nfingers < 2 || nfingers > 3)
+ goto out;
+
+ /* two or three fingers down on the touchpad. Check for distance
+ * between the fingers. */
+ tp_for_each_touch(tp, t) {
+ if (t->state != TOUCH_BEGIN && t->state != TOUCH_UPDATE)
+ continue;
+
+ if (!first)
+ first = t;
+ else if (!second)
+ second = t;
+ else if (!third) {
+ third = t;
+ break;
+ }
+ }
+
+ if (!first || !second) {
+ nfingers = 1;
+ goto out;
+ }
+
+ close_touches |= tp_check_clickfinger_distance(tp, first, second) << 0;
+ close_touches |= tp_check_clickfinger_distance(tp, second, third) << 1;
+ close_touches |= tp_check_clickfinger_distance(tp, first, third) << 2;
+
+ switch(__builtin_popcount(close_touches)) {
+ case 0: nfingers = 1; break;
+ case 1: nfingers = 2; break;
+ default: nfingers = 3; break;
+ }
+
+out:
+ switch (nfingers) {
+ case 0:
+ case 1: button = BTN_LEFT; break;
+ case 2: button = BTN_RIGHT; break;
+ case 3: button = BTN_MIDDLE; break;
+ default:
+ button = 0;
+ break;
+ }
+
+ return button;
+}
+
static int
tp_notify_clickpadbutton(struct tp_dispatch *tp,
uint64_t time,
@@ -816,14 +916,7 @@ tp_notify_clickpadbutton(struct tp_dispatch *tp,
*/
if (tp->buttons.click_method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER &&
state == LIBINPUT_BUTTON_STATE_PRESSED) {
- switch (tp->nfingers_down) {
- case 0:
- case 1: button = BTN_LEFT; break;
- case 2: button = BTN_RIGHT; break;
- case 3: button = BTN_MIDDLE; break;
- default:
- button = 0;
- }
+ button = tp_clickfinger_set_button(tp);
tp->buttons.active = button;
if (!button)
diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c
index 369fded..2302d2c 100644
--- a/src/evdev-mt-touchpad-edge-scroll.c
+++ b/src/evdev-mt-touchpad-edge-scroll.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -20,6 +20,8 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include "config.h"
+
#include <errno.h>
#include <limits.h>
#include <math.h>
@@ -121,7 +123,7 @@ tp_edge_scroll_handle_none(struct tp_dispatch *tp,
struct tp_touch *t,
enum scroll_event event)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case SCROLL_EVENT_TOUCH:
@@ -149,7 +151,7 @@ tp_edge_scroll_handle_edge_new(struct tp_dispatch *tp,
struct tp_touch *t,
enum scroll_event event)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case SCROLL_EVENT_TOUCH:
@@ -178,7 +180,7 @@ tp_edge_scroll_handle_edge(struct tp_dispatch *tp,
struct tp_touch *t,
enum scroll_event event)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case SCROLL_EVENT_TOUCH:
@@ -209,7 +211,7 @@ tp_edge_scroll_handle_area(struct tp_dispatch *tp,
struct tp_touch *t,
enum scroll_event event)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case SCROLL_EVENT_TOUCH:
@@ -232,7 +234,7 @@ tp_edge_scroll_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum scroll_event event)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
enum tp_edge_scroll_touch_state current = t->scroll.edge_state;
switch (current) {
@@ -301,7 +303,7 @@ tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device)
tp_for_each_touch(tp, t) {
t->scroll.direction = -1;
libinput_timer_init(&t->scroll.timer,
- device->base.seat->libinput,
+ tp_libinput_context(tp),
tp_edge_scroll_handle_timeout, t);
}
@@ -361,6 +363,9 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
if (!t->dirty)
continue;
+ if (t->palm.state != PALM_NONE)
+ continue;
+
switch (t->scroll.edge) {
case EDGE_NONE:
if (t->scroll.direction != -1) {
@@ -438,6 +443,10 @@ tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time)
&zero,
&zero_discrete);
t->scroll.direction = -1;
+ /* reset touch to area state, avoids loading the
+ * state machine with special case handling */
+ t->scroll.edge = EDGE_NONE;
+ t->scroll.edge_state = EDGE_SCROLL_TOUCH_STATE_AREA;
}
}
}
diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c
index fb8c9e4..b51f083 100644
--- a/src/evdev-mt-touchpad-tap.c
+++ b/src/evdev-mt-touchpad-tap.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
@@ -37,7 +37,7 @@
#define CASE_RETURN_STRING(a) case a: return #a
#define DEFAULT_TAP_TIMEOUT_PERIOD 180
-#define DEFAULT_DRAG_TIMEOUT_PERIOD 500
+#define DEFAULT_DRAG_TIMEOUT_PERIOD 300
#define DEFAULT_TAP_MOVE_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3)
enum tap_event {
@@ -147,7 +147,7 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case TAP_EVENT_TOUCH:
@@ -223,7 +223,7 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case TAP_EVENT_MOTION:
@@ -483,7 +483,7 @@ tp_tap_multitap_handle_event(struct tp_dispatch *tp,
struct tp_touch *t,
enum tap_event event, uint64_t time)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
switch (event) {
case TAP_EVENT_RELEASE:
@@ -576,7 +576,7 @@ tp_tap_handle_event(struct tp_dispatch *tp,
enum tap_event event,
uint64_t time)
{
- struct libinput *libinput = tp->device->base.seat->libinput;
+ struct libinput *libinput = tp_libinput_context(tp);
enum tp_tap_state current;
current = tp->tap.state;
@@ -857,7 +857,7 @@ tp_init_tap(struct tp_dispatch *tp)
tp->tap.enabled = tp_tap_default(tp->device);
libinput_timer_init(&tp->tap.timer,
- tp->device->base.seat->libinput,
+ tp_libinput_context(tp),
tp_tap_handle_timeout, tp);
return 0;
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 79cc051..e7abe94 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -237,6 +237,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
t->state = TOUCH_END;
t->pinned.is_pinned = false;
t->millis = time;
+ t->palm.time = 0;
assert(tp->nfingers_down >= 1);
tp->nfingers_down--;
tp->queued |= TOUCHPAD_EVENT_MOTION;
@@ -310,6 +311,7 @@ tp_process_absolute(struct tp_dispatch *tp,
tp_new_touch(tp, t, time);
else
tp_end_sequence(tp, t, time);
+ break;
}
}
@@ -475,12 +477,45 @@ tp_palm_tap_is_palm(struct tp_dispatch *tp, struct tp_touch *t)
/* We're inside the left/right palm edge and in the northern half of
* the touchpad - this tap is a palm */
- if (t->point.y < tp->palm.vert_center)
+ if (t->point.y < tp->palm.vert_center) {
+ log_debug(tp_libinput_context(tp),
+ "palm: palm-tap detected\n");
return true;
+ }
return false;
}
+static int
+tp_palm_detect_dwt(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
+{
+ if (tp->dwt.keyboard_active &&
+ t->state == TOUCH_BEGIN) {
+ t->palm.state = PALM_TYPING;
+ t->palm.time = time;
+ t->palm.first = t->point;
+ return 1;
+ } else if (!tp->dwt.keyboard_active &&
+ t->state == TOUCH_UPDATE &&
+ t->palm.state == PALM_TYPING)
+ {
+ /* If a touch has started before the first or after the last
+ key press, release it on timeout. Benefit: a palm rested
+ while typing on the touchpad will be ignored, but a touch
+ started once we stop typing will be able to control the
+ pointer (alas not tap, etc.).
+ */
+ if (t->palm.time == 0 ||
+ t->palm.time > tp->dwt.keyboard_last_press_time) {
+ t->palm.state = PALM_NONE;
+ log_debug(tp_libinput_context(tp),
+ "palm: touch released, timeout after typing\n");
+ }
+ }
+
+ return 0;
+}
+
static void
tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
{
@@ -489,13 +524,8 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
struct device_float_coords delta;
int dirs;
- if (tp->sendevents.keyboard_active &&
- t->state == TOUCH_BEGIN) {
- t->palm.state = PALM_TYPING;
- t->palm.time = time;
- t->palm.first = t->point;
- return;
- }
+ if (tp_palm_detect_dwt(tp, t, time))
+ goto out;
/* If labelled a touch as palm, we unlabel as palm when
we move out of the palm edge zone within the timeout, provided
@@ -509,6 +539,8 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
tp_normalize_delta(tp, delta));
if ((dirs & DIRECTIONS) && !(dirs & ~DIRECTIONS)) {
t->palm.state = PALM_NONE;
+ log_debug(tp_libinput_context(tp),
+ "palm: touch released, out of edge zone\n");
}
}
return;
@@ -530,6 +562,11 @@ tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
t->palm.state = PALM_EDGE;
t->palm.time = time;
t->palm.first = t->point;
+
+out:
+ log_debug(tp_libinput_context(tp),
+ "palm: palm detected (%s)\n",
+ t->palm.state == PALM_EDGE ? "edge" : "typing");
}
static void
@@ -717,7 +754,7 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time)
if (filter_motion ||
tp->sendevents.trackpoint_active ||
- tp->sendevents.keyboard_active) {
+ tp->dwt.keyboard_active) {
tp_edge_scroll_stop_events(tp, time);
tp_gesture_stop(tp, time);
return;
@@ -767,15 +804,15 @@ static void
tp_remove_sendevents(struct tp_dispatch *tp)
{
libinput_timer_cancel(&tp->sendevents.trackpoint_timer);
- libinput_timer_cancel(&tp->sendevents.keyboard_timer);
+ libinput_timer_cancel(&tp->dwt.keyboard_timer);
if (tp->buttons.trackpoint)
libinput_device_remove_event_listener(
&tp->sendevents.trackpoint_listener);
- if (tp->sendevents.keyboard)
+ if (tp->dwt.keyboard)
libinput_device_remove_event_listener(
- &tp->sendevents.keyboard_listener);
+ &tp->dwt.keyboard_listener);
}
static void
@@ -810,7 +847,7 @@ tp_release_fake_touches(struct tp_dispatch *tp)
static void
tp_clear_state(struct tp_dispatch *tp)
{
- uint64_t now = libinput_now(tp->device->base.seat->libinput);
+ uint64_t now = libinput_now(tp_libinput_context(tp));
struct tp_touch *t;
/* Unroll the touchpad state.
@@ -912,7 +949,41 @@ tp_keyboard_timeout(uint64_t now, void *data)
struct tp_dispatch *tp = data;
tp_tap_resume(tp, now);
- tp->sendevents.keyboard_active = false;
+
+ tp->dwt.keyboard_active = false;
+
+ log_debug(tp_libinput_context(tp), "palm: keyboard timeout\n");
+}
+
+static inline bool
+tp_key_ignore_for_dwt(unsigned int keycode)
+{
+ switch (keycode) {
+ /* Ignore modifiers to be responsive to ctrl-click, alt-tab, etc. */
+ case KEY_LEFTCTRL:
+ case KEY_RIGHTCTRL:
+ case KEY_LEFTALT:
+ case KEY_RIGHTALT:
+ case KEY_LEFTSHIFT:
+ case KEY_RIGHTSHIFT:
+ case KEY_FN:
+ case KEY_CAPSLOCK:
+ case KEY_TAB:
+ case KEY_COMPOSE:
+ case KEY_RIGHTMETA:
+ case KEY_LEFTMETA:
+ return true;
+ default:
+ break;
+ }
+
+ /* Ignore keys not part of the "typewriter set", i.e. F-keys,
+ * multimedia keys, numpad, etc.
+ */
+ if (keycode >= KEY_F1)
+ return true;
+
+ return false;
}
static void
@@ -934,42 +1005,61 @@ tp_keyboard_event(uint64_t time, struct libinput_event *event, void *data)
/* modifier keys don't trigger disable-while-typing so things like
* ctrl+zoom or ctrl+click are possible */
- switch (libinput_event_keyboard_get_key(kbdev)) {
- case KEY_LEFTCTRL:
- case KEY_RIGHTCTRL:
- case KEY_LEFTALT:
- case KEY_RIGHTALT:
- case KEY_LEFTSHIFT:
- case KEY_RIGHTSHIFT:
- case KEY_FN:
- return;
- default:
- break;
- }
+ if (tp_key_ignore_for_dwt(libinput_event_keyboard_get_key(kbdev)))
+ return;
- if (!tp->sendevents.keyboard_active) {
+ if (!tp->dwt.keyboard_active) {
tp_edge_scroll_stop_events(tp, time);
tp_gesture_stop(tp, time);
tp_tap_suspend(tp, time);
- tp->sendevents.keyboard_active = true;
+ tp->dwt.keyboard_active = true;
timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1;
} else {
timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2;
}
- libinput_timer_set(&tp->sendevents.keyboard_timer,
+ tp->dwt.keyboard_last_press_time = time;
+ libinput_timer_set(&tp->dwt.keyboard_timer,
time + timeout);
}
+static bool
+tp_want_dwt(struct evdev_device *touchpad,
+ struct evdev_device *keyboard)
+{
+ unsigned int bus_tp = libevdev_get_id_bustype(touchpad->evdev),
+ bus_kbd = libevdev_get_id_bustype(keyboard->evdev);
+
+ if (bus_tp == BUS_BLUETOOTH || bus_kbd == BUS_BLUETOOTH)
+ return false;
+
+ /* evemu will set the right bus type */
+ if (bus_tp == BUS_VIRTUAL || bus_kbd == BUS_VIRTUAL)
+ return false;
+
+ /* If the touchpad is on serio, the keyboard is too, so ignore any
+ other devices */
+ if (bus_tp == BUS_I8042 && bus_kbd != bus_tp)
+ return false;
+
+ /* Wacom makes touchpads, but not internal ones */
+ if (libevdev_get_id_vendor(touchpad->evdev) == VENDOR_ID_WACOM)
+ return false;
+
+ /* everything else we don't really know, so we have to assume
+ they go together */
+
+ return true;
+}
+
static void
tp_interface_device_added(struct evdev_device *device,
struct evdev_device *added_device)
{
struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch;
unsigned int bus_tp = libevdev_get_id_bustype(device->evdev),
- bus_trp = libevdev_get_id_bustype(added_device->evdev),
- bus_kbd = libevdev_get_id_bustype(added_device->evdev);
- bool tp_is_internal, trp_is_internal, kbd_is_internal;
+ bus_trp = libevdev_get_id_bustype(added_device->evdev);
+ bool tp_is_internal, trp_is_internal;
tp_is_internal = bus_tp != BUS_USB && bus_tp != BUS_BLUETOOTH;
trp_is_internal = bus_trp != BUS_USB && bus_trp != BUS_BLUETOOTH;
@@ -985,16 +1075,19 @@ tp_interface_device_added(struct evdev_device *device,
tp_trackpoint_event, tp);
}
- /* FIXME: detect external keyboard better */
- kbd_is_internal = bus_tp != BUS_BLUETOOTH &&
- bus_kbd == bus_tp;
- if (tp_is_internal && kbd_is_internal &&
- tp->sendevents.keyboard == NULL) {
+ if (added_device->tags & EVDEV_TAG_KEYBOARD &&
+ tp->dwt.keyboard == NULL &&
+ tp_want_dwt(device, added_device)) {
+ log_debug(tp_libinput_context(tp),
+ "palm: dwt activated with %s<->%s\n",
+ device->devname,
+ added_device->devname);
+
libinput_device_add_event_listener(&added_device->base,
- &tp->sendevents.keyboard_listener,
+ &tp->dwt.keyboard_listener,
tp_keyboard_event, tp);
- tp->sendevents.keyboard = added_device;
- tp->sendevents.keyboard_active = false;
+ tp->dwt.keyboard = added_device;
+ tp->dwt.keyboard_active = false;
}
if (tp->sendevents.current_mode !=
@@ -1023,10 +1116,10 @@ tp_interface_device_removed(struct evdev_device *device,
tp->buttons.trackpoint = NULL;
}
- if (removed_device == tp->sendevents.keyboard) {
+ if (removed_device == tp->dwt.keyboard) {
libinput_device_remove_event_listener(
- &tp->sendevents.keyboard_listener);
- tp->sendevents.keyboard = NULL;
+ &tp->dwt.keyboard_listener);
+ tp->dwt.keyboard = NULL;
}
if (tp->sendevents.current_mode !=
@@ -1044,9 +1137,9 @@ tp_interface_device_removed(struct evdev_device *device,
tp_resume(tp, device);
}
-static void
-tp_interface_tag_device(struct evdev_device *device,
- struct udev_device *udev_device)
+void
+evdev_tag_touchpad(struct evdev_device *device,
+ struct udev_device *udev_device)
{
int bustype;
@@ -1076,7 +1169,6 @@ static struct evdev_dispatch_interface tp_interface = {
tp_interface_device_removed,
tp_interface_device_removed, /* device_suspended, treat as remove */
tp_interface_device_added, /* device_resumed, treat as add */
- tp_interface_tag_device,
NULL, /* post_added */
};
@@ -1182,7 +1274,7 @@ tp_init_accel(struct tp_dispatch *tp, double diagonal)
* and y resolution, so that a circle on the
* touchpad does not turn into an elipse on the screen.
*/
- if (res_x > 1 && res_y > 1) {
+ if (!tp->device->abs.fake_resolution) {
tp->accel.x_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_x;
tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y;
} else {
@@ -1231,7 +1323,7 @@ tp_scroll_config_scroll_method_set_method(struct libinput_device *device,
{
struct evdev_device *evdev = (struct evdev_device*)device;
struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
- uint64_t time = libinput_now(device->seat->libinput);
+ uint64_t time = libinput_now(tp_libinput_context(tp));
if (method == tp->scroll.method)
return LIBINPUT_CONFIG_STATUS_SUCCESS;
@@ -1297,6 +1389,7 @@ tp_init_palmdetect(struct tp_dispatch *tp,
struct evdev_device *device)
{
int width, height;
+ unsigned int vendor_id;
tp->palm.right_edge = INT_MAX;
tp->palm.left_edge = INT_MIN;
@@ -1307,8 +1400,13 @@ tp_init_palmdetect(struct tp_dispatch *tp,
height = abs(device->abs.absinfo_y->maximum -
device->abs.absinfo_y->minimum);
- /* Apple touchpads are always big enough to warrant palm detection */
- if (evdev_device_get_id_vendor(device) != VENDOR_ID_APPLE) {
+ vendor_id = evdev_device_get_id_vendor(device);
+
+ /* Wacom doesn't have internal touchpads,
+ * Apple touchpads are always big enough to warrant palm detection */
+ if (vendor_id == VENDOR_ID_WACOM) {
+ return 0;
+ } else if (vendor_id != VENDOR_ID_APPLE) {
/* We don't know how big the touchpad is */
if (device->abs.absinfo_x->resolution == 1)
return 0;
@@ -1332,16 +1430,40 @@ tp_init_sendevents(struct tp_dispatch *tp,
struct evdev_device *device)
{
libinput_timer_init(&tp->sendevents.trackpoint_timer,
- tp->device->base.seat->libinput,
+ tp_libinput_context(tp),
tp_trackpoint_timeout, tp);
- libinput_timer_init(&tp->sendevents.keyboard_timer,
- tp->device->base.seat->libinput,
+ libinput_timer_init(&tp->dwt.keyboard_timer,
+ tp_libinput_context(tp),
tp_keyboard_timeout, tp);
return 0;
}
static int
+tp_sanity_check(struct tp_dispatch *tp,
+ struct evdev_device *device)
+{
+ struct libevdev *evdev = device->evdev;
+ struct libinput *libinput = tp_libinput_context(tp);
+
+ if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X))
+ goto error;
+
+ if (!libevdev_has_event_code(evdev, EV_KEY, BTN_TOUCH))
+ goto error;
+
+ if (!libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_FINGER))
+ goto error;
+
+ return 0;
+
+error:
+ log_bug_kernel(libinput,
+ "device %s failed touchpad sanity checks\n");
+ return -1;
+}
+
+static int
tp_init(struct tp_dispatch *tp,
struct evdev_device *device)
{
@@ -1351,6 +1473,9 @@ tp_init(struct tp_dispatch *tp,
tp->base.interface = &tp_interface;
tp->device = device;
+ if (tp_sanity_check(tp, device) != 0)
+ return -1;
+
if (tp_init_slots(tp, device) != 0)
return -1;
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index 3d51a39..fef5cb3 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -1,5 +1,5 @@
/*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -32,8 +32,6 @@
#define TOUCHPAD_HISTORY_LENGTH 4
#define TOUCHPAD_MIN_SAMPLES 4
-#define VENDOR_ID_APPLE 0x5ac
-
/* Convert mm to a distance normalized to DEFAULT_MOUSE_DPI */
#define TP_MM_TO_DPI_NORMALIZED(mm) (DEFAULT_MOUSE_DPI/25.4 * mm)
@@ -282,17 +280,27 @@ struct tp_dispatch {
bool trackpoint_active;
struct libinput_event_listener trackpoint_listener;
struct libinput_timer trackpoint_timer;
+ } sendevents;
+ struct {
bool keyboard_active;
struct libinput_event_listener keyboard_listener;
struct libinput_timer keyboard_timer;
struct evdev_device *keyboard;
- } sendevents;
+
+ uint64_t keyboard_last_press_time;
+ } dwt;
};
#define tp_for_each_touch(_tp, _t) \
for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++)
+static inline struct libinput*
+tp_libinput_context(struct tp_dispatch *tp)
+{
+ return tp->device->base.seat->libinput;
+}
+
static inline struct normalized_coords
tp_normalize_delta(struct tp_dispatch *tp, struct device_float_coords delta)
{
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 301fe24..a0ce509 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -1029,7 +1029,6 @@ static struct evdev_dispatch_interface tablet_interface = {
NULL, /* device_removed */
NULL, /* device_suspended */
NULL, /* device_resumed */
- NULL, /* tag_device */
tablet_check_initial_proximity,
};
diff --git a/src/evdev.c b/src/evdev.c
index 3d8cace..3e66df3 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -1,6 +1,7 @@
/*
* Copyright © 2010 Intel Corporation
* Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -59,6 +60,7 @@ enum evdev_device_udev_tags {
EVDEV_UDEV_TAG_JOYSTICK = (1 << 6),
EVDEV_UDEV_TAG_ACCELEROMETER = (1 << 7),
EVDEV_UDEV_TAG_BUTTONSET = (1 << 8),
+ EVDEV_UDEV_TAG_POINTINGSTICK = (1 << 9),
};
struct evdev_udev_tag_match {
@@ -77,6 +79,7 @@ static const struct evdev_udev_tag_match evdev_udev_tag_matches[] = {
{"ID_INPUT_TABLET_PAD", EVDEV_UDEV_TAG_BUTTONSET},
{"ID_INPUT_JOYSTICK", EVDEV_UDEV_TAG_JOYSTICK},
{"ID_INPUT_ACCELEROMETER", EVDEV_UDEV_TAG_ACCELEROMETER},
+ {"ID_INPUT_POINTINGSTICK", EVDEV_UDEV_TAG_POINTINGSTICK},
/* sentinel value */
{ 0 },
@@ -439,6 +442,7 @@ evdev_button_scroll_button(struct evdev_device *device,
if (is_press) {
libinput_timer_set(&device->scroll.timer,
time + DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT);
+ device->scroll.button_down_time = time;
} else {
libinput_timer_cancel(&device->scroll.timer);
if (device->scroll.button_scroll_active) {
@@ -448,7 +452,8 @@ evdev_button_scroll_button(struct evdev_device *device,
} else {
/* If the button is released quickly enough emit the
* button press/release events. */
- evdev_pointer_notify_physical_button(device, time,
+ evdev_pointer_notify_physical_button(device,
+ device->scroll.button_down_time,
device->scroll.button,
LIBINPUT_BUTTON_STATE_PRESSED);
evdev_pointer_notify_physical_button(device, time,
@@ -719,21 +724,41 @@ evdev_tag_external_mouse(struct evdev_device *device,
int bustype;
bustype = libevdev_get_id_bustype(device->evdev);
- if (bustype == BUS_USB || bustype == BUS_BLUETOOTH) {
- if (device->seat_caps & EVDEV_DEVICE_POINTER)
- device->tags |= EVDEV_TAG_EXTERNAL_MOUSE;
- }
+ if (bustype == BUS_USB || bustype == BUS_BLUETOOTH)
+ device->tags |= EVDEV_TAG_EXTERNAL_MOUSE;
}
static void
evdev_tag_trackpoint(struct evdev_device *device,
struct udev_device *udev_device)
{
- if (libevdev_has_property(device->evdev, INPUT_PROP_POINTING_STICK))
+ if (libevdev_has_property(device->evdev,
+ INPUT_PROP_POINTING_STICK) ||
+ udev_device_get_property_value(udev_device,
+ "ID_INPUT_POINTINGSTICK"))
device->tags |= EVDEV_TAG_TRACKPOINT;
}
static void
+evdev_tag_keyboard(struct evdev_device *device,
+ struct udev_device *udev_device)
+{
+ int code;
+
+ if (!libevdev_has_event_type(device->evdev, EV_KEY))
+ return;
+
+ for (code = KEY_Q; code <= KEY_P; code++) {
+ if (!libevdev_has_event_code(device->evdev,
+ EV_KEY,
+ code))
+ return;
+ }
+
+ device->tags |= EVDEV_TAG_KEYBOARD;
+}
+
+static void
fallback_process(struct evdev_dispatch *dispatch,
struct evdev_device *device,
struct input_event *event,
@@ -825,14 +850,6 @@ fallback_destroy(struct evdev_dispatch *dispatch)
free(dispatch);
}
-static void
-fallback_tag_device(struct evdev_device *device,
- struct udev_device *udev_device)
-{
- evdev_tag_external_mouse(device, udev_device);
- evdev_tag_trackpoint(device, udev_device);
-}
-
static int
evdev_calibration_has_matrix(struct libinput_device *libinput_device)
{
@@ -883,7 +900,6 @@ struct evdev_dispatch_interface fallback_interface = {
NULL, /* device_removed */
NULL, /* device_suspended */
NULL, /* device_resumed */
- fallback_tag_device,
NULL, /* post_added */
};
@@ -1045,7 +1061,7 @@ evdev_scroll_get_default_method(struct libinput_device *device)
{
struct evdev_device *evdev = (struct evdev_device *)device;
- if (libevdev_has_property(evdev->evdev, INPUT_PROP_POINTING_STICK))
+ if (evdev->tags & EVDEV_TAG_TRACKPOINT)
return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
/* Mice without a scroll wheel but with middle button have on-button
@@ -1085,14 +1101,7 @@ evdev_scroll_get_default_button(struct libinput_device *device)
{
struct evdev_device *evdev = (struct evdev_device *)device;
- if (libevdev_has_property(evdev->evdev, INPUT_PROP_POINTING_STICK))
- return BTN_MIDDLE;
-
- /* A device that defaults to button scrolling defaults
- to BTN_MIDDLE */
- if (evdev_scroll_get_default_method(device) ==
- LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN &&
- libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE))
+ if( libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE))
return BTN_MIDDLE;
return 0;
@@ -1412,14 +1421,6 @@ evdev_need_mtdev(struct evdev_device *device)
!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT));
}
-static void
-evdev_tag_device(struct evdev_device *device)
-{
- if (device->dispatch->interface->tag_device)
- device->dispatch->interface->tag_device(device,
- device->udev_device);
-}
-
static inline int
evdev_read_wheel_click_prop(struct evdev_device *device)
{
@@ -1480,7 +1481,7 @@ evdev_read_dpi_prop(struct evdev_device *device)
* POINTINGSTICK_CONST_ACCEL value to compensate for sensitivity
* differences between models, we translate this to a fake dpi.
*/
- if (libevdev_has_property(device->evdev, INPUT_PROP_POINTING_STICK))
+ if (device->tags & EVDEV_TAG_TRACKPOINT)
return evdev_get_trackpoint_dpi(device);
mouse_dpi = udev_device_get_property_value(device->udev_device,
@@ -1820,13 +1821,14 @@ evdev_configure_device(struct evdev_device *device)
}
log_info(libinput,
- "input device '%s', %s is tagged by udev as:%s%s%s%s%s%s%s%s\n",
+ "input device '%s', %s is tagged by udev as:%s%s%s%s%s%s%s%s%s\n",
device->devname, devnode,
udev_tags & EVDEV_UDEV_TAG_KEYBOARD ? " Keyboard" : "",
udev_tags & EVDEV_UDEV_TAG_MOUSE ? " Mouse" : "",
udev_tags & EVDEV_UDEV_TAG_TOUCHPAD ? " Touchpad" : "",
udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN ? " Touchscreen" : "",
udev_tags & EVDEV_UDEV_TAG_TABLET ? " Tablet" : "",
+ udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK ? " Pointingstick" : "",
udev_tags & EVDEV_UDEV_TAG_JOYSTICK ? " Joystick" : "",
udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER ? " Accelerometer" : "",
udev_tags & EVDEV_UDEV_TAG_BUTTONSET ? " Buttonset" : "");
@@ -1897,10 +1899,13 @@ evdev_configure_device(struct evdev_device *device)
log_info(libinput,
"input device '%s', %s is a touchpad\n",
device->devname, devnode);
+
+ evdev_tag_touchpad(device, device->udev_device);
return device->dispatch == NULL ? -1 : 0;
}
- if (udev_tags & EVDEV_UDEV_TAG_MOUSE) {
+ if (udev_tags & EVDEV_UDEV_TAG_MOUSE ||
+ udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK) {
if (libevdev_has_event_code(evdev, EV_REL, REL_X) &&
libevdev_has_event_code(evdev, EV_REL, REL_Y) &&
evdev_device_init_pointer_acceleration(
@@ -1920,6 +1925,9 @@ evdev_configure_device(struct evdev_device *device)
device->scroll.natural_scrolling_enabled = true;
/* want button scrolling config option */
device->scroll.want_button = 1;
+
+ evdev_tag_external_mouse(device, device->udev_device);
+ evdev_tag_trackpoint(device, device->udev_device);
}
if (udev_tags & EVDEV_UDEV_TAG_KEYBOARD) {
@@ -1934,6 +1942,8 @@ evdev_configure_device(struct evdev_device *device)
device->scroll.natural_scrolling_enabled = true;
device->seat_caps |= EVDEV_DEVICE_POINTER;
}
+
+ evdev_tag_keyboard(device, device->udev_device);
}
if (udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN) {
@@ -2089,7 +2099,6 @@ evdev_device_create(struct libinput_seat *seat,
device->scroll.direction = 0;
device->scroll.wheel_click_angle =
evdev_read_wheel_click_prop(device);
- device->dpi = evdev_read_dpi_prop(device);
device->model = evdev_read_model(device);
/* at most 5 SYN_DROPPED log-messages per 30s */
ratelimit_init(&device->syn_drop_limit, 30ULL * 1000, 5);
@@ -2101,6 +2110,8 @@ evdev_device_create(struct libinput_seat *seat,
if (evdev_configure_device(device) == -1)
goto err;
+ device->dpi = evdev_read_dpi_prop(device);
+
if (device->seat_caps == 0) {
unhandled_device = 1;
goto err;
@@ -2122,7 +2133,6 @@ evdev_device_create(struct libinput_seat *seat,
list_insert(seat->devices_list.prev, &device->base.link);
- evdev_tag_device(device);
evdev_notify_added_device(device);
return device;
diff --git a/src/evdev.h b/src/evdev.h
index 19454ec..17ae0d9 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -1,6 +1,7 @@
/*
* Copyright © 2011, 2012 Intel Corporation
* Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -69,6 +70,7 @@ enum evdev_device_tags {
EVDEV_TAG_INTERNAL_TOUCHPAD = (1 << 1),
EVDEV_TAG_TRACKPOINT = (1 << 2),
EVDEV_TAG_TOUCHPAD_TRACKPOINT = (1 << 3),
+ EVDEV_TAG_KEYBOARD = (1 << 4),
};
enum evdev_middlebutton_state {
@@ -149,6 +151,8 @@ struct evdev_device {
/* Currently enabled method, button */
enum libinput_config_scroll_method method;
uint32_t button;
+ uint64_t button_down_time;
+
/* set during device init, used at runtime to delay changes
* until all buttons are up */
enum libinput_config_scroll_method want_method;
@@ -254,10 +258,6 @@ struct evdev_dispatch_interface {
void (*device_resumed)(struct evdev_device *device,
struct evdev_device *resumed_device);
- /* Tag device with one of EVDEV_TAG */
- void (*tag_device)(struct evdev_device *device,
- struct udev_device *udev_device);
-
/* Called immediately after the LIBINPUT_EVENT_DEVICE_ADDED event
* was sent */
void (*post_added)(struct evdev_device *device,
@@ -299,6 +299,10 @@ struct evdev_dispatch *
evdev_tablet_create(struct evdev_device *device);
void
+evdev_tag_touchpad(struct evdev_device *device,
+ struct udev_device *udev_device);
+
+void
evdev_device_led_update(struct evdev_device *device, enum libinput_led leds);
int
diff --git a/src/filter.c b/src/filter.c
index 626cb0a..c54d866 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -1,6 +1,7 @@
/*
* Copyright © 2006-2009 Simon Thum
* Copyright © 2012 Jonas Ådahl
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -77,7 +78,7 @@ filter_get_speed(struct motion_filter *filter)
*/
#define MAX_VELOCITY_DIFF 1.0 /* units/ms */
-#define MOTION_TIMEOUT 300 /* (ms) */
+#define MOTION_TIMEOUT 1000 /* (ms) */
#define NUM_POINTER_TRACKERS 16
struct pointer_tracker {
@@ -143,6 +144,24 @@ calculate_tracker_velocity(struct pointer_tracker *tracker, uint64_t time)
return normalized_length(tracker->delta) / tdelta; /* units/ms */
}
+static inline double
+calculate_velocity_after_timeout(struct pointer_tracker *tracker)
+{
+ /* First movement after timeout needs special handling.
+ *
+ * When we trigger the timeout, the last event is too far in the
+ * past to use it for velocity calculation across multiple tracker
+ * values.
+ *
+ * Use the motion timeout itself to calculate the speed rather than
+ * the last tracker time. This errs on the side of being too fast
+ * for really slow movements but provides much more useful initial
+ * movement in normal use-cases (pause, move, pause, move)
+ */
+ return calculate_tracker_velocity(tracker,
+ tracker->time + MOTION_TIMEOUT);
+}
+
static double
calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
{
@@ -162,15 +181,23 @@ calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
/* Stop if too far away in time */
if (time - tracker->time > MOTION_TIMEOUT ||
- tracker->time > time)
+ tracker->time > time) {
+ if (offset == 1)
+ result = calculate_velocity_after_timeout(tracker);
break;
+ }
+
+ velocity = calculate_tracker_velocity(tracker, time);
/* Stop if direction changed */
dir &= tracker->dir;
- if (dir == 0)
+ if (dir == 0) {
+ /* First movement after dirchange - velocity is that
+ * of the last movement */
+ if (offset == 1)
+ result = velocity;
break;
-
- velocity = calculate_tracker_velocity(tracker, time);
+ }
if (initial_velocity == 0.0) {
result = initial_velocity = velocity;
@@ -196,17 +223,20 @@ acceleration_profile(struct pointer_accelerator *accel,
static double
calculate_acceleration(struct pointer_accelerator *accel,
- void *data, double velocity, uint64_t time)
+ void *data,
+ double velocity,
+ double last_velocity,
+ uint64_t time)
{
double factor;
/* Use Simpson's rule to calculate the avarage acceleration between
* the previous motion and the most recent. */
factor = acceleration_profile(accel, data, velocity, time);
- factor += acceleration_profile(accel, data, accel->last_velocity, time);
+ factor += acceleration_profile(accel, data, last_velocity, time);
factor += 4.0 *
acceleration_profile(accel, data,
- (accel->last_velocity + velocity) / 2,
+ (last_velocity + velocity) / 2,
time);
factor = factor / 6.0;
@@ -227,7 +257,11 @@ accelerator_filter(struct motion_filter *filter,
feed_trackers(accel, unaccelerated, time);
velocity = calculate_velocity(accel, time);
- accel_value = calculate_acceleration(accel, data, velocity, time);
+ accel_value = calculate_acceleration(accel,
+ data,
+ velocity,
+ accel->last_velocity,
+ time);
accelerated.x = accel_value * unaccelerated->x;
accelerated.y = accel_value * unaccelerated->y;
diff --git a/src/filter.h b/src/filter.h
index a053860..16896a4 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2012 Jonas Ådahl
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
diff --git a/src/libinput-private.h b/src/libinput-private.h
index ae20f80..d825eb6 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
diff --git a/src/libinput-util.c b/src/libinput-util.c
index 4857435..ceea054 100644
--- a/src/libinput-util.c
+++ b/src/libinput-util.c
@@ -1,6 +1,7 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
diff --git a/src/libinput-util.h b/src/libinput-util.h
index 04623cc..5db3e3f 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
@@ -25,11 +26,16 @@
#include <unistd.h>
#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
#include <string.h>
#include <time.h>
#include "libinput.h"
+#define VENDOR_ID_APPLE 0x5ac
+#define VENDOR_ID_WACOM 0x56a
+
void
set_logging_enabled(int enabled);
@@ -250,6 +256,38 @@ matrix_to_farray6(const struct matrix *m, float out[6])
out[5] = m->val[1][2];
}
+/**
+ * Simple wrapper for asprintf that ensures the passed in-pointer is set
+ * to NULL upon error.
+ * The standard asprintf() call does not guarantee the passed in pointer
+ * will be NULL'ed upon failure, whereas this wrapper does.
+ *
+ * @param strp pointer to set to newly allocated string.
+ * This pointer should be passed to free() to release when done.
+ * @param fmt the format string to use for printing.
+ * @return The number of bytes printed (excluding the null byte terminator)
+ * upon success or -1 upon failure. In the case of failure the pointer is set
+ * to NULL.
+ */
+static inline int
+xasprintf(char **strp, const char *fmt, ...)
+ LIBINPUT_ATTRIBUTE_PRINTF(2, 3);
+
+static inline int
+xasprintf(char **strp, const char *fmt, ...)
+{
+ int rc = 0;
+ va_list args;
+
+ va_start(args, fmt);
+ rc = vasprintf(strp, fmt, args);
+ va_end(args);
+ if ((rc == -1) && strp)
+ *strp = NULL;
+
+ return rc;
+}
+
enum ratelimit_state {
RATELIMIT_EXCEEDED,
RATELIMIT_THRESHOLD,
diff --git a/src/libinput.c b/src/libinput.c
index fd607d1..7f43086 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -1,5 +1,6 @@
/*
* Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
diff --git a/src/libinput.h b/src/libinput.h
index b08e9d6..6043166 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
diff --git a/src/path.c b/src/path.c
index ab5587b..5ec8bf5 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
diff --git a/src/timer.c b/src/timer.c
index d1d3c10..1e507df 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
@@ -20,6 +20,8 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include "config.h"
+
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
diff --git a/src/udev-seat.c b/src/udev-seat.c
index 8dc0c23..588e1b4 100644
--- a/src/udev-seat.c
+++ b/src/udev-seat.c
@@ -1,5 +1,6 @@
/*
* Copyright © 2013 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
diff --git a/test/device.c b/test/device.c
index 193c3ff..d1f4f21 100644
--- a/test/device.c
+++ b/test/device.c
@@ -69,7 +69,7 @@ START_TEST(device_sendevents_config_touchpad)
expected = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
/* The wacom devices in the test suite are external */
- if (libevdev_get_id_vendor(dev->evdev) != 0x56a) /* wacom */
+ if (libevdev_get_id_vendor(dev->evdev) != VENDOR_ID_WACOM)
expected |=
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
diff --git a/test/litest.c b/test/litest.c
index fbbdaeb..8c82cc7 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -84,8 +84,10 @@ litest_backtrace_get_lineno(const char *executable,
char *s;
unsigned int i;
- if (!cwd[0])
- getcwd(cwd, sizeof(cwd));
+ if (!cwd[0]) {
+ if (getcwd(cwd, sizeof(cwd)) == NULL)
+ cwd[0] = 0; /* contents otherwise undefined. */
+ }
sprintf (buffer,
ADDR2LINE " -C -e %s -i %lx",
@@ -383,7 +385,17 @@ static struct list all_tests;
static void
litest_reload_udev_rules(void)
{
- system("udevadm control --reload-rules");
+ int ret = system("udevadm control --reload-rules");
+ if (ret == -1) {
+ litest_abort_msg("Failed to execute: udevadm");
+ } else if (WIFEXITED(ret)) {
+ if (WEXITSTATUS(ret))
+ litest_abort_msg("udevadm failed with %d",
+ WEXITSTATUS(ret));
+ } else if (WIFSIGNALED(ret)) {
+ litest_abort_msg("udevadm terminated with signal %d",
+ WTERMSIG(ret));
+ }
}
static int
@@ -648,6 +660,7 @@ _litest_add_ranged_for_device(const char *name,
{
struct suite *s;
struct litest_test_device **dev = devices;
+ bool device_filtered = false;
assert(type < LITEST_NO_DEVICE);
@@ -662,8 +675,10 @@ _litest_add_ranged_for_device(const char *name,
s = get_suite(name);
for (; *dev; dev++) {
if (filter_device &&
- fnmatch(filter_device, (*dev)->shortname, 0) != 0)
+ fnmatch(filter_device, (*dev)->shortname, 0) != 0) {
+ device_filtered = true;
continue;
+ }
if ((*dev)->type == type) {
litest_add_tcase_for_device(s,
@@ -675,7 +690,9 @@ _litest_add_ranged_for_device(const char *name,
}
}
- litest_abort_msg("Invalid test device type");
+ /* only abort if no filter was set, that's a bug */
+ if (!device_filtered)
+ litest_abort_msg("Invalid test device type");
}
static int
@@ -879,7 +896,7 @@ litest_init_udev_rules(struct litest_test_device *dev)
ck_abort_msg("Failed to create udev rules directory (%s)\n",
strerror(errno));
- rc = asprintf(&path,
+ rc = xasprintf(&path,
"%s/%s%s.rules",
UDEV_RULES_D,
UDEV_RULE_PREFIX,
@@ -985,6 +1002,32 @@ litest_restore_log_handler(struct libinput *libinput)
libinput_log_set_handler(libinput, litest_log_handler);
}
+static inline void
+litest_wait_for_udev(int fd)
+{
+ struct udev *udev;
+ struct udev_device *device;
+ struct stat st;
+ int loop_count = 0;
+
+ litest_assert_int_ge(fstat(fd, &st), 0);
+
+ udev = udev_new();
+ device = udev_device_new_from_devnum(udev, 'c', st.st_rdev);
+ litest_assert_ptr_notnull(device);
+ while (device && !udev_device_get_property_value(device, "ID_INPUT")) {
+ loop_count++;
+ litest_assert_int_lt(loop_count, 300);
+
+ udev_device_unref(device);
+ msleep(2);
+ device = udev_device_new_from_devnum(udev, 'c', st.st_rdev);
+ }
+
+ udev_device_unref(device);
+ udev_unref(udev);
+}
+
struct litest_device *
litest_add_device_with_overrides(struct libinput *libinput,
enum litest_device_type which,
@@ -1012,6 +1055,8 @@ litest_add_device_with_overrides(struct libinput *libinput,
rc = libevdev_new_from_fd(fd, &d->evdev);
litest_assert_int_eq(rc, 0);
+ litest_wait_for_udev(fd);
+
d->libinput = libinput;
d->libinput_device = libinput_path_add_device(d->libinput, path);
litest_assert(d->libinput_device != NULL);
@@ -1097,6 +1142,11 @@ litest_delete_device(struct litest_device *d)
free(d->private);
memset(d,0, sizeof(*d));
free(d);
+
+ /* zzz so udev can catch up with things, so we don't accidentally open
+ * an old device in the next test and then get all upset when things blow
+ * up */
+ msleep(10);
}
void
@@ -2116,6 +2166,18 @@ litest_timeout_middlebutton(void)
}
void
+litest_timeout_dwt_short(void)
+{
+ msleep(220);
+}
+
+void
+litest_timeout_dwt_long(void)
+{
+ msleep(520);
+}
+
+void
litest_push_event_frame(struct litest_device *dev)
{
assert(!dev->skip_ev_syn);
diff --git a/test/litest.h b/test/litest.h
index 9efacde..e23388c 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -416,6 +416,8 @@ void litest_timeout_buttonscroll(void);
void litest_timeout_edgescroll(void);
void litest_timeout_finger_switch(void);
void litest_timeout_middlebutton(void);
+void litest_timeout_dwt_short(void);
+void litest_timeout_dwt_long(void);
void litest_push_event_frame(struct litest_device *dev);
void litest_pop_event_frame(struct litest_device *dev);
diff --git a/test/touch.c b/test/touch.c
index 2c07e09..be2bea9 100644
--- a/test/touch.c
+++ b/test/touch.c
@@ -586,7 +586,8 @@ START_TEST(touch_initial_state)
{
struct litest_device *dev;
struct libinput *libinput1, *libinput2;
- struct libinput_event *ev1, *ev2;
+ struct libinput_event *ev1 = NULL;
+ struct libinput_event *ev2 = NULL;
struct libinput_event_touch *t1, *t2;
struct libinput_device *device1, *device2;
int axis = _i; /* looped test */
diff --git a/test/touchpad.c b/test/touchpad.c
index c8ecb32..692698c 100644
--- a/test/touchpad.c
+++ b/test/touchpad.c
@@ -1808,6 +1808,51 @@ START_TEST(touchpad_2fg_clickfinger)
}
END_TEST
+START_TEST(touchpad_2fg_clickfinger_distance)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+
+ libinput_device_config_click_set_method(dev->libinput_device,
+ LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+ litest_drain_events(li);
+
+ litest_touch_down(dev, 0, 90, 50);
+ litest_touch_down(dev, 1, 10, 50);
+ 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_touch_up(dev, 0);
+ litest_touch_up(dev, 1);
+
+ litest_assert_button_event(li,
+ BTN_LEFT,
+ LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_assert_button_event(li,
+ BTN_LEFT,
+ LIBINPUT_BUTTON_STATE_RELEASED);
+
+ litest_assert_empty_queue(li);
+
+ litest_touch_down(dev, 0, 50, 5);
+ litest_touch_down(dev, 1, 50, 95);
+ 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_touch_up(dev, 0);
+ litest_touch_up(dev, 1);
+
+ litest_assert_button_event(li,
+ BTN_LEFT,
+ LIBINPUT_BUTTON_STATE_PRESSED);
+ litest_assert_button_event(li,
+ BTN_LEFT,
+ LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
START_TEST(touchpad_clickfinger_to_area_method)
{
struct litest_device *dev = litest_current_device();
@@ -2636,7 +2681,7 @@ START_TEST(clickpad_topsoftbuttons_clickfinger)
litest_assert_empty_queue(li);
litest_touch_down(dev, 0, 90, 5);
- litest_touch_down(dev, 1, 10, 5);
+ litest_touch_down(dev, 1, 80, 5);
litest_event(dev, EV_KEY, BTN_LEFT, 1);
litest_event(dev, EV_SYN, SYN_REPORT, 0);
litest_event(dev, EV_KEY, BTN_LEFT, 0);
@@ -2959,7 +3004,7 @@ START_TEST(touchpad_edge_scroll)
litest_touch_up(dev, 0);
libinput_dispatch(li);
- litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 10);
+ litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
litest_assert_empty_queue(li);
litest_touch_down(dev, 0, 99, 80);
@@ -2967,7 +3012,7 @@ START_TEST(touchpad_edge_scroll)
litest_touch_up(dev, 0);
libinput_dispatch(li);
- litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -10);
+ litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -4);
litest_assert_empty_queue(li);
litest_touch_down(dev, 0, 20, 99);
@@ -2975,7 +3020,7 @@ START_TEST(touchpad_edge_scroll)
litest_touch_up(dev, 0);
libinput_dispatch(li);
- litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 10);
+ litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 4);
litest_assert_empty_queue(li);
litest_touch_down(dev, 0, 70, 99);
@@ -2983,7 +3028,7 @@ START_TEST(touchpad_edge_scroll)
litest_touch_up(dev, 0);
libinput_dispatch(li);
- litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -10);
+ litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -4);
litest_assert_empty_queue(li);
}
END_TEST
@@ -3065,7 +3110,7 @@ START_TEST(touchpad_edge_scroll_no_motion)
litest_touch_up(dev, 0);
libinput_dispatch(li);
- litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 5);
+ litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
litest_assert_empty_queue(li);
}
END_TEST
@@ -3198,9 +3243,13 @@ static int
touchpad_has_palm_detect_size(struct litest_device *dev)
{
double width, height;
+ unsigned int vendor;
int rc;
- if (libinput_device_get_id_vendor(dev->libinput_device) == 0x5ac) /* Apple */
+ vendor = libinput_device_get_id_vendor(dev->libinput_device);
+ if (vendor == VENDOR_ID_WACOM)
+ return 0;
+ if (vendor == VENDOR_ID_APPLE)
return 1;
rc = libinput_device_get_size(dev->libinput_device, &width, &height);
@@ -4590,6 +4639,434 @@ START_TEST(touchpad_initial_state)
}
END_TEST
+static inline bool
+has_disable_while_typing(struct litest_device *device)
+{
+ if (libevdev_get_id_vendor(device->evdev) == VENDOR_ID_WACOM)
+ return false;
+
+ return true;
+}
+
+START_TEST(touchpad_dwt)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ libinput_dispatch(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_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ /* within timeout - no events */
+ 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();
+ libinput_dispatch(li);
+
+ /* after timeout - motion events*/
+ 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_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_enable_touch)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ libinput_dispatch(li);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ /* finger down after last key event, but
+ we're still within timeout - no events */
+ msleep(10);
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+ litest_assert_empty_queue(li);
+
+ litest_timeout_dwt_short();
+ libinput_dispatch(li);
+
+ /* same touch after timeout - motion events*/
+ litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 10, 1);
+ litest_touch_up(touchpad, 0);
+
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_touch_hold)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ msleep(1); /* make sure touch starts after key press */
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ /* touch still down - no events */
+ litest_keyboard_key(keyboard, KEY_A, false);
+ libinput_dispatch(li);
+ litest_touch_move_to(touchpad, 0, 70, 50, 30, 50, 5, 1);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ /* touch still down - no events */
+ litest_timeout_dwt_short();
+ libinput_dispatch(li);
+ litest_touch_move_to(touchpad, 0, 30, 50, 50, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_key_hold)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ libinput_dispatch(li);
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_type)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+ int i;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ for (i = 0; i < 5; i++) {
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ libinput_dispatch(li);
+ }
+
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+ litest_assert_empty_queue(li);
+
+ litest_timeout_dwt_long();
+ libinput_dispatch(li);
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_type_short_timeout)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+ int i;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ for (i = 0; i < 5; i++) {
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ libinput_dispatch(li);
+ }
+
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+ litest_assert_empty_queue(li);
+
+ litest_timeout_dwt_short();
+ libinput_dispatch(li);
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+ litest_assert_empty_queue(li);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_tap)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_ENABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ libinput_dispatch(li);
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_up(touchpad, 0);
+
+ litest_keyboard_key(keyboard, KEY_A, false);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_timeout_dwt_short();
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_up(touchpad, 0);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_tap_drag)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_ENABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ libinput_dispatch(li);
+ msleep(1); /* make sure touch starts after key press */
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_up(touchpad, 0);
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+
+ litest_keyboard_key(keyboard, KEY_A, false);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_timeout_dwt_short();
+ libinput_dispatch(li);
+ litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 5, 1);
+ litest_touch_up(touchpad, 0);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_click)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ libinput_device_config_tap_set_enabled(touchpad->libinput_device,
+ LIBINPUT_CONFIG_TAP_DISABLED);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_touch_down(touchpad, 0, 50, 50);
+ litest_button_click(touchpad, BTN_LEFT, true);
+ litest_button_click(touchpad, BTN_LEFT, false);
+ libinput_dispatch(li);
+ litest_touch_up(touchpad, 0);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+
+ litest_keyboard_key(keyboard, KEY_A, false);
+
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_edge_scroll)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ enable_edge_scroll(touchpad);
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ litest_drain_events(li);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_touch_down(touchpad, 0, 99, 20);
+ libinput_dispatch(li);
+ litest_timeout_edgescroll();
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+
+ /* edge scroll timeout is 300ms atm, make sure we don't accidentally
+ exit the DWT timeout */
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ libinput_dispatch(li);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_touch_move_to(touchpad, 0, 99, 20, 99, 80, 60, 10);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+
+ litest_touch_move_to(touchpad, 0, 99, 80, 99, 20, 60, 10);
+ litest_touch_up(touchpad, 0);
+ libinput_dispatch(li);
+ litest_assert_empty_queue(li);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_edge_scroll_interrupt)
+{
+ struct litest_device *touchpad = litest_current_device();
+ struct litest_device *keyboard;
+ struct libinput *li = touchpad->libinput;
+ struct libinput_event_pointer *stop_event;
+
+ if (!has_disable_while_typing(touchpad))
+ return;
+
+ enable_edge_scroll(touchpad);
+
+ keyboard = litest_add_device(li, LITEST_KEYBOARD);
+ litest_drain_events(li);
+
+ litest_touch_down(touchpad, 0, 99, 20);
+ libinput_dispatch(li);
+ litest_timeout_edgescroll();
+ litest_touch_move_to(touchpad, 0, 99, 20, 99, 30, 10, 10);
+ libinput_dispatch(li);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+ litest_keyboard_key(keyboard, KEY_A, true);
+ litest_keyboard_key(keyboard, KEY_A, false);
+
+ /* scroll stop event */
+ litest_wait_for_event(li);
+ stop_event = litest_is_axis_event(libinput_get_event(li),
+ LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+ LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+ libinput_event_destroy(libinput_event_pointer_get_base_event(stop_event));
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+ litest_timeout_dwt_long();
+
+ /* Known bad behavior: a touch starting to edge-scroll before dwt
+ * kicks in will stop to scroll but be recognized as normal
+ * pointer-moving touch once the timeout expires. We'll fix that
+ * when we need to.
+ */
+ litest_touch_move_to(touchpad, 0, 99, 30, 99, 80, 10, 5);
+ litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+ litest_delete_device(keyboard);
+}
+END_TEST
+
void
litest_setup_tests(void)
{
@@ -4652,6 +5129,7 @@ litest_setup_tests(void)
litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger_no_touch, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+ litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger_distance, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:clickfinger", touchpad_clickfinger_to_area_method, LITEST_CLICKPAD, LITEST_ANY);
litest_add("touchpad:clickfinger",
touchpad_clickfinger_to_area_method_while_down, LITEST_CLICKPAD, LITEST_ANY);
@@ -4738,4 +5216,16 @@ litest_setup_tests(void)
litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_no_trackpoint, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
litest_add_ranged("touchpad:state", touchpad_initial_state, LITEST_TOUCHPAD, LITEST_ANY, &axis_range);
+
+ litest_add("touchpad:dwt", touchpad_dwt, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_enable_touch, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_touch_hold, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_key_hold, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_type, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_type_short_timeout, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_tap, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_tap_drag, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_click, LITEST_TOUCHPAD, LITEST_ANY);
+ litest_add("touchpad:dwt", touchpad_dwt_edge_scroll, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+ litest_add("touchpad:dwt", touchpad_dwt_edge_scroll_interrupt, LITEST_TOUCHPAD, LITEST_CLICKPAD);
}
diff --git a/test/trackpoint.c b/test/trackpoint.c
index 9fcce6f..0a6f6b0 100644
--- a/test/trackpoint.c
+++ b/test/trackpoint.c
@@ -35,15 +35,34 @@ START_TEST(trackpoint_middlebutton)
{
struct litest_device *dev = litest_current_device();
struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_pointer *ptrev;
+ uint64_t ptime, rtime;
litest_drain_events(li);
/* A quick middle button click should get reported normally */
litest_button_click(dev, BTN_MIDDLE, 1);
+ msleep(2);
litest_button_click(dev, BTN_MIDDLE, 0);
- litest_assert_button_event(li, BTN_MIDDLE, 1);
- litest_assert_button_event(li, BTN_MIDDLE, 0);
+ litest_wait_for_event(li);
+
+ event = libinput_get_event(li);
+ ptrev = litest_is_button_event(event,
+ BTN_MIDDLE,
+ LIBINPUT_BUTTON_STATE_PRESSED);
+ ptime = libinput_event_pointer_get_time(ptrev);
+ libinput_event_destroy(event);
+
+ event = libinput_get_event(li);
+ ptrev = litest_is_button_event(event,
+ BTN_MIDDLE,
+ LIBINPUT_BUTTON_STATE_RELEASED);
+ rtime = libinput_event_pointer_get_time(ptrev);
+ libinput_event_destroy(event);
+
+ ck_assert_int_lt(ptime, rtime);
litest_assert_empty_queue(li);
}
diff --git a/tools/Makefile.am b/tools/Makefile.am
index b24c560..68e60cb 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -13,9 +13,9 @@ libshared_la_CFLAGS = $(LIBEVDEV_CFLAGS)
libshared_la_LIBADD = $(LIBEVDEV_LIBS)
event_debug_SOURCES = event-debug.c
-event_debug_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS)
+event_debug_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) $(LIBEVDEV_LIBS)
event_debug_LDFLAGS = -no-install
-event_debug_CFLAGS = $(LIBUDEV_CFLAGS)
+event_debug_CFLAGS = $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS)
ptraccel_debug_SOURCES = ptraccel-debug.c
ptraccel_debug_LDADD = ../src/libfilter.la
diff --git a/tools/event-debug.c b/tools/event-debug.c
index e8e49cd..36c7cd3 100644
--- a/tools/event-debug.c
+++ b/tools/event-debug.c
@@ -34,6 +34,7 @@
#include <sys/ioctl.h>
#include <libinput.h>
+#include <libevdev/libevdev.h>
#include "shared.h"
@@ -205,11 +206,17 @@ print_key_event(struct libinput_event *ev)
{
struct libinput_event_keyboard *k = libinput_event_get_keyboard_event(ev);
enum libinput_key_state state;
+ uint32_t key;
+ const char *keyname;
print_event_time(libinput_event_keyboard_get_time(k));
state = libinput_event_keyboard_get_key_state(k);
- printf("%d %s\n",
- libinput_event_keyboard_get_key(k),
+
+ key = libinput_event_keyboard_get_key(k);
+ keyname = libevdev_event_code_get_name(EV_KEY, key);
+ printf("%s (%d) %s\n",
+ keyname ? keyname : "???",
+ key,
state == LIBINPUT_KEY_STATE_PRESSED ? "pressed" : "released");
}
diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c
index 6625173..68ddb61 100644
--- a/tools/libinput-list-devices.c
+++ b/tools/libinput-list-devices.c
@@ -120,17 +120,17 @@ calibration_default(struct libinput_device *device)
float calibration[6];
if (!libinput_device_config_calibration_has_matrix(device)) {
- asprintf(&str, "n/a");
+ xasprintf(&str, "n/a");
return str;
}
if (libinput_device_config_calibration_get_default_matrix(device,
calibration) == 0) {
- asprintf(&str, "identity matrix");
+ xasprintf(&str, "identity matrix");
return str;
}
- asprintf(&str,
+ xasprintf(&str,
"%.2f %.2f %.2f %.2f %.2f %.2f",
calibration[0],
calibration[1],
@@ -150,13 +150,13 @@ scroll_defaults(struct libinput_device *device)
scroll_methods = libinput_device_config_scroll_get_methods(device);
if (scroll_methods == LIBINPUT_CONFIG_SCROLL_NO_SCROLL) {
- asprintf(&str, "none");
+ xasprintf(&str, "none");
return str;
}
method = libinput_device_config_scroll_get_default_method(device);
- asprintf(&str,
+ xasprintf(&str,
"%s%s%s%s%s%s",
(method == LIBINPUT_CONFIG_SCROLL_2FG) ? "*" : "",
(scroll_methods & LIBINPUT_CONFIG_SCROLL_2FG) ? "two-finger " : "",
@@ -176,12 +176,12 @@ click_defaults(struct libinput_device *device)
click_methods = libinput_device_config_click_get_methods(device);
if (click_methods == LIBINPUT_CONFIG_CLICK_METHOD_NONE) {
- asprintf(&str, "none");
+ xasprintf(&str, "none");
return str;
}
method = libinput_device_config_click_get_default_method(device);
- asprintf(&str,
+ xasprintf(&str,
"%s%s%s%s",
(method == LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? "*" : "",
(click_methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? "button-areas " : "",