diff options
-rw-r--r-- | CODING_STYLE | 26 | ||||
-rw-r--r-- | doc/Makefile.am | 4 | ||||
-rw-r--r-- | doc/faqs.dox | 6 | ||||
-rw-r--r-- | doc/gestures.dox | 91 | ||||
-rw-r--r-- | doc/normalization-of-relative-motion.dox | 3 | ||||
-rw-r--r-- | doc/svg/pinch-gestures.svg | 612 | ||||
-rw-r--r-- | doc/svg/swipe-gestures.svg | 512 | ||||
-rw-r--r-- | doc/svg/touchscreen-gestures.svg | 440 | ||||
-rw-r--r-- | src/evdev-mt-touchpad-buttons.c | 6 | ||||
-rw-r--r-- | src/evdev-mt-touchpad-gestures.c | 340 | ||||
-rw-r--r-- | src/evdev-mt-touchpad.c | 6 | ||||
-rw-r--r-- | src/evdev-mt-touchpad.h | 21 | ||||
-rw-r--r-- | src/evdev.c | 3 | ||||
-rw-r--r-- | src/evdev.h | 1 | ||||
-rw-r--r-- | src/libinput-private.h | 62 | ||||
-rw-r--r-- | src/libinput.c | 256 | ||||
-rw-r--r-- | src/libinput.h | 211 | ||||
-rw-r--r-- | src/libinput.sym | 16 | ||||
-rw-r--r-- | test/Makefile.am | 2 | ||||
-rw-r--r-- | test/litest-nexus4-touch-screen.c | 99 | ||||
-rw-r--r-- | test/litest-selftest.c | 66 | ||||
-rw-r--r-- | test/litest-touch-screen.c | 105 | ||||
-rw-r--r-- | test/litest.c | 132 | ||||
-rw-r--r-- | test/litest.h | 80 | ||||
-rw-r--r-- | test/touchpad.c | 4 | ||||
-rw-r--r-- | tools/event-debug.c | 80 | ||||
-rw-r--r-- | tools/event-gui.c | 133 |
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 (> 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, ¢er); + + 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: |