summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CODING_STYLE26
-rw-r--r--doc/Makefile.am4
-rw-r--r--doc/faqs.dox6
-rw-r--r--doc/gestures.dox91
-rw-r--r--doc/normalization-of-relative-motion.dox3
-rw-r--r--doc/svg/pinch-gestures.svg612
-rw-r--r--doc/svg/swipe-gestures.svg512
-rw-r--r--doc/svg/touchscreen-gestures.svg440
-rw-r--r--src/evdev-mt-touchpad-buttons.c6
-rw-r--r--src/evdev-mt-touchpad-gestures.c340
-rw-r--r--src/evdev-mt-touchpad.c6
-rw-r--r--src/evdev-mt-touchpad.h21
-rw-r--r--src/evdev.c3
-rw-r--r--src/evdev.h1
-rw-r--r--src/libinput-private.h62
-rw-r--r--src/libinput.c256
-rw-r--r--src/libinput.h211
-rw-r--r--src/libinput.sym16
-rw-r--r--test/Makefile.am2
-rw-r--r--test/litest-nexus4-touch-screen.c99
-rw-r--r--test/litest-selftest.c66
-rw-r--r--test/litest-touch-screen.c105
-rw-r--r--test/litest.c132
-rw-r--r--test/litest.h80
-rw-r--r--test/touchpad.c4
-rw-r--r--tools/event-debug.c80
-rw-r--r--tools/event-gui.c133
27 files changed, 3251 insertions, 66 deletions
diff --git a/CODING_STYLE b/CODING_STYLE
index 3648a4e2..c5336cc0 100644
--- a/CODING_STYLE
+++ b/CODING_STYLE
@@ -62,6 +62,26 @@
useit(c);
}
+- do not mix function invocations and variable definitions.
+
+ wrong:
+
+ {
+ int a = foo();
+ int b = 7;
+ }
+
+ right:
+ {
+ int a;
+ int b = 7;
+
+ a = foo();
+ }
+
+ There are exceptions here, e.g. tp_libinput_context(),
+ litest_current_device()
+
- if/else: { on the same line, no curly braces if both blocks are a single
statement. If either if or else block are multiple statements, both must
have curly braces.
@@ -88,3 +108,9 @@
#include <libevdev/libevdev.h>
#include "libinput-private.h"
+
+- goto jumps only to the end of the function, and only for good reasons
+ (usually cleanup). goto never jumps backwards
+
+- Use stdbool.h's bool for booleans within the library (instead of 'int').
+ Exception: the public API uses int, not bool.
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 113666cc..981cab3d 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -14,6 +14,7 @@ header_files = \
$(srcdir)/clickpad-softbuttons.dox \
$(srcdir)/device-configuration-via-udev.dox \
$(srcdir)/faqs.dox \
+ $(srcdir)/gestures.dox \
$(srcdir)/normalization-of-relative-motion.dox \
$(srcdir)/palm-detection.dox \
$(srcdir)/scrolling.dox \
@@ -34,8 +35,11 @@ diagram_files = \
$(srcdir)/svg/button-scrolling.svg \
$(srcdir)/svg/edge-scrolling.svg \
$(srcdir)/svg/palm-detection.svg \
+ $(srcdir)/svg/pinch-gestures.svg \
+ $(srcdir)/svg/swipe-gestures.svg \
$(srcdir)/svg/tap-n-drag.svg \
$(srcdir)/svg/top-software-buttons.svg \
+ $(srcdir)/svg/touchscreen-gestures.svg \
$(srcdir)/svg/twofinger-scrolling.svg
html/index.html: libinput.doxygen $(header_files) $(diagram_files)
diff --git a/doc/faqs.dox b/doc/faqs.dox
index 92a42285..9a78dead 100644
--- a/doc/faqs.dox
+++ b/doc/faqs.dox
@@ -3,6 +3,12 @@
Frequently asked questions about libinput.
+@section faq_fast_mouse My mouse moves too fast, even at the slowest setting
+
+This is a symptom of high-dpi mice (greater than 1000dpi). These devices
+need a udev hwdb entry to normalize their motion. See @ref
+motion_normalization for a detailed explanation.
+
@section faq_kinetic_scrolling Kinetic scrolling does not work
The X.Org synaptics driver implemented kinetic scrolling in the driver. It
diff --git a/doc/gestures.dox b/doc/gestures.dox
new file mode 100644
index 00000000..a8cef36d
--- /dev/null
+++ b/doc/gestures.dox
@@ -0,0 +1,91 @@
+/**
+@page gestures Gestures
+
+libinput supports basic gestures on touchpads and other indirect input
+devices. Two types of gestures are supported: @ref gestures_pinch and @ref
+gestures_swipe. Support for gestures depends on the hardware device, most
+touchpads support both gestures and any device that may send gesture events
+has the @ref LIBINPUT_DEVICE_CAP_GESTURE capability set.
+
+Note that libinput **does not** support gestures on touchscreens, see
+@ref gestures_touchscreens.
+
+@section gestures_lifetime Lifetime of a gesture
+
+A gesture's lifetime has three distinct stages: begin, update and end, each
+with their own event types. Begin is sent when the fingers are first set
+down or libinput decides that the gesture begins. For @ref gestures_pinch
+this sets the initial scale. Any events changing properties of the gesture
+are sent as update events. On termination of the gesture, an end event is
+sent.
+
+A gesture includes the finger count (see
+libinput_event_gesture_get_finger_count()) and that finger count remains the
+same for the lifetime of a gesture. Thus, if a user puts down a fourth
+finger during a three-finger swipe gesture, libinput will end
+the three-finger gesture and, if applicable, start a four-finger swipe
+gesture. A caller may decide that those gestures are semantically identical
+and continue the two gestures as one single gesture.
+
+@see LIBINPUT_EVENT_GESTURE_PINCH_BEGIN
+@see LIBINPUT_EVENT_GESTURE_PINCH_UPDATE
+@see LIBINPUT_EVENT_GESTURE_PINCH_END
+@see LIBINPUT_EVENT_GESTURE_PINCH_BEGIN
+@see LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE
+@see LIBINPUT_EVENT_GESTURE_SWIPE_END
+
+@section gestures_pinch Pinch gestures
+
+Pinch gestures are executed when two or more fingers are located on the
+touchpad and are either changing the relative distance to each other
+(pinching) or are changing the relative angle (rotate). Pinch gestures may
+change both rotation and distance at the same time. For such gestures,
+libinput calculates a logical center for the gestures and provides the
+caller with the delta x/y coordinates of that center, the relative angle of
+the fingers compared to the previous event, and the absolute scale compared
+to the initial finger position.
+
+@image html pinch-gestures.svg "The pinch and rotate gestures"
+
+The illustration above shows a basic pinch in the left image and a rotate in
+the right angle. Not shown is a movement of the logical center if the
+fingers move unevenly. Such a movement is supported by libinput, it is
+merely left out of the illustration.
+
+Note that while position and angle is relative to the previous event, the
+scale is always absolute and a multiplier of the initial finger position's
+scale.
+
+@section gestures_swipe Swipe gestures
+
+Swipe gestures are executed when three or more fingers are moved
+synchronously in the same direction. libinput provides x and y coordinates
+in the gesture and thus allows swipe gestures in any direction, including
+the tracing of complex paths. It is up to the caller to interpret the
+gesture into an action or limit a gesture to specific directions only.
+
+@image html swipe-gestures.svg "The swipe gestures"
+
+The illustration above shows a vertical three-finger swipe. The coordinates
+provided during the gesture are the movements of the logical center.
+
+@section gestures_touchscreens Touchscreen gestures
+
+Touchscreen gestures are **not** interpreted by libinput. Rather, any touch
+point is passed to the caller and any interpretation of gestures is up to
+the caller or, eventually, the X or Wayland client.
+
+Interpreting gestures on a touchscreen requires context that libinput does
+not have, such as the location of windows and other virtual objects on the
+screen as well as the context of those virtual objects:
+
+@image html touchscreen-gestures.svg "Context-sensitivity of touchscreen gestures"
+
+In this example, the finger movements are identical but in the left case
+both fingers are located within the same window, thus suggesting an attemp
+to zoom. In the right case both fingers are located on a window border,
+thus suggesting a window movement. libinput only has knowledge of the finger
+coordinates (and even then only in device coordinates, not in screen
+coordinates) and thus cannot differentiate the two.
+
+*/
diff --git a/doc/normalization-of-relative-motion.dox b/doc/normalization-of-relative-motion.dox
index dd5d39b3..58ed151b 100644
--- a/doc/normalization-of-relative-motion.dox
+++ b/doc/normalization-of-relative-motion.dox
@@ -44,6 +44,9 @@ Devices usually do not advertise their resolution and libinput relies on
the udev property <b>MOUSE_DPI</b> for this information. This property is usually
set via the <a
href="http://cgit.freedesktop.org/systemd/systemd/tree/hwdb/70-mouse.hwdb">udev hwdb</a>.
+The "mouse-dpi-tool" utility provided by <a
+href="http://freedesktop.org/wiki/Software/libevdev/">libevdev</a> should be
+used to measure a device's resolution.
The format of the property for single-resolution mice is:
@code
diff --git a/doc/svg/pinch-gestures.svg b/doc/svg/pinch-gestures.svg
new file mode 100644
index 00000000..4dca4cf4
--- /dev/null
+++ b/doc/svg/pinch-gestures.svg
@@ -0,0 +1,612 @@
+<?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="193.91415mm"
+ height="71.032608mm"
+ viewBox="0 0 687.09741 251.69034"
+ id="svg4313"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="pinch-gestures.svg">
+ <defs
+ id="defs4315">
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7694"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path7696"
+ 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)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7562"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path7564"
+ 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="Arrow2Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4995"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ 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:isstock="true"
+ style="overflow:visible"
+ id="marker7356"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mstart">
+ <path
+ transform="matrix(0.4,0,0,0.4,4,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="path7358"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker7250"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mend"
+ inkscape:collect="always">
+ <path
+ transform="matrix(-0.4,0,0,-0.4,-4,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="path7252"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path4986"
+ 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="Arrow1Send"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Send"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4992"
+ 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.2,0,0,-0.2,-1.2,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path4983"
+ 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="Arrow2Sstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Sstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5007"
+ 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(0.3,0,0,0.3,-0.69,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6499"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lstart">
+ <path
+ 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="path6501"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6261"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend"
+ inkscape:collect="always">
+ <path
+ 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="path6263"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5837"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5839"
+ 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="marker5365"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend">
+ <path
+ 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="path5367"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path4998"
+ 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="marker5291"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5293"
+ 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="path4980"
+ 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="path4977"
+ 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>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.93677625"
+ inkscape:cx="60.063165"
+ inkscape:cy="195.28138"
+ 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="metadata4318">
+ <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(-1.110285,-124.21518)">
+ <rect
+ width="309.33386"
+ height="209.27208"
+ x="375.99207"
+ y="127.09696"
+ id="rect14688"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.76355982;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.76355982;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858-0"
+ y="127.09696"
+ x="3.992065"
+ height="209.27208"
+ width="309.33386" />
+ <g
+ id="g3663-9-5"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,-40.638425,-251.35321)">
+ <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
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,87.872503,-305.95371)"
+ id="g4877">
+ <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="path4879"
+ 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="path4881"
+ 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="path4883"
+ 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.64805599,0.12052062,-0.12052062,0.64805599,-40.638425,-251.35321)"
+ id="g4889">
+ <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="path4891"
+ 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="path4893"
+ 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="path4895"
+ 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="g4897"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,87.872503,-305.95371)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4899"
+ 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="path4901"
+ 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="path4903"
+ 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
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,327.582,-251.35321)"
+ id="g4915">
+ <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="path4917"
+ 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="path4919"
+ 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="path4921"
+ 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="g4923"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,456.09293,-305.95371)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4925"
+ 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="path4927"
+ 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="path4929"
+ 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="g4931"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,327.582,-251.35321)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4933"
+ 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="path4935"
+ 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="path4937"
+ 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
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,456.09293,-305.95371)"
+ id="g4939">
+ <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="path4941"
+ 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="path4943"
+ 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="path4945"
+ 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:1.31833494;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Lend)"
+ d="M 237.82329,186.65441 272.448,168.6762"
+ id="path4971"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5363"
+ d="m 43.293248,279.41755 34.62471,-17.97821"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.31833494;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker6499)" />
+ <circle
+ style="opacity:0.92000002;fill:#ffdd55;fill-opacity:0.07734806;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path6880"
+ cx="158.49547"
+ cy="227.65926"
+ r="5.3374538" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.97016698px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker7356);marker-end:url(#marker7250)"
+ d="M 88.03522,236.61109 213.47708,179.47406"
+ id="path6882"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6884"
+ d="M 25.998133,239.56832 257.65533,138.66416"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.97016698px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:12.50000095px;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="139.81439"
+ y="246.73911"
+ id="text6886"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan6888"
+ x="139.81439"
+ y="246.73911">logical</tspan><tspan
+ sodipodi:role="line"
+ x="139.81439"
+ y="262.36411"
+ id="tspan6890">center</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:12.50000095px;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="-0.65095264"
+ y="247.5675"
+ id="text6892"
+ sodipodi:linespacing="125%"
+ transform="matrix(0.90931568,-0.41610697,0.41610692,0.90931568,0,0)"><tspan
+ sodipodi:role="line"
+ id="tspan6894"
+ x="-0.65095264"
+ y="247.5675">initial scale (1.0)</tspan></text>
+ <text
+ transform="matrix(0.90931568,-0.41610697,0.41610692,0.90931568,0,0)"
+ sodipodi:linespacing="125%"
+ id="text6896"
+ y="225.43965"
+ x="-16.939013"
+ style="font-style:normal;font-weight:normal;font-size:12.50000095px;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
+ y="225.43965"
+ x="-16.939013"
+ id="tspan6898"
+ sodipodi:role="line">current scale (&gt; 1.0)</tspan></text>
+ <circle
+ r="5.3374538"
+ cy="226.45061"
+ cx="529.7337"
+ id="circle7474"
+ style="opacity:0.92000002;fill:#ffdd55;fill-opacity:0.07734806;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ sodipodi:linespacing="125%"
+ id="text7476"
+ y="245.53046"
+ x="511.05258"
+ style="font-style:normal;font-weight:normal;font-size:12.50000095px;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
+ y="245.53046"
+ x="511.05258"
+ id="tspan7478"
+ sodipodi:role="line">logical</tspan><tspan
+ id="tspan7480"
+ y="261.15546"
+ x="511.05258"
+ sodipodi:role="line">center</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker6261)"
+ d="m 470.41432,259.01526 c 18.14735,33.09222 51.23956,27.22102 51.23956,27.22102"
+ id="path7554"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path7692"
+ d="M 592.09959,192.40501 C 567.10018,164.13044 536.11794,177.15603 536.11794,177.15603"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker7694)" />
+ <path
+ style="fill:#ff0000;fill-rule:evenodd;stroke:#dd0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 469.81386,258.48153 636.94289,169.34604"
+ id="path9270"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 540.33497,130.38263 523.25512,286.23628"
+ id="path9272"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:12.5px;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="575.56219"
+ y="156.53615"
+ id="text9274"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan9276"
+ x="575.56219"
+ y="156.53615">delta</tspan><tspan
+ sodipodi:role="line"
+ x="575.56219"
+ y="172.16115"
+ id="tspan9278">angle</tspan></text>
+ </g>
+</svg>
diff --git a/doc/svg/swipe-gestures.svg b/doc/svg/swipe-gestures.svg
new file mode 100644
index 00000000..a88f646b
--- /dev/null
+++ b/doc/svg/swipe-gestures.svg
@@ -0,0 +1,512 @@
+<?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="88.927498mm"
+ height="77.985535mm"
+ viewBox="0 0 315.09744 276.3267"
+ id="svg4313"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="swipe-gestures.svg">
+ <defs
+ id="defs4315">
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker13223"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path13225"
+ style="fill:#fb0000;fill-opacity:1;fill-rule:evenodd;stroke:#fb0000;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="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker11669"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path11671"
+ 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:isstock="true"
+ style="overflow:visible"
+ id="marker11171"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend"
+ inkscape:collect="always">
+ <path
+ 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="path11173"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4995"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ 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:isstock="true"
+ style="overflow:visible"
+ id="marker10193"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lstart">
+ <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="path10195" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path4998"
+ 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="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4986"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,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="path4983"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7694"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path7696"
+ 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)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7562"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path7564"
+ 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:isstock="true"
+ style="overflow:visible"
+ id="marker7356"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mstart">
+ <path
+ transform="matrix(0.4,0,0,0.4,4,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="path7358"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Send"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4992"
+ 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.2,0,0,-0.2,-1.2,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Sstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Sstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5007"
+ 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(0.3,0,0,0.3,-0.69,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6499"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lstart">
+ <path
+ 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="path6501"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5837"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5839"
+ 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="marker5365"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend">
+ <path
+ 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="path5367"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5291"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5293"
+ 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="path4980"
+ 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="path4977"
+ 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
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow1Lstart-5"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ id="path3707"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow1Lend-9"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ id="path3710"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow1Lstart-7"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ id="path3707-7"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow1Lend-7"
+ style="overflow:visible">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ id="path3710-9"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.3248017"
+ inkscape:cx="390.51795"
+ inkscape:cy="169.53168"
+ 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="metadata4318">
+ <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(-6.8742073,-88.520861)">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5.76355982;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2858-0"
+ y="91.402641"
+ x="9.7559872"
+ height="209.27208"
+ width="309.33386" />
+ <g
+ id="g3663-9-5"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,-24.296656,-262.41117)">
+ <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
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,18.050732,-296.51834)"
+ id="g9299">
+ <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="path9301"
+ 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="path9303"
+ 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="path9305"
+ 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="g4897"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,70.021073,-291.1216)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4899"
+ 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="path4901"
+ 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="path4903"
+ 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>
+ <circle
+ style="opacity:0.92000002;fill:#ffdd55;fill-opacity:0.07734806;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path6880"
+ cx="158.49547"
+ cy="227.65924"
+ r="5.3374538" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:12.50000095px;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="157.93031"
+ y="147.10155"
+ id="text6886"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan6888"
+ x="157.93031"
+ y="147.10155">logical</tspan><tspan
+ sodipodi:role="line"
+ x="157.93031"
+ y="162.72655"
+ id="tspan6890">center</tspan></text>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Lend)"
+ d="m 110.34988,168.32728 0,55.85742"
+ id="path11149"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11169"
+ d="m 152.62036,133.6051 0,55.85742"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker11171)" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker11669)"
+ d="m 206.21329,138.13408 0,55.85742"
+ id="path11667"
+ inkscape:connector-curvature="0" />
+ <circle
+ r="5.3374538"
+ cy="173.65924"
+ cx="158.49547"
+ id="circle13213"
+ style="opacity:0.92000002;fill:#ffdd55;fill-opacity:0.07734806;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#fb0000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker13223);stroke-miterlimit:4;stroke-dasharray:4,1;stroke-dashoffset:0"
+ d="m 158.659,172.10143 0,55.85742"
+ id="path13221"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/doc/svg/touchscreen-gestures.svg b/doc/svg/touchscreen-gestures.svg
new file mode 100644
index 00000000..8452c7e0
--- /dev/null
+++ b/doc/svg/touchscreen-gestures.svg
@@ -0,0 +1,440 @@
+<?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="198.41673mm"
+ height="68.753075mm"
+ viewBox="0 0 703.05143 243.61326"
+ id="svg4313"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="touchscreen-gestures.svg">
+ <defs
+ id="defs4315">
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6499"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lstart">
+ <path
+ 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="path6501"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker6261"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend">
+ <path
+ 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="path6263"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path4995"
+ 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="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker5837"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5839"
+ 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="marker5365"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend">
+ <path
+ 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="path5367"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible"
+ inkscape:isstock="true"
+ inkscape:collect="always">
+ <path
+ id="path4998"
+ 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="marker5291"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path5293"
+ 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="path4980"
+ 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="path4977"
+ 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>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.93677625"
+ inkscape:cx="610.35826"
+ inkscape:cy="81.237892"
+ 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="metadata4318">
+ <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(-21.856644,-132.29226)">
+ <rect
+ style="opacity:0.92000002;fill:#000000;fill-opacity:0.07734806;stroke:#000000;stroke-width:1.22726846;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4905"
+ width="333.6037"
+ height="240.70985"
+ x="22.470278"
+ y="132.9059" />
+ <g
+ id="g4885"
+ style="fill:#ffeeaa"
+ transform="matrix(0.65916749,0,0,0.65916749,7.4494548,128.12082)">
+ <rect
+ y="53.624214"
+ x="49.833439"
+ height="221.76169"
+ width="360.3331"
+ id="rect4873"
+ style="opacity:0.92000002;fill:#ffeeaa;fill-opacity:0.2983426;stroke:#000000;stroke-width:0.66787815;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:0.92000002;fill:#ffeeaa;fill-opacity:0.2983426;stroke:#000000;stroke-width:0.70335335;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4875"
+ width="360.29761"
+ height="245.96988"
+ x="49.851177"
+ y="29.398293" />
+ </g>
+ <g
+ id="g3663-9-5"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,-40.638425,-251.35321)">
+ <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
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,87.872503,-305.95371)"
+ id="g4877">
+ <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="path4879"
+ 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="path4881"
+ 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="path4883"
+ 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.64805599,0.12052062,-0.12052062,0.64805599,-40.638425,-251.35321)"
+ id="g4889">
+ <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="path4891"
+ 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="path4893"
+ 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="path4895"
+ 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="g4897"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,87.872503,-305.95371)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4899"
+ 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="path4901"
+ 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="path4903"
+ 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>
+ <rect
+ y="132.9059"
+ x="390.6907"
+ height="240.70985"
+ width="333.6037"
+ id="rect4907"
+ style="opacity:0.92000002;fill:#000000;fill-opacity:0.07734806;stroke:#000000;stroke-width:1.22726846;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <g
+ id="g4951"
+ transform="matrix(0.65916749,0,0,0.65916749,136.45794,-94.79591)">
+ <rect
+ style="opacity:0.92000002;fill:#ffeeaa;fill-opacity:0.2983426;stroke:#000000;stroke-width:0.39509788;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4911"
+ width="240.39772"
+ height="116.3256"
+ x="390.11847"
+ y="546.44226" />
+ <rect
+ y="522.21863"
+ x="390.13852"
+ height="141.18213"
+ width="240.35757"
+ id="rect4913"
+ style="opacity:0.92000002;fill:#ffeeaa;fill-opacity:0.2983426;stroke:#000000;stroke-width:0.43523186;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,327.582,-251.35321)"
+ id="g4915">
+ <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="path4917"
+ 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="path4919"
+ 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="path4921"
+ 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="g4923"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,456.09293,-305.95371)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4925"
+ 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="path4927"
+ 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="path4929"
+ 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="g4931"
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,327.582,-251.35321)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path4933"
+ 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="path4935"
+ 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="path4937"
+ 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="g4955"
+ transform="matrix(0.65916749,0,0,0.65916749,296.60088,-154.71595)">
+ <rect
+ y="546.44226"
+ x="390.11847"
+ height="116.3256"
+ width="240.39772"
+ id="rect4957"
+ style="opacity:0.92000002;fill:#ffeeaa;fill-opacity:0.2983426;stroke:#000000;stroke-width:0.39509788;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="opacity:0.92000002;fill:#ffeeaa;fill-opacity:0.2983426;stroke:#000000;stroke-width:0.43523186;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4959"
+ width="240.35757"
+ height="141.18213"
+ x="390.13852"
+ y="522.21863" />
+ </g>
+ <g
+ transform="matrix(0.64805599,0.12052062,-0.12052062,0.64805599,456.09293,-305.95371)"
+ id="g4939">
+ <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="path4941"
+ 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="path4943"
+ 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="path4945"
+ 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:1.31833494;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Lend)"
+ d="m 113.99435,244.83266 34.62471,-17.97821"
+ id="path4971"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5363"
+ d="m 173.52713,216.96933 34.62471,-17.97821"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.31833494;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker6499)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path6257"
+ d="m 481.80981,244.83266 34.62471,-17.97821"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.31833494;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6261)" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.31833494;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow2Lstart)"
+ d="M 541.34259,216.96933 575.9673,198.99112"
+ id="path6259"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c
index 9c1c096c..f0857747 100644
--- a/src/evdev-mt-touchpad-buttons.c
+++ b/src/evdev-mt-touchpad-buttons.c
@@ -832,12 +832,10 @@ tp_check_clickfinger_distance(struct tp_dispatch *tp,
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 */
+ /* no resolution, so let's assume they're close enough together if
+ they're within 30% of the touchpad width or height */
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.dimensions.x;
h = tp->device->abs.dimensions.y;
diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c
index e85a9d77..cd853ecf 100644
--- a/src/evdev-mt-touchpad-gestures.c
+++ b/src/evdev-mt-touchpad-gestures.c
@@ -23,7 +23,6 @@
#include "config.h"
-#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <limits.h>
@@ -31,6 +30,7 @@
#include "evdev-mt-touchpad.h"
#define DEFAULT_GESTURE_SWITCH_TIMEOUT 100 /* ms */
+#define DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT 1000 /* ms */
static struct normalized_coords
tp_get_touches_delta(struct tp_dispatch *tp, bool average)
@@ -76,12 +76,37 @@ tp_get_average_touches_delta(struct tp_dispatch *tp)
static void
tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+ const struct normalized_coords zero = { 0.0, 0.0 };
+
if (tp->gesture.started)
return;
switch (tp->gesture.finger_count) {
case 2:
- /* NOP */
+ switch (tp->gesture.twofinger_state) {
+ case GESTURE_2FG_STATE_NONE:
+ case GESTURE_2FG_STATE_UNKNOWN:
+ log_bug_libinput(libinput,
+ "%s in unknown gesture mode\n",
+ __func__);
+ break;
+ case GESTURE_2FG_STATE_SCROLL:
+ /* NOP */
+ break;
+ case GESTURE_2FG_STATE_PINCH:
+ gesture_notify_pinch(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ &zero, &zero, 1.0, 0.0);
+ break;
+ }
+ break;
+ case 3:
+ case 4:
+ gesture_notify_swipe(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ tp->gesture.finger_count,
+ &zero, &zero);
break;
}
tp->gesture.started = true;
@@ -110,19 +135,191 @@ tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
}
}
+static unsigned int
+tp_gesture_get_active_touches(struct tp_dispatch *tp,
+ struct tp_touch **touches,
+ unsigned int count)
+{
+ unsigned int i, n = 0;
+ struct tp_touch *t;
+
+ memset(touches, 0, count * sizeof(struct tp_touch *));
+
+ for (i = 0; i < tp->num_slots; i++) {
+ t = &tp->touches[i];
+ if (tp_touch_active(tp, t)) {
+ touches[n++] = t;
+ if (n == count)
+ return count;
+ }
+ }
+
+ /*
+ * This can happen when the user does .e.g:
+ * 1) Put down 1st finger in center (so active)
+ * 2) Put down 2nd finger in a button area (so inactive)
+ * 3) Put down 3th finger somewhere, gets reported as a fake finger,
+ * so gets same coordinates as 1st -> active
+ *
+ * We could avoid this by looking at all touches, be we really only
+ * want to look at real touches.
+ */
+ return n;
+}
+
+static int
+tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch)
+{
+ struct normalized_coords normalized;
+ struct device_float_coords delta;
+ double move_threshold;
+
+ /*
+ * Semi-mt touchpads have somewhat inaccurate coordinates when
+ * 2 fingers are down, so use a slightly larger threshold.
+ */
+ if (tp->semi_mt)
+ move_threshold = TP_MM_TO_DPI_NORMALIZED(4);
+ else
+ move_threshold = TP_MM_TO_DPI_NORMALIZED(3);
+
+ delta = device_delta(touch->point, touch->gesture.initial);
+ normalized = tp_normalize_delta(tp, delta);
+
+ if (normalized_length(normalized) < move_threshold)
+ return UNDEFINED_DIRECTION;
+
+ return normalized_get_direction(normalized);
+}
+
static void
-tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
+tp_gesture_get_pinch_info(struct tp_dispatch *tp,
+ double *distance,
+ double *angle,
+ struct device_float_coords *center)
+{
+ struct normalized_coords normalized;
+ struct device_float_coords delta;
+ struct tp_touch *first = tp->gesture.touches[0],
+ *second = tp->gesture.touches[1];
+
+ delta = device_delta(first->point, second->point);
+ normalized = tp_normalize_delta(tp, delta);
+ *distance = normalized_length(normalized);
+
+ if (!tp->semi_mt)
+ *angle = atan2(normalized.y, normalized.x) * 180.0 / M_PI;
+ else
+ *angle = 0.0;
+
+ *center = device_average(first->point, second->point);
+}
+
+static void
+tp_gesture_set_scroll_buildup(struct tp_dispatch *tp)
+{
+ struct device_float_coords d0, d1;
+ struct device_float_coords average;
+ struct tp_touch *first = tp->gesture.touches[0],
+ *second = tp->gesture.touches[1];
+
+ d0 = device_delta(first->point, first->gesture.initial);
+ d1 = device_delta(second->point, second->gesture.initial);
+
+ average = device_float_average(d0, d1);
+ tp->device->scroll.buildup = tp_normalize_delta(tp, average);
+}
+
+static enum tp_gesture_2fg_state
+tp_gesture_twofinger_handle_state_none(struct tp_dispatch *tp, uint64_t time)
+{
+ struct tp_touch *first, *second;
+
+ if (tp_gesture_get_active_touches(tp, tp->gesture.touches, 2) != 2)
+ return GESTURE_2FG_STATE_NONE;
+
+ first = tp->gesture.touches[0];
+ second = tp->gesture.touches[1];
+
+ tp->gesture.initial_time = time;
+ first->gesture.initial = first->point;
+ second->gesture.initial = second->point;
+
+ return GESTURE_2FG_STATE_UNKNOWN;
+}
+
+static enum tp_gesture_2fg_state
+tp_gesture_twofinger_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
+{
+ struct normalized_coords normalized;
+ struct device_float_coords delta;
+ struct tp_touch *first = tp->gesture.touches[0],
+ *second = tp->gesture.touches[1];
+ int dir1, dir2;
+
+ delta = device_delta(first->point, second->point);
+ normalized = tp_normalize_delta(tp, delta);
+
+ /* If fingers are further than 3 cm apart assume pinch */
+ if (normalized_length(normalized) > TP_MM_TO_DPI_NORMALIZED(30)) {
+ tp_gesture_get_pinch_info(tp,
+ &tp->gesture.initial_distance,
+ &tp->gesture.angle,
+ &tp->gesture.center);
+ tp->gesture.prev_scale = 1.0;
+ return GESTURE_2FG_STATE_PINCH;
+ }
+
+ /* Elif fingers have been close together for a while, scroll */
+ if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) {
+ tp_gesture_set_scroll_buildup(tp);
+ return GESTURE_2FG_STATE_SCROLL;
+ }
+
+ /* Else wait for both fingers to have moved */
+ dir1 = tp_gesture_get_direction(tp, first);
+ dir2 = tp_gesture_get_direction(tp, second);
+ if (dir1 == UNDEFINED_DIRECTION || dir2 == UNDEFINED_DIRECTION)
+ return GESTURE_2FG_STATE_UNKNOWN;
+
+ /*
+ * If both touches are moving in the same direction assume scroll.
+ *
+ * In some cases (semi-mt touchpads) We may seen one finger move
+ * e.g. N/NE and the other W/NW so we not only check for overlapping
+ * directions, but also for neighboring bits being set.
+ * The ((dira & 0x80) && (dirb & 0x01)) checks are to check for bit 0
+ * and 7 being set as they also represent neighboring directions.
+ */
+ if (((dir1 | (dir1 >> 1)) & dir2) ||
+ ((dir2 | (dir2 >> 1)) & dir1) ||
+ ((dir1 & 0x80) && (dir2 & 0x01)) ||
+ ((dir2 & 0x80) && (dir1 & 0x01))) {
+ tp_gesture_set_scroll_buildup(tp);
+ return GESTURE_2FG_STATE_SCROLL;
+ } else {
+ tp_gesture_get_pinch_info(tp,
+ &tp->gesture.initial_distance,
+ &tp->gesture.angle,
+ &tp->gesture.center);
+ tp->gesture.prev_scale = 1.0;
+ return GESTURE_2FG_STATE_PINCH;
+ }
+}
+
+static enum tp_gesture_2fg_state
+tp_gesture_twofinger_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
{
struct normalized_coords delta;
if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
- return;
+ return GESTURE_2FG_STATE_SCROLL;
/* On some semi-mt models slot 0 is more accurate, so for semi-mt
* we only use slot 0. */
if (tp->semi_mt) {
if (!tp->touches[0].dirty)
- return;
+ return GESTURE_2FG_STATE_SCROLL;
delta = tp_get_delta(&tp->touches[0]);
} else {
@@ -132,13 +329,89 @@ tp_gesture_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
delta = tp_filter_motion(tp, &delta, time);
if (normalized_is_zero(delta))
- return;
+ return GESTURE_2FG_STATE_SCROLL;
tp_gesture_start(tp, time);
evdev_post_scroll(tp->device,
time,
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
&delta);
+
+ return GESTURE_2FG_STATE_SCROLL;
+}
+
+static enum tp_gesture_2fg_state
+tp_gesture_twofinger_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
+{
+ double angle, angle_delta, distance, scale;
+ struct device_float_coords center, fdelta;
+ struct normalized_coords delta, unaccel;
+
+ tp_gesture_get_pinch_info(tp, &distance, &angle, &center);
+
+ scale = distance / tp->gesture.initial_distance;
+
+ angle_delta = angle - tp->gesture.angle;
+ tp->gesture.angle = angle;
+ if (angle_delta > 180.0)
+ angle_delta -= 360.0;
+ else if (angle_delta < -180.0)
+ angle_delta += 360.0;
+
+ fdelta = device_float_delta(center, tp->gesture.center);
+ tp->gesture.center = center;
+ unaccel = tp_normalize_delta(tp, fdelta);
+ delta = tp_filter_motion(tp, &unaccel, time);
+
+ if (normalized_is_zero(delta) && normalized_is_zero(unaccel) &&
+ scale == tp->gesture.prev_scale && angle_delta == 0.0)
+ return GESTURE_2FG_STATE_PINCH;
+
+ tp_gesture_start(tp, time);
+ gesture_notify_pinch(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ &delta, &unaccel, scale, angle_delta);
+
+ tp->gesture.prev_scale = scale;
+
+ return GESTURE_2FG_STATE_PINCH;
+}
+
+static void
+tp_gesture_post_twofinger(struct tp_dispatch *tp, uint64_t time)
+{
+ if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_NONE)
+ tp->gesture.twofinger_state =
+ tp_gesture_twofinger_handle_state_none(tp, time);
+
+ if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_UNKNOWN)
+ tp->gesture.twofinger_state =
+ tp_gesture_twofinger_handle_state_unknown(tp, time);
+
+ if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_SCROLL)
+ tp->gesture.twofinger_state =
+ tp_gesture_twofinger_handle_state_scroll(tp, time);
+
+ if (tp->gesture.twofinger_state == GESTURE_2FG_STATE_PINCH)
+ tp->gesture.twofinger_state =
+ tp_gesture_twofinger_handle_state_pinch(tp, time);
+}
+
+static void
+tp_gesture_post_swipe(struct tp_dispatch *tp, uint64_t time)
+{
+ struct normalized_coords delta, unaccel;
+
+ unaccel = tp_get_average_touches_delta(tp);
+ delta = tp_filter_motion(tp, &unaccel, time);
+
+ if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) {
+ tp_gesture_start(tp, time);
+ gesture_notify_swipe(&tp->device->base, time,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ tp->gesture.finger_count,
+ &delta, &unaccel);
+ }
}
void
@@ -149,7 +422,7 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
/* When tap-and-dragging, or a clickpad is clicked force 1fg mode */
if (tp_tap_dragging(tp) || (tp->buttons.is_clickpad && tp->buttons.state)) {
- tp_gesture_stop(tp, time);
+ tp_gesture_cancel(tp, time);
tp->gesture.finger_count = 1;
tp->gesture.finger_count_pending = 0;
}
@@ -163,7 +436,11 @@ tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
tp_gesture_post_pointer_motion(tp, time);
break;
case 2:
- tp_gesture_post_twofinger_scroll(tp, time);
+ tp_gesture_post_twofinger(tp, time);
+ break;
+ case 3:
+ case 4:
+ tp_gesture_post_swipe(tp, time);
break;
}
}
@@ -179,20 +456,57 @@ tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
}
-void
-tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
+static void
+tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled)
{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+ enum tp_gesture_2fg_state twofinger_state = tp->gesture.twofinger_state;
+
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_NONE;
+
if (!tp->gesture.started)
return;
switch (tp->gesture.finger_count) {
case 2:
- tp_gesture_stop_twofinger_scroll(tp, time);
+ switch (twofinger_state) {
+ case GESTURE_2FG_STATE_NONE:
+ case GESTURE_2FG_STATE_UNKNOWN:
+ log_bug_libinput(libinput,
+ "%s in unknown gesture mode\n",
+ __func__);
+ break;
+ case GESTURE_2FG_STATE_SCROLL:
+ tp_gesture_stop_twofinger_scroll(tp, time);
+ break;
+ case GESTURE_2FG_STATE_PINCH:
+ gesture_notify_pinch_end(&tp->device->base, time,
+ tp->gesture.prev_scale,
+ cancelled);
+ break;
+ }
+ break;
+ case 3:
+ case 4:
+ gesture_notify_swipe_end(&tp->device->base, time,
+ tp->gesture.finger_count, cancelled);
break;
}
tp->gesture.started = false;
}
+void
+tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time)
+{
+ tp_gesture_end(tp, time, true);
+}
+
+void
+tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
+{
+ tp_gesture_end(tp, time, false);
+}
+
static void
tp_gesture_finger_count_switch_timeout(uint64_t now, void *data)
{
@@ -201,7 +515,7 @@ tp_gesture_finger_count_switch_timeout(uint64_t now, void *data)
if (!tp->gesture.finger_count_pending)
return;
- tp_gesture_stop(tp, now); /* End current gesture */
+ tp_gesture_cancel(tp, now); /* End current gesture */
tp->gesture.finger_count = tp->gesture.finger_count_pending;
tp->gesture.finger_count_pending = 0;
}
@@ -240,6 +554,8 @@ tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
int
tp_init_gesture(struct tp_dispatch *tp)
{
+ tp->gesture.twofinger_state = GESTURE_2FG_STATE_NONE;
+
libinput_timer_init(&tp->gesture.finger_count_switch_timer,
tp->device->base.seat->libinput,
tp_gesture_finger_count_switch_timeout, tp);
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 9d4c27d9..f13873b5 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -797,7 +797,7 @@ tp_post_events(struct tp_dispatch *tp, uint64_t time)
tp->palm.trackpoint_active ||
tp->dwt.keyboard_active) {
tp_edge_scroll_stop_events(tp, time);
- tp_gesture_stop(tp, time);
+ tp_gesture_cancel(tp, time);
return;
}
@@ -976,7 +976,7 @@ tp_trackpoint_event(uint64_t time, struct libinput_event *event, void *data)
if (!tp->palm.trackpoint_active) {
tp_edge_scroll_stop_events(tp, time);
- tp_gesture_stop(tp, time);
+ tp_gesture_cancel(tp, time);
tp_tap_suspend(tp, time);
tp->palm.trackpoint_active = true;
}
@@ -1053,7 +1053,7 @@ tp_keyboard_event(uint64_t time, struct libinput_event *event, void *data)
if (!tp->dwt.keyboard_active) {
tp_edge_scroll_stop_events(tp, time);
- tp_gesture_stop(tp, time);
+ tp_gesture_cancel(tp, time);
tp_tap_suspend(tp, time);
tp->dwt.keyboard_active = true;
timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1;
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index edad6110..df8be94f 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -129,6 +129,13 @@ enum tp_edge_scroll_touch_state {
EDGE_SCROLL_TOUCH_STATE_AREA,
};
+enum tp_gesture_2fg_state {
+ GESTURE_2FG_STATE_NONE,
+ GESTURE_2FG_STATE_UNKNOWN,
+ GESTURE_2FG_STATE_SCROLL,
+ GESTURE_2FG_STATE_PINCH,
+};
+
struct tp_touch {
struct tp_dispatch *tp;
enum touch_state state;
@@ -181,6 +188,10 @@ struct tp_touch {
struct device_coords first; /* first coordinates if is_palm == true */
uint32_t time; /* first timestamp if is_palm == true */
} palm;
+
+ struct {
+ struct device_coords initial;
+ } gesture;
};
struct tp_dispatch {
@@ -216,6 +227,13 @@ struct tp_dispatch {
unsigned int finger_count;
unsigned int finger_count_pending;
struct libinput_timer finger_count_switch_timer;
+ enum tp_gesture_2fg_state twofinger_state;
+ struct tp_touch *touches[2];
+ uint64_t initial_time;
+ double initial_distance;
+ double prev_scale;
+ double angle;
+ struct device_float_coords center;
} gesture;
struct {
@@ -432,6 +450,9 @@ void
tp_gesture_stop(struct tp_dispatch *tp, uint64_t time);
void
+tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time);
+
+void
tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time);
void
diff --git a/src/evdev.c b/src/evdev.c
index e0bfe2ed..b5cf3332 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -1941,6 +1941,7 @@ evdev_configure_device(struct evdev_device *device)
if (udev_tags & EVDEV_UDEV_TAG_TOUCHPAD) {
device->dispatch = evdev_mt_touchpad_create(device);
+ device->seat_caps |= EVDEV_DEVICE_GESTURE;
log_info(libinput,
"input device '%s', %s is a touchpad\n",
device->devname, devnode);
@@ -2308,6 +2309,8 @@ evdev_device_has_capability(struct evdev_device *device,
return !!(device->seat_caps & EVDEV_DEVICE_KEYBOARD);
case LIBINPUT_DEVICE_CAP_TOUCH:
return !!(device->seat_caps & EVDEV_DEVICE_TOUCH);
+ case LIBINPUT_DEVICE_CAP_GESTURE:
+ return !!(device->seat_caps & EVDEV_DEVICE_GESTURE);
case LIBINPUT_DEVICE_CAP_TABLET:
return !!(device->seat_caps & EVDEV_DEVICE_TABLET);
default:
diff --git a/src/evdev.h b/src/evdev.h
index b9082d2d..00898811 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -61,6 +61,7 @@ enum evdev_device_seat_capability {
EVDEV_DEVICE_KEYBOARD = (1 << 1),
EVDEV_DEVICE_TOUCH = (1 << 2),
EVDEV_DEVICE_TABLET = (1 << 3),
+ EVDEV_DEVICE_GESTURE = (1 << 5),
};
enum evdev_device_tags {
diff --git a/src/libinput-private.h b/src/libinput-private.h
index 766f09b2..3c8fad25 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -381,6 +381,35 @@ touch_notify_touch_up(struct libinput_device *device,
int32_t seat_slot);
void
+gesture_notify_swipe(struct libinput_device *device,
+ uint64_t time,
+ enum libinput_event_type type,
+ int finger_count,
+ const struct normalized_coords *delta,
+ const struct normalized_coords *unaccel);
+
+void
+gesture_notify_swipe_end(struct libinput_device *device,
+ uint64_t time,
+ int finger_count,
+ int cancelled);
+
+void
+gesture_notify_pinch(struct libinput_device *device,
+ uint64_t time,
+ enum libinput_event_type type,
+ const struct normalized_coords *delta,
+ const struct normalized_coords *unaccel,
+ double scale,
+ double angle);
+
+void
+gesture_notify_pinch_end(struct libinput_device *device,
+ uint64_t time,
+ double scale,
+ int cancelled);
+
+void
touch_notify_frame(struct libinput_device *device,
uint64_t time);
@@ -433,6 +462,39 @@ device_delta(struct device_coords a, struct device_coords b)
return delta;
}
+static inline struct device_float_coords
+device_average(struct device_coords a, struct device_coords b)
+{
+ struct device_float_coords average;
+
+ average.x = (a.x + b.x) / 2.0;
+ average.y = (a.y + b.y) / 2.0;
+
+ return average;
+}
+
+static inline struct device_float_coords
+device_float_delta(struct device_float_coords a, struct device_float_coords b)
+{
+ struct device_float_coords delta;
+
+ delta.x = a.x - b.x;
+ delta.y = a.y - b.y;
+
+ return delta;
+}
+
+static inline struct device_float_coords
+device_float_average(struct device_float_coords a, struct device_float_coords b)
+{
+ struct device_float_coords average;
+
+ average.x = (a.x + b.x) / 2.0;
+ average.y = (a.y + b.y) / 2.0;
+
+ return average;
+}
+
static inline double
normalized_length(struct normalized_coords norm)
{
diff --git a/src/libinput.c b/src/libinput.c
index 5fccf941..7c438659 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -114,6 +114,17 @@ struct libinput_event_touch {
struct device_coords point;
};
+struct libinput_event_gesture {
+ struct libinput_event base;
+ uint32_t time;
+ int finger_count;
+ int cancelled;
+ struct normalized_coords delta;
+ struct normalized_coords delta_unaccel;
+ double scale;
+ double angle;
+};
+
struct libinput_event_tablet {
struct libinput_event base;
uint32_t button;
@@ -250,6 +261,22 @@ libinput_event_get_touch_event(struct libinput_event *event)
return (struct libinput_event_touch *) event;
}
+LIBINPUT_EXPORT struct libinput_event_gesture *
+libinput_event_get_gesture_event(struct libinput_event *event)
+{
+ require_event_type(libinput_event_get_context(event),
+ event->type,
+ NULL,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END);
+
+ return (struct libinput_event_gesture *) event;
+}
+
LIBINPUT_EXPORT struct libinput_event_tablet *
libinput_event_get_tablet_event(struct libinput_event *event)
{
@@ -259,6 +286,7 @@ libinput_event_get_tablet_event(struct libinput_event *event)
LIBINPUT_EVENT_TABLET_AXIS,
LIBINPUT_EVENT_TABLET_PROXIMITY,
LIBINPUT_EVENT_TABLET_BUTTON);
+
return (struct libinput_event_tablet *) event;
}
@@ -662,6 +690,142 @@ libinput_event_touch_get_y(struct libinput_event_touch *event)
return evdev_convert_to_mm(device->abs.absinfo_y, event->point.y);
}
+LIBINPUT_EXPORT uint32_t
+libinput_event_gesture_get_time(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->time;
+}
+
+LIBINPUT_EXPORT int
+libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->finger_count;
+}
+
+LIBINPUT_EXPORT int
+libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->cancelled;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dx(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0.0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->delta.x;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dy(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0.0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->delta.y;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dx_unaccelerated(
+ struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0.0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->delta_unaccel.x;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dy_unaccelerated(
+ struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0.0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+ return event->delta_unaccel.y;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_scale(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0.0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END);
+
+ return event->scale;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event)
+{
+ require_event_type(libinput_event_get_context(&event->base),
+ event->base.type,
+ 0.0,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END);
+
+ return event->angle;
+}
+
LIBINPUT_EXPORT int
libinput_event_tablet_axis_has_changed(struct libinput_event_tablet *event,
enum libinput_tablet_axis axis)
@@ -1347,6 +1511,9 @@ device_has_cap(struct libinput_device *device,
case LIBINPUT_DEVICE_CAP_TOUCH:
capability = "CAP_TOUCH";
break;
+ case LIBINPUT_DEVICE_CAP_GESTURE:
+ capability = "CAP_GESTURE";
+ break;
case LIBINPUT_DEVICE_CAP_TABLET:
capability = "CAP_TABLET";
break;
@@ -1710,6 +1877,89 @@ tablet_notify_button(struct libinput_device *device,
}
static void
+gesture_notify(struct libinput_device *device,
+ uint64_t time,
+ enum libinput_event_type type,
+ int finger_count,
+ int cancelled,
+ const struct normalized_coords *delta,
+ const struct normalized_coords *unaccel,
+ double scale,
+ double angle)
+{
+ struct libinput_event_gesture *gesture_event;
+
+ if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_GESTURE))
+ return;
+
+ gesture_event = zalloc(sizeof *gesture_event);
+ if (!gesture_event)
+ return;
+
+ *gesture_event = (struct libinput_event_gesture) {
+ .time = time,
+ .finger_count = finger_count,
+ .cancelled = cancelled,
+ .delta = *delta,
+ .delta_unaccel = *unaccel,
+ .scale = scale,
+ .angle = angle,
+ };
+
+ post_device_event(device, time, type,
+ &gesture_event->base);
+}
+
+void
+gesture_notify_swipe(struct libinput_device *device,
+ uint64_t time,
+ enum libinput_event_type type,
+ int finger_count,
+ const struct normalized_coords *delta,
+ const struct normalized_coords *unaccel)
+{
+ gesture_notify(device, time, type, finger_count, 0, delta, unaccel,
+ 0.0, 0.0);
+}
+
+void
+gesture_notify_swipe_end(struct libinput_device *device,
+ uint64_t time,
+ int finger_count,
+ int cancelled)
+{
+ const struct normalized_coords zero = { 0.0, 0.0 };
+
+ gesture_notify(device, time, LIBINPUT_EVENT_GESTURE_SWIPE_END,
+ finger_count, cancelled, &zero, &zero, 0.0, 0.0);
+}
+
+void
+gesture_notify_pinch(struct libinput_device *device,
+ uint64_t time,
+ enum libinput_event_type type,
+ const struct normalized_coords *delta,
+ const struct normalized_coords *unaccel,
+ double scale,
+ double angle)
+{
+ gesture_notify(device, time, type, 2, 0, delta, unaccel,
+ scale, angle);
+}
+
+void
+gesture_notify_pinch_end(struct libinput_device *device,
+ uint64_t time,
+ double scale,
+ int cancelled)
+{
+ const struct normalized_coords zero = { 0.0, 0.0 };
+
+ gesture_notify(device, time, LIBINPUT_EVENT_GESTURE_PINCH_END,
+ 2, cancelled, &zero, &zero, scale, 0.0);
+}
+
+static void
libinput_post_event(struct libinput *libinput,
struct libinput_event *event)
{
@@ -1975,6 +2225,12 @@ libinput_event_touch_get_base_event(struct libinput_event_touch *event)
}
LIBINPUT_EXPORT struct libinput_event *
+libinput_event_gesture_get_base_event(struct libinput_event_gesture *event)
+{
+ return &event->base;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
libinput_event_tablet_get_base_event(struct libinput_event_tablet *event)
{
require_event_type(libinput_event_get_context(&event->base),
diff --git a/src/libinput.h b/src/libinput.h
index 82e4fadd..bb24c959 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -58,7 +58,8 @@ enum libinput_device_capability {
LIBINPUT_DEVICE_CAP_KEYBOARD = 0,
LIBINPUT_DEVICE_CAP_POINTER = 1,
LIBINPUT_DEVICE_CAP_TOUCH = 2,
- LIBINPUT_DEVICE_CAP_TABLET = 3
+ LIBINPUT_DEVICE_CAP_TABLET = 3,
+ LIBINPUT_DEVICE_CAP_GESTURE = 5,
};
/**
@@ -273,7 +274,14 @@ enum libinput_event_type {
* initial proximity out event.
*/
LIBINPUT_EVENT_TABLET_PROXIMITY,
- LIBINPUT_EVENT_TABLET_BUTTON
+ LIBINPUT_EVENT_TABLET_BUTTON,
+
+ LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800,
+ LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+ LIBINPUT_EVENT_GESTURE_SWIPE_END,
+ LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+ LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+ LIBINPUT_EVENT_GESTURE_PINCH_END,
};
/**
@@ -468,6 +476,17 @@ struct libinput_event_touch *
libinput_event_get_touch_event(struct libinput_event *event);
/**
+ * Return the gesture event that is this input event. If the event type does
+ * not match the gesture event types, this function returns NULL.
+ *
+ * The inverse of this function is libinput_event_gesture_get_base_event().
+ *
+ * @return A gesture event, or NULL for other events
+ */
+struct libinput_event_gesture *
+libinput_event_get_gesture_event(struct libinput_event *event);
+
+/**
* @ingroup event
*
* Return the tablet event that is this input event. If the event type does not
@@ -1045,6 +1064,194 @@ struct libinput_event *
libinput_event_touch_get_base_event(struct libinput_event_touch *event);
/**
+ * @defgroup event_gesture Gesture events
+ *
+ * Gesture events are generated when a gesture is recognized on a touchpad.
+ *
+ * Gesture sequences always start with a LIBINPUT_EVENT_GESTURE_FOO_START
+ * event. All following gesture events will be of the
+ * LIBINPUT_EVENT_GESTURE_FOO_UPDATE type until a
+ * LIBINPUT_EVENT_GESTURE_FOO_END is generated which signals the end of the
+ * gesture.
+ *
+ * See @ref gestures for more information on gesture handling.
+ */
+
+/**
+ * @ingroup event_gesture
+ *
+ * @return The event time for this event
+ */
+uint32_t
+libinput_event_gesture_get_time(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * @return The generic libinput_event of this event
+ */
+struct libinput_event *
+libinput_event_gesture_get_base_event(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the number of fingers used for a gesture. This can be used e.g.
+ * to differentiate between 3 or 4 finger swipes.
+ *
+ * This function can be called on all gesture events and the returned finger
+ * count value will not change during a sequence.
+ *
+ * @return the number of fingers used for a gesture
+ */
+int
+libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return if the gesture ended normally, or if it was cancelled.
+ * For gesture events that are not of type
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_END or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_END, this function returns 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_END or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_END.
+ *
+ * @return 0 or 1, with 1 indicating that the gesture was cancelled.
+ */
+int
+libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the delta between the last event and the current event. For gesture
+ * events that are not of type @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
+ *
+ * If a device employs pointer acceleration, the delta returned by this
+ * function is the accelerated delta.
+ *
+ * Relative motion deltas are normalized to represent those of a device with
+ * 1000dpi resolution. See @ref motion_normalization for more details.
+ *
+ * @return the relative x movement since the last event
+ */
+double
+libinput_event_gesture_get_dx(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the delta between the last event and the current event. For gesture
+ * events that are not of type @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
+ *
+ * If a device employs pointer acceleration, the delta returned by this
+ * function is the accelerated delta.
+ *
+ * Relative motion deltas are normalized to represent those of a device with
+ * 1000dpi resolution. See @ref motion_normalization for more details.
+ *
+ * @return the relative y movement since the last event
+ */
+double
+libinput_event_gesture_get_dy(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the relative delta of the unaccelerated motion vector of the
+ * current event. For gesture events that are not of type
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
+ *
+ * Relative unaccelerated motion deltas are normalized to represent those of a
+ * device with 1000dpi resolution. See @ref motion_normalization for more
+ * details. Note that unaccelerated events are not equivalent to 'raw' events
+ * as read from the device.
+ *
+ * @return the unaccelerated relative x movement since the last event
+ */
+double
+libinput_event_gesture_get_dx_unaccelerated(
+ struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the relative delta of the unaccelerated motion vector of the
+ * current event. For gesture events that are not of type
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
+ *
+ * Relative unaccelerated motion deltas are normalized to represent those of a
+ * device with 1000dpi resolution. See @ref motion_normalization for more
+ * details. Note that unaccelerated events are not equivalent to 'raw' events
+ * as read from the device.
+ *
+ * @return the unaccelerated relative y movement since the last event
+ */
+double
+libinput_event_gesture_get_dy_unaccelerated(
+ struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the absolute scale of a pinch gesture, the scale is the division
+ * of the current distance between the fingers and the distance at the start
+ * of the gesture. The scale begins at 1.0, and if e.g. the fingers moved
+ * together by 50% then the scale will become 0.5, if they move twice as far
+ * apart as initially the scale becomes 2.0, etc.
+ *
+ * For gesture events that are of type @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_BEGIN, this function returns 1.0.
+ *
+ * For gesture events that are of type @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_END, this function returns the scale value
+ * of the most recent @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE event (if
+ * any) or 1.0 otherwise.
+ *
+ * For all other events this function returns 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_BEGIN, @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_END or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE.
+ *
+ * @return the absolute scale of a pinch gesture
+ */
+double
+libinput_event_gesture_get_scale(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the angle delta in degrees between the last and the current @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_UPDATE event. For gesture events that
+ * are not of type @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this
+ * function returns 0.
+ *
+ * The angle delta is defined as the change in angle of the line formed by
+ * the 2 fingers of a pinch gesture. Clockwise rotation is represented
+ * by a postive delta, counter-clockwise by a negative delta. If e.g. the
+ * fingers are on the 12 and 6 location of a clock face plate and they move
+ * to the 1 resp. 7 location in a single event then the angle delta is
+ * 30 degrees.
+ *
+ * If more than two fingers are present, the angle represents the rotation
+ * around the center of gravity. The calculation of the center of gravity is
+ * implementation-dependent.
+ *
+ * @return the angle delta since the last event
+ */
+double
+libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event);
+
+/**
* @defgroup event_tablet Tablet events
*
* Events that come from tablet devices.
diff --git a/src/libinput.sym b/src/libinput.sym
index eeb16ee6..7cbc4a5d 100644
--- a/src/libinput.sym
+++ b/src/libinput.sym
@@ -149,6 +149,20 @@ global:
libinput_device_config_tap_get_default_drag_lock_enabled;
} LIBINPUT_0.15.0;
+LIBINPUT_0.20.0 {
+ libinput_event_gesture_get_angle_delta;
+ libinput_event_gesture_get_base_event;
+ libinput_event_gesture_get_cancelled;
+ libinput_event_gesture_get_dx;
+ libinput_event_gesture_get_dx_unaccelerated;
+ libinput_event_gesture_get_dy;
+ libinput_event_gesture_get_dy_unaccelerated;
+ libinput_event_gesture_get_finger_count;
+ libinput_event_gesture_get_scale;
+ libinput_event_gesture_get_time;
+ libinput_event_get_gesture_event;
+} LIBINPUT_0.19.0;
+
/* tablet APIs, they are not part of any stable API promise yet.
* keep them separate */
LIBINPUT_TABLET_SUPPORT {
@@ -175,4 +189,4 @@ LIBINPUT_TABLET_SUPPORT {
libinput_tool_ref;
libinput_tool_set_user_data;
libinput_tool_unref;
-} LIBINPUT_0.19.0;
+} LIBINPUT_0.20.0;
diff --git a/test/Makefile.am b/test/Makefile.am
index c6a52afe..e0b46496 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -21,6 +21,7 @@ liblitest_la_SOURCES = \
litest-keyboard.c \
litest-keyboard-razer-blackwidow.c \
litest-logitech-trackball.c \
+ litest-nexus4-touch-screen.c \
litest-mouse.c \
litest-mouse-roccat.c \
litest-mouse-low-dpi.c \
@@ -33,6 +34,7 @@ liblitest_la_SOURCES = \
litest-synaptics-t440.c \
litest-synaptics-x1-carbon-3rd.c \
litest-trackpoint.c \
+ litest-touch-screen.c \
litest-wacom-bamboo-tablet.c \
litest-wacom-cintiq-tablet.c \
litest-wacom-intuos-tablet.c \
diff --git a/test/litest-nexus4-touch-screen.c b/test/litest-nexus4-touch-screen.c
new file mode 100644
index 00000000..d1930053
--- /dev/null
+++ b/test/litest-nexus4-touch-screen.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2015 Canonical, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_nexus4_setup(void)
+{
+ struct litest_device *d =
+ litest_create_device(LITEST_NEXUS4_TOUCH_SCREEN);
+ litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+ { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+ { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+ { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+ { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+ .touch_down_events = down,
+ .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+ { ABS_X, 0, 1500, 0, 0, 0 },
+ { ABS_Y, 0, 2500, 0, 0, 0 },
+ { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+ { ABS_MT_POSITION_X, 0, 1500, 0, 0, 0 },
+ { ABS_MT_POSITION_Y, 0, 2500, 0, 0, 0 },
+ { ABS_MT_TOUCH_MAJOR, 0, 15, 1, 0, 0 },
+ { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+ { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+ { .value = -1 },
+};
+
+static struct input_id input_id = {
+ .bustype = 0x1,
+ .vendor = 0x43e,
+ .product = 0x26,
+};
+
+static int events[] = {
+ EV_KEY, BTN_TOUCH,
+ INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+ -1, -1
+};
+
+struct litest_test_device litest_nexus4_device = {
+ .type = LITEST_NEXUS4_TOUCH_SCREEN,
+ .features = LITEST_TOUCH|LITEST_ELLIPSE,
+ .shortname = "nexus4",
+ .setup = litest_nexus4_setup,
+ .interface = &interface,
+
+ .name = "Nexus 4 touch screen",
+ .id = &input_id,
+ .events = events,
+ .absinfo = absinfo,
+};
diff --git a/test/litest-selftest.c b/test/litest-selftest.c
index f7974770..5016514d 100644
--- a/test/litest-selftest.c
+++ b/test/litest-selftest.c
@@ -289,6 +289,61 @@ START_TEST(litest_ptr_notnull_trigger_NULL)
}
END_TEST
+START_TEST(ck_double_eq_and_ne)
+{
+ ck_assert_double_eq(0.4,0.4);
+ ck_assert_double_eq(0.4,0.4 + 1E-6);
+ ck_assert_double_ne(0.4,0.4 + 1E-3);
+}
+END_TEST
+
+START_TEST(ck_double_lt_gt)
+{
+ ck_assert_double_lt(12.0,13.0);
+ ck_assert_double_gt(15.4,13.0);
+ ck_assert_double_le(12.0,12.0);
+ ck_assert_double_le(12.0,20.0);
+ ck_assert_double_ge(12.0,12.0);
+ ck_assert_double_ge(20.0,12.0);
+}
+END_TEST
+
+START_TEST(ck_double_eq_fails)
+{
+ ck_assert_double_eq(0.41,0.4);
+}
+END_TEST
+
+START_TEST(ck_double_ne_fails)
+{
+ ck_assert_double_ne(0.4 + 1E-7,0.4);
+}
+END_TEST
+
+START_TEST(ck_double_lt_fails)
+{
+ ck_assert_double_lt(6,5);
+}
+END_TEST
+
+START_TEST(ck_double_gt_fails)
+{
+ ck_assert_double_gt(5,6);
+}
+END_TEST
+
+START_TEST(ck_double_le_fails)
+{
+ ck_assert_double_le(6,5);
+}
+END_TEST
+
+START_TEST(ck_double_ge_fails)
+{
+ ck_assert_double_ge(5,6);
+}
+END_TEST
+
static Suite *
litest_assert_macros_suite(void)
{
@@ -342,6 +397,17 @@ litest_assert_macros_suite(void)
tcase_add_test(tc, litest_ptr_notnull_notrigger);
suite_add_tcase(s, tc);
+ tc = tcase_create("double comparison ");
+ tcase_add_test(tc, ck_double_eq_and_ne);
+ tcase_add_test(tc, ck_double_lt_gt);
+ tcase_add_exit_test(tc, ck_double_eq_fails, 1);
+ tcase_add_exit_test(tc, ck_double_ne_fails, 1);
+ tcase_add_exit_test(tc, ck_double_lt_fails, 1);
+ tcase_add_exit_test(tc, ck_double_gt_fails, 1);
+ tcase_add_exit_test(tc, ck_double_le_fails, 1);
+ tcase_add_exit_test(tc, ck_double_ge_fails, 1);
+ suite_add_tcase(s, tc);
+
return s;
}
diff --git a/test/litest-touch-screen.c b/test/litest-touch-screen.c
new file mode 100644
index 00000000..e91458a2
--- /dev/null
+++ b/test/litest-touch-screen.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2015 Canonical, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_generic_mt_setup(void)
+{
+ struct litest_device *d =
+ litest_create_device(LITEST_GENERIC_MULTITOUCH_SCREEN);
+ litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+ { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+ { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+ { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = LITEST_AUTO_ASSIGN },
+ { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+ { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+ .touch_down_events = down,
+ .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+ { ABS_X, 0, 1500, 0, 0, 0 },
+ { ABS_Y, 0, 2500, 0, 0, 0 },
+ { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+ { ABS_MT_POSITION_X, 0, 1500, 0, 0, 0 },
+ { ABS_MT_POSITION_Y, 0, 2500, 0, 0, 0 },
+ { ABS_MT_ORIENTATION, -256, 255, 0, 0, 0 },
+ { ABS_MT_TOUCH_MAJOR, 0, 255, 1, 0, 0 },
+ { ABS_MT_TOUCH_MINOR, 0, 255, 1, 0, 0 },
+ { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+ { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+ { .value = -1 },
+};
+
+static struct input_id input_id = {
+ .bustype = 0x1,
+ .vendor = 0x0,
+ .product = 0x25,
+};
+
+static int events[] = {
+ EV_KEY, BTN_TOUCH,
+ INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+ -1, -1
+};
+
+struct litest_test_device litest_generic_multitouch_screen_device = {
+ .type = LITEST_GENERIC_MULTITOUCH_SCREEN,
+ .features = LITEST_TOUCH|LITEST_ELLIPSE,
+ .shortname = "generic-mt",
+ .setup = litest_generic_mt_setup,
+ .interface = &interface,
+
+ .name = "generic-mt",
+ .id = &input_id,
+ .events = events,
+ .absinfo = absinfo,
+};
diff --git a/test/litest.c b/test/litest.c
index 0835ddfd..9e236456 100644
--- a/test/litest.c
+++ b/test/litest.c
@@ -358,6 +358,8 @@ extern struct litest_test_device litest_logitech_trackball_device;
extern struct litest_test_device litest_atmel_hover_device;
extern struct litest_test_device litest_alps_dualpoint_device;
extern struct litest_test_device litest_mouse_low_dpi_device;
+extern struct litest_test_device litest_generic_multitouch_screen_device;
+extern struct litest_test_device litest_nexus4_device;
extern struct litest_test_device litest_waltop_tablet_device;
struct litest_test_device* devices[] = {
@@ -390,6 +392,8 @@ struct litest_test_device* devices[] = {
&litest_atmel_hover_device,
&litest_alps_dualpoint_device,
&litest_mouse_low_dpi_device,
+ &litest_generic_multitouch_screen_device,
+ &litest_nexus4_device,
&litest_waltop_tablet_device,
NULL,
};
@@ -1218,10 +1222,26 @@ litest_event(struct litest_device *d, unsigned int type,
litest_assert_int_eq(ret, 0);
}
+static int32_t
+axis_replacement_value(struct axis_replacement *axes,
+ int32_t evcode)
+{
+ struct axis_replacement *axis = axes;
+
+ while (axis->evcode != -1) {
+ if (axis->evcode == evcode)
+ return axis->value;
+ axis++;
+ }
+
+ return -1;
+}
+
int
litest_auto_assign_value(struct litest_device *d,
const struct input_event *ev,
int slot, double x, double y,
+ struct axis_replacement *axes,
bool touching)
{
static int tracking_id;
@@ -1248,6 +1268,10 @@ litest_auto_assign_value(struct litest_device *d,
case ABS_MT_DISTANCE:
value = touching ? 0 : 1;
break;
+ default:
+ if (axes)
+ value = axis_replacement_value(axes, ev->code);
+ break;
}
return value;
@@ -1265,8 +1289,12 @@ send_btntool(struct litest_device *d)
}
static void
-litest_slot_start(struct litest_device *d, unsigned int slot,
- double x, double y, bool touching)
+litest_slot_start(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y,
+ struct axis_replacement *axes,
+ bool touching)
{
struct input_event *ev;
@@ -1287,6 +1315,7 @@ litest_slot_start(struct litest_device *d, unsigned int slot,
slot,
x,
y,
+ axes,
touching);
litest_event(d, ev->type, ev->code, value);
@@ -1295,10 +1324,22 @@ litest_slot_start(struct litest_device *d, unsigned int slot,
}
void
-litest_touch_down(struct litest_device *d, unsigned int slot,
- double x, double y)
+litest_touch_down(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y)
{
- litest_slot_start(d, slot, x, y, 1);
+ litest_slot_start(d, slot, x, y, NULL, true);
+}
+
+void
+litest_touch_down_extended(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y,
+ struct axis_replacement *axes)
+{
+ litest_slot_start(d, slot, x, y, axes, true);
}
void
@@ -1331,6 +1372,7 @@ litest_touch_up(struct litest_device *d, unsigned int slot)
slot,
0,
0,
+ NULL,
false);
litest_event(d, ev->type, ev->code, value);
ev++;
@@ -1338,8 +1380,12 @@ litest_touch_up(struct litest_device *d, unsigned int slot)
}
static void
-litest_slot_move(struct litest_device *d, unsigned int slot,
- double x, double y, bool touching)
+litest_slot_move(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y,
+ struct axis_replacement *axes,
+ bool touching)
{
struct input_event *ev;
@@ -1355,6 +1401,7 @@ litest_slot_move(struct litest_device *d, unsigned int slot,
slot,
x,
y,
+ axes,
touching);
litest_event(d, ev->type, ev->code, value);
ev++;
@@ -1362,10 +1409,22 @@ litest_slot_move(struct litest_device *d, unsigned int slot,
}
void
-litest_touch_move(struct litest_device *d, unsigned int slot,
- double x, double y)
+litest_touch_move(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y)
+{
+ litest_slot_move(d, slot, x, y, NULL, true);
+}
+
+void
+litest_touch_move_extended(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y,
+ struct axis_replacement *axes)
{
- litest_slot_move(d, slot, x, y, true);
+ litest_slot_move(d, slot, x, y, axes, true);
}
void
@@ -1388,21 +1447,6 @@ litest_touch_move_to(struct litest_device *d,
litest_touch_move(d, slot, x_to, y_to);
}
-static int32_t
-axis_replacement_value(struct axis_replacement *axes,
- int32_t evcode)
-{
- struct axis_replacement *axis = axes;
-
- while (axis->evcode != -1) {
- if (axis->evcode == evcode)
- return axis->value;
- axis++;
- }
-
- return -1;
-}
-
static int
auto_assign_tablet_value(struct litest_device *d,
const struct input_event *ev,
@@ -1500,10 +1544,12 @@ litest_touch_move_two_touches(struct litest_device *d,
}
void
-litest_hover_start(struct litest_device *d, unsigned int slot,
- double x, double y)
+litest_hover_start(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y)
{
- litest_slot_start(d, slot, x, y, 0);
+ litest_slot_start(d, slot, x, y, NULL, 0);
}
void
@@ -1532,7 +1578,7 @@ litest_hover_end(struct litest_device *d, unsigned int slot)
ev = up;
while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
- int value = litest_auto_assign_value(d, ev, slot, 0, 0, 0);
+ int value = litest_auto_assign_value(d, ev, slot, 0, 0, NULL, false);
litest_event(d, ev->type, ev->code, value);
ev++;
}
@@ -1542,7 +1588,7 @@ void
litest_hover_move(struct litest_device *d, unsigned int slot,
double x, double y)
{
- litest_slot_move(d, slot, x, y, false);
+ litest_slot_move(d, slot, x, y, NULL, false);
}
void
@@ -1754,6 +1800,24 @@ litest_event_type_str(struct libinput_event *event)
case LIBINPUT_EVENT_TOUCH_FRAME:
str = "TOUCH FRAME";
break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+ str = "GESTURE SWIPE START";
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+ str = "GESTURE SWIPE UPDATE";
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+ str = "GESTURE SWIPE END";
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+ str = "GESTURE PINCH START";
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+ str = "GESTURE PINCH UPDATE";
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_END:
+ str = "GESTURE PINCH END";
+ break;
case LIBINPUT_EVENT_TABLET_AXIS:
str = "TABLET AXIS";
break;
@@ -2314,11 +2378,11 @@ send_abs_xy(struct litest_device *d, double x, double y)
e.type = EV_ABS;
e.code = ABS_X;
e.value = LITEST_AUTO_ASSIGN;
- val = litest_auto_assign_value(d, &e, 0, x, y, true);
+ val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
litest_event(d, EV_ABS, ABS_X, val);
e.code = ABS_Y;
- val = litest_auto_assign_value(d, &e, 0, x, y, true);
+ val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
litest_event(d, EV_ABS, ABS_Y, val);
}
@@ -2331,12 +2395,12 @@ send_abs_mt_xy(struct litest_device *d, double x, double y)
e.type = EV_ABS;
e.code = ABS_MT_POSITION_X;
e.value = LITEST_AUTO_ASSIGN;
- val = litest_auto_assign_value(d, &e, 0, x, y, true);
+ val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
litest_event(d, EV_ABS, ABS_MT_POSITION_X, val);
e.code = ABS_MT_POSITION_Y;
e.value = LITEST_AUTO_ASSIGN;
- val = litest_auto_assign_value(d, &e, 0, x, y, true);
+ val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
litest_event(d, EV_ABS, ABS_MT_POSITION_Y, val);
}
diff --git a/test/litest.h b/test/litest.h
index d7c2fb29..dfc193a6 100644
--- a/test/litest.h
+++ b/test/litest.h
@@ -138,11 +138,13 @@ enum litest_device_type {
LITEST_ATMEL_HOVER = -24,
LITEST_ALPS_DUALPOINT = -25,
LITEST_MOUSE_LOW_DPI = -26,
- LITEST_WACOM_BAMBOO = -27,
- LITEST_WACOM_CINTIQ = -28,
- LITEST_WACOM_INTUOS = -29,
- LITEST_WACOM_ISDV4 = -30,
- LITEST_WALTOP = -31,
+ LITEST_GENERIC_MULTITOUCH_SCREEN = -27,
+ LITEST_NEXUS4_TOUCH_SCREEN = -28,
+ LITEST_WACOM_BAMBOO = -29,
+ LITEST_WACOM_CINTIQ = -30,
+ LITEST_WACOM_INTUOS = -31,
+ LITEST_WACOM_ISDV4 = -32,
+ LITEST_WALTOP = -33,
};
enum litest_device_feature {
@@ -164,9 +166,10 @@ enum litest_device_feature {
LITEST_ABSOLUTE = 1 << 13,
LITEST_PROTOCOL_A = 1 << 14,
LITEST_HOVER = 1 << 15,
- LITEST_TABLET = 1 << 16,
- LITEST_DISTANCE = 1 << 17,
- LITEST_TOOL_SERIAL = 1 << 18,
+ LITEST_ELLIPSE = 1 << 16,
+ LITEST_TABLET = 1 << 17,
+ LITEST_DISTANCE = 1 << 18,
+ LITEST_TOOL_SERIAL = 1 << 19,
};
struct litest_device {
@@ -298,16 +301,27 @@ void litest_event(struct litest_device *t,
int litest_auto_assign_value(struct litest_device *d,
const struct input_event *ev,
int slot, double x, double y,
+ struct axis_replacement *axes,
bool touching);
void litest_touch_up(struct litest_device *d, unsigned int slot);
void litest_touch_move(struct litest_device *d,
unsigned int slot,
double x,
double y);
+void litest_touch_move_extended(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y,
+ struct axis_replacement *axes);
void litest_touch_down(struct litest_device *d,
unsigned int slot,
double x,
double y);
+void litest_touch_down_extended(struct litest_device *d,
+ unsigned int slot,
+ double x,
+ double y,
+ struct axis_replacement *axes);
void litest_touch_move_to(struct litest_device *d,
unsigned int slot,
double x_from, double y_from,
@@ -481,4 +495,54 @@ litest_disable_tap(struct libinput_device *device)
litest_assert_int_eq(status, expected);
}
+#define CK_DOUBLE_EQ_EPSILON 1E-3
+#define ck_assert_double_eq(X,Y) \
+ do { \
+ double _ck_x = X; \
+ double _ck_y = Y; \
+ ck_assert_msg(fabs(_ck_x - _ck_y) < CK_DOUBLE_EQ_EPSILON, \
+ "Assertion '" #X " == " #Y \
+ "' failed: "#X"==%f, "#Y"==%f", \
+ _ck_x, \
+ _ck_y); \
+ } while (0)
+
+#define ck_assert_double_ne(X,Y) \
+ do { \
+ double _ck_x = X; \
+ double _ck_y = Y; \
+ ck_assert_msg(fabs(_ck_x - _ck_y) > CK_DOUBLE_EQ_EPSILON, \
+ "Assertion '" #X " != " #Y \
+ "' failed: "#X"==%f, "#Y"==%f", \
+ _ck_x, \
+ _ck_y); \
+ } while (0)
+
+#define _ck_assert_double_eq(X, OP, Y) \
+ do { \
+ double _ck_x = X; \
+ double _ck_y = Y; \
+ ck_assert_msg(_ck_x OP _ck_y || \
+ fabs(_ck_x - _ck_y) < CK_DOUBLE_EQ_EPSILON, \
+ "Assertion '" #X#OP#Y \
+ "' failed: "#X"==%f, "#Y"==%f", \
+ _ck_x, \
+ _ck_y); \
+ } while (0)
+
+#define _ck_assert_double_ne(X, OP,Y) \
+ do { \
+ double _ck_x = X; \
+ double _ck_y = Y; \
+ ck_assert_msg(_ck_x OP _ck_y && \
+ fabs(_ck_x - _ck_y) > CK_DOUBLE_EQ_EPSILON, \
+ "Assertion '" #X#OP#Y \
+ "' failed: "#X"==%f, "#Y"==%f", \
+ _ck_x, \
+ _ck_y); \
+ } while (0)
+#define ck_assert_double_lt(X, Y) _ck_assert_double_ne(X, <, Y)
+#define ck_assert_double_le(X, Y) _ck_assert_double_eq(X, <=, Y)
+#define ck_assert_double_gt(X, Y) _ck_assert_double_ne(X, >, Y)
+#define ck_assert_double_ge(X, Y) _ck_assert_double_eq(X, >=, Y)
#endif /* LITEST_H */
diff --git a/test/touchpad.c b/test/touchpad.c
index 443c8c1f..1f11cfa6 100644
--- a/test/touchpad.c
+++ b/test/touchpad.c
@@ -1271,7 +1271,7 @@ START_TEST(touchpad_2fg_scroll_slow_distance)
y_move = 7.0 * y->resolution /
(y->maximum - y->minimum) * 100;
} else {
- y_move = 10.0;
+ y_move = 20.0;
}
litest_drain_events(li);
@@ -1320,7 +1320,7 @@ START_TEST(touchpad_2fg_scroll_source)
litest_drain_events(li);
- test_2fg_scroll(dev, 0, 20, 0);
+ test_2fg_scroll(dev, 0, 30, 0);
litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
while ((event = libinput_get_event(li))) {
diff --git a/tools/event-debug.c b/tools/event-debug.c
index 834a3247..d84c35fd 100644
--- a/tools/event-debug.c
+++ b/tools/event-debug.c
@@ -90,6 +90,24 @@ print_event_header(struct libinput_event *ev)
case LIBINPUT_EVENT_TOUCH_FRAME:
type = "TOUCH_FRAME";
break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+ type = "GESTURE_SWIPE_BEGIN";
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+ type = "GESTURE_SWIPE_UPDATE";
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+ type = "GESTURE_SWIPE_END";
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+ type = "GESTURE_PINCH_BEGIN";
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+ type = "GESTURE_PINCH_UPDATE";
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_END:
+ type = "GESTURE_PINCH_END";
+ break;
case LIBINPUT_EVENT_TABLET_AXIS:
type = "TABLET_AXIS";
break;
@@ -145,6 +163,9 @@ print_device_notify(struct libinput_event *ev)
LIBINPUT_DEVICE_CAP_TOUCH))
printf("t");
if (libinput_device_has_capability(dev,
+ LIBINPUT_DEVICE_CAP_GESTURE))
+ printf("g");
+ if (libinput_device_has_capability(dev,
LIBINPUT_DEVICE_CAP_TABLET))
printf("T");
@@ -517,6 +538,47 @@ print_touch_event_with_coords(struct libinput_event *ev)
xmm, ymm);
}
+static void
+print_gesture_event_without_coords(struct libinput_event *ev)
+{
+ struct libinput_event_gesture *t = libinput_event_get_gesture_event(ev);
+ int finger_count = libinput_event_gesture_get_finger_count(t);
+ int cancelled = 0;
+ enum libinput_event_type type;
+
+ type = libinput_event_get_type(ev);
+
+ if (type == LIBINPUT_EVENT_GESTURE_SWIPE_END ||
+ type == LIBINPUT_EVENT_GESTURE_PINCH_END)
+ cancelled = libinput_event_gesture_get_cancelled(t);
+
+ print_event_time(libinput_event_gesture_get_time(t));
+ printf("%d%s\n", finger_count, cancelled ? " cancelled" : "");
+}
+
+static void
+print_gesture_event_with_coords(struct libinput_event *ev)
+{
+ struct libinput_event_gesture *t = libinput_event_get_gesture_event(ev);
+ double dx = libinput_event_gesture_get_dx(t);
+ double dy = libinput_event_gesture_get_dy(t);
+ double dx_unaccel = libinput_event_gesture_get_dx_unaccelerated(t);
+ double dy_unaccel = libinput_event_gesture_get_dy_unaccelerated(t);
+ double scale = libinput_event_gesture_get_scale(t);
+ double angle = libinput_event_gesture_get_angle_delta(t);
+
+ print_event_time(libinput_event_gesture_get_time(t));
+
+ printf("%d %5.2f/%5.2f (%5.2f/%5.2f unaccelerated)",
+ libinput_event_gesture_get_finger_count(t),
+ dx, dy, dx_unaccel, dy_unaccel);
+
+ if (libinput_event_get_type(ev) == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE)
+ printf(" %5.2f @ %5.2f\n", scale, angle);
+ else
+ printf("\n");
+}
+
static int
handle_and_print_events(struct libinput *li)
{
@@ -566,6 +628,24 @@ handle_and_print_events(struct libinput *li)
case LIBINPUT_EVENT_TOUCH_FRAME:
print_touch_event_without_coords(ev);
break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+ print_gesture_event_without_coords(ev);
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+ print_gesture_event_with_coords(ev);
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+ print_gesture_event_without_coords(ev);
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+ print_gesture_event_without_coords(ev);
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+ print_gesture_event_with_coords(ev);
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_END:
+ print_gesture_event_without_coords(ev);
+ break;
case LIBINPUT_EVENT_TABLET_AXIS:
print_tablet_axis_event(ev);
break;
diff --git a/tools/event-gui.c b/tools/event-gui.c
index 6c9cf03f..c682b256 100644
--- a/tools/event-gui.c
+++ b/tools/event-gui.c
@@ -72,6 +72,19 @@ struct window {
/* l/m/r mouse buttons */
int l, m, r;
+ /* touchpad swipe */
+ struct {
+ int nfingers;
+ double x, y;
+ } swipe;
+
+ struct {
+ int nfingers;
+ double scale;
+ double angle;
+ double x, y;
+ } pinch;
+
struct libinput_device *devices[50];
};
@@ -104,11 +117,48 @@ draw(GtkWidget *widget, cairo_t *cr, gpointer data)
{
struct window *w = data;
struct touch *t;
+ int i, offset;
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_rectangle(cr, 0, 0, w->width, w->height);
cairo_fill(cr);
+ /* swipe */
+ cairo_save(cr);
+ cairo_translate(cr, w->swipe.x, w->swipe.y);
+ for (i = 0; i < w->swipe.nfingers; i++) {
+ cairo_set_source_rgb(cr, .8, .8, .4);
+ cairo_arc(cr, (i - 2) * 40, 0, 20, 0, 2 * M_PI);
+ cairo_fill(cr);
+ }
+
+ for (i = 0; i < 4; i++) { /* 4 fg max */
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_arc(cr, (i - 2) * 40, 0, 20, 0, 2 * M_PI);
+ cairo_stroke(cr);
+ }
+ cairo_restore(cr);
+
+ /* pinch */
+ cairo_save(cr);
+ offset = w->pinch.scale * 100;
+ cairo_translate(cr, w->pinch.x, w->pinch.y);
+ cairo_rotate(cr, w->pinch.angle * M_PI/180.0);
+ if (w->pinch.nfingers > 0) {
+ cairo_set_source_rgb(cr, .4, .4, .8);
+ cairo_arc(cr, offset, -offset, 20, 0, 2 * M_PI);
+ cairo_arc(cr, -offset, offset, 20, 0, 2 * M_PI);
+ cairo_fill(cr);
+ }
+
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_arc(cr, offset, -offset, 20, 0, 2 * M_PI);
+ cairo_stroke(cr);
+ cairo_arc(cr, -offset, offset, 20, 0, 2 * M_PI);
+ cairo_stroke(cr);
+
+ cairo_restore(cr);
+
/* draw pointer sprite */
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_save(cr);
@@ -186,6 +236,13 @@ map_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
w->hx = w->width/2;
w->hy = w->height/2;
+ w->swipe.x = w->width/2;
+ w->swipe.y = w->height/2;
+
+ w->pinch.scale = 1.0;
+ w->pinch.x = w->width/2;
+ w->pinch.y = w->height/2;
+
g_signal_connect(G_OBJECT(w->area), "draw", G_CALLBACK(draw), w);
window = gdk_event_get_window(event);
@@ -428,6 +485,72 @@ handle_event_button(struct libinput_event *ev, struct window *w)
}
+static void
+handle_event_swipe(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_gesture *g = libinput_event_get_gesture_event(ev);
+ int nfingers;
+ double dx, dy;
+
+ nfingers = libinput_event_gesture_get_finger_count(g);
+
+ switch (libinput_event_get_type(ev)) {
+ case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+ w->swipe.nfingers = nfingers;
+ w->swipe.x = w->width/2;
+ w->swipe.y = w->height/2;
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+ dx = libinput_event_gesture_get_dx(g);
+ dy = libinput_event_gesture_get_dy(g);
+ w->swipe.x += dx;
+ w->swipe.y += dy;
+ break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+ w->swipe.nfingers = 0;
+ w->swipe.x = w->width/2;
+ w->swipe.y = w->height/2;
+ break;
+ default:
+ abort();
+ }
+}
+
+static void
+handle_event_pinch(struct libinput_event *ev, struct window *w)
+{
+ struct libinput_event_gesture *g = libinput_event_get_gesture_event(ev);
+ int nfingers;
+ double dx, dy;
+
+ nfingers = libinput_event_gesture_get_finger_count(g);
+
+ switch (libinput_event_get_type(ev)) {
+ case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+ w->pinch.nfingers = nfingers;
+ w->pinch.x = w->width/2;
+ w->pinch.y = w->height/2;
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+ dx = libinput_event_gesture_get_dx(g);
+ dy = libinput_event_gesture_get_dy(g);
+ w->pinch.x += dx;
+ w->pinch.y += dy;
+ w->pinch.scale = libinput_event_gesture_get_scale(g);
+ w->pinch.angle += libinput_event_gesture_get_angle_delta(g);
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_END:
+ w->pinch.nfingers = 0;
+ w->pinch.x = w->width/2;
+ w->pinch.y = w->height/2;
+ w->pinch.angle = 0.0;
+ w->pinch.scale = 1.0;
+ break;
+ default:
+ abort();
+ }
+}
+
static gboolean
handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data)
{
@@ -473,6 +596,16 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data)
return FALSE;
}
break;
+ case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+ case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+ case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+ handle_event_swipe(ev, w);
+ break;
+ case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+ case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+ case LIBINPUT_EVENT_GESTURE_PINCH_END:
+ handle_event_pinch(ev, w);
+ break;
case LIBINPUT_EVENT_TABLET_AXIS:
case LIBINPUT_EVENT_TABLET_PROXIMITY:
case LIBINPUT_EVENT_TABLET_BUTTON: