diff options
40 files changed, 10054 insertions, 82 deletions
diff --git a/configure.ac b/configure.ac index 711aa42a..5afb8248 100644 --- a/configure.ac +++ b/configure.ac @@ -189,6 +189,16 @@ if test "x$build_tests" = "xyes"; then AC_PATH_PROG(VALGRIND, [valgrind]) fi +AC_ARG_ENABLE(libwacom, + AS_HELP_STRING([--enable-libwacom], + [Use libwacom for tablet identification (default=enabled)]), + [use_libwacom="$enableval"], + [use_libwacom="yes"]) +if test "x$use_libwacom" = "xyes"; then + PKG_CHECK_MODULES(LIBWACOM, [libwacom >= 0.12], [HAVE_LIBWACOM="yes"]) + AC_DEFINE(HAVE_LIBWACOM, 1, [Build with libwacom]) +fi + AM_CONDITIONAL(HAVE_VALGRIND, [test "x$VALGRIND" != "x"]) AM_CONDITIONAL(BUILD_TESTS, [test "x$build_tests" = "xyes"]) AM_CONDITIONAL(BUILD_DOCS, [test "x$build_documentation" = "xyes"]) @@ -218,6 +228,7 @@ AC_MSG_RESULT([ Prefix ${prefix} udev base dir ${UDEV_DIR} + libwacom enabled ${use_libwacom} Build documentation ${build_documentation} Build tests ${build_tests} Tests use valgrind ${VALGRIND} diff --git a/doc/Makefile.am b/doc/Makefile.am index fe70f6a5..7a7c6cf1 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -23,6 +23,7 @@ header_files = \ $(srcdir)/scrolling.dox \ $(srcdir)/seats.dox \ $(srcdir)/t440-support.dox \ + $(srcdir)/tablet-support.dox \ $(srcdir)/tapping.dox \ $(srcdir)/test-suite.dox \ $(srcdir)/tools.dox \ diff --git a/doc/absolute-axes.dox b/doc/absolute-axes.dox index 664c6164..0e1a21bb 100644 --- a/doc/absolute-axes.dox +++ b/doc/absolute-axes.dox @@ -10,7 +10,7 @@ libinput supports three types of devices with absolute axes: - multi-touch screens - single-touch screens - - graphics tablets (currently WIP) + - @ref tablet-support "graphics tablets" Touchpads are technically absolute devices but libinput converts the axis values to directional motion and posts events as relative events. Touchpads do not count @@ -18,7 +18,10 @@ as absolute devices in libinput. For all absolute devices in libinput, the default unit for x/y coordinates is in mm off the top left corner on the device, or more specifically off the -device's sensor. +device's sensor. If the device is physically rotated from its natural +position and this rotation was communicated to libinput (e.g. +@ref libinput_device_config_left_handed_set "by setting the device left-handed"), +the coordinate origin is the top left corner of in the current rotation. @section absolute_axes_handling Handling of absolute coordinates @@ -40,7 +43,7 @@ coordinate. @section absolute_axes_nores Devices without x/y resolution An absolute device that does not provide a valid resolution is considered -buggy and must be fixed in the kernel. Some touchpad devices that do not +buggy and must be fixed in the kernel. Some touchpad devices do not provide resolution, those devices are correctly handled within libinput (touchpads are not absolute devices, as mentioned above). @@ -107,11 +110,14 @@ The most common matrices are: See Wikipedia's <a href="http://en.wikipedia.org/wiki/Transformation_matrix">Transformation -Matrix article</a> for more information on the matrix maths. - -See libinput_device_config_calibration_get_default_matrix() for how these +Matrix article</a> for more information on the matrix maths. See +libinput_device_config_calibration_get_default_matrix() for how these matrices must be supplied to libinput. +Once applied, any x and y axis value has the calibration applied before it +is made available to the caller. libinput does not provide access to the +raw coordinates before the calibration is applied. + @section absolute_axes_nonorm Why x/y coordinates are not normalized x/y are not given in @ref motion_normalization "normalized coordinates" diff --git a/doc/device-configuration-via-udev.dox b/doc/device-configuration-via-udev.dox index 6e9a3dc0..79870736 100644 --- a/doc/device-configuration-via-udev.dox +++ b/doc/device-configuration-via-udev.dox @@ -64,11 +64,11 @@ libinput_pointer_get_axis_source() for details. <dd>A constant (linear) acceleration factor to apply to pointingstick deltas to normalize them. <dt>LIBINPUT_MODEL_*</dt> -<dd><b>This prefix is reserved as private API, do not use.</b>. See @ref +<dd><b>This prefix is reserved as private API, do not use.</b> See @ref model_specific_configuration for details. </dd> <dt>LIBINPUT_ATTR_*</dt> -<dd><b>This prefix is reserved as private API, do not use.</b>. See @ref +<dd><b>This prefix is reserved as private API, do not use.</b> See @ref model_specific_configuration for details. </dd> </dl> diff --git a/doc/normalization-of-relative-motion.dox b/doc/normalization-of-relative-motion.dox index d0a42e03..d0ab5603 100644 --- a/doc/normalization-of-relative-motion.dox +++ b/doc/normalization-of-relative-motion.dox @@ -38,6 +38,10 @@ libinput scales unaccelerated touchpad motion to the resolution of the touchpad's x axis, i.e. the unaccelerated value for the y axis is: y = (x / resolution_x) * resolution_y +@section motion_normalization_tablet Normalization of tablet coordinates + +See @ref tablet-relative-motion + @section motion_normalization_customization Setting custom DPI settings Devices usually do not advertise their resolution and libinput relies on diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox index 7ec5e74d..2fbb4cc2 100644 --- a/doc/pointer-acceleration.dox +++ b/doc/pointer-acceleration.dox @@ -124,4 +124,10 @@ velocity of the pointer and each delta (dx, dy) results in an accelerated delta (dx * factor, dy * factor). This provides 1:1 movement between the device and the pointer on-screen. +@section ptraccel-tablet Pointer acceleration on tablets + +Pointer acceleration for relative motion on tablet devices is a flat +acceleration, with the speed seeting slowing down or speeding up the pointer +motion by a constant factor. Tablets do not allow for switchable profiles. + */ diff --git a/doc/svg/tablet-axes.svg b/doc/svg/tablet-axes.svg new file mode 100644 index 00000000..59b86482 --- /dev/null +++ b/doc/svg/tablet-axes.svg @@ -0,0 +1,563 @@ +<?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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="161.49548mm" + height="76.025978mm" + viewBox="0 0 572.22809 269.38339" + id="svg4321" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="tablet-axes.svg"> + <defs + id="defs4323"> + <marker + inkscape:stockid="Arrow1Lstart" + orient="auto" + refY="0" + refX="0" + id="Arrow1Lstart" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path4966" + 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="marker7788" + 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="path7790" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:isstock="true" + style="overflow:visible" + id="marker7664" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="StopL"> + <path + transform="scale(0.8,0.8)" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 0,5.65 0,-5.65" + id="path7666" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="TriangleOutM" + orient="auto" + refY="0" + refX="0" + id="TriangleOutM" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path5111" + d="m 5.77,0 -8.65,5 0,-10 8.65,5 z" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" + transform="scale(0.4,0.4)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="StopL" + orient="auto" + refY="0" + refX="0" + id="StopL" + style="overflow:visible" + inkscape:isstock="true" + inkscape:collect="always"> + <path + id="path5135" + d="M 0,5.65 0,-5.65" + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + transform="scale(0.8,0.8)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="DotM" + orient="auto" + refY="0" + refX="0" + id="DotM" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path5030" + d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 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,2.96,0.4)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="SquareS" + orient="auto" + refY="0" + refX="0" + id="SquareS" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path5042" + d="M -5,-5 -5,5 5,5 5,-5 -5,-5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + transform="scale(0.2,0.2)" + inkscape:connector-curvature="0" /> + </marker> + <marker + style="overflow:visible" + id="DistanceEnd" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="DistanceEnd" + inkscape:isstock="true"> + <g + id="g2301" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"> + <path + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.14999998;stroke-linecap:square;stroke-opacity:1" + d="M 0,0 -2,0" + id="path2316" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-opacity:1" + d="M 0,0 -13,4 -9,0 -13,-4 0,0 Z" + id="path2312" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-opacity:1" + d="M 0,-4 0,40" + id="path2314" + inkscape:connector-curvature="0" /> + </g> + </marker> + <marker + style="overflow:visible" + id="DistanceStart" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="DistanceStart" + inkscape:isstock="true"> + <g + id="g2300" + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1"> + <path + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.14999998;stroke-linecap:square;stroke-opacity:1" + d="M 0,0 2,0" + id="path2306" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-opacity:1" + d="M 0,0 13,4 9,0 13,-4 0,0 Z" + id="path2302" + inkscape:connector-curvature="0" /> + <path + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-opacity:1" + d="M 0,-4 0,40" + id="path2304" + inkscape:connector-curvature="0" /> + </g> + </marker> + <marker + inkscape:stockid="Arrow2Send" + orient="auto" + refY="0" + refX="0" + id="Arrow2Send" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path4999" + 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:stockid="Arrow2Lend" + orient="auto" + refY="0" + refX="0" + id="Arrow2Lend" + style="overflow:visible" + inkscape:isstock="true" + inkscape:collect="always"> + <path + id="path4987" + 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="Arrow1Lend" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path4969" + 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="marker6024" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mend"> + <path + transform="scale(-0.6,-0.6)" + 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="path6026" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:isstock="true" + style="overflow:visible" + id="marker5972" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mend"> + <path + transform="scale(-0.6,-0.6)" + 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="path5974" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:isstock="true" + style="overflow:visible" + id="marker5782" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mend"> + <path + transform="scale(-0.6,-0.6)" + 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="path5784" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:isstock="true" + style="overflow:visible" + id="marker5754" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mstart"> + <path + transform="scale(0.6,0.6)" + 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="path5756" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow2Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow2Mend" + style="overflow:visible" + inkscape:isstock="true" + inkscape:collect="always"> + <path + id="path4993" + 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="scale(-0.6,-0.6)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow2Mstart" + orient="auto" + refY="0" + refX="0" + id="Arrow2Mstart" + style="overflow:visible" + inkscape:isstock="true" + inkscape:collect="always"> + <path + id="path4990" + 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="scale(0.6,0.6)" + inkscape:connector-curvature="0" /> + </marker> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4294" + id="linearGradient4300" + x1="465.81339" + y1="666.13727" + x2="454.82117" + y2="658.65521" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + id="linearGradient4294"> + <stop + style="stop-color:#1a1a1a;stop-opacity:1;" + offset="0" + id="stop4296" /> + <stop + style="stop-color:#808080;stop-opacity:1" + offset="1" + id="stop4298" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4294" + id="linearGradient5721" + gradientUnits="userSpaceOnUse" + x1="465.81339" + y1="666.13727" + x2="454.82117" + y2="658.65521" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4294" + id="linearGradient7662" + gradientUnits="userSpaceOnUse" + x1="465.81339" + y1="666.13727" + x2="454.82117" + y2="658.65521" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="468.45173" + inkscape:cy="131.02294" + 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="metadata4326"> + <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(-64.198799,-423.87021)"> + <g + id="g5688"> + <g + transform="matrix(0.35596319,-0.1450925,0.14027908,0.34415419,-92.870773,417.67084)" + id="g4304" + style="display:inline"> + <path + style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z" + id="path4286" + inkscape:connector-curvature="0" + sodipodi:nodetypes="czcc" /> + <path + style="display:inline;fill:#000000" + d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z" + id="path4283" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssssccssscsssssssssssssssssss" /> + <path + style="fill:url(#linearGradient4300);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 0,0 0,0 11.27622,7.27812 z" + id="path4292" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scccs" /> + </g> + <rect + transform="scale(1,-1)" + y="-693.18524" + x="67.81031" + height="16.036251" + width="168.54825" + id="rect4136" + style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.13670856;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.54683419, 0.13670855;stroke-dashoffset:0;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4960" + d="m 136.32111,642.81599 0,34.04419" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.80699879px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart);marker-end:url(#Arrow2Mend)" /> + <text + sodipodi:linespacing="125%" + id="text5286" + y="665.58148" + x="62.726631" + style="font-style:normal;font-weight:normal;font-size:15px;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="665.58148" + x="62.726631" + id="tspan5288" + sodipodi:role="line">Distance</tspan></text> + <path + inkscape:connector-curvature="0" + id="path5290" + d="m 136.42857,641.82649 58.75,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.40000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <g + transform="matrix(0.35596319,-0.1450925,0.14027908,0.34415419,107.12923,453.67084)" + id="g5701" + style="display:inline"> + <path + style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z" + id="path5703" + inkscape:connector-curvature="0" + sodipodi:nodetypes="czcc" /> + <path + style="display:inline;fill:#000000" + d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z" + id="path5705" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssssccssscsssssssssssssssssss" /> + <path + style="fill:url(#linearGradient5721);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 0,0 0,0 11.27622,7.27812 z" + id="path5707" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scccs" /> + </g> + <rect + transform="scale(1,-1)" + y="-693.18524" + x="267.8103" + height="16.036251" + width="168.54825" + id="rect5709" + style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.13670856;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.54683419, 0.13670855;stroke-dashoffset:0;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path5711" + d="m 336.32111,642.81599 0,34.04419" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.80699879px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#StopL);marker-end:url(#Arrow2Lend)" /> + <text + sodipodi:linespacing="125%" + id="text5713" + y="665.58148" + x="262.72662" + style="font-style:normal;font-weight:normal;font-size:15px;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="665.58148" + x="262.72662" + id="tspan5715" + sodipodi:role="line">Pressure</tspan></text> + <g + style="display:inline" + id="g7646" + transform="matrix(0.35596319,-0.1450925,0.14027908,0.34415419,307.12923,445.67084)"> + <path + sodipodi:nodetypes="czcc" + inkscape:connector-curvature="0" + id="path7648" + d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z" + style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ssssccssscsssssssssssssssssss" + inkscape:connector-curvature="0" + id="path7650" + d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z" + style="display:inline;fill:#000000" /> + <path + sodipodi:nodetypes="scccs" + inkscape:connector-curvature="0" + id="path7652" + d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 0,0 0,0 11.27622,7.27812 z" + style="fill:url(#linearGradient7662);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <rect + style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.13670856;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.54683419, 0.13670855;stroke-dashoffset:0;stroke-opacity:1" + id="rect7654" + width="168.54825" + height="16.036251" + x="467.8103" + y="-693.18524" + transform="scale(1,-1)" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:15px;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="567.2774" + y="435.26669" + id="text7658" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan7660" + x="567.2774" + y="435.26669">Tilt</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#727272;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + d="m 559.28572,676.29078 0,-230.49149" + id="path7954" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#727272;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" + d="M 557.43833,676.12153 600.28315,446.14441" + id="path7956" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#marker5754);marker-end:url(#marker5972)" + d="m 600.17857,445.75506 -40.71428,0" + id="path7958" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/doc/svg/tablet.svg b/doc/svg/tablet.svg new file mode 100644 index 00000000..7a9dda83 --- /dev/null +++ b/doc/svg/tablet.svg @@ -0,0 +1,199 @@ +<?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:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="147.67236mm" + height="86.663628mm" + viewBox="0 0 523.24853 307.07585" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="tablet.svg"> + <defs + id="defs4"> + <linearGradient + inkscape:collect="always" + id="linearGradient4294"> + <stop + style="stop-color:#1a1a1a;stop-opacity:1;" + offset="0" + id="stop4296" /> + <stop + style="stop-color:#808080;stop-opacity:1" + offset="1" + id="stop4298" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4294" + id="linearGradient4300" + x1="465.81339" + y1="666.13727" + x2="454.82117" + y2="658.65521" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="605.97861" + inkscape:cy="-35.324315" + inkscape:document-units="px" + inkscape:current-layer="layer3" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + 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"> + <sodipodi:guide + position="127.77903,266.16996" + orientation="0,1" + id="guide4164" /> + <sodipodi:guide + position="125.25365,38.380555" + orientation="0,1" + id="guide4166" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <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="tablet" + inkscape:groupmode="layer" + id="layer1" + style="display:inline" + transform="translate(-75.261625,-133.63374)"> + <g + id="g4309" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <rect + y="134.15933" + x="75.787216" + height="306.02466" + width="522.19733" + id="rect4136" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:1.05118144;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:4.20472551, 1.05118138;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.74813837;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:2.99255325, 0.74813831;stroke-dashoffset:0;stroke-opacity:1" + id="rect4140" + width="357.34042" + height="226.52563" + x="199.33878" + y="175.42407" /> + <rect + y="175.72914" + x="103.10225" + height="22.142857" + width="65" + id="rect4142" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" + id="rect4148" + width="65" + height="22.142857" + x="103.10225" + y="203.72914" /> + <rect + y="231.72913" + x="103.10225" + height="22.142857" + width="65" + id="rect4150" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + y="323.72913" + x="103.10225" + height="22.142857" + width="65" + id="rect4154" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" + id="rect4156" + width="65" + height="22.142857" + x="103.10225" + y="351.72913" /> + <circle + r="22.98097" + cy="287.06125" + cx="135.61298" + id="path4158" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" /> + <ellipse + ry="12.608653" + rx="11.5985" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.52043104;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:2.08172421, 0.52043105;stroke-dashoffset:0;stroke-opacity:1" + id="circle4160" + cx="135.61298" + cy="287.06125" /> + <rect + y="379.72913" + x="103.10225" + height="22.142857" + width="65" + id="rect4162" + style="opacity:0.92000002;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.98900002;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:3.956, 0.989;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="stylus" + style="display:inline" + transform="translate(-75.261625,-133.63374)"> + <g + id="g4304" + transform="matrix(0.37129971,0.09948946,-0.09618892,0.35898192,295.60339,7.6883643)"> + <path + sodipodi:nodetypes="czcc" + inkscape:connector-curvature="0" + id="path4286" + d="m 387.83544,799.76093 c -1.1128,3.61694 -3.2211,13.05163 -1.08543,14.07769 2.13567,1.02606 7.81039,-3.72162 10.99756,-6.69095 z" + style="display:inline;fill:#cccccc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + sodipodi:nodetypes="ssssccssscsssssssssssssssssss" + inkscape:connector-curvature="0" + id="path4283" + d="m 392.64431,804.79039 c -8.52094,-5.90399 -8.49394,-11.01546 0.22879,-43.30647 1.03999,-3.85 2.46829,-9.67602 3.17399,-12.9467 0.99731,-4.62219 2.39455,-7.29497 6.27321,-12 2.74456,-3.32932 5.25157,-6.2783 5.57113,-6.5533 40.78433,-60.97488 80.48307,-125.1652 118.27253,-184 9.86283,-15.675 26.59424,-42.225 37.18089,-59 10.58666,-16.775 34.01422,-53.9 52.06125,-82.5 18.04703,-28.6 35.04505,-55.31677 37.77338,-59.37059 l 4.9606,-7.3706 4.1828,0.57332 c 4.16371,0.5707 4.19706,0.54958 7.30887,-4.62941 3.75631,-6.2516 8.82067,-11.57582 12.2516,-12.88026 5.99391,-2.27888 14.03303,2.9506 14.03303,9.12854 0,3.90203 -2.51704,10.62127 -6.02878,16.09385 -1.63417,2.54664 -2.97122,4.85949 -2.97122,5.13969 0,0.28019 0.9,1.54715 2,2.81546 2.28453,2.63408 2.47267,4.21918 0.86833,7.31574 -1.28218,2.47476 -26.61383,45.18798 -55.85724,94.18426 -10.83283,18.15 -25.72943,43.1137 -33.10357,55.47489 -7.37413,12.3612 -13.69273,23.17153 -14.04131,24.02297 -0.34859,0.85144 -7.50972,12.78774 -15.91363,26.52511 -15.54138,25.40455 -32.24417,52.9052 -70.74345,116.47703 -40.26028,66.47968 -43.66308,72.46026 -49.21634,86.5 -1.74036,4.4 -3.92035,8.675 -4.8444,9.5 -0.92405,0.825 -4.36246,3.75 -7.6409,6.5 -3.27845,2.75 -9.57132,8.3067 -13.98415,12.34823 -10.62726,9.73304 -16.99729,13.87361 -22.52334,14.64034 -3.99187,0.55386 -5.03885,0.251 -9.27207,-2.6821 z" + style="display:inline;fill:#000000" /> + <path + sodipodi:nodetypes="scccs" + inkscape:connector-curvature="0" + id="path4292" + d="m 450.89044,688.88586 c 8.71518,5.62513 45.74035,-59.18436 43.57923,-75.43494 l -7.07107,-6.56599 c -29.93081,25.86352 -47.78438,74.72281 -47.78438,74.72281 0,0 0,0 11.27622,7.27812 z" + style="fill:url(#linearGradient4300);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + </g> +</svg> diff --git a/doc/tablet-support.dox b/doc/tablet-support.dox new file mode 100644 index 00000000..cc5d4091 --- /dev/null +++ b/doc/tablet-support.dox @@ -0,0 +1,185 @@ +/** +@page tablet-support Tablet support + +This page provides details about the graphics tablet +support in libinput. Note that the term "tablet" in libinput refers to +graphics tablets only (e.g. Wacom Intuos), not to tablet devices like the +Apple iPad. + +@image html tablet.svg "Illustration of a graphics tablet" + +@section tablet-tools Tablet buttons vs. tablet tools + +Most tablets provide two types of devices. The pysical tablet often provides +a number of buttons and a touch ring or strip. Interaction on the drawing +surface of the tablet requires a tool, usually in the shape of a stylus. +The libinput interface exposed by devices with the @ref +LIBINPUT_DEVICE_CAP_TABLET_TOOL applies only to events generated by tools. + +Touch events on the tablet integrated into a screen itself are exposed +through the @ref LIBINPUT_DEVICE_CAP_TOUCH capability. Touch events on a +standalone tablet are exposed through the @ref LIBINPUT_DEVICE_CAP_POINTER +capability. In both cases, the kernel usually provides a separate event +node for the touch device, resulting in a separate libinput device. +See libinput_device_get_device_group() for information on how to associate +the touch part with other devices exposed by the same physical hardware. + +@section tablet-tip Tool tip events vs. button events + +The primary use of a tablet tool is to draw on the surface of the tablet. +When the tool tip comes into contact with the surface, libinput sends an +event of type @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, and again when the tip +ceases contact with the surface. + +Tablet tools may send button events; these are exclusively for extra buttons +unrelated to the tip. A button event is independent of the tip and can while +the tip is down or up. + +Some tablet tools' pressure detection is too sensitive, causing phantom +touches when the user only slightly brushes the surfaces. For example, some +tools are capable of detecting 1 gram of pressure. + +libinput uses a device-specific pressure threshold to determine when the tip +is considered logically down. As a result, libinput may send a nonzero +pressure value while the tip is logically up. Most application can and +should ignore pressure information until they receive the event of type @ref +LIBINPUT_EVENT_TABLET_TOOL_TIP. Applications that require extremely +fine-grained pressure sensitivity should use the pressure data instead of +the tip events to determine a logical tip down state and treat the tip +events like axis events otherwise. + +Note that the pressure threshold to trigger a logical tip event may be zero +on some devices. On tools without pressure sensitivity, determining when a +tip is down is device-specific. + +@section tablet-relative-motion Relative motion for tablet tools + +libinput calculates the relative motion vector for each event and converts +it to the same coordinate space that a normal mouse device would use. For +the caller, this means that the delta coordinates returned by +libinput_event_tablet_tool_get_dx() and +libinput_event_tablet_tool_get_dy() can be used identical to the delta +coordinates from any other pointer event. Any resolution differences between +the x and y axes are accommodated for, a delta of N/N represents a 45 degree +diagonal move on the tablet. + +The delta coordinates are available for all tablet events, it is up to the +caller to decide when a tool should be used in relative mode. It is +recommended that mouse and lens cursor tool default to relative mode and +all pen-like tools to absolute mode. + +If a tool in relative mode must not use pointer acceleration, callers +should use the absolute coordinates returned by +libinput_event_tablet_tool_get_x() and libinput_event_tablet_tool_get_y() +and calculate the delta themselves. Callers that require exact physical +distance should also use these functions to calculate delta movements. + +@section tablet-axes Special axes on tablet tools + +A tablet tool usually provides additional information beyond x/y positional +information and the tip state. A tool may provide the distance to the tablet +surface and the pressure exerted on the tip when in contact. Some tablets +additionally provide tilt information along the x and y axis. + +@image html tablet-axes.svg "Illustration of the distance, pressure and tilt axes" + +The granularity and precision of these axes varies between tablet devices +and cannot usually be mapped into a physical unit. +libinput normalizes distance and pressure into the [0, 1] range and the tilt +axes into the [-1, 1] range with 0 as the neutral point. + +While the normalization range is identical for these axes, a caller should +not interpret identical values as identical across axes, i.e. a value v1 on +the distance axis has no relation to the same value v1 on the pressure axis. + +@section tablet-fake-proximity Handling of proximity events + +libinput's @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY events notify a caller +when a tool comes into sensor range or leaves the sensor range. On some +tools this range does not represent the physical range but a reduced +tool-specific logical range. If the range is reduced, this is done +transparent to the caller. + +For example, the Wacom mouse and lens cursor tools are usually +used in relative mode, lying flat on the tablet. Movement typically follows +the interaction normal mouse movements have, i.e. slightly lift the tool and +place it in a separate location. The proximity detection on Wacom +tablets however extends further than the user may lift the mouse, i.e. the +tool may not be lifted out of physical proximity. For such tools, libinput +provides software-emulated proximity. + +@section tablet-pressure-offset Pressure offset on worn-out tools + +When a tool is used for an extended period it can wear down physically. A +worn-down tool may never return a zero pressure value. Even when hovering +above the surface, the pressure value returned by the tool is nonzero, +creating a fake surface touch and making interaction with the tablet less +predictable. + +libinput automatically detects pressure offsets and rescales the remaining +pressure range into the available range, making pressure-offsets transparent +to the caller. A tool with a pressure offset will thus send a 0 pressure +value for the detected offset and nonzero pressure values for values higher +than that offset. + +Some limitations apply to avoid misdetection of pressure offsets, +specifically: +- pressure offset is only detected on proximity in, and if a device is + capable of detection distances, +- pressure offset is only detected if the distance between the tool and the + tablet is high enough, +- pressure offset is only used if it is 20% or less of the pressure range + available to the tool. A pressure offset higher than 20% indicates either + a misdetection or a tool that should be replaced, and +- if a pressure value less than the current pressure offset is seen, the + offset resets to that value. + +Pressure offsets are not detected on @ref LIBINPUT_TABLET_TOOL_TYPE_MOUSE +and @ref LIBINPUT_TABLET_TOOL_TYPE_LENS tools. + +@section tablet-serial-numbers Tracking unique tools + +Some tools provide hardware information that enables libinput to uniquely +identify the physical device. For example, tools compatible with the Wacom +Intuos 4, Intuos 5, Intuos Pro and Cintiq series are uniquely identifiable +through a serial number. libinput does not specify how a tool can be +identified uniquely, a caller should use libinput_tablet_tool_is_unique() to +check if the tool is unique. + +libinput creates a struct libinput_tablet_tool on the first proximity in of +this tool. By default, this struct is destroyed on proximity out and +re-initialized on the next proximity in. If a caller keeps a reference to +the tool by using libinput_tablet_tool_ref() libinput re-uses this struct +whenever that same physical tool comes into proximity on any tablet +recognized by libinput. It is possible to attach tool-specific virtual state +to the tool. For example, a graphics program such as the GIMP may assign a +specific color to each tool, allowing the artist to use the tools like +physical pens of different color. In multi-tablet setups it is also +possible to track the tool across devices. + +If the tool does not have a unique identifier, libinput creates a single +struct libinput_tablet_tool per tool type on each tablet the tool is used +on. + +@section tablet-tool-types Vendor-specific tablet tool types + +libinput supports a number of high-level tool types that describe the +general interaction expected with the tool. For example, a user would expect +a tool of type @ref LIBINPUT_TABLET_TOOL_TYPE_PEN to interact with a +graphics application taking pressure and tilt into account. The default +virtual tool assigned should be a drawing tool, e.g. a virtual pen or brush. +A tool of type @ref LIBINPUT_TABLET_TOOL_TYPE_ERASER would normally be +mapped to an eraser-like virtual tool. See @ref libinput_tablet_tool_type +for the list of all available tools. + +Vendors may provide more fine-grained information about the tool in use by +adding a hardware-specific tool ID. libinput provides this ID to the caller +with libinput_tablet_tool_get_tool_id() but makes no promises about the +content or format of the ID. + +libinput currently supports Wacom-style tool IDs as provided on the Wacom +Intuos 3, 4, 5, Wacon Cintiq and Wacom Intuos Pro series. The tool ID can +be used to distinguish between e.g. a Wacom Classic Pen or a Wacom Pro Pen. +It is the caller's responsibility to interpret the tool ID. + +*/ diff --git a/src/Makefile.am b/src/Makefile.am index 90bdc9a8..343e75c7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,8 @@ libinput_la_SOURCES = \ evdev-mt-touchpad-buttons.c \ evdev-mt-touchpad-edge-scroll.c \ evdev-mt-touchpad-gestures.c \ + evdev-tablet.c \ + evdev-tablet.h \ filter.c \ filter.h \ filter-private.h \ @@ -32,12 +34,14 @@ libinput_la_SOURCES = \ libinput_la_LIBADD = $(MTDEV_LIBS) \ $(LIBUDEV_LIBS) \ $(LIBEVDEV_LIBS) \ + $(LIBWACOM_LIBS) \ libinput-util.la libinput_la_CFLAGS = -I$(top_srcdir)/include \ $(MTDEV_CFLAGS) \ $(LIBUDEV_CFLAGS) \ $(LIBEVDEV_CFLAGS) \ + $(LIBWACOM_CFLAGS) \ $(GCC_CFLAGS) EXTRA_libinput_la_DEPENDENCIES = $(srcdir)/libinput.sym diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index a995e225..f2491164 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -1436,6 +1436,7 @@ static struct evdev_dispatch_interface tp_interface = { tp_interface_device_removed, tp_interface_device_removed, /* device_suspended, treat as remove */ tp_interface_device_added, /* device_resumed, treat as add */ + NULL, /* post_added */ }; static void diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c new file mode 100644 index 00000000..e684055b --- /dev/null +++ b/src/evdev-tablet.c @@ -0,0 +1,1655 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * Copyright © 2014 Stephen Chandler "Lyude" Paul + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "config.h" +#include "libinput-version.h" +#include "evdev-tablet.h" + +#include <assert.h> +#include <stdbool.h> +#include <string.h> + +#if HAVE_LIBWACOM +#include <libwacom/libwacom.h> +#endif + +#define tablet_set_status(tablet_,s_) (tablet_)->status |= (s_) +#define tablet_unset_status(tablet_,s_) (tablet_)->status &= ~(s_) +#define tablet_has_status(tablet_,s_) (!!((tablet_)->status & (s_))) + +static inline void +tablet_get_pressed_buttons(struct tablet_dispatch *tablet, + unsigned char *buttons, + unsigned int buttons_len) +{ + size_t i; + const struct button_state *state = &tablet->button_state, + *prev_state = &tablet->prev_button_state; + + assert(buttons_len <= ARRAY_LENGTH(state->stylus_buttons)); + + for (i = 0; i < buttons_len; i++) + buttons[i] = state->stylus_buttons[i] & + ~(prev_state->stylus_buttons[i]); +} + +static inline void +tablet_get_released_buttons(struct tablet_dispatch *tablet, + unsigned char *buttons, + unsigned int buttons_len) +{ + size_t i; + const struct button_state *state = &tablet->button_state, + *prev_state = &tablet->prev_button_state; + + assert(buttons_len <= ARRAY_LENGTH(state->stylus_buttons)); + + for (i = 0; i < buttons_len; i++) + buttons[i] = prev_state->stylus_buttons[i] & + ~(state->stylus_buttons[i]); +} + +/* Merge the previous state with the current one so all buttons look like + * they just got pressed in this frame */ +static inline void +tablet_force_button_presses(struct tablet_dispatch *tablet) +{ + struct button_state *state = &tablet->button_state, + *prev_state = &tablet->prev_button_state; + size_t i; + + for (i = 0; i < sizeof(state->stylus_buttons); i++) { + state->stylus_buttons[i] = state->stylus_buttons[i] | + prev_state->stylus_buttons[i]; + prev_state->stylus_buttons[i] = 0; + } +} + +static int +tablet_device_has_axis(struct tablet_dispatch *tablet, + enum libinput_tablet_tool_axis axis) +{ + struct libevdev *evdev = tablet->device->evdev; + bool has_axis = false; + unsigned int code; + + if (axis == LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z) { + has_axis = (libevdev_has_event_code(evdev, + EV_KEY, + BTN_TOOL_MOUSE) && + libevdev_has_event_code(evdev, + EV_ABS, + ABS_TILT_X) && + libevdev_has_event_code(evdev, + EV_ABS, + ABS_TILT_Y)); + code = axis_to_evcode(axis); + has_axis |= libevdev_has_event_code(evdev, + EV_ABS, + code); + } else if (axis == LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL) { + has_axis = libevdev_has_event_code(evdev, + EV_REL, + REL_WHEEL); + } else { + code = axis_to_evcode(axis); + has_axis = libevdev_has_event_code(evdev, + EV_ABS, + code); + } + + return has_axis; +} + +static void +tablet_process_absolute(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + enum libinput_tablet_tool_axis axis; + + switch (e->code) { + case ABS_X: + case ABS_Y: + case ABS_Z: + case ABS_PRESSURE: + case ABS_TILT_X: + case ABS_TILT_Y: + case ABS_DISTANCE: + case ABS_WHEEL: + axis = evcode_to_axis(e->code); + if (axis == LIBINPUT_TABLET_TOOL_AXIS_NONE) { + log_bug_libinput(device->base.seat->libinput, + "Invalid ABS event code %#x\n", + e->code); + break; + } + + set_bit(tablet->changed_axes, axis); + tablet_set_status(tablet, TABLET_AXES_UPDATED); + break; + /* tool_id is the identifier for the tool we can use in libwacom + * to identify it (if we have one anyway) */ + case ABS_MISC: + tablet->current_tool_id = e->value; + break; + /* Intuos 3 strip data. Should only happen on the Pad device, not on + the Pen device. */ + case ABS_RX: + case ABS_RY: + /* Only on the 4D mouse (Intuos2), obsolete */ + case ABS_RZ: + /* Only on the 4D mouse (Intuos2), obsolete. + The 24HD sends ABS_THROTTLE on the Pad device for the second + wheel but we shouldn't get here on kernel >= 3.17. + */ + case ABS_THROTTLE: + default: + log_info(device->base.seat->libinput, + "Unhandled ABS event code %#x\n", e->code); + break; + } +} + +static void +tablet_change_to_left_handed(struct evdev_device *device) +{ + struct tablet_dispatch *tablet = + (struct tablet_dispatch*)device->dispatch; + + if (device->left_handed.enabled == device->left_handed.want_enabled) + return; + + if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) + return; + + device->left_handed.enabled = device->left_handed.want_enabled; +} + +static void +tablet_update_tool(struct tablet_dispatch *tablet, + struct evdev_device *device, + enum libinput_tablet_tool_type tool, + bool enabled) +{ + assert(tool != LIBINPUT_TOOL_NONE); + + if (enabled) { + tablet->current_tool_type = tool; + tablet_set_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY); + tablet_unset_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY); + } + else if (!tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) + tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY); +} + +static inline double +normalize_dist_slider(const struct input_absinfo *absinfo) +{ + double range = absinfo->maximum - absinfo->minimum; + double value = (absinfo->value - absinfo->minimum) / range; + + return value; +} + +static inline double +normalize_pressure(const struct input_absinfo *absinfo, + struct libinput_tablet_tool *tool) +{ + double range = absinfo->maximum - absinfo->minimum; + int offset = tool->has_pressure_offset ? + tool->pressure_offset : 0; + double value = (absinfo->value - offset - absinfo->minimum) / range; + + return value; +} + +static inline double +normalize_tilt(const struct input_absinfo *absinfo) +{ + double range = absinfo->maximum - absinfo->minimum; + double value = (absinfo->value - absinfo->minimum) / range; + + /* Map to the (-1, 1) range */ + return (value * 2) - 1; +} + +static inline int32_t +invert_axis(const struct input_absinfo *absinfo) +{ + return absinfo->maximum - (absinfo->value - absinfo->minimum); +} + +static void +convert_tilt_to_rotation(struct tablet_dispatch *tablet) +{ + const int offset = 5; + double x, y; + double angle = 0.0; + + /* Wacom Intuos 4, 5, Pro mouse calculates rotation from the x/y tilt + values. The device has a 175 degree CCW hardware offset but since we use + atan2 the effective offset is just 5 degrees. + */ + x = tablet->axes.tilt.x; + y = tablet->axes.tilt.y; + clear_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_TILT_X); + clear_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_TILT_Y); + + /* atan2 is CCW, we want CW -> negate x */ + if (x || y) + angle = ((180.0 * atan2(-x, y)) / M_PI); + + angle = fmod(360 + angle - offset, 360); + + tablet->axes.rotation = angle; + set_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); +} + +static double +convert_to_degrees(const struct input_absinfo *absinfo, double offset) +{ + /* range is [0, 360[, i.e. range + 1 */ + double range = absinfo->maximum - absinfo->minimum + 1; + double value = (absinfo->value - absinfo->minimum) / range; + + return fmod(value * 360.0 + offset, 360.0); +} + +static inline double +normalize_wheel(struct tablet_dispatch *tablet, + int value) +{ + struct evdev_device *device = tablet->device; + + return value * device->scroll.wheel_click_angle; +} + +static inline void +tablet_handle_xy(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct device_coords *point_out, + struct device_coords *delta_out) +{ + struct device_coords point; + struct device_coords delta = { 0, 0 }; + const struct input_absinfo *absinfo; + int value; + + if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_X); + + if (device->left_handed.enabled) + value = invert_axis(absinfo); + else + value = absinfo->value; + + if (!tablet_has_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY)) + delta.x = value - tablet->axes.point.x; + tablet->axes.point.x = value; + } + point.x = tablet->axes.point.x; + + if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_Y); + + if (device->left_handed.enabled) + value = invert_axis(absinfo); + else + value = absinfo->value; + + if (!tablet_has_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY)) + delta.y = value - tablet->axes.point.y; + tablet->axes.point.y = value; + } + point.y = tablet->axes.point.y; + + evdev_transform_absolute(device, &point); + evdev_transform_relative(device, &delta); + + *delta_out = delta; + *point_out = point; +} + +static inline struct normalized_coords +tablet_process_delta(struct tablet_dispatch *tablet, + const struct evdev_device *device, + const struct device_coords *delta, + uint64_t time) +{ + struct normalized_coords accel; + + /* The tablet accel code uses mm as input */ + accel.x = 1.0 * delta->x/device->abs.absinfo_x->resolution; + accel.y = 1.0 * delta->y/device->abs.absinfo_y->resolution; + + if (normalized_is_zero(accel)) + return accel; + + return filter_dispatch(device->pointer.filter, + &accel, tablet, time); +} + +static inline double +tablet_handle_pressure(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool) +{ + const struct input_absinfo *absinfo; + + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); + tablet->axes.pressure = normalize_pressure(absinfo, tool); + } + + return tablet->axes.pressure; +} + +static inline double +tablet_handle_distance(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + const struct input_absinfo *absinfo; + + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_DISTANCE)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); + tablet->axes.distance = normalize_dist_slider(absinfo); + } + + return tablet->axes.distance; +} + +static inline double +tablet_handle_slider(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + const struct input_absinfo *absinfo; + + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_SLIDER)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_WHEEL); + tablet->axes.slider = normalize_dist_slider(absinfo); + } + + return tablet->axes.slider; +} + +static inline struct normalized_range_coords +tablet_handle_tilt(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + struct normalized_range_coords tilt; + const struct input_absinfo *absinfo; + + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_TILT_X)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_TILT_X); + tablet->axes.tilt.x = normalize_tilt(absinfo); + if (device->left_handed.enabled) + tablet->axes.tilt.x *= -1; + } + tilt.x = tablet->axes.tilt.x; + + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_TILT_Y)) { + absinfo = libevdev_get_abs_info(device->evdev, ABS_TILT_Y); + tablet->axes.tilt.y = normalize_tilt(absinfo); + if (device->left_handed.enabled) + tablet->axes.tilt.y *= -1; + } + tilt.y = tablet->axes.tilt.y; + + return tilt; +} + +static inline double +tablet_handle_artpen_rotation(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + const struct input_absinfo *absinfo; + + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z)) { + absinfo = libevdev_get_abs_info(device->evdev, + ABS_Z); + /* artpen has 0 with buttons pointing east */ + tablet->axes.rotation = convert_to_degrees(absinfo, 90); + } + + return tablet->axes.rotation; +} + +static inline double +tablet_handle_mouse_rotation(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + if (bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_TILT_X) || + bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_TILT_Y)) { + convert_tilt_to_rotation(tablet); + } + + return tablet->axes.rotation; +} + +static inline double +tablet_handle_wheel(struct tablet_dispatch *tablet, + struct evdev_device *device, + int *wheel_discrete) +{ + int a; + + a = LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL; + if (bit_is_set(tablet->changed_axes, a)) { + *wheel_discrete = tablet->axes.wheel_discrete; + tablet->axes.wheel = normalize_wheel(tablet, + tablet->axes.wheel_discrete); + } else { + tablet->axes.wheel = 0; + *wheel_discrete = 0; + } + + return tablet->axes.wheel; +} + +static bool +tablet_check_notify_axes(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool, + struct tablet_axes *axes_out, + uint64_t time) +{ + struct tablet_axes axes = {0}; + const char tmp[sizeof(tablet->changed_axes)] = {0}; + struct device_coords delta; + + if (memcmp(tmp, tablet->changed_axes, sizeof(tmp)) == 0) + return false; + + tablet_handle_xy(tablet, device, &axes.point, &delta); + axes.pressure = tablet_handle_pressure(tablet, device, tool); + axes.distance = tablet_handle_distance(tablet, device); + axes.slider = tablet_handle_slider(tablet, device); + axes.tilt = tablet_handle_tilt(tablet, device); + axes.delta = tablet_process_delta(tablet, device, &delta, time); + + /* We must check ROTATION_Z after TILT_X/Y so that the tilt axes are + * already normalized and set if we have the mouse/lens tool */ + if (tablet->current_tool_type == LIBINPUT_TABLET_TOOL_TYPE_MOUSE || + tablet->current_tool_type == LIBINPUT_TABLET_TOOL_TYPE_LENS) { + axes.rotation = tablet_handle_mouse_rotation(tablet, device); + axes.tilt.x = 0; + axes.tilt.y = 0; + + } else { + axes.rotation = tablet_handle_artpen_rotation(tablet, device); + } + + axes.wheel = tablet_handle_wheel(tablet, device, &axes.wheel_discrete); + + *axes_out = axes; + + return true; +} + +static void +tablet_update_button(struct tablet_dispatch *tablet, + uint32_t evcode, + uint32_t enable) +{ + switch (evcode) { + case BTN_TOUCH: + return; + case BTN_LEFT: + case BTN_RIGHT: + case BTN_MIDDLE: + case BTN_SIDE: + case BTN_EXTRA: + case BTN_FORWARD: + case BTN_BACK: + case BTN_TASK: + case BTN_STYLUS: + case BTN_STYLUS2: + break; + default: + log_info(tablet->device->base.seat->libinput, + "Unhandled button %s (%#x)\n", + libevdev_event_code_get_name(EV_KEY, evcode), evcode); + return; + } + + if (enable) { + set_bit(tablet->button_state.stylus_buttons, evcode); + tablet_set_status(tablet, TABLET_BUTTONS_PRESSED); + } else { + clear_bit(tablet->button_state.stylus_buttons, evcode); + tablet_set_status(tablet, TABLET_BUTTONS_RELEASED); + } +} + +static inline enum libinput_tablet_tool_type +tablet_evcode_to_tool(int code) +{ + enum libinput_tablet_tool_type type; + + switch (code) { + case BTN_TOOL_PEN: type = LIBINPUT_TABLET_TOOL_TYPE_PEN; break; + case BTN_TOOL_RUBBER: type = LIBINPUT_TABLET_TOOL_TYPE_ERASER; break; + case BTN_TOOL_BRUSH: type = LIBINPUT_TABLET_TOOL_TYPE_BRUSH; break; + case BTN_TOOL_PENCIL: type = LIBINPUT_TABLET_TOOL_TYPE_PENCIL; break; + case BTN_TOOL_AIRBRUSH: type = LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH; break; + case BTN_TOOL_MOUSE: type = LIBINPUT_TABLET_TOOL_TYPE_MOUSE; break; + case BTN_TOOL_LENS: type = LIBINPUT_TABLET_TOOL_TYPE_LENS; break; + default: + abort(); + } + + return type; +} + +static void +tablet_process_key(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + switch (e->code) { + case BTN_TOOL_FINGER: + log_bug_libinput(device->base.seat->libinput, + "Invalid tool 'finger' on tablet interface\n"); + break; + case BTN_TOOL_PEN: + case BTN_TOOL_RUBBER: + case BTN_TOOL_BRUSH: + case BTN_TOOL_PENCIL: + case BTN_TOOL_AIRBRUSH: + case BTN_TOOL_MOUSE: + case BTN_TOOL_LENS: + tablet_update_tool(tablet, + device, + tablet_evcode_to_tool(e->code), + e->value); + break; + case BTN_TOUCH: + if (!bit_is_set(tablet->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) { + if (e->value) + tablet_set_status(tablet, + TABLET_TOOL_ENTERING_CONTACT); + else + tablet_set_status(tablet, + TABLET_TOOL_LEAVING_CONTACT); + } + break; + case BTN_LEFT: + case BTN_RIGHT: + case BTN_MIDDLE: + case BTN_SIDE: + case BTN_EXTRA: + case BTN_FORWARD: + case BTN_BACK: + case BTN_TASK: + case BTN_STYLUS: + case BTN_STYLUS2: + default: + tablet_update_button(tablet, e->code, e->value); + break; + } +} + +static void +tablet_process_relative(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + enum libinput_tablet_tool_axis axis; + + switch (e->code) { + case REL_WHEEL: + axis = rel_evcode_to_axis(e->code); + if (axis == LIBINPUT_TABLET_TOOL_AXIS_NONE) { + log_bug_libinput(device->base.seat->libinput, + "Invalid ABS event code %#x\n", + e->code); + break; + } + set_bit(tablet->changed_axes, axis); + tablet->axes.wheel_discrete = -1 * e->value; + tablet_set_status(tablet, TABLET_AXES_UPDATED); + break; + default: + log_info(tablet->device->base.seat->libinput, + "Unhandled relative axis %s (%#x)\n", + libevdev_event_code_get_name(EV_REL, e->code), + e->code); + return; + } +} + +static void +tablet_process_misc(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + switch (e->code) { + case MSC_SERIAL: + if (e->value != -1) + tablet->current_tool_serial = e->value; + + break; + default: + log_info(device->base.seat->libinput, + "Unhandled MSC event code %s (%#x)\n", + libevdev_event_code_get_name(EV_MSC, e->code), + e->code); + break; + } +} + +static inline void +copy_axis_cap(const struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_axis axis) +{ + if (bit_is_set(tablet->axis_caps, axis)) + set_bit(tool->axis_caps, axis); +} + +static inline void +copy_button_cap(const struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool, + uint32_t button) +{ + struct libevdev *evdev = tablet->device->evdev; + if (libevdev_has_event_code(evdev, EV_KEY, button)) + set_bit(tool->buttons, button); +} + +static inline int +tool_set_bits_from_libwacom(const struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + int rc = 1; + +#if HAVE_LIBWACOM + struct libinput *libinput = tablet->device->base.seat->libinput; + WacomDeviceDatabase *db; + const WacomStylus *s = NULL; + int code; + WacomStylusType type; + WacomAxisTypeFlags axes; + + db = libwacom_database_new(); + if (!db) { + log_info(libinput, + "Failed to initialize libwacom context.\n"); + goto out; + } + s = libwacom_stylus_get_for_id(db, tool->tool_id); + if (!s) + goto out; + + type = libwacom_stylus_get_type(s); + if (type == WSTYLUS_PUCK) { + for (code = BTN_LEFT; + code < BTN_LEFT + libwacom_stylus_get_num_buttons(s); + code++) + copy_button_cap(tablet, tool, code); + } else { + if (libwacom_stylus_get_num_buttons(s) >= 2) + copy_button_cap(tablet, tool, BTN_STYLUS2); + if (libwacom_stylus_get_num_buttons(s) >= 1) + copy_button_cap(tablet, tool, BTN_STYLUS); + } + + if (libwacom_stylus_has_wheel(s)) + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL); + + axes = libwacom_stylus_get_axes(s); + + if (axes & WACOM_AXIS_TYPE_TILT) { + /* tilt on the puck is converted to rotation */ + if (type == WSTYLUS_PUCK) { + set_bit(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); + } else { + copy_axis_cap(tablet, + tool, + LIBINPUT_TABLET_TOOL_AXIS_TILT_X); + copy_axis_cap(tablet, + tool, + LIBINPUT_TABLET_TOOL_AXIS_TILT_Y); + } + } + if (axes & WACOM_AXIS_TYPE_ROTATION_Z) + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); + if (axes & WACOM_AXIS_TYPE_DISTANCE) + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_DISTANCE); + if (axes & WACOM_AXIS_TYPE_SLIDER) + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_SLIDER); + if (axes & WACOM_AXIS_TYPE_PRESSURE) + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); + + rc = 0; +out: + if (db) + libwacom_database_destroy(db); +#endif + return rc; +} + +static void +tool_set_bits(const struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + enum libinput_tablet_tool_type type = tool->type; + + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_X); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_Y); + +#if HAVE_LIBWACOM + if (tool_set_bits_from_libwacom(tablet, tool) == 0) + return; +#endif + /* If we don't have libwacom, we simply copy any axis we have on the + tablet onto the tool. Except we know that mice only have rotation + anyway. + */ + switch (type) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_DISTANCE); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_TILT_X); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_TILT_Y); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_SLIDER); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); + break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); + copy_axis_cap(tablet, tool, LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL); + break; + default: + break; + } + + /* If we don't have libwacom, copy all pen-related buttons from the + tablet vs all mouse-related buttons */ + switch (type) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + copy_button_cap(tablet, tool, BTN_STYLUS); + copy_button_cap(tablet, tool, BTN_STYLUS2); + break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + copy_button_cap(tablet, tool, BTN_LEFT); + copy_button_cap(tablet, tool, BTN_MIDDLE); + copy_button_cap(tablet, tool, BTN_RIGHT); + copy_button_cap(tablet, tool, BTN_SIDE); + copy_button_cap(tablet, tool, BTN_EXTRA); + break; + default: + break; + } +} + +static inline int +axis_range_percentage(const struct input_absinfo *a, double percent) +{ + return (a->maximum - a->minimum) * percent/100.0 + a->minimum; +} + +static struct libinput_tablet_tool * +tablet_get_tool(struct tablet_dispatch *tablet, + enum libinput_tablet_tool_type type, + uint32_t tool_id, + uint32_t serial) +{ + struct libinput_tablet_tool *tool = NULL, *t; + struct list *tool_list; + + if (serial) { + tool_list = &tablet->device->base.seat->libinput->tool_list; + + /* Check if we already have the tool in our list of tools */ + list_for_each(t, tool_list, link) { + if (type == t->type && serial == t->serial) { + tool = t; + break; + } + } + } else { + /* We can't guarantee that tools without serial numbers are + * unique, so we keep them local to the tablet that they come + * into proximity of instead of storing them in the global tool + * list */ + tool_list = &tablet->tool_list; + + /* Same as above, but don't bother checking the serial number */ + list_for_each(t, tool_list, link) { + if (type == t->type) { + tool = t; + break; + } + } + } + + /* If we didn't already have the new_tool in our list of tools, + * add it */ + if (!tool) { + const struct input_absinfo *pressure; + + tool = zalloc(sizeof *tool); + if (!tool) + return NULL; + *tool = (struct libinput_tablet_tool) { + .type = type, + .serial = serial, + .tool_id = tool_id, + .refcount = 1, + }; + + tool->pressure_offset = 0; + tool->has_pressure_offset = false; + tool->pressure_threshold.lower = 0; + tool->pressure_threshold.upper = 1; + + pressure = libevdev_get_abs_info(tablet->device->evdev, + ABS_PRESSURE); + if (pressure) { + tool->pressure_offset = pressure->minimum; + + /* 5% of the pressure range */ + tool->pressure_threshold.upper = + axis_range_percentage(pressure, 5); + tool->pressure_threshold.lower = + pressure->minimum; + } + + tool_set_bits(tablet, tool); + + list_insert(tool_list, &tool->link); + } + + return tool; +} + +static void +tablet_notify_button_mask(struct tablet_dispatch *tablet, + struct evdev_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + const unsigned char *buttons, + unsigned int buttons_len, + enum libinput_button_state state) +{ + struct libinput_device *base = &device->base; + size_t i; + size_t nbits = 8 * sizeof(buttons[0]) * buttons_len; + enum libinput_tablet_tool_tip_state tip_state; + + tip_state = tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT) ? + LIBINPUT_TABLET_TOOL_TIP_DOWN : LIBINPUT_TABLET_TOOL_TIP_UP; + + for (i = 0; i < nbits; i++) { + if (!bit_is_set(buttons, i)) + continue; + + tablet_notify_button(base, + time, + tool, + tip_state, + &tablet->axes, + i, + state); + } +} + +static void +tablet_notify_buttons(struct tablet_dispatch *tablet, + struct evdev_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_button_state state) +{ + unsigned char buttons[ARRAY_LENGTH(tablet->button_state.stylus_buttons)]; + + if (state == LIBINPUT_BUTTON_STATE_PRESSED) + tablet_get_pressed_buttons(tablet, buttons, sizeof(buttons)); + else + tablet_get_released_buttons(tablet, + buttons, + sizeof(buttons)); + + tablet_notify_button_mask(tablet, + device, + time, + tool, + buttons, + sizeof(buttons), + state); +} + +static void +sanitize_pressure_distance(struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + bool tool_in_contact; + const struct input_absinfo *distance, + *pressure; + + distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE); + pressure = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE); + + if (!pressure || !distance) + return; + + if (!bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_DISTANCE) && + !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) + return; + + tool_in_contact = (pressure->value > tool->pressure_offset); + + /* Keep distance and pressure mutually exclusive */ + if (distance && + (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_DISTANCE) || + bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) && + distance->value > distance->minimum && + pressure->value > pressure->minimum) { + if (tool_in_contact) { + clear_bit(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_DISTANCE); + tablet->axes.distance = 0; + } else { + clear_bit(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); + tablet->axes.pressure = 0; + } + } else if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE) && + !tool_in_contact) { + /* Make sure that the last axis value sent to the caller is a 0 */ + if (tablet->axes.pressure == 0) + clear_bit(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); + else + tablet->axes.pressure = 0; + } +} + +static inline void +sanitize_mouse_lens_rotation(struct tablet_dispatch *tablet) +{ + /* If we have a mouse/lens cursor and the tilt changed, the rotation + changed. Mark this, calculate the angle later */ + if ((tablet->current_tool_type == LIBINPUT_TABLET_TOOL_TYPE_MOUSE || + tablet->current_tool_type == LIBINPUT_TABLET_TOOL_TYPE_LENS) && + (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_TILT_X) || + bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_TILT_Y))) + set_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); +} + +static void +sanitize_tablet_axes(struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + sanitize_pressure_distance(tablet, tool); + sanitize_mouse_lens_rotation(tablet); +} + +static void +detect_pressure_offset(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool) +{ + const struct input_absinfo *pressure, *distance; + int offset; + + if (!bit_is_set(tablet->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) + return; + + pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); + distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); + + if (!pressure || !distance) + return; + + offset = pressure->value - pressure->minimum; + + if (tool->has_pressure_offset) { + if (offset < tool->pressure_offset) + tool->pressure_offset = offset; + return; + } + + if (offset == 0) + return; + + /* we only set a pressure offset on proximity in */ + if (!tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) + return; + + /* If we're closer than 50% of the distance axis, skip pressure + * offset detection, too likely to be wrong */ + if (distance->value < axis_range_percentage(distance, 50)) + return; + + if (offset > axis_range_percentage(pressure, 20)) { + log_error(device->base.seat->libinput, + "Ignoring pressure offset greater than 20%% detected on tool %s (serial %#x). " + "See http://wayland.freedesktop.org/libinput/doc/%s/tablet-support.html\n", + tablet_tool_type_to_string(tool->type), + tool->serial, + LIBINPUT_VERSION); + return; + } + + log_info(device->base.seat->libinput, + "Pressure offset detected on tool %s (serial %#x). " + "See http://wayland.freedesktop.org/libinput/doc/%s/tablet-support.html\n", + tablet_tool_type_to_string(tool->type), + tool->serial, + LIBINPUT_VERSION); + tool->pressure_offset = offset; + tool->has_pressure_offset = true; +} + +static void +detect_tool_contact(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool) +{ + const struct input_absinfo *p; + int pressure; + + if (!bit_is_set(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) + return; + + /* if we have pressure, always use that for contact, not BTN_TOUCH */ + if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_CONTACT)) + log_bug_libinput(device->base.seat->libinput, + "Invalid status: entering contact\n"); + if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_CONTACT) && + !tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) + log_bug_libinput(device->base.seat->libinput, + "Invalid status: leaving contact\n"); + + p = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE); + if (!p) { + log_bug_libinput(device->base.seat->libinput, + "Missing pressure axis\n"); + return; + } + pressure = p->value; + + if (tool->has_pressure_offset) + pressure -= (tool->pressure_offset - p->minimum); + + if (pressure <= tool->pressure_threshold.lower && + tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { + tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT); + } else if (pressure >= tool->pressure_threshold.upper && + !tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { + tablet_set_status(tablet, TABLET_TOOL_ENTERING_CONTACT); + } +} + +static void +tablet_mark_all_axes_changed(struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + static_assert(sizeof(tablet->changed_axes) == + sizeof(tool->axis_caps), + "Mismatching array sizes"); + + memcpy(tablet->changed_axes, + tool->axis_caps, + sizeof(tablet->changed_axes)); +} + +static void +tablet_update_proximity_state(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool) +{ + const struct input_absinfo *distance; + int dist_max = tablet->cursor_proximity_threshold; + int dist; + + distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE); + if (!distance) + return; + + dist = distance->value; + if (dist == 0) + return; + + /* Tool got into permitted range */ + if (dist < dist_max && + (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE) || + tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))) { + tablet_unset_status(tablet, + TABLET_TOOL_OUT_OF_RANGE); + tablet_unset_status(tablet, + TABLET_TOOL_OUT_OF_PROXIMITY); + tablet_set_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY); + tablet_mark_all_axes_changed(tablet, tool); + + tablet_set_status(tablet, TABLET_BUTTONS_PRESSED); + tablet_force_button_presses(tablet); + return; + } + + if (dist < dist_max) + return; + + /* Still out of range/proximity */ + if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE) || + tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) + return; + + /* Tool entered prox but is outside of permitted range */ + if (tablet_has_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY)) { + tablet_set_status(tablet, + TABLET_TOOL_OUT_OF_RANGE); + tablet_unset_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY); + return; + } + + /* Tool was in prox and is now outside of range. Set leaving + * proximity, on the next event it will be OUT_OF_PROXIMITY and thus + * caught by the above conditions */ + tablet_set_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY); +} + +static void +tablet_send_axis_proximity_tip_down_events(struct tablet_dispatch *tablet, + struct evdev_device *device, + struct libinput_tablet_tool *tool, + uint64_t time) +{ + struct tablet_axes axes = {0}; + + /* We need to make sure that we check that the tool is not out of + * proximity before we send any axis updates. This is because many + * tablets will send axis events with incorrect values if the tablet + * tool is close enough so that the tablet can partially detect that + * it's there, but can't properly receive any data from the tool. */ + if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY)) + goto out; + else if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) { + /* Tool is leaving proximity, we can't rely on the last axis + * information (it'll be mostly 0), so we just get the + * current state and skip over updating the axes. + */ + axes = tablet->axes; + + /* Dont' send an axis event, but we may have a tip event + * update */ + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + } else if (!tablet_check_notify_axes(tablet, + device, + tool, + &axes, + time)) { + goto out; + } + + if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) { + tablet_notify_proximity(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + tablet->changed_axes, + &axes); + tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + } + + if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_CONTACT)) { + tablet_notify_tip(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_TIP_DOWN, + tablet->changed_axes, + &tablet->axes); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + tablet_unset_status(tablet, TABLET_TOOL_ENTERING_CONTACT); + tablet_set_status(tablet, TABLET_TOOL_IN_CONTACT); + } else if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_CONTACT)) { + tablet_notify_tip(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_TIP_UP, + tablet->changed_axes, + &tablet->axes); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + tablet_unset_status(tablet, TABLET_TOOL_LEAVING_CONTACT); + tablet_unset_status(tablet, TABLET_TOOL_IN_CONTACT); + } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED)) { + enum libinput_tablet_tool_tip_state tip_state; + + if (tablet_has_status(tablet, + TABLET_TOOL_IN_CONTACT)) + tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN; + else + tip_state = LIBINPUT_TABLET_TOOL_TIP_UP; + + tablet_notify_axis(&device->base, + time, + tool, + tip_state, + tablet->changed_axes, + &axes); + tablet_unset_status(tablet, TABLET_AXES_UPDATED); + } + +out: + memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes)); + tablet_unset_status(tablet, TABLET_TOOL_ENTERING_CONTACT); +} + +static void +tablet_flush(struct tablet_dispatch *tablet, + struct evdev_device *device, + uint64_t time) +{ + struct libinput_tablet_tool *tool = + tablet_get_tool(tablet, + tablet->current_tool_type, + tablet->current_tool_id, + tablet->current_tool_serial); + + if (!tool) + return; /* OOM */ + + if (tool->type == LIBINPUT_TABLET_TOOL_TYPE_MOUSE || + tool->type == LIBINPUT_TABLET_TOOL_TYPE_LENS) + tablet_update_proximity_state(tablet, device, tool); + + if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY) || + tablet_has_status(tablet, TABLET_TOOL_OUT_OF_RANGE)) + return; + + if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) { + /* Release all stylus buttons */ + memset(tablet->button_state.stylus_buttons, + 0, + sizeof(tablet->button_state.stylus_buttons)); + tablet_set_status(tablet, TABLET_BUTTONS_RELEASED); + if (tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) + tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT); + } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED) || + tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) { + if (tablet_has_status(tablet, + TABLET_TOOL_ENTERING_PROXIMITY)) + tablet_mark_all_axes_changed(tablet, tool); + detect_pressure_offset(tablet, device, tool); + detect_tool_contact(tablet, device, tool); + sanitize_tablet_axes(tablet, tool); + } + + tablet_send_axis_proximity_tip_down_events(tablet, + device, + tool, + time); + + if (tablet_has_status(tablet, TABLET_BUTTONS_RELEASED)) { + tablet_notify_buttons(tablet, + device, + time, + tool, + LIBINPUT_BUTTON_STATE_RELEASED); + tablet_unset_status(tablet, TABLET_BUTTONS_RELEASED); + } + + if (tablet_has_status(tablet, TABLET_BUTTONS_PRESSED)) { + tablet_notify_buttons(tablet, + device, + time, + tool, + LIBINPUT_BUTTON_STATE_PRESSED); + tablet_unset_status(tablet, TABLET_BUTTONS_PRESSED); + } + + if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) { + memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes)); + tablet_notify_proximity(&device->base, + time, + tool, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT, + tablet->changed_axes, + &tablet->axes); + + tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY); + tablet_unset_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY); + + tablet_change_to_left_handed(device); + } +} + +static inline void +tablet_reset_state(struct tablet_dispatch *tablet) +{ + /* Update state */ + memcpy(&tablet->prev_button_state, + &tablet->button_state, + sizeof(tablet->button_state)); +} + +static void +tablet_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *e, + uint64_t time) +{ + struct tablet_dispatch *tablet = + (struct tablet_dispatch *)dispatch; + + switch (e->type) { + case EV_ABS: + tablet_process_absolute(tablet, device, e, time); + break; + case EV_REL: + tablet_process_relative(tablet, device, e, time); + break; + case EV_KEY: + tablet_process_key(tablet, device, e, time); + break; + case EV_MSC: + tablet_process_misc(tablet, device, e, time); + break; + case EV_SYN: + tablet_flush(tablet, device, time); + tablet_reset_state(tablet); + break; + default: + log_error(device->base.seat->libinput, + "Unexpected event type %s (%#x)\n", + libevdev_event_type_get_name(e->type), + e->type); + break; + } +} + +static void +tablet_destroy(struct evdev_dispatch *dispatch) +{ + struct tablet_dispatch *tablet = + (struct tablet_dispatch*)dispatch; + struct libinput_tablet_tool *tool, *tmp; + + list_for_each_safe(tool, tmp, &tablet->tool_list, link) { + libinput_tablet_tool_unref(tool); + } + + free(tablet); +} + +static void +tablet_check_initial_proximity(struct evdev_device *device, + struct evdev_dispatch *dispatch) +{ + bool tool_in_prox = false; + int code, state; + enum libinput_tablet_tool_type tool; + struct tablet_dispatch *tablet = (struct tablet_dispatch*)dispatch; + + for (tool = LIBINPUT_TABLET_TOOL_TYPE_PEN; tool <= LIBINPUT_TABLET_TOOL_TYPE_MAX; tool++) { + code = tablet_tool_to_evcode(tool); + + /* we only expect one tool to be in proximity at a time */ + if (libevdev_fetch_event_value(device->evdev, + EV_KEY, + code, + &state) && state) { + tool_in_prox = true; + break; + } + } + + if (!tool_in_prox) + return; + + tablet_update_tool(tablet, device, tool, state); + + tablet->current_tool_id = + libevdev_get_event_value(device->evdev, + EV_ABS, + ABS_MISC); + tablet->current_tool_serial = + libevdev_get_event_value(device->evdev, + EV_MSC, + MSC_SERIAL); + + tablet_flush(tablet, + device, + libinput_now(device->base.seat->libinput)); +} + +static struct evdev_dispatch_interface tablet_interface = { + tablet_process, + NULL, /* suspend */ + NULL, /* remove */ + tablet_destroy, + NULL, /* device_added */ + NULL, /* device_removed */ + NULL, /* device_suspended */ + NULL, /* device_resumed */ + tablet_check_initial_proximity, +}; + +static void +tablet_init_calibration(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + if (libevdev_has_property(device->evdev, INPUT_PROP_DIRECT)) + evdev_init_calibration(device, &tablet->base); +} + +static void +tablet_init_proximity_threshold(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + /* This rules out most of the bamboos and other devices, we're + * pretty much down to + */ + if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_MOUSE) && + !libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_LENS)) + return; + + /* 42 is the default proximity threshold the xf86-input-wacom driver + * uses for Intuos/Cintiq models. Graphire models have a threshold + * of 10 but since they haven't been manufactured in ages and the + * intersection of users having a graphire, running libinput and + * wanting to use the mouse/lens cursor tool is small enough to not + * worry about it for now. If we need to, we can introduce a udev + * property later. + * + * Value is in device coordinates. + */ + tablet->cursor_proximity_threshold = 42; +} + +static uint32_t +tablet_accel_config_get_profiles(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static enum libinput_config_status +tablet_accel_config_set_profile(struct libinput_device *libinput_device, + enum libinput_config_accel_profile profile) +{ + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; +} + +static enum libinput_config_accel_profile +tablet_accel_config_get_profile(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static enum libinput_config_accel_profile +tablet_accel_config_get_default_profile(struct libinput_device *libinput_device) +{ + return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; +} + +static int +tablet_init_accel(struct tablet_dispatch *tablet, struct evdev_device *device) +{ + const struct input_absinfo *x, *y; + struct motion_filter *filter; + int rc; + + x = device->abs.absinfo_x; + y = device->abs.absinfo_y; + + filter = create_pointer_accelerator_filter_tablet(x->resolution, + y->resolution); + + rc = evdev_device_init_pointer_acceleration(device, filter); + if (rc != 0) + return rc; + + /* we override the profile hooks for accel configuration with hooks + * that don't allow selection of profiles */ + device->pointer.config.get_profiles = tablet_accel_config_get_profiles; + device->pointer.config.set_profile = tablet_accel_config_set_profile; + device->pointer.config.get_profile = tablet_accel_config_get_profile; + device->pointer.config.get_default_profile = tablet_accel_config_get_default_profile; + + return 0; +} + +static int +tablet_init(struct tablet_dispatch *tablet, + struct evdev_device *device) +{ + enum libinput_tablet_tool_axis axis; + int rc; + + tablet->base.interface = &tablet_interface; + tablet->device = device; + tablet->status = TABLET_NONE; + tablet->current_tool_type = LIBINPUT_TOOL_NONE; + list_init(&tablet->tool_list); + + tablet_init_calibration(tablet, device); + tablet_init_proximity_threshold(tablet, device); + rc = tablet_init_accel(tablet, device); + if (rc != 0) + return rc; + + for (axis = LIBINPUT_TABLET_TOOL_AXIS_X; + axis <= LIBINPUT_TABLET_TOOL_AXIS_MAX; + axis++) { + if (tablet_device_has_axis(tablet, axis)) + set_bit(tablet->axis_caps, axis); + } + + tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY); + + return 0; +} + +static void +tablet_init_left_handed(struct evdev_device *device) +{ +#if HAVE_LIBWACOM + struct libinput *libinput = device->base.seat->libinput; + WacomDeviceDatabase *db; + WacomDevice *d = NULL; + WacomError *error; + const char *devnode; + + db = libwacom_database_new(); + if (!db) { + log_info(libinput, + "Failed to initialize libwacom context.\n"); + return; + } + error = libwacom_error_new(); + devnode = udev_device_get_devnode(device->udev_device); + + d = libwacom_new_from_path(db, + devnode, + WFALLBACK_NONE, + error); + + if (d) { + if (libwacom_is_reversible(d)) + evdev_init_left_handed(device, + tablet_change_to_left_handed); + } else if (libwacom_error_get_code(error) == WERROR_UNKNOWN_MODEL) { + log_info(libinput, "Tablet unknown to libwacom\n"); + } else { + log_error(libinput, + "libwacom error: %s\n", + libwacom_error_get_message(error)); + } + + if (error) + libwacom_error_free(&error); + if (d) + libwacom_destroy(d); + libwacom_database_destroy(db); +#endif +} + +struct evdev_dispatch * +evdev_tablet_create(struct evdev_device *device) +{ + struct tablet_dispatch *tablet; + + tablet = zalloc(sizeof *tablet); + if (!tablet) + return NULL; + + if (tablet_init(tablet, device) != 0) { + tablet_destroy(&tablet->base); + return NULL; + } + + tablet_init_left_handed(device); + + return &tablet->base; +} diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h new file mode 100644 index 00000000..1d6fc936 --- /dev/null +++ b/src/evdev-tablet.h @@ -0,0 +1,204 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * Copyright © 2014 Stephen Chandler "Lyude" Paul + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef EVDEV_TABLET_H +#define EVDEV_TABLET_H + +#include "evdev.h" + +#define LIBINPUT_TABLET_TOOL_AXIS_NONE 0 +#define LIBINPUT_TOOL_NONE 0 +#define LIBINPUT_TABLET_TOOL_TYPE_MAX LIBINPUT_TABLET_TOOL_TYPE_LENS + +enum tablet_status { + TABLET_NONE = 0, + TABLET_AXES_UPDATED = 1 << 0, + TABLET_BUTTONS_PRESSED = 1 << 1, + TABLET_BUTTONS_RELEASED = 1 << 2, + TABLET_TOOL_IN_CONTACT = 1 << 3, + TABLET_TOOL_LEAVING_PROXIMITY = 1 << 4, + TABLET_TOOL_OUT_OF_PROXIMITY = 1 << 5, + TABLET_TOOL_ENTERING_PROXIMITY = 1 << 6, + TABLET_TOOL_ENTERING_CONTACT = 1 << 7, + TABLET_TOOL_LEAVING_CONTACT = 1 << 8, + TABLET_TOOL_OUT_OF_RANGE = 1 << 9, +}; + +struct button_state { + unsigned char stylus_buttons[NCHARS(KEY_CNT)]; +}; + +struct tablet_dispatch { + struct evdev_dispatch base; + struct evdev_device *device; + unsigned int status; + unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; + struct tablet_axes axes; + unsigned char axis_caps[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; + + /* Only used for tablets that don't report serial numbers */ + struct list tool_list; + + struct button_state button_state; + struct button_state prev_button_state; + + enum libinput_tablet_tool_type current_tool_type; + uint32_t current_tool_id; + uint32_t current_tool_serial; + + uint32_t cursor_proximity_threshold; +}; + +static inline enum libinput_tablet_tool_axis +evcode_to_axis(const uint32_t evcode) +{ + enum libinput_tablet_tool_axis axis; + + switch (evcode) { + case ABS_X: + axis = LIBINPUT_TABLET_TOOL_AXIS_X; + break; + case ABS_Y: + axis = LIBINPUT_TABLET_TOOL_AXIS_Y; + break; + case ABS_Z: + axis = LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z; + break; + case ABS_DISTANCE: + axis = LIBINPUT_TABLET_TOOL_AXIS_DISTANCE; + break; + case ABS_PRESSURE: + axis = LIBINPUT_TABLET_TOOL_AXIS_PRESSURE; + break; + case ABS_TILT_X: + axis = LIBINPUT_TABLET_TOOL_AXIS_TILT_X; + break; + case ABS_TILT_Y: + axis = LIBINPUT_TABLET_TOOL_AXIS_TILT_Y; + break; + case ABS_WHEEL: + axis = LIBINPUT_TABLET_TOOL_AXIS_SLIDER; + break; + default: + axis = LIBINPUT_TABLET_TOOL_AXIS_NONE; + break; + } + + return axis; +} + +static inline enum libinput_tablet_tool_axis +rel_evcode_to_axis(const uint32_t evcode) +{ + enum libinput_tablet_tool_axis axis; + + switch (evcode) { + case REL_WHEEL: + axis = LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL; + break; + default: + axis = LIBINPUT_TABLET_TOOL_AXIS_NONE; + break; + } + + return axis; +} + +static inline uint32_t +axis_to_evcode(const enum libinput_tablet_tool_axis axis) +{ + uint32_t evcode; + + switch (axis) { + case LIBINPUT_TABLET_TOOL_AXIS_X: + evcode = ABS_X; + break; + case LIBINPUT_TABLET_TOOL_AXIS_Y: + evcode = ABS_Y; + break; + case LIBINPUT_TABLET_TOOL_AXIS_DISTANCE: + evcode = ABS_DISTANCE; + break; + case LIBINPUT_TABLET_TOOL_AXIS_PRESSURE: + evcode = ABS_PRESSURE; + break; + case LIBINPUT_TABLET_TOOL_AXIS_TILT_X: + evcode = ABS_TILT_X; + break; + case LIBINPUT_TABLET_TOOL_AXIS_TILT_Y: + evcode = ABS_TILT_Y; + break; + case LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z: + evcode = ABS_Z; + break; + case LIBINPUT_TABLET_TOOL_AXIS_SLIDER: + evcode = ABS_WHEEL; + break; + default: + abort(); + } + + return evcode; +} + +static inline int +tablet_tool_to_evcode(enum libinput_tablet_tool_type type) +{ + int code; + + switch (type) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: code = BTN_TOOL_PEN; break; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: code = BTN_TOOL_RUBBER; break; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: code = BTN_TOOL_BRUSH; break; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: code = BTN_TOOL_PENCIL; break; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: code = BTN_TOOL_AIRBRUSH; break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: code = BTN_TOOL_MOUSE; break; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: code = BTN_TOOL_LENS; break; + default: + abort(); + } + + return code; +} + +static inline const char * +tablet_tool_type_to_string(enum libinput_tablet_tool_type type) +{ + const char *str; + + switch (type) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: str = "pen"; break; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: str = "eraser"; break; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: str = "brush"; break; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: str = "pencil"; break; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: str = "airbrush"; break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: str = "mouse"; break; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: str = "lens"; break; + default: + abort(); + } + + return str; +} + +#endif diff --git a/src/evdev.c b/src/evdev.c index 430d7de2..8f0a6079 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -212,9 +212,9 @@ evdev_device_led_update(struct evdev_device *device, enum libinput_led leds) (void)i; /* no, we really don't care about the return value */ } -static void -transform_absolute(struct evdev_device *device, - struct device_coords *point) +void +evdev_transform_absolute(struct evdev_device *device, + struct device_coords *point) { if (!device->abs.apply_calibration) return; @@ -222,6 +222,19 @@ transform_absolute(struct evdev_device *device, matrix_mult_vec(&device->abs.calibration, &point->x, &point->y); } +void +evdev_transform_relative(struct evdev_device *device, + struct device_coords *point) +{ + struct matrix rel_matrix; + + if (!device->abs.apply_calibration) + return; + + matrix_to_relative(&rel_matrix, &device->abs.calibration); + matrix_mult_vec(&rel_matrix, &point->x, &point->y); +} + static inline double scale_axis(const struct input_absinfo *absinfo, double val, double to_range) { @@ -340,7 +353,7 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) seat->slot_map |= 1 << seat_slot; point = device->mt.slots[slot].point; - transform_absolute(device, &point); + evdev_transform_absolute(device, &point); touch_notify_touch_down(base, time, slot, seat_slot, &point); @@ -355,7 +368,7 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) if (seat_slot == -1) break; - transform_absolute(device, &point); + evdev_transform_absolute(device, &point); touch_notify_touch_motion(base, time, slot, seat_slot, &point); break; @@ -394,13 +407,13 @@ evdev_flush_pending_event(struct evdev_device *device, uint64_t time) seat->slot_map |= 1 << seat_slot; point = device->abs.point; - transform_absolute(device, &point); + evdev_transform_absolute(device, &point); touch_notify_touch_down(base, time, -1, seat_slot, &point); break; case EVDEV_ABSOLUTE_MOTION: point = device->abs.point; - transform_absolute(device, &point); + evdev_transform_absolute(device, &point); if (device->seat_caps & EVDEV_DEVICE_TOUCH) { seat_slot = device->abs.seat_slot; @@ -962,6 +975,7 @@ struct evdev_dispatch_interface fallback_interface = { NULL, /* device_removed */ NULL, /* device_suspended */ NULL, /* device_resumed */ + NULL, /* post_added */ }; static uint32_t @@ -1191,7 +1205,7 @@ evdev_init_button_scroll(struct evdev_device *device, return 0; } -static void +void evdev_init_calibration(struct evdev_device *device, struct evdev_dispatch *dispatch) { @@ -1986,6 +2000,7 @@ evdev_configure_device(struct evdev_device *device) struct libevdev *evdev = device->evdev; const char *devnode = udev_device_get_devnode(device->udev_device); enum evdev_device_udev_tags udev_tags; + unsigned int tablet_tags; udev_tags = evdev_device_get_udev_tags(device, device->udev_device); @@ -2063,6 +2078,21 @@ evdev_configure_device(struct evdev_device *device) } } + /* libwacom assigns touchpad (or touchscreen) _and_ tablet to the + tablet touch bits, so make sure we don't initialize the tablet + interface for the touch device */ + tablet_tags = EVDEV_UDEV_TAG_TABLET | + EVDEV_UDEV_TAG_TOUCHPAD | + EVDEV_UDEV_TAG_TOUCHSCREEN; + if ((udev_tags & tablet_tags) == EVDEV_UDEV_TAG_TABLET) { + device->dispatch = evdev_tablet_create(device); + device->seat_caps |= EVDEV_DEVICE_TABLET; + log_info(libinput, + "input device '%s', %s is a tablet\n", + device->devname, devnode); + return device->dispatch == NULL ? -1 : 0; + } + if (udev_tags & EVDEV_UDEV_TAG_TOUCHPAD) { device->dispatch = evdev_mt_touchpad_create(device); log_info(libinput, @@ -2153,6 +2183,10 @@ evdev_notify_added_device(struct evdev_device *device) } notify_added_device(&device->base); + + if (device->dispatch->interface->post_added) + device->dispatch->interface->post_added(device, + device->dispatch); } static bool @@ -2442,6 +2476,8 @@ evdev_device_has_capability(struct evdev_device *device, return !!(device->seat_caps & EVDEV_DEVICE_TOUCH); case LIBINPUT_DEVICE_CAP_GESTURE: return !!(device->seat_caps & EVDEV_DEVICE_GESTURE); + case LIBINPUT_DEVICE_CAP_TABLET_TOOL: + return !!(device->seat_caps & EVDEV_DEVICE_TABLET); default: return 0; } diff --git a/src/evdev.h b/src/evdev.h index 97177ec2..02b51126 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -60,6 +60,7 @@ enum evdev_device_seat_capability { EVDEV_DEVICE_POINTER = (1 << 0), EVDEV_DEVICE_KEYBOARD = (1 << 1), EVDEV_DEVICE_TOUCH = (1 << 2), + EVDEV_DEVICE_TABLET = (1 << 3), EVDEV_DEVICE_GESTURE = (1 << 5), }; @@ -266,6 +267,11 @@ struct evdev_dispatch_interface { /* A device was resumed */ void (*device_resumed)(struct evdev_device *device, struct evdev_device *resumed_device); + + /* Called immediately after the LIBINPUT_EVENT_DEVICE_ADDED event + * was sent */ + void (*post_added)(struct evdev_device *device, + struct evdev_dispatch *dispatch); }; struct evdev_dispatch { @@ -282,6 +288,18 @@ struct evdev_device * evdev_device_create(struct libinput_seat *seat, struct udev_device *device); +void +evdev_transform_absolute(struct evdev_device *device, + struct device_coords *point); + +void +evdev_transform_relative(struct evdev_device *device, + struct device_coords *point); + +void +evdev_init_calibration(struct evdev_device *device, + struct evdev_dispatch *dispatch); + int evdev_device_init_pointer_acceleration(struct evdev_device *device, struct motion_filter *filter); @@ -292,6 +310,9 @@ evdev_touchpad_create(struct evdev_device *device); struct evdev_dispatch * evdev_mt_touchpad_create(struct evdev_device *device); +struct evdev_dispatch * +evdev_tablet_create(struct evdev_device *device); + void evdev_tag_touchpad(struct evdev_device *device, struct udev_device *udev_device); diff --git a/src/filter.c b/src/filter.c index 0d0b95d4..4c39b0e2 100644 --- a/src/filter.c +++ b/src/filter.c @@ -163,6 +163,13 @@ struct pointer_accelerator_flat { double dpi_factor; }; +struct tablet_accelerator_flat { + struct motion_filter base; + + double factor; + int xres, yres; +}; + static void feed_trackers(struct pointer_accelerator *accel, const struct normalized_coords *delta, @@ -964,3 +971,90 @@ create_pointer_accelerator_filter_flat(int dpi) return &filter->base; } + +/* The tablet accel code uses mm as input */ +static struct normalized_coords +tablet_accelerator_filter_flat(struct motion_filter *filter, + const struct normalized_coords *mm, + void *data, uint64_t time) +{ + struct tablet_accelerator_flat *accel_filter = + (struct tablet_accelerator_flat *)filter; + struct normalized_coords accelerated; + + /* Tablet input is in mm, output is supposed to be in logical + * pixels roughly equivalent to a mouse/touchpad. + * + * This is a magical constant found by trial and error. On a 96dpi + * screen 0.4mm of movement correspond to 1px logical pixel which + * is almost identical to the tablet mapped to screen in absolute + * mode. Tested on a Intuos5, other tablets may vary. + */ + const double DPI_CONVERSION = 96.0/25.4 * 2.5; /* unitless factor */ + + accelerated.x = mm->x * accel_filter->factor * DPI_CONVERSION; + accelerated.y = mm->y * accel_filter->factor * DPI_CONVERSION; + + return accelerated; +} + +static bool +tablet_accelerator_set_speed(struct motion_filter *filter, + double speed_adjustment) +{ + struct tablet_accelerator_flat *accel_filter = + (struct tablet_accelerator_flat *)filter; + + assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0); + + accel_filter->factor = speed_adjustment + 1.0; + + return true; +} + +static void +tablet_accelerator_destroy(struct motion_filter *filter) +{ + struct tablet_accelerator_flat *accel_filter = + (struct tablet_accelerator_flat *)filter; + + free(accel_filter); +} + +struct motion_filter_interface accelerator_interface_tablet = { + .type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, + .filter = tablet_accelerator_filter_flat, + .filter_constant = NULL, + .restart = NULL, + .destroy = tablet_accelerator_destroy, + .set_speed = tablet_accelerator_set_speed, +}; + +static struct tablet_accelerator_flat * +create_tablet_filter_flat(int xres, int yres) +{ + struct tablet_accelerator_flat *filter; + + filter = zalloc(sizeof *filter); + if (filter == NULL) + return NULL; + + filter->xres = xres; + filter->yres = yres; + + return filter; +} + +struct motion_filter * +create_pointer_accelerator_filter_tablet(int xres, int yres) +{ + struct tablet_accelerator_flat *filter; + + filter = create_tablet_filter_flat(xres, yres); + if (!filter) + return NULL; + + filter->base.interface = &accelerator_interface_tablet; + + return &filter->base; +} diff --git a/src/filter.h b/src/filter.h index e1566429..c1b43a5d 100644 --- a/src/filter.h +++ b/src/filter.h @@ -101,6 +101,9 @@ create_pointer_accelerator_filter_lenovo_x230(int dpi); struct motion_filter * create_pointer_accelerator_filter_trackpoint(int dpi); +struct motion_filter * +create_pointer_accelerator_filter_tablet(int xres, int yres); + /* * Pointer acceleration profiles. */ diff --git a/src/libinput-private.h b/src/libinput-private.h index eee200d1..bc7000d7 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -58,6 +58,29 @@ struct discrete_coords { int x, y; }; +/* A pair of coordinates normalized to a [0,1] or [-1, 1] range */ +struct normalized_range_coords { + double x, y; +}; + +/* A threshold with an upper and lower limit */ +struct threshold { + int upper; + int lower; +}; + +struct tablet_axes { + struct device_coords point; + struct normalized_coords delta; + double distance; + double pressure; + struct normalized_range_coords tilt; + double rotation; + double slider; + double wheel; + int wheel_discrete; +}; + struct libinput_interface_backend { int (*resume)(struct libinput *libinput); void (*suspend)(struct libinput *libinput); @@ -84,6 +107,8 @@ struct libinput { size_t events_in; size_t events_out; + struct list tool_list; + const struct libinput_interface *interface; const struct libinput_interface_backend *interface_backend; @@ -251,6 +276,36 @@ struct libinput_device { struct libinput_device_config config; }; +enum libinput_tablet_tool_axis { + LIBINPUT_TABLET_TOOL_AXIS_X = 1, + LIBINPUT_TABLET_TOOL_AXIS_Y = 2, + LIBINPUT_TABLET_TOOL_AXIS_DISTANCE = 3, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE = 4, + LIBINPUT_TABLET_TOOL_AXIS_TILT_X = 5, + LIBINPUT_TABLET_TOOL_AXIS_TILT_Y = 6, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z = 7, + LIBINPUT_TABLET_TOOL_AXIS_SLIDER = 8, + LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL = 9, +}; + +#define LIBINPUT_TABLET_TOOL_AXIS_MAX LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL + +struct libinput_tablet_tool { + struct list link; + uint32_t serial; + uint32_t tool_id; + enum libinput_tablet_tool_type type; + unsigned char axis_caps[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; + unsigned char buttons[NCHARS(KEY_MAX) + 1]; + int refcount; + void *user_data; + + /* The pressure threshold assumes a pressure_offset of 0 */ + struct threshold pressure_threshold; + int pressure_offset; /* in device coordinates */ + bool has_pressure_offset; +}; + struct libinput_event { enum libinput_event_type type; struct libinput_device *device; @@ -451,6 +506,39 @@ void touch_notify_frame(struct libinput_device *device, uint64_t time); +void +tablet_notify_axis(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_tip_state tip_state, + unsigned char *changed_axes, + const struct tablet_axes *axes); + +void +tablet_notify_proximity(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_proximity_state state, + unsigned char *changed_axes, + const struct tablet_axes *axes); + +void +tablet_notify_tip(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_tip_state tip_state, + unsigned char *changed_axes, + const struct tablet_axes *axes); + +void +tablet_notify_button(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_tip_state tip_state, + const struct tablet_axes *axes, + int32_t button, + enum libinput_button_state state); + static inline uint64_t libinput_now(struct libinput *libinput) { diff --git a/src/libinput-util.h b/src/libinput-util.h index 25de8e5d..6adbbc91 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -96,6 +96,8 @@ int list_empty(const struct list *list); #define streq(s1, s2) (strcmp((s1), (s2)) == 0) #define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0) +#define NCHARS(x) ((size_t)(((x) + 7) / 8)) + #ifdef DEBUG_TRACE #define debug_trace(...) \ do { \ @@ -105,6 +107,7 @@ int list_empty(const struct list *list); #else #define debug_trace(...) { } #endif + #define LIBINPUT_EXPORT __attribute__ ((visibility("default"))) static inline void * @@ -113,6 +116,28 @@ zalloc(size_t size) return calloc(1, size); } +/* This bitfield helper implementation is taken from from libevdev-util.h, + * except that it has been modified to work with arrays of unsigned chars + */ + +static inline int +bit_is_set(const unsigned char *array, int bit) +{ + return !!(array[bit / 8] & (1 << (bit % 8))); +} + +static inline void +set_bit(unsigned char *array, int bit) +{ + array[bit / 8] |= (1 << (bit % 8)); +} + +static inline void +clear_bit(unsigned char *array, int bit) +{ + array[bit / 8] &= ~(1 << (bit % 8)); +} + static inline void msleep(unsigned int ms) { @@ -245,6 +270,16 @@ matrix_to_farray6(const struct matrix *m, float out[6]) out[5] = m->val[1][2]; } +static inline void +matrix_to_relative(struct matrix *dest, const struct matrix *src) +{ + matrix_init_identity(dest); + dest->val[0][0] = src->val[0][0]; + dest->val[0][1] = src->val[0][1]; + dest->val[1][0] = src->val[1][0]; + dest->val[1][1] = src->val[1][1]; +} + /** * Simple wrapper for asprintf that ensures the passed in-pointer is set * to NULL upon error. diff --git a/src/libinput.c b/src/libinput.c index 4ce98e70..2bcd416e 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -125,6 +125,19 @@ struct libinput_event_gesture { double angle; }; +struct libinput_event_tablet_tool { + struct libinput_event base; + uint32_t button; + enum libinput_button_state state; + uint32_t seat_button_count; + uint64_t time; + struct tablet_axes axes; + unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; + struct libinput_tablet_tool *tool; + enum libinput_tablet_tool_proximity_state proximity_state; + enum libinput_tablet_tool_tip_state tip_state; +}; + static void libinput_default_log_func(struct libinput *libinput, enum libinput_log_priority priority, @@ -272,7 +285,6 @@ libinput_event_get_touch_event(struct libinput_event *event) LIBINPUT_EVENT_TOUCH_MOTION, LIBINPUT_EVENT_TOUCH_CANCEL, LIBINPUT_EVENT_TOUCH_FRAME); - return (struct libinput_event_touch *) event; } @@ -292,6 +304,20 @@ libinput_event_get_gesture_event(struct libinput_event *event) return (struct libinput_event_gesture *) event; } +LIBINPUT_EXPORT struct libinput_event_tablet_tool * +libinput_event_get_tablet_tool_event(struct libinput_event *event) +{ + require_event_type(libinput_event_get_context(event), + event->type, + NULL, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + return (struct libinput_event_tablet_tool *) event; +} + LIBINPUT_EXPORT struct libinput_event_device_notify * libinput_event_get_device_notify_event(struct libinput_event *event) { @@ -884,6 +910,569 @@ libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event) return event->angle; } +LIBINPUT_EXPORT int +libinput_event_tablet_tool_x_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_X); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_y_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_Y); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_pressure_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_distance_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_DISTANCE); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_tilt_x_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_TILT_X); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_tilt_y_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_TILT_Y); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_rotation_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_slider_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_SLIDER); +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_wheel_has_changed( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return bit_is_set(event->changed_axes, + LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL); +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_x(struct libinput_event_tablet_tool *event) +{ + struct evdev_device *device = + (struct evdev_device *) event->base.device; + + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return evdev_convert_to_mm(device->abs.absinfo_x, + event->axes.point.x); +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_y(struct libinput_event_tablet_tool *event) +{ + struct evdev_device *device = + (struct evdev_device *) event->base.device; + + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return evdev_convert_to_mm(device->abs.absinfo_y, + event->axes.point.y); +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_dx(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.delta.x; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_dy(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.delta.y; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_pressure(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.pressure; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_distance(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.distance; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_tilt_x(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.tilt.x; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_tilt_y(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.tilt.y; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_rotation(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.rotation; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_slider_position(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.slider; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_wheel_delta(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.wheel; +} + +LIBINPUT_EXPORT int +libinput_event_tablet_tool_get_wheel_delta_discrete( + struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->axes.wheel_discrete; +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_x_transformed(struct libinput_event_tablet_tool *event, + uint32_t width) +{ + struct evdev_device *device = + (struct evdev_device *) event->base.device; + + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return evdev_device_transform_x(device, + event->axes.point.x, + width); +} + +LIBINPUT_EXPORT double +libinput_event_tablet_tool_get_y_transformed(struct libinput_event_tablet_tool *event, + uint32_t height) +{ + struct evdev_device *device = + (struct evdev_device *) event->base.device; + + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return evdev_device_transform_y(device, + event->axes.point.y, + height); +} + +LIBINPUT_EXPORT struct libinput_tablet_tool * +libinput_event_tablet_tool_get_tool(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->tool; +} + +LIBINPUT_EXPORT enum libinput_tablet_tool_proximity_state +libinput_event_tablet_tool_get_proximity_state(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->proximity_state; +} + +LIBINPUT_EXPORT enum libinput_tablet_tool_tip_state +libinput_event_tablet_tool_get_tip_state(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->tip_state; +} + +LIBINPUT_EXPORT uint32_t +libinput_event_tablet_tool_get_time(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return us2ms(event->time); +} + +LIBINPUT_EXPORT uint64_t +libinput_event_tablet_tool_get_time_usec(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + return event->time; +} + +LIBINPUT_EXPORT uint32_t +libinput_event_tablet_tool_get_button(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + return event->button; +} + +LIBINPUT_EXPORT enum libinput_button_state +libinput_event_tablet_tool_get_button_state(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + return event->state; +} + +LIBINPUT_EXPORT uint32_t +libinput_event_tablet_tool_get_seat_button_count(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + 0, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + return event->seat_button_count; +} + +LIBINPUT_EXPORT enum libinput_tablet_tool_type +libinput_tablet_tool_get_type(struct libinput_tablet_tool *tool) +{ + return tool->type; +} + +LIBINPUT_EXPORT uint64_t +libinput_tablet_tool_get_tool_id(struct libinput_tablet_tool *tool) +{ + return tool->tool_id; +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_is_unique(struct libinput_tablet_tool *tool) +{ + return tool->serial != 0; +} + +LIBINPUT_EXPORT uint64_t +libinput_tablet_tool_get_serial(struct libinput_tablet_tool *tool) +{ + return tool->serial; +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_pressure(struct libinput_tablet_tool *tool) +{ + return bit_is_set(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_PRESSURE); +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_distance(struct libinput_tablet_tool *tool) +{ + return bit_is_set(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_DISTANCE); +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_tilt(struct libinput_tablet_tool *tool) +{ + return bit_is_set(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_TILT_X); +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_rotation(struct libinput_tablet_tool *tool) +{ + return bit_is_set(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z); +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_slider(struct libinput_tablet_tool *tool) +{ + return bit_is_set(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_SLIDER); +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_wheel(struct libinput_tablet_tool *tool) +{ + return bit_is_set(tool->axis_caps, + LIBINPUT_TABLET_TOOL_AXIS_REL_WHEEL); +} + +LIBINPUT_EXPORT int +libinput_tablet_tool_has_button(struct libinput_tablet_tool *tool, + uint32_t code) +{ + if (NCHARS(code) > sizeof(tool->buttons)) + return 0; + + return bit_is_set(tool->buttons, code); +} + +LIBINPUT_EXPORT void +libinput_tablet_tool_set_user_data(struct libinput_tablet_tool *tool, + void *user_data) +{ + tool->user_data = user_data; +} + +LIBINPUT_EXPORT void * +libinput_tablet_tool_get_user_data(struct libinput_tablet_tool *tool) +{ + return tool->user_data; +} + +LIBINPUT_EXPORT struct libinput_tablet_tool * +libinput_tablet_tool_ref(struct libinput_tablet_tool *tool) +{ + tool->refcount++; + return tool; +} + +LIBINPUT_EXPORT struct libinput_tablet_tool * +libinput_tablet_tool_unref(struct libinput_tablet_tool *tool) +{ + assert(tool->refcount > 0); + + tool->refcount--; + if (tool->refcount > 0) + return tool; + + list_remove(&tool->link); + free(tool); + return NULL; +} + struct libinput_source * libinput_add_fd(struct libinput *libinput, int fd, @@ -948,6 +1537,7 @@ libinput_init(struct libinput *libinput, list_init(&libinput->source_destroy_list); list_init(&libinput->seat_list); list_init(&libinput->device_group_list); + list_init(&libinput->tool_list); if (libinput_timer_subsys_init(libinput) != 0) { free(libinput->events); @@ -987,6 +1577,7 @@ libinput_unref(struct libinput *libinput) struct libinput_event *event; struct libinput_device *device, *next_device; struct libinput_seat *seat, *next_seat; + struct libinput_tablet_tool *tool, *next_tool; struct libinput_device_group *group, *next_group; if (libinput == NULL) @@ -1022,6 +1613,10 @@ libinput_unref(struct libinput *libinput) libinput_device_group_destroy(group); } + list_for_each_safe(tool, next_tool, &libinput->tool_list, link) { + libinput_tablet_tool_unref(tool); + } + libinput_timer_subsys_destroy(libinput); libinput_drop_destroyed_sources(libinput); close(libinput->epoll_fd); @@ -1355,6 +1950,9 @@ device_has_cap(struct libinput_device *device, case LIBINPUT_DEVICE_CAP_GESTURE: capability = "CAP_GESTURE"; break; + case LIBINPUT_DEVICE_CAP_TABLET_TOOL: + capability = "CAP_TABLET"; + break; } log_bug_libinput(device->seat->libinput, @@ -1611,6 +2209,137 @@ touch_notify_frame(struct libinput_device *device, &touch_event->base); } +void +tablet_notify_axis(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_tip_state tip_state, + unsigned char *changed_axes, + const struct tablet_axes *axes) +{ + struct libinput_event_tablet_tool *axis_event; + + axis_event = zalloc(sizeof *axis_event); + if (!axis_event) + return; + + *axis_event = (struct libinput_event_tablet_tool) { + .time = time, + .tool = tool, + .proximity_state = LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + .tip_state = tip_state, + .axes = *axes, + }; + + memcpy(axis_event->changed_axes, + changed_axes, + sizeof(axis_event->changed_axes)); + + post_device_event(device, + time, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + &axis_event->base); +} + +void +tablet_notify_proximity(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_proximity_state proximity_state, + unsigned char *changed_axes, + const struct tablet_axes *axes) +{ + struct libinput_event_tablet_tool *proximity_event; + + proximity_event = zalloc(sizeof *proximity_event); + if (!proximity_event) + return; + + *proximity_event = (struct libinput_event_tablet_tool) { + .time = time, + .tool = tool, + .tip_state = LIBINPUT_TABLET_TOOL_TIP_UP, + .proximity_state = proximity_state, + .axes = *axes, + }; + memcpy(proximity_event->changed_axes, + changed_axes, + sizeof(proximity_event->changed_axes)); + + post_device_event(device, + time, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, + &proximity_event->base); +} + +void +tablet_notify_tip(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_tip_state tip_state, + unsigned char *changed_axes, + const struct tablet_axes *axes) +{ + struct libinput_event_tablet_tool *tip_event; + + tip_event = zalloc(sizeof *tip_event); + if (!tip_event) + return; + + *tip_event = (struct libinput_event_tablet_tool) { + .time = time, + .tool = tool, + .tip_state = tip_state, + .proximity_state = LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + .axes = *axes, + }; + memcpy(tip_event->changed_axes, + changed_axes, + sizeof(tip_event->changed_axes)); + + post_device_event(device, + time, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + &tip_event->base); +} + +void +tablet_notify_button(struct libinput_device *device, + uint64_t time, + struct libinput_tablet_tool *tool, + enum libinput_tablet_tool_tip_state tip_state, + const struct tablet_axes *axes, + int32_t button, + enum libinput_button_state state) +{ + struct libinput_event_tablet_tool *button_event; + int32_t seat_button_count; + + button_event = zalloc(sizeof *button_event); + if (!button_event) + return; + + seat_button_count = update_seat_button_count(device->seat, + button, + state); + + *button_event = (struct libinput_event_tablet_tool) { + .time = time, + .tool = tool, + .button = button, + .state = state, + .seat_button_count = seat_button_count, + .proximity_state = LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, + .tip_state = tip_state, + .axes = *axes, + }; + + post_device_event(device, + time, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + &button_event->base); +} + static void gesture_notify(struct libinput_device *device, uint64_t time, @@ -1696,6 +2425,39 @@ gesture_notify_pinch_end(struct libinput_device *device, finger_count, cancelled, &zero, &zero, scale, 0.0); } +static inline const char * +event_type_to_str(enum libinput_event_type type) +{ + switch(type) { + CASE_RETURN_STRING(LIBINPUT_EVENT_DEVICE_ADDED); + CASE_RETURN_STRING(LIBINPUT_EVENT_DEVICE_REMOVED); + CASE_RETURN_STRING(LIBINPUT_EVENT_KEYBOARD_KEY); + CASE_RETURN_STRING(LIBINPUT_EVENT_POINTER_MOTION); + CASE_RETURN_STRING(LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); + CASE_RETURN_STRING(LIBINPUT_EVENT_POINTER_BUTTON); + CASE_RETURN_STRING(LIBINPUT_EVENT_POINTER_AXIS); + CASE_RETURN_STRING(LIBINPUT_EVENT_TOUCH_DOWN); + CASE_RETURN_STRING(LIBINPUT_EVENT_TOUCH_UP); + CASE_RETURN_STRING(LIBINPUT_EVENT_TOUCH_MOTION); + CASE_RETURN_STRING(LIBINPUT_EVENT_TOUCH_CANCEL); + CASE_RETURN_STRING(LIBINPUT_EVENT_TOUCH_FRAME); + CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_TOOL_AXIS); + CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_TOOL_TIP); + CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_SWIPE_END); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_PINCH_BEGIN); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_PINCH_UPDATE); + CASE_RETURN_STRING(LIBINPUT_EVENT_GESTURE_PINCH_END); + case LIBINPUT_EVENT_NONE: + abort(); + } + + return NULL; +} + static void libinput_post_event(struct libinput *libinput, struct libinput_event *event) @@ -1706,6 +2468,10 @@ libinput_post_event(struct libinput *libinput, size_t move_len; size_t new_out; +#if 0 + log_debug(libinput, "Queuing %s\n", event_type_to_str(event->type)); +#endif + events_count++; if (events_count > events_len) { events_len *= 2; @@ -1968,6 +2734,20 @@ libinput_event_gesture_get_base_event(struct libinput_event_gesture *event) return &event->base; } +LIBINPUT_EXPORT struct libinput_event * +libinput_event_tablet_tool_get_base_event(struct libinput_event_tablet_tool *event) +{ + require_event_type(libinput_event_get_context(&event->base), + event->base.type, + NULL, + LIBINPUT_EVENT_TABLET_TOOL_AXIS, + LIBINPUT_EVENT_TABLET_TOOL_TIP, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + + return &event->base; +} + LIBINPUT_EXPORT struct libinput_device_group * libinput_device_group_ref(struct libinput_device_group *group) { diff --git a/src/libinput.h b/src/libinput.h index e590d735..8ed5632b 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -58,6 +58,7 @@ enum libinput_device_capability { LIBINPUT_DEVICE_CAP_KEYBOARD = 0, LIBINPUT_DEVICE_CAP_POINTER = 1, LIBINPUT_DEVICE_CAP_TOUCH = 2, + LIBINPUT_DEVICE_CAP_TABLET_TOOL = 3, LIBINPUT_DEVICE_CAP_GESTURE = 5, }; @@ -134,6 +135,87 @@ enum libinput_pointer_axis_source { }; /** + * @ingroup device + * @struct libinput_tablet_tool + * + * An object representing a tool being used by a device with the @ref + * LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * Tablet events generated by such a device are bound to a specific tool + * rather than coming from the device directly. Depending on the hardware it + * is possible to track the same physical tool across multiple + * struct libinput_device devices, see @ref tablet-serial-numbers. + * + * This struct is refcounted, use libinput_tablet_tool_ref() and + * libinput_tablet_tool_unref(). + */ +struct libinput_tablet_tool; + +/** + * @ingroup device + * + * Available tool types for a device with the @ref + * LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. The tool type defines the default + * usage of the tool as advertised by the manufacturer. Multiple different + * physical tools may share the same tool type, e.g. a Wacom Classic Pen, + * Wacom Pro Pen and a Wacom Grip Pen are all of type @ref + * LIBINPUT_TABLET_TOOL_TYPE_PEN. + * Use libinput_tablet_tool_get_tool_id() to get a specific model where applicable. + * + * Note that on some device, the eraser tool is on the tail end of a pen + * device. On other devices, e.g. MS Surface 3, the eraser is the pen tip + * while a button is held down. + * + * @note The @ref libinput_tablet_tool_type can only describe the default physical + * type of the device. For devices with adjustible physical properties + * the tool type remains the same, i.e. putting a Wacom stroke nib into a + * classic pen leaves the tool type as @ref LIBINPUT_TABLET_TOOL_TYPE_PEN. + */ +enum libinput_tablet_tool_type { + LIBINPUT_TABLET_TOOL_TYPE_PEN = 1, /**< A generic pen */ + LIBINPUT_TABLET_TOOL_TYPE_ERASER, /**< Eraser */ + LIBINPUT_TABLET_TOOL_TYPE_BRUSH, /**< A paintbrush-like tool */ + LIBINPUT_TABLET_TOOL_TYPE_PENCIL, /**< Physical drawing tool, e.g. + Wacom Inking Pen */ + LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH, /**< An airbrush-like tool */ + LIBINPUT_TABLET_TOOL_TYPE_MOUSE, /**< A mouse bound to the tablet */ + LIBINPUT_TABLET_TOOL_TYPE_LENS, /**< A mouse tool with a lens */ +}; + +/** + * @ingroup device + * + * The state of proximity for a tool on a device. The device must have the @ref + * LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * The proximity of a tool is a binary state signalling whether the tool is + * within detectable distance of the tablet device. A tool that is out of + * proximity cannot generate events. + * + * On some hardware a tool goes out of proximity when it ceases to touch the + * surface. On other hardware, the tool is still detectable within a short + * distance (a few cm) off the surface. + */ +enum libinput_tablet_tool_proximity_state { + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT = 0, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN = 1, +}; + +/** + * @ingroup device + * + * The tip contact state for a tool on a device. The device must have + * the @ref LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * The tip contact state of a tool is a binary state signalling whether the tool is + * touching the surface of the tablet device. + */ +enum libinput_tablet_tool_tip_state { + LIBINPUT_TABLET_TOOL_TIP_UP = 0, + LIBINPUT_TABLET_TOOL_TIP_DOWN = 1, +}; + +/** * @ingroup base * * Event type for events returned by libinput_get_event(). @@ -179,6 +261,84 @@ enum libinput_event_type { */ LIBINPUT_EVENT_TOUCH_FRAME, + /** + * One or more axes have changed state on a device with the @ref + * LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. This event is only sent + * when the tool is in proximity, see @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY for details. + * + * The proximity event contains the initial state of the axis as the + * tool comes into proximity. An event of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS is only sent when an axis value + * changes from this initial state. It is possible for a tool to + * enter and leave proximity without sending an event of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS. + * + * An event of type @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS is sent + * when the tip state does not change. See the documentation for + * @ref LIBINPUT_EVENT_TABLET_TOOL_TIP for more details. + */ + LIBINPUT_EVENT_TABLET_TOOL_AXIS = 600, + /** + * Signals that a tool has come in or out of proximity of a device with + * the @ref LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * Proximity events contain each of the current values for each axis, + * and these values may be extracted from them in the same way they are + * with @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS events. + * + * Some tools may always be in proximity. For these tools, events of + * type @ref LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN are sent only once after @ref + * LIBINPUT_EVENT_DEVICE_ADDED, and events of type @ref + * LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT are sent only once before @ref + * LIBINPUT_EVENT_DEVICE_REMOVED. + * + * If the tool that comes into proximity supports x/y coordinates, + * libinput guarantees that both x and y are set in the proximity + * event. + * + * When a tool goes out of proximity, the value of every axis should be + * assumed to have an undefined state and any buttons that are currently held + * down on the stylus are marked as released. Button release events for + * each button that was held down on the stylus are sent before the + * proximity out event. + */ + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, + /** + * Signals that a tool has come in contact with the surface of a + * device with the @ref LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * On devices without distance proximity detection, the @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP is sent immediately after @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY for the tip down event, and + * immediately before for the tip up event. + * + * The decision when a tip touches the surface is device-dependent + * and may be derived from pressure data or other means. If the tip + * state is changed by axes changing state, the + * @ref LIBINPUT_EVENT_TABLET_TOOL_TIP event includes the changed + * axes and no additional axis event is sent for this state change. + * In other words, a caller must look at both @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS and @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP events to know the current state + * of the axes. + * + * If a button state change occurs at the same time as a tip state + * change, the order of events is device-dependent. + */ + LIBINPUT_EVENT_TABLET_TOOL_TIP, + /** + * Signals that a tool has changed a logical button state on a + * device with the @ref LIBINPUT_DEVICE_CAP_TABLET_TOOL capability. + * + * Button state changes occur on their own and do not include axis + * state changes. If button and axis state changes occur within the + * same logical hardware event, the order of the @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON and @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS event is device-specific. + */ + LIBINPUT_EVENT_TABLET_TOOL_BUTTON, + LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800, LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE, LIBINPUT_EVENT_GESTURE_SWIPE_END, @@ -274,6 +434,16 @@ struct libinput_event_pointer; struct libinput_event_touch; /** + * @ingroup event_tablet + * @struct libinput_event_tablet_tool + * + * Tablet event representing an axis update, button press, or tool update. Valid + * event types for this event are @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY and @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + */ +struct libinput_event_tablet_tool; + +/** * @defgroup event Accessing and destruction of events */ @@ -369,8 +539,6 @@ struct libinput_event_touch * libinput_event_get_touch_event(struct libinput_event *event); /** - * @ingroup 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. * @@ -384,6 +552,19 @@ 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 + * match the tablet event types, this function returns NULL. + * + * The inverse of this function is libinput_event_tablet_tool_get_base_event(). + * + * @return A tablet event, or NULL for other events + */ +struct libinput_event_tablet_tool * +libinput_event_get_tablet_tool_event(struct libinput_event *event); + +/** + * @ingroup event + * * Return the device event that is this input event. If the event type does * not match the device event types, this function returns NULL. * @@ -1170,6 +1351,737 @@ double libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event); /** + * @defgroup event_tablet Tablet events + * + * Events that come from tools on tablet devices. + */ + +/** + * @ingroup event_tablet + * + * @return The generic libinput_event of this event + */ +struct libinput_event * +libinput_event_tablet_tool_get_base_event(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Check if the x axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_x_has_changed( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Check if the y axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_y_has_changed( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Check if the pressure axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_pressure_has_changed( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Check if the distance axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * For tablet events of type @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this + * function always returns 1. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_distance_has_changed( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Check if the tilt x axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_tilt_x_has_changed( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Check if the tilt y axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_tilt_y_has_changed( + struct libinput_event_tablet_tool *event); +/** + * @ingroup event_tablet + * + * Check if the z-rotation axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_rotation_has_changed( + struct libinput_event_tablet_tool *event); +/** + * @ingroup event_tablet + * + * Check if the slider axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_slider_has_changed( + struct libinput_event_tablet_tool *event); +/** + * @ingroup event_tablet + * + * Check if the wheel axis was updated in this event. + * For tablet events that are not of type @ref + * LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref LIBINPUT_EVENT_TABLET_TOOL_TIP, or + * @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, this function returns 0. + * + * @note It is an application bug to call this function for events other + * than @ref LIBINPUT_EVENT_TABLET_TOOL_AXIS, @ref + * LIBINPUT_EVENT_TABLET_TOOL_TIP, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, or @ref + * LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return 1 if the axis was updated or 0 otherwise + */ +int +libinput_event_tablet_tool_wheel_has_changed( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the X coordinate of the tablet tool, in mm from the top left + * corner of the tablet in its current logical orientation. Use + * libinput_event_tablet_tool_get_x_transformed() for transforming the axis + * value into a different coordinate space. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_x(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the Y coordinate of the tablet tool, in mm from the top left + * corner of the tablet in its current logical orientation. Use + * libinput_event_tablet_tool_get_y_transformed() for transforming the axis + * value into a different coordinate space. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_y(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the delta between the last event and the current event. + * If the tool employs pointer acceleration, the delta returned by this + * function is the accelerated delta. + * + * This value is in screen coordinate space, the delta is to be interpreted + * like the return value of libinput_event_pointer_get_dx(). + * See @ref tablet-relative-motion for more details. + * + * @param event The libinput tablet event + * @return The relative x movement since the last event + */ +double +libinput_event_tablet_tool_get_dx(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the delta between the last event and the current event. + * If the tool employs pointer acceleration, the delta returned by this + * function is the accelerated delta. + * + * This value is in screen coordinate space, the delta is to be interpreted + * like the return value of libinput_event_pointer_get_dx(). + * See @ref tablet-relative-motion for more details. + * + * @param event The libinput tablet event + * @return The relative y movement since the last event + */ +double +libinput_event_tablet_tool_get_dy(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the current pressure being applied on the tool in use, normalized + * to the range [0, 1]. + * + * If this axis does not exist on the current tool, this function returns 0. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_pressure(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the current distance from the tablet's sensor, normalized to the + * range [0, 1]. + * + * If this axis does not exist on the current tool, this function returns 0. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_distance(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the current tilt along the X axis of the tablet's current logical + * orientation, normalized to the range [-1, 1]. + * + * If this axis does not exist on the current tool, this function returns 0. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_tilt_x(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the current tilt along the Y axis of the tablet's current logical + * orientation, normalized to the range [-1, 1]. + * + * If this axis does not exist on the current tool, this function returns 0. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_tilt_y(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the current z rotation of the tool in degrees, clockwise from the + * tool's logical neutral position. + * + * For tools of type @ref LIBINPUT_TABLET_TOOL_TYPE_MOUSE and @ref + * LIBINPUT_TABLET_TOOL_TYPE_LENS the logical neutral position is + * pointing to the current logical north of the tablet. For tools of type @ref + * LIBINPUT_TABLET_TOOL_TYPE_BRUSH, the logical neutral position is with the + * buttons pointing up. + * + * If this axis does not exist on the current tool, this function returns 0. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_rotation(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the current position of the slider on the tool, normalized to the + * range [-1, 1]. The logical zero is the neutral position of the slider, or + * the logical center of the axis. This axis is available on e.g. the Wacom + * Airbrush. + * + * If this axis does not exist on the current tool, this function returns 0. + * + * @param event The libinput tablet event + * @return The current value of the the axis + */ +double +libinput_event_tablet_tool_get_slider_position(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the delta for the wheel in degrees. + * + * @param event The libinput tablet event + * @return The delta of the wheel, in degrees, compared to the last event + * + * @see libinput_event_tablet_tool_get_wheel_delta_discrete + */ +double +libinput_event_tablet_tool_get_wheel_delta( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the delta for the wheel in discrete steps (e.g. wheel clicks). + + * @param event The libinput tablet event + * @return The delta of the wheel, in discrete steps, compared to the last event + * + * @see libinput_event_tablet_tool_get_wheel_delta_discrete + */ +int +libinput_event_tablet_tool_get_wheel_delta_discrete( + struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the current absolute x coordinate of the tablet event, transformed to + * screen coordinates. + * + * @note This function may be called for a specific axis even if + * libinput_event_tablet_tool_*_has_changed() returns 0 for that axis. + * libinput always includes all device axes in the event. + * + * @param event The libinput tablet event + * @param width The current output screen width + * @return the current absolute x coordinate transformed to a screen coordinate + */ +double +libinput_event_tablet_tool_get_x_transformed(struct libinput_event_tablet_tool *event, + uint32_t width); + +/** + * @ingroup event_tablet + * + * Return the current absolute y coordinate of the tablet event, transformed to + * screen coordinates. + * + * @note This function may be called for a specific axis even if + * libinput_event_tablet_tool_*_has_changed() returns 0 for that axis. + * libinput always includes all device axes in the event. + * + * @param event The libinput tablet event + * @param height The current output screen height + * @return the current absolute y coordinate transformed to a screen coordinate + */ +double +libinput_event_tablet_tool_get_y_transformed(struct libinput_event_tablet_tool *event, + uint32_t height); + +/** + * @ingroup event_tablet + * + * Returns the tool that was in use during this event. + * + * If the caller holds at least one reference (see + * libinput_tablet_tool_ref()), this struct is used whenever the + * tools enters proximity. Otherwise, if no references remain when the tool + * leaves proximity, the tool may be destroyed. + * + * @note Physical tool tracking requires hardware support. If unavailable, + * libinput creates one tool per type per tablet. See @ref + * tablet-serial-numbers for more details. + * + * @param event The libinput tablet event + * @return The new tool triggering this event + */ +struct libinput_tablet_tool * +libinput_event_tablet_tool_get_tool(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the new proximity state of a tool from a proximity event. + * Used to check whether or not a tool came in or out of proximity during an + * event of type @ref LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY. + * + * See @ref tablet-fake-proximity for recommendations on proximity handling. + * + * @param event The libinput tablet event + * @return The new proximity state of the tool from the event. + */ +enum libinput_tablet_tool_proximity_state +libinput_event_tablet_tool_get_proximity_state(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Returns the new tip state of a tool from a tip event. + * Used to check whether or not a tool came in contact with the tablet + * surface or left contact with the tablet surface during an + * event of type @ref LIBINPUT_EVENT_TABLET_TOOL_TIP. + * + * @param event The libinput tablet event + * @return The new tip state of the tool from the event. + */ +enum libinput_tablet_tool_tip_state +libinput_event_tablet_tool_get_tip_state(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the button that triggered this event. + * For tablet events that are not of type @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON, this + * function returns 0. + * + * @note It is an application bug to call this function for events other than + * @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return the button triggering this event + */ +uint32_t +libinput_event_tablet_tool_get_button(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the button state of the event. + * + * @note It is an application bug to call this function for events other than + * @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON. + * + * @param event The libinput tablet event + * @return the button state triggering this event + */ +enum libinput_button_state +libinput_event_tablet_tool_get_button_state(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * For the button of a @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON event, return the total + * number of buttons pressed on all devices on the associated seat after the + * the event was triggered. + * + " @note It is an application bug to call this function for events other than + * @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON. For other events, this function returns 0. + * + * @return the seat wide pressed button count for the key of this event + */ +uint32_t +libinput_event_tablet_tool_get_seat_button_count(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * @param event The libinput tablet event + * @return The event time for this event + */ +uint32_t +libinput_event_tablet_tool_get_time(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * @param event The libinput tablet event + * @return The event time for this event in microseconds + */ +uint64_t +libinput_event_tablet_tool_get_time_usec(struct libinput_event_tablet_tool *event); + +/** + * @ingroup event_tablet + * + * Return the type of tool type for a tool object, see @ref + * tablet-tool-types for details. + * + * @param tool The libinput tool + * @return The tool type for this tool object + * + * @see libinput_tablet_tool_get_tool_id + */ +enum libinput_tablet_tool_type +libinput_tablet_tool_get_type(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return the tool ID for a tool object. If nonzero, this number identifies + * the specific type of the tool with more precision than the type returned in + * libinput_tablet_tool_get_type(), see @ref tablet-tool-types. Not all + * tablets support a tool ID. + * + * Tablets known to support tool IDs include the Wacom Intuos 3, 4, 5, Wacom + * Cintiq and Wacom Intuos Pro series. + * + * @param tool The libinput tool + * @return The tool ID for this tool object or 0 if none is provided + * + * @see libinput_tablet_tool_get_type + */ +uint64_t +libinput_tablet_tool_get_tool_id(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Increment the reference count of the tool by one. A tool is destroyed + * whenever the reference count reaches 0. See libinput_tablet_tool_unref(). + * + * @param tool The tool to increment the ref count of + * @return The passed tool + * + * @see libinput_tablet_tool_unref + */ +struct libinput_tablet_tool * +libinput_tablet_tool_ref(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Decrement the reference count of the tool by one. When the reference + * count of tool reaches 0, the memory allocated for tool will be freed. + * + * @param tool The tool to decrement the ref count of + * @return NULL if the tool was destroyed otherwise the passed tool + * + * @see libinput_tablet_tool_ref + */ +struct libinput_tablet_tool * +libinput_tablet_tool_unref(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return whether the tablet tool supports pressure. + * + * @param tool The tool to check the axis capabilities of + * @return Nonzero if the axis is available, zero otherwise. + */ +int +libinput_tablet_tool_has_pressure(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return whether the tablet tool supports distance. + * + * @param tool The tool to check the axis capabilities of + * @return Nonzero if the axis is available, zero otherwise. + */ +int +libinput_tablet_tool_has_distance(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return whether the tablet tool supports tilt. + * + * @param tool The tool to check the axis capabilities of + * @return Nonzero if the axis is available, zero otherwise. + */ +int +libinput_tablet_tool_has_tilt(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return whether the tablet tool supports z-rotation. + * + * @param tool The tool to check the axis capabilities of + * @return Nonzero if the axis is available, zero otherwise. + */ +int +libinput_tablet_tool_has_rotation(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return whether the tablet tool has a slider axis. + * + * @param tool The tool to check the axis capabilities of + * @return Nonzero if the axis is available, zero otherwise. + */ +int +libinput_tablet_tool_has_slider(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return whether the tablet tool has a relative wheel. + * + * @param tool The tool to check the axis capabilities of + * @return Nonzero if the axis is available, zero otherwise. + */ +int +libinput_tablet_tool_has_wheel(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Check if a tablet tool has a button with the + * passed-in code (see linux/input.h). + * + * @param tool A tablet tool + * @param code button code to check for + * + * @return 1 if the tool supports this button code, 0 if it does not + */ +int +libinput_tablet_tool_has_button(struct libinput_tablet_tool *tool, + uint32_t code); + +/** + * @ingroup event_tablet + * + * Return nonzero if the physical tool can be uniquely identified by + * libinput, or nonzero otherwise. If a tool can be uniquely identified, + * keeping a reference to the tool allows tracking the tool across + * proximity out sequences and across compatible tablets. + * See @ref tablet-serial-numbers for more details. + * + * @param tool A tablet tool + * @return 1 if the tool can be uniquely identified, 0 otherwise. + * + * @see libinput_tablet_tool_get_serial + */ +int +libinput_tablet_tool_is_unique(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return the serial number of a tool. If the tool does not report a serial + * number, this function returns zero. See @ref tablet-serial-numbers for + * details. + * + * @param tool The libinput tool + * @return The tool serial number + * + * @see libinput_tablet_tool_is_unique + */ +uint64_t +libinput_tablet_tool_get_serial(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Return the user data associated with a tool object. libinput does + * not manage, look at, or modify this data. The caller must ensure the + * data is valid. + * + * @param tool The libinput tool + * @return The user data associated with the tool object + */ +void * +libinput_tablet_tool_get_user_data(struct libinput_tablet_tool *tool); + +/** + * @ingroup event_tablet + * + * Set the user data associated with a tool object, if any. + * + * @param tool The libinput tool + * @param user_data The user data to associate with the tool object + */ +void +libinput_tablet_tool_set_user_data(struct libinput_tablet_tool *tool, + void *user_data); + +/** * @defgroup base Initialization and manipulation of libinput contexts */ diff --git a/src/libinput.sym b/src/libinput.sym index 030dd281..a2113883 100644 --- a/src/libinput.sym +++ b/src/libinput.sym @@ -124,6 +124,7 @@ global: libinput_udev_assign_seat; libinput_udev_create_context; libinput_unref; + local: *; }; @@ -184,4 +185,52 @@ LIBINPUT_1.2 { libinput_device_config_tap_get_drag_enabled; libinput_device_config_tap_get_default_drag_enabled; libinput_device_config_tap_set_drag_enabled; + libinput_event_get_tablet_tool_event; + libinput_event_tablet_tool_x_has_changed; + libinput_event_tablet_tool_y_has_changed; + libinput_event_tablet_tool_pressure_has_changed; + libinput_event_tablet_tool_distance_has_changed; + libinput_event_tablet_tool_rotation_has_changed; + libinput_event_tablet_tool_tilt_x_has_changed; + libinput_event_tablet_tool_tilt_y_has_changed; + libinput_event_tablet_tool_wheel_has_changed; + libinput_event_tablet_tool_slider_has_changed; + libinput_event_tablet_tool_get_dx; + libinput_event_tablet_tool_get_dy; + libinput_event_tablet_tool_get_x; + libinput_event_tablet_tool_get_y; + libinput_event_tablet_tool_get_pressure; + libinput_event_tablet_tool_get_distance; + libinput_event_tablet_tool_get_tilt_x; + libinput_event_tablet_tool_get_tilt_y; + libinput_event_tablet_tool_get_rotation; + libinput_event_tablet_tool_get_slider_position; + libinput_event_tablet_tool_get_wheel_delta; + libinput_event_tablet_tool_get_wheel_delta_discrete; + libinput_event_tablet_tool_get_base_event; + libinput_event_tablet_tool_get_button; + libinput_event_tablet_tool_get_button_state; + libinput_event_tablet_tool_get_proximity_state; + libinput_event_tablet_tool_get_seat_button_count; + libinput_event_tablet_tool_get_time; + libinput_event_tablet_tool_get_tip_state; + libinput_event_tablet_tool_get_tool; + libinput_event_tablet_tool_get_x_transformed; + libinput_event_tablet_tool_get_y_transformed; + libinput_event_tablet_tool_get_time_usec; + libinput_tablet_tool_get_serial; + libinput_tablet_tool_get_tool_id; + libinput_tablet_tool_get_type; + libinput_tablet_tool_get_user_data; + libinput_tablet_tool_has_pressure; + libinput_tablet_tool_has_distance; + libinput_tablet_tool_has_rotation; + libinput_tablet_tool_has_tilt; + libinput_tablet_tool_has_wheel; + libinput_tablet_tool_has_slider; + libinput_tablet_tool_has_button; + libinput_tablet_tool_is_unique; + libinput_tablet_tool_ref; + libinput_tablet_tool_set_user_data; + libinput_tablet_tool_unref; } LIBINPUT_1.1; diff --git a/test/Makefile.am b/test/Makefile.am index 4c394bf9..885d9c6c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -22,6 +22,7 @@ liblitest_la_SOURCES = \ litest-device-bcm5974.c \ litest-device-elantech-touchpad.c \ litest-device-generic-singletouch.c \ + litest-device-huion-pentablet.c \ litest-device-keyboard.c \ litest-device-keyboard-razer-blackwidow.c \ litest-device-logitech-trackball.c \ @@ -41,8 +42,13 @@ liblitest_la_SOURCES = \ litest-device-synaptics-x1-carbon-3rd.c \ litest-device-trackpoint.c \ litest-device-touch-screen.c \ + litest-device-wacom-bamboo-tablet.c \ + litest-device-wacom-cintiq-tablet.c \ + litest-device-wacom-intuos-tablet.c \ + litest-device-wacom-isdv4-tablet.c \ litest-device-wacom-touch.c \ litest-device-wacom-intuos-finger.c \ + litest-device-waltop-tablet.c \ litest-device-wheel-only.c \ litest-device-xen-virtual-pointer.c \ litest-device-vmware-virtual-usb-mouse.c \ @@ -61,6 +67,7 @@ run_tests = \ test-touchpad \ test-touchpad-tap \ test-touchpad-buttons \ + test-tablet \ test-device \ test-gestures \ test-pointer \ @@ -105,6 +112,10 @@ test_log_SOURCES = log.c test_log_LDADD = $(TEST_LIBS) test_log_LDFLAGS = -no-install +test_tablet_SOURCES = tablet.c +test_tablet_LDADD = $(TEST_LIBS) +test_tablet_LDFLAGS = -static + test_touchpad_SOURCES = touchpad.c test_touchpad_LDADD = $(TEST_LIBS) test_touchpad_LDFLAGS = -no-install diff --git a/test/device.c b/test/device.c index 03659ace..351dffe7 100644 --- a/test/device.c +++ b/test/device.c @@ -1104,6 +1104,22 @@ START_TEST(device_udev_tag_synaptics_serial) } END_TEST +START_TEST(device_udev_tag_wacom_tablet) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + struct udev_device *d; + const char *prop; + + d = libinput_device_get_udev_device(device); + prop = udev_device_get_property_value(d, + "ID_INPUT_TABLET"); + + ck_assert_notnull(prop); + udev_device_unref(d); +} +END_TEST + START_TEST(device_nonpointer_rel) { struct libevdev_uinput *uinput; @@ -1289,16 +1305,16 @@ litest_setup_tests(void) struct range abs_range = { 0, ABS_MISC }; struct range abs_mt_range = { ABS_MT_SLOT + 1, ABS_CNT }; - litest_add("device:sendevents", device_sendevents_config, LITEST_ANY, LITEST_TOUCHPAD); - litest_add("device:sendevents", device_sendevents_config_invalid, LITEST_ANY, LITEST_ANY); - litest_add("device:sendevents", device_sendevents_config_touchpad, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("device:sendevents", device_sendevents_config_touchpad_superset, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("device:sendevents", device_sendevents_config_default, LITEST_ANY, LITEST_ANY); - litest_add("device:sendevents", device_disable, LITEST_RELATIVE, LITEST_ANY); - litest_add("device:sendevents", device_disable_touchpad, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("device:sendevents", device_disable_events_pending, LITEST_RELATIVE, LITEST_TOUCHPAD); - litest_add("device:sendevents", device_double_disable, LITEST_ANY, LITEST_ANY); - litest_add("device:sendevents", device_double_enable, LITEST_ANY, LITEST_ANY); + litest_add("device:sendevents", device_sendevents_config, LITEST_ANY, LITEST_TOUCHPAD|LITEST_TABLET); + litest_add("device:sendevents", device_sendevents_config_invalid, LITEST_ANY, LITEST_TABLET); + litest_add("device:sendevents", device_sendevents_config_touchpad, LITEST_TOUCHPAD, LITEST_TABLET); + litest_add("device:sendevents", device_sendevents_config_touchpad_superset, LITEST_TOUCHPAD, LITEST_TABLET); + litest_add("device:sendevents", device_sendevents_config_default, LITEST_ANY, LITEST_TABLET); + litest_add("device:sendevents", device_disable, LITEST_RELATIVE, LITEST_TABLET); + litest_add("device:sendevents", device_disable_touchpad, LITEST_TOUCHPAD, LITEST_TABLET); + litest_add("device:sendevents", device_disable_events_pending, LITEST_RELATIVE, LITEST_TOUCHPAD|LITEST_TABLET); + litest_add("device:sendevents", device_double_disable, LITEST_ANY, LITEST_TABLET); + litest_add("device:sendevents", device_double_enable, LITEST_ANY, LITEST_TABLET); litest_add_no_device("device:sendevents", device_reenable_syspath_changed); litest_add_no_device("device:sendevents", device_reenable_device_removed); litest_add_for_device("device:sendevents", device_disable_release_buttons, LITEST_MOUSE); @@ -1325,13 +1341,14 @@ litest_setup_tests(void) litest_add_no_device("device:invalid devices", abs_device_missing_res); litest_add_no_device("device:invalid devices", abs_mt_device_missing_res); - litest_add("device:wheel", device_wheel_only, LITEST_WHEEL, LITEST_RELATIVE|LITEST_ABSOLUTE); + litest_add("device:wheel", device_wheel_only, LITEST_WHEEL, LITEST_RELATIVE|LITEST_ABSOLUTE|LITEST_TABLET); litest_add_no_device("device:accelerometer", device_accelerometer); litest_add("device:udev tags", device_udev_tag_alps, LITEST_TOUCHPAD, LITEST_ANY); litest_add("device:udev tags", device_udev_tag_wacom, LITEST_TOUCHPAD, LITEST_ANY); litest_add("device:udev tags", device_udev_tag_apple, LITEST_TOUCHPAD, LITEST_ANY); litest_add("device:udev tags", device_udev_tag_synaptics_serial, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("device:udev tags", device_udev_tag_wacom_tablet, LITEST_TABLET, LITEST_ANY); litest_add_no_device("device:invalid rel events", device_nonpointer_rel); litest_add_no_device("device:invalid rel events", device_touchpad_rel); diff --git a/test/litest-device-huion-pentablet.c b/test/litest-device-huion-pentablet.c new file mode 100644 index 00000000..e030f208 --- /dev/null +++ b/test/litest-device-huion-pentablet.c @@ -0,0 +1,113 @@ +/* + * Copyright © 2015 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_huion_tablet_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_HUION_TABLET); + litest_set_current_device(d); +} + +static struct input_event proximity_in[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_ABS, .code = ABS_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_Y, .value = 0 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_PRESSURE: + *value = 100; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 40000, 0, 0, 157 }, + { ABS_Y, 0, 25000, 0, 0, 157 }, + { ABS_PRESSURE, 0, 2047, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x256c, + .product = 0x6e, +}; + +static int events[] = { + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_KEY, BTN_STYLUS2, + EV_MSC, MSC_SCAN, + -1, -1, +}; + +struct litest_test_device litest_huion_tablet_device = { + .type = LITEST_HUION_TABLET, + .features = LITEST_TABLET, + .shortname = "huion-tablet", + .setup = litest_huion_tablet_setup, + .interface = &interface, + + .name = "HUION PenTablet Pen", + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; diff --git a/test/litest-device-wacom-bamboo-tablet.c b/test/litest-device-wacom-bamboo-tablet.c new file mode 100644 index 00000000..d1f5fc99 --- /dev/null +++ b/test/litest-device-wacom-bamboo-tablet.c @@ -0,0 +1,119 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_wacom_bamboo_tablet_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_WACOM_BAMBOO); + litest_set_current_device(d); +} + +static struct input_event proximity_in[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_ABS, .code = ABS_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_Y, .value = 0 }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = 0 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_PRESSURE: + *value = 100; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 14720, 4, 0, 100 }, + { ABS_Y, 0, 9200, 4, 0, 100 }, + { ABS_PRESSURE, 0, 1023, 0, 0, 0 }, + { ABS_DISTANCE, 0, 31, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x56a, + .product = 0xde, + .version = 0x100, +}; + +static int events[] = { + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOOL_RUBBER, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_KEY, BTN_STYLUS2, + INPUT_PROP_MAX, INPUT_PROP_POINTER, + -1, -1, +}; + +struct litest_test_device litest_wacom_bamboo_tablet_device = { + .type = LITEST_WACOM_BAMBOO, + .features = LITEST_TABLET | LITEST_DISTANCE, + .shortname = "wacom-bamboo-tablet", + .setup = litest_wacom_bamboo_tablet_setup, + .interface = &interface, + + .name = "Wacom Bamboo 16FG 4x5 Pen", + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; diff --git a/test/litest-device-wacom-cintiq-tablet.c b/test/litest-device-wacom-cintiq-tablet.c new file mode 100644 index 00000000..4685668d --- /dev/null +++ b/test/litest-device-wacom-cintiq-tablet.c @@ -0,0 +1,158 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_wacom_cintiq_tablet_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_WACOM_CINTIQ); + litest_set_current_device(d); +} + +static struct input_event proximity_in[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MISC, .value = 2083 }, + { .type = EV_MSC, .code = MSC_SERIAL, .value = 297797542 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_ABS, .code = ABS_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_Y, .value = 0 }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = 0 }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = 0 }, + { .type = EV_ABS, .code = ABS_MISC, .value = 0 }, + { .type = EV_MSC, .code = MSC_SERIAL, .value = 297797542 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_MSC, .code = MSC_SERIAL, .value = 297797542 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_TILT_X: + case ABS_TILT_Y: + *value = 0; + return 0; + case ABS_PRESSURE: + *value = 100; + return 0; + case ABS_DISTANCE: + *value = 0; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 53020, 4, 0, 200 }, + { ABS_Y, 0, 33440, 4, 0, 200 }, + { ABS_Z, -900, 899, 0, 0, 0 }, + { ABS_RX, 0, 4096, 0, 0, 0 }, + { ABS_RY, 0, 4096, 0, 0, 0 }, + { ABS_WHEEL, 0, 1023, 0, 0, 0 }, + { ABS_PRESSURE, 0, 1023, 0, 0, 0 }, + { ABS_DISTANCE, 0, 63, 0, 0, 0 }, + { ABS_TILT_X, 0, 127, 0, 0, 0 }, + { ABS_TILT_Y, 0, 127, 0, 0, 0 }, + { ABS_MISC, 0, 0, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x56a, + .product = 0xc6, + .version = 0x113, +}; + +static int events[] = { + EV_KEY, BTN_0, + EV_KEY, BTN_1, + EV_KEY, BTN_2, + EV_KEY, BTN_3, + EV_KEY, BTN_4, + EV_KEY, BTN_5, + EV_KEY, BTN_6, + EV_KEY, BTN_7, + EV_KEY, BTN_8, + EV_KEY, BTN_9, + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOOL_RUBBER, + EV_KEY, BTN_TOOL_BRUSH, + EV_KEY, BTN_TOOL_PENCIL, + EV_KEY, BTN_TOOL_AIRBRUSH, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_KEY, BTN_STYLUS2, + EV_MSC, MSC_SERIAL, + INPUT_PROP_MAX, INPUT_PROP_DIRECT, + -1, -1, +}; + +struct litest_test_device litest_wacom_cintiq_tablet_device = { + .type = LITEST_WACOM_CINTIQ, + .features = LITEST_TABLET | LITEST_DISTANCE | LITEST_TOOL_SERIAL | LITEST_TILT, + .shortname = "wacom-cintiq-tablet", + .setup = litest_wacom_cintiq_tablet_setup, + .interface = &interface, + + .name = "Wacom Cintiq 12WX", + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; diff --git a/test/litest-device-wacom-intuos-tablet.c b/test/litest-device-wacom-intuos-tablet.c new file mode 100644 index 00000000..b31f6a5c --- /dev/null +++ b/test/litest-device-wacom-intuos-tablet.c @@ -0,0 +1,163 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_wacom_intuos_tablet_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_WACOM_INTUOS); + litest_set_current_device(d); +} + +static struct input_event proximity_in[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_MISC, .value = 1050626 }, + { .type = EV_MSC, .code = MSC_SERIAL, .value = 578837976 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_ABS, .code = ABS_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_Y, .value = 0 }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = 0 }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = 0 }, + { .type = EV_ABS, .code = ABS_MISC, .value = 0 }, + { .type = EV_MSC, .code = MSC_SERIAL, .value = 578837976 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_DISTANCE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_MSC, .code = MSC_SERIAL, .value = 578837976 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_TILT_X: + case ABS_TILT_Y: + *value = 0; + return 0; + case ABS_PRESSURE: + *value = 100; + return 0; + case ABS_DISTANCE: + *value = 0; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 44704, 4, 0, 200 }, + { ABS_Y, 0, 27940, 4, 0, 200 }, + { ABS_Z, -900, 899, 0, 0, 0 }, + { ABS_THROTTLE, -1023, 1023, 0, 0, 0 }, + { ABS_WHEEL, 0, 1023, 0, 0, 0 }, + { ABS_PRESSURE, 0, 2047, 0, 0, 0 }, + { ABS_DISTANCE, 0, 63, 0, 0, 0 }, + { ABS_TILT_X, 0, 127, 0, 0, 0 }, + { ABS_TILT_Y, 0, 127, 0, 0, 0 }, + { ABS_MISC, 0, 0, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x56a, + .product = 0x27, +}; + +static int events[] = { + EV_KEY, BTN_0, + EV_KEY, BTN_1, + EV_KEY, BTN_2, + EV_KEY, BTN_3, + EV_KEY, BTN_4, + EV_KEY, BTN_5, + EV_KEY, BTN_6, + EV_KEY, BTN_7, + EV_KEY, BTN_8, + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + EV_KEY, BTN_SIDE, + EV_KEY, BTN_EXTRA, + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOOL_RUBBER, + EV_KEY, BTN_TOOL_BRUSH, + EV_KEY, BTN_TOOL_PENCIL, + EV_KEY, BTN_TOOL_AIRBRUSH, + EV_KEY, BTN_TOOL_MOUSE, + EV_KEY, BTN_TOOL_LENS, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_KEY, BTN_STYLUS2, + EV_REL, REL_WHEEL, + EV_MSC, MSC_SERIAL, + INPUT_PROP_MAX, INPUT_PROP_POINTER, + -1, -1, +}; + +struct litest_test_device litest_wacom_intuos_tablet_device = { + .type = LITEST_WACOM_INTUOS, + .features = LITEST_TABLET | LITEST_DISTANCE | LITEST_TOOL_SERIAL | LITEST_TILT, + .shortname = "wacom-intuos-tablet", + .setup = litest_wacom_intuos_tablet_setup, + .interface = &interface, + + .name = "Wacom Intuos5 touch M Pen", + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; diff --git a/test/litest-device-wacom-isdv4-tablet.c b/test/litest-device-wacom-isdv4-tablet.c new file mode 100644 index 00000000..400fb57b --- /dev/null +++ b/test/litest-device-wacom-isdv4-tablet.c @@ -0,0 +1,112 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_wacom_isdv4_tablet_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_WACOM_ISDV4); + litest_set_current_device(d); +} + +static struct input_event proximity_in[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_PRESSURE: + *value = 100; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 27760, 4, 0, 100 }, + { ABS_Y, 0, 15694, 4, 0, 100 }, + { ABS_PRESSURE, 0, 255, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x56a, + .product = 0xe6, +}; + +static int events[] = { + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOOL_RUBBER, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_KEY, BTN_STYLUS2, + INPUT_PROP_MAX, INPUT_PROP_DIRECT, + -1, -1, +}; + +struct litest_test_device litest_wacom_isdv4_tablet_device = { + .type = LITEST_WACOM_ISDV4, + .features = LITEST_TABLET, + .shortname = "wacom-isdv4-tablet", + .setup = litest_wacom_isdv4_tablet_setup, + .interface = &interface, + + .name = "Wacom ISDv4 E6 Pen", + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; diff --git a/test/litest-device-waltop-tablet.c b/test/litest-device-waltop-tablet.c new file mode 100644 index 00000000..880ddf3e --- /dev/null +++ b/test/litest-device-waltop-tablet.c @@ -0,0 +1,241 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "litest.h" +#include "litest-int.h" + +static void litest_waltop_tablet_setup(void) +{ + struct litest_device *d = litest_create_device(LITEST_WALTOP); + litest_set_current_device(d); +} + +static struct input_event proximity_in[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 1 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event proximity_out[] = { + { .type = EV_ABS, .code = ABS_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_Y, .value = 0 }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = 0 }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = 0 }, + { .type = EV_KEY, .code = BTN_TOOL_PEN, .value = 0 }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static struct input_event motion[] = { + { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_PRESSURE, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_X, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_ABS, .code = ABS_TILT_Y, .value = LITEST_AUTO_ASSIGN }, + { .type = EV_SYN, .code = SYN_REPORT, .value = 0 }, + { .type = -1, .code = -1 }, +}; + +static int +get_axis_default(struct litest_device *d, unsigned int evcode, int32_t *value) +{ + switch (evcode) { + case ABS_TILT_X: + case ABS_TILT_Y: + *value = 0; + return 0; + case ABS_PRESSURE: + *value = 100; + return 0; + } + return 1; +} + +static struct litest_device_interface interface = { + .tablet_proximity_in_events = proximity_in, + .tablet_proximity_out_events = proximity_out, + .tablet_motion_events = motion, + + .get_axis_default = get_axis_default, +}; + +static struct input_absinfo absinfo[] = { + { ABS_X, 0, 32000, 0, 0, 0 }, + { ABS_Y, 0, 32000, 0, 0, 0 }, + { ABS_PRESSURE, 0, 2047, 0, 0, 0 }, + { ABS_TILT_X, -127, 127, 0, 0, 0 }, + { ABS_TILT_Y, -127, 127, 0, 0, 0 }, + { .value = -1 }, +}; + +static struct input_id input_id = { + .bustype = 0x3, + .vendor = 0x172f, + .product = 0x509, +}; + +static int events[] = { + EV_KEY, KEY_ESC, + EV_KEY, KEY_1, + EV_KEY, KEY_2, + EV_KEY, KEY_3, + EV_KEY, KEY_4, + EV_KEY, KEY_5, + EV_KEY, KEY_6, + EV_KEY, KEY_7, + EV_KEY, KEY_8, + EV_KEY, KEY_9, + EV_KEY, KEY_0, + EV_KEY, KEY_MINUS, + EV_KEY, KEY_EQUAL, + EV_KEY, KEY_BACKSPACE, + EV_KEY, KEY_TAB, + EV_KEY, KEY_Q, + EV_KEY, KEY_W, + EV_KEY, KEY_E, + EV_KEY, KEY_R, + EV_KEY, KEY_T, + EV_KEY, KEY_Y, + EV_KEY, KEY_U, + EV_KEY, KEY_I, + EV_KEY, KEY_O, + EV_KEY, KEY_P, + EV_KEY, KEY_LEFTBRACE, + EV_KEY, KEY_RIGHTBRACE, + EV_KEY, KEY_ENTER, + EV_KEY, KEY_LEFTCTRL, + EV_KEY, KEY_A, + EV_KEY, KEY_S, + EV_KEY, KEY_D, + EV_KEY, KEY_F, + EV_KEY, KEY_G, + EV_KEY, KEY_H, + EV_KEY, KEY_J, + EV_KEY, KEY_K, + EV_KEY, KEY_L, + EV_KEY, KEY_SEMICOLON, + EV_KEY, KEY_APOSTROPHE, + EV_KEY, KEY_GRAVE, + EV_KEY, KEY_LEFTSHIFT, + EV_KEY, KEY_BACKSLASH, + EV_KEY, KEY_Z, + EV_KEY, KEY_X, + EV_KEY, KEY_C, + EV_KEY, KEY_V, + EV_KEY, KEY_B, + EV_KEY, KEY_N, + EV_KEY, KEY_M, + EV_KEY, KEY_COMMA, + EV_KEY, KEY_DOT, + EV_KEY, KEY_SLASH, + EV_KEY, KEY_RIGHTSHIFT, + EV_KEY, KEY_KPASTERISK, + EV_KEY, KEY_LEFTALT, + EV_KEY, KEY_SPACE, + EV_KEY, KEY_CAPSLOCK, + EV_KEY, KEY_F1, + EV_KEY, KEY_F2, + EV_KEY, KEY_F3, + EV_KEY, KEY_F4, + EV_KEY, KEY_F5, + EV_KEY, KEY_F6, + EV_KEY, KEY_F7, + EV_KEY, KEY_F8, + EV_KEY, KEY_F9, + EV_KEY, KEY_F10, + EV_KEY, KEY_NUMLOCK, + EV_KEY, KEY_SCROLLLOCK, + EV_KEY, KEY_KP7, + EV_KEY, KEY_KP8, + EV_KEY, KEY_KP9, + EV_KEY, KEY_KPMINUS, + EV_KEY, KEY_KP4, + EV_KEY, KEY_KP5, + EV_KEY, KEY_KP6, + EV_KEY, KEY_KPPLUS, + EV_KEY, KEY_KP1, + EV_KEY, KEY_KP2, + EV_KEY, KEY_KP3, + EV_KEY, KEY_KP0, + EV_KEY, KEY_KPDOT, + EV_KEY, KEY_102ND, + EV_KEY, KEY_F11, + EV_KEY, KEY_F12, + EV_KEY, KEY_KPENTER, + EV_KEY, KEY_RIGHTCTRL, + EV_KEY, KEY_KPSLASH, + EV_KEY, KEY_SYSRQ, + EV_KEY, KEY_RIGHTALT, + EV_KEY, KEY_HOME, + EV_KEY, KEY_UP, + EV_KEY, KEY_PAGEUP, + EV_KEY, KEY_LEFT, + EV_KEY, KEY_RIGHT, + EV_KEY, KEY_END, + EV_KEY, KEY_DOWN, + EV_KEY, KEY_PAGEDOWN, + EV_KEY, KEY_INSERT, + EV_KEY, KEY_DELETE, + EV_KEY, KEY_MUTE, + EV_KEY, KEY_VOLUMEDOWN, + EV_KEY, KEY_VOLUMEUP, + EV_KEY, KEY_PAUSE, + EV_KEY, KEY_LEFTMETA, + EV_KEY, KEY_RIGHTMETA, + EV_KEY, KEY_COMPOSE, + EV_KEY, BTN_0, + EV_KEY, BTN_LEFT, + EV_KEY, BTN_RIGHT, + EV_KEY, BTN_MIDDLE, + EV_KEY, BTN_SIDE, + EV_KEY, BTN_EXTRA, + EV_KEY, BTN_TOOL_PEN, + EV_KEY, BTN_TOOL_RUBBER, + EV_KEY, BTN_TOUCH, + EV_KEY, BTN_STYLUS, + EV_REL, REL_HWHEEL, + EV_REL, REL_WHEEL, + EV_MSC, MSC_SERIAL, + -1, -1, +}; + +struct litest_test_device litest_waltop_tablet_device = { + .type = LITEST_WALTOP, + .features = LITEST_TABLET | LITEST_WHEEL | LITEST_TILT, + .shortname = "waltop-tablet", + .setup = litest_waltop_tablet_setup, + .interface = &interface, + + .name = " WALTOP Batteryless Tablet ", /* sic */ + .id = &input_id, + .events = events, + .absinfo = absinfo, +}; diff --git a/test/litest-int.h b/test/litest-int.h index ffa30a74..8d0308b3 100644 --- a/test/litest-int.h +++ b/test/litest-int.h @@ -97,6 +97,14 @@ struct litest_device_interface { struct input_event *touch_move_events; struct input_event *touch_up_events; + /** + * Tablet events, LITEST_AUTO_ASSIGN is allowed on event values for + * ABS_X, ABS_Y, ABS_DISTANCE and ABS_PRESSURE. + */ + struct input_event *tablet_proximity_in_events; + struct input_event *tablet_proximity_out_events; + struct input_event *tablet_motion_events; + int min[2]; /* x/y axis minimum */ int max[2]; /* x/y axis maximum */ }; diff --git a/test/litest.c b/test/litest.c index 1551dc74..99bed061 100644 --- a/test/litest.c +++ b/test/litest.c @@ -344,6 +344,10 @@ extern struct litest_test_device litest_trackpoint_device; extern struct litest_test_device litest_bcm5974_device; extern struct litest_test_device litest_mouse_device; extern struct litest_test_device litest_wacom_touch_device; +extern struct litest_test_device litest_wacom_bamboo_tablet_device; +extern struct litest_test_device litest_wacom_cintiq_tablet_device; +extern struct litest_test_device litest_wacom_intuos_tablet_device; +extern struct litest_test_device litest_wacom_isdv4_tablet_device; extern struct litest_test_device litest_alps_device; extern struct litest_test_device litest_generic_singletouch_device; extern struct litest_test_device litest_qemu_tablet_device; @@ -369,6 +373,8 @@ extern struct litest_test_device litest_mouse_gladius_device; extern struct litest_test_device litest_mouse_wheel_click_angle_device; extern struct litest_test_device litest_apple_keyboard_device; extern struct litest_test_device litest_anker_mouse_kbd_device; +extern struct litest_test_device litest_waltop_tablet_device; +extern struct litest_test_device litest_huion_tablet_device; struct litest_test_device* devices[] = { &litest_synaptics_clickpad_device, @@ -379,6 +385,10 @@ struct litest_test_device* devices[] = { &litest_bcm5974_device, &litest_mouse_device, &litest_wacom_touch_device, + &litest_wacom_bamboo_tablet_device, + &litest_wacom_cintiq_tablet_device, + &litest_wacom_intuos_tablet_device, + &litest_wacom_isdv4_tablet_device, &litest_alps_device, &litest_generic_singletouch_device, &litest_qemu_tablet_device, @@ -404,6 +414,8 @@ struct litest_test_device* devices[] = { &litest_mouse_wheel_click_angle_device, &litest_apple_keyboard_device, &litest_anker_mouse_kbd_device, + &litest_waltop_tablet_device, + &litest_huion_tablet_device, NULL, }; @@ -1492,6 +1504,82 @@ litest_touch_move_to(struct litest_device *d, litest_touch_move(d, slot, x_to, y_to); } +static int +auto_assign_tablet_value(struct litest_device *d, + const struct input_event *ev, + int x, int y, + struct axis_replacement *axes) +{ + int value = ev->value; + + if (value != LITEST_AUTO_ASSIGN || ev->type != EV_ABS) + return value; + + switch (ev->code) { + case ABS_X: + value = litest_scale(d, ABS_X, x); + break; + case ABS_Y: + value = litest_scale(d, ABS_Y, y); + break; + default: + if (!axis_replacement_value(d, axes, ev->code, &value) && + d->interface->get_axis_default) + d->interface->get_axis_default(d, ev->code, &value); + break; + } + + return value; +} + +static int +tablet_ignore_event(const struct input_event *ev, int value) +{ + return value == -1 && (ev->code == ABS_PRESSURE || ev->code == ABS_DISTANCE); +} + +void +litest_tablet_proximity_in(struct litest_device *d, int x, int y, struct axis_replacement *axes) +{ + struct input_event *ev; + + ev = d->interface->tablet_proximity_in_events; + while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { + int value = auto_assign_tablet_value(d, ev, x, y, axes); + if (!tablet_ignore_event(ev, value)) + litest_event(d, ev->type, ev->code, value); + ev++; + } +} + +void +litest_tablet_proximity_out(struct litest_device *d) +{ + struct input_event *ev; + + ev = d->interface->tablet_proximity_out_events; + while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { + int value = auto_assign_tablet_value(d, ev, -1, -1, NULL); + if (!tablet_ignore_event(ev, value)) + litest_event(d, ev->type, ev->code, value); + ev++; + } +} + +void +litest_tablet_motion(struct litest_device *d, int x, int y, struct axis_replacement *axes) +{ + struct input_event *ev; + + ev = d->interface->tablet_motion_events; + while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) { + int value = auto_assign_tablet_value(d, ev, x, y, axes); + if (!tablet_ignore_event(ev, value)) + litest_event(d, ev->type, ev->code, value); + ev++; + } +} + void litest_touch_move_two_touches(struct litest_device *d, double x0, double y0, @@ -1838,6 +1926,18 @@ litest_event_type_str(struct libinput_event *event) case LIBINPUT_EVENT_GESTURE_PINCH_END: str = "GESTURE PINCH END"; break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + str = "TABLET AXIS"; + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + str = "TABLET PROX"; + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + str = "TABLET TIP"; + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + str = "TABLET BUTTON"; + break; } return str; } @@ -1846,6 +1946,7 @@ static void litest_print_event(struct libinput_event *event) { struct libinput_event_pointer *p; + struct libinput_event_tablet_tool *t; struct libinput_device *dev; enum libinput_event_type type; double x, y; @@ -1891,6 +1992,22 @@ litest_print_event(struct libinput_event *event) LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL); fprintf(stderr, "vert %.f horiz %.2f", y, x); break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + t = libinput_event_get_tablet_tool_event(event); + fprintf(stderr, "proximity %d\n", + libinput_event_tablet_tool_get_proximity_state(t)); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + t = libinput_event_get_tablet_tool_event(event); + fprintf(stderr, "tip %d\n", + libinput_event_tablet_tool_get_tip_state(t)); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + t = libinput_event_get_tablet_tool_event(event); + fprintf(stderr, "button %d state %d\n", + libinput_event_tablet_tool_get_button(t), + libinput_event_tablet_tool_get_button_state(t)); + break; default: break; } @@ -2258,6 +2375,60 @@ litest_is_gesture_event(struct libinput_event *event, return gevent; } +struct libinput_event_tablet_tool * litest_is_tablet_event( + struct libinput_event *event, + enum libinput_event_type type) +{ + struct libinput_event_tablet_tool *tevent; + + litest_assert(event != NULL); + litest_assert_int_eq(libinput_event_get_type(event), type); + + tevent = libinput_event_get_tablet_tool_event(event); + litest_assert(tevent != NULL); + + return tevent; +} + +void +litest_assert_tablet_button_event(struct libinput *li, unsigned int button, + enum libinput_button_state state) +{ + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + enum libinput_event_type type = LIBINPUT_EVENT_TABLET_TOOL_BUTTON; + + litest_wait_for_event(li); + event = libinput_get_event(li); + + litest_assert_notnull(event); + litest_assert_int_eq(libinput_event_get_type(event), type); + tev = libinput_event_get_tablet_tool_event(event); + litest_assert_int_eq(libinput_event_tablet_tool_get_button(tev), + button); + litest_assert_int_eq(libinput_event_tablet_tool_get_button_state(tev), + state); + libinput_event_destroy(event); +} + +void litest_assert_tablet_proximity_event(struct libinput *li, + enum libinput_tablet_tool_proximity_state state) +{ + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + enum libinput_event_type type = LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY; + + litest_wait_for_event(li); + event = libinput_get_event(li); + + litest_assert_notnull(event); + litest_assert_int_eq(libinput_event_get_type(event), type); + tev = libinput_event_get_tablet_tool_event(event); + litest_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tev), + state); + libinput_event_destroy(event); +} + void litest_assert_scroll(struct libinput *li, enum libinput_pointer_axis axis, diff --git a/test/litest.h b/test/litest.h index a6fe0b5c..4d965769 100644 --- a/test/litest.h +++ b/test/litest.h @@ -35,6 +35,28 @@ #include <libinput.h> #include <math.h> +void +litest_fail_condition(const char *file, + int line, + const char *func, + const char *condition, + const char *message, + ...); +void +litest_fail_comparison_int(const char *file, + int line, + const char *func, + const char *operator, + int a, + int b, + const char *astr, + const char *bstr); +void +litest_fail_comparison_ptr(const char *file, + int line, + const char *func, + const char *comparison); + #define litest_assert(cond) \ do { \ if (!(cond)) \ @@ -111,6 +133,24 @@ #define litest_assert_ptr_notnull(a_) \ litest_assert_comparison_ptr_(a_, !=, NULL) +#define litest_assert_double_eq(a_, b_)\ + ck_assert_int_eq((int)((a_) * 256), (int)((b_) * 256)) + +#define litest_assert_double_ne(a_, b_)\ + ck_assert_int_ne((int)((a_) * 256), (int)((b_) * 256)) + +#define litest_assert_double_lt(a_, b_)\ + ck_assert_int_lt((int)((a_) * 256), (int)((b_) * 256)) + +#define litest_assert_double_le(a_, b_)\ + ck_assert_int_le((int)((a_) * 256), (int)((b_) * 256)) + +#define litest_assert_double_gt(a_, b_)\ + ck_assert_int_gt((int)((a_) * 256), (int)((b_) * 256)) + +#define litest_assert_double_ge(a_, b_)\ + ck_assert_int_ge((int)((a_) * 256), (int)((b_) * 256)) + enum litest_device_type { LITEST_NO_DEVICE = -1, LITEST_SYNAPTICS_CLICKPAD = -2, @@ -146,6 +186,12 @@ enum litest_device_type { LITEST_MOUSE_WHEEL_CLICK_ANGLE = -32, LITEST_APPLE_KEYBOARD = -33, LITEST_ANKER_MOUSE_KBD = -34, + LITEST_WACOM_BAMBOO = -35, + LITEST_WACOM_CINTIQ = -36, + LITEST_WACOM_INTUOS = -37, + LITEST_WACOM_ISDV4 = -38, + LITEST_WALTOP = -39, + LITEST_HUION_TABLET = -40, }; enum litest_device_feature { @@ -168,6 +214,10 @@ enum litest_device_feature { LITEST_PROTOCOL_A = 1 << 14, LITEST_HOVER = 1 << 15, LITEST_ELLIPSE = 1 << 16, + LITEST_TABLET = 1 << 17, + LITEST_DISTANCE = 1 << 18, + LITEST_TOOL_SERIAL = 1 << 19, + LITEST_TILT = 1 << 20, }; struct litest_device { @@ -188,9 +238,27 @@ struct litest_device { struct axis_replacement { int32_t evcode; - int32_t value; + double value; }; +static inline void litest_axis_set_value(struct axis_replacement *axes, + int code, + double value) +{ + litest_assert_double_ge(value, 0.0); + litest_assert_double_le(value, 100.0); + + while (axes->evcode != -1) { + if (axes->evcode == code) { + axes->value = value; + return; + } + axes++; + } + + litest_abort_msg("Missing axis code %d\n", code); +} + /* A loop range, resolves to: for (i = lower; i < upper; i++) */ @@ -203,28 +271,6 @@ struct libinput *litest_create_context(void); void litest_disable_log_handler(struct libinput *libinput); void litest_restore_log_handler(struct libinput *libinput); -void -litest_fail_condition(const char *file, - int line, - const char *func, - const char *condition, - const char *message, - ...); -void -litest_fail_comparison_int(const char *file, - int line, - const char *func, - const char *operator, - int a, - int b, - const char *astr, - const char *bstr); -void -litest_fail_comparison_ptr(const char *file, - int line, - const char *func, - const char *comparison); - #define litest_add(name_, func_, ...) \ _litest_add(name_, #func_, func_, __VA_ARGS__) #define litest_add_ranged(name_, func_, ...) \ @@ -336,6 +382,15 @@ void litest_touch_move_three_touches(struct litest_device *d, double x2, double y2, double dx, double dy, int steps, int sleep_ms); + +void litest_tablet_proximity_in(struct litest_device *d, + int x, int y, + struct axis_replacement *axes); +void litest_tablet_proximity_out(struct litest_device *d); +void litest_tablet_motion(struct litest_device *d, + int x, int y, + struct axis_replacement *axes); + void litest_hover_start(struct litest_device *d, unsigned int slot, double x, @@ -389,6 +444,9 @@ struct libinput_event_gesture * litest_is_gesture_event( struct libinput_event *event, enum libinput_event_type type, int nfingers); +struct libinput_event_tablet_tool * litest_is_tablet_event( + struct libinput_event *event, + enum libinput_event_type type); void litest_assert_button_event(struct libinput *li, unsigned int button, @@ -398,7 +456,11 @@ void litest_assert_scroll(struct libinput *li, int minimum_movement); void litest_assert_only_typed_events(struct libinput *li, enum libinput_event_type type); - +void litest_assert_tablet_button_event(struct libinput *li, + unsigned int button, + enum libinput_button_state state); +void litest_assert_tablet_proximity_event(struct libinput *li, + enum libinput_tablet_tool_proximity_state state); struct libevdev_uinput * litest_create_uinput_device(const char *name, struct input_id *id, ...); @@ -406,24 +468,6 @@ struct libevdev_uinput * litest_create_uinput_abs_device(const char *name, struct input_id *id, const struct input_absinfo *abs, ...); -#define litest_assert_double_eq(a_, b_)\ - ck_assert_int_eq((int)(a_ * 256), (int)(b_ * 256)) - -#define litest_assert_double_ne(a_, b_)\ - ck_assert_int_ne((int)(a_ * 256), (int)(b_ * 256)) - -#define litest_assert_double_lt(a_, b_)\ - ck_assert_int_lt((int)(a_ * 256), (int)(b_ * 256)) - -#define litest_assert_double_le(a_, b_)\ - ck_assert_int_le((int)(a_ * 256), (int)(b_ * 256)) - -#define litest_assert_double_gt(a_, b_)\ - ck_assert_int_gt((int)(a_ * 256), (int)(b_ * 256)) - -#define litest_assert_double_ge(a_, b_)\ - ck_assert_int_ge((int)(a_ * 256), (int)(b_ * 256)) - void litest_timeout_tap(void); void litest_timeout_tapndrag(void); void litest_timeout_softbuttons(void); diff --git a/test/misc.c b/test/misc.c index b962cc5a..5ae87a07 100644 --- a/test/misc.c +++ b/test/misc.c @@ -31,6 +31,7 @@ #include <unistd.h> #include "litest.h" +#include "libinput-util.h" static int open_restricted(const char *path, int flags, void *data) { @@ -132,6 +133,7 @@ START_TEST(event_conversion_device_notify) ck_assert(libinput_event_get_keyboard_event(event) == NULL); ck_assert(libinput_event_get_touch_event(event) == NULL); ck_assert(libinput_event_get_gesture_event(event) == NULL); + ck_assert(libinput_event_get_tablet_tool_event(event) == NULL); litest_restore_log_handler(li); } @@ -187,6 +189,7 @@ START_TEST(event_conversion_pointer) ck_assert(libinput_event_get_keyboard_event(event) == NULL); ck_assert(libinput_event_get_touch_event(event) == NULL); ck_assert(libinput_event_get_gesture_event(event) == NULL); + ck_assert(libinput_event_get_tablet_tool_event(event) == NULL); litest_restore_log_handler(li); } libinput_event_destroy(event); @@ -236,6 +239,7 @@ START_TEST(event_conversion_pointer_abs) ck_assert(libinput_event_get_keyboard_event(event) == NULL); ck_assert(libinput_event_get_touch_event(event) == NULL); ck_assert(libinput_event_get_gesture_event(event) == NULL); + ck_assert(libinput_event_get_tablet_tool_event(event) == NULL); litest_restore_log_handler(li); } libinput_event_destroy(event); @@ -278,6 +282,7 @@ START_TEST(event_conversion_key) ck_assert(libinput_event_get_pointer_event(event) == NULL); ck_assert(libinput_event_get_touch_event(event) == NULL); ck_assert(libinput_event_get_gesture_event(event) == NULL); + ck_assert(libinput_event_get_tablet_tool_event(event) == NULL); litest_restore_log_handler(li); } libinput_event_destroy(event); @@ -327,6 +332,7 @@ START_TEST(event_conversion_touch) ck_assert(libinput_event_get_pointer_event(event) == NULL); ck_assert(libinput_event_get_keyboard_event(event) == NULL); ck_assert(libinput_event_get_gesture_event(event) == NULL); + ck_assert(libinput_event_get_tablet_tool_event(event) == NULL); litest_restore_log_handler(li); } libinput_event_destroy(event); @@ -384,6 +390,89 @@ START_TEST(event_conversion_gesture) } END_TEST +START_TEST(event_conversion_tablet) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + int events = 0; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 50, 50, axes); + litest_tablet_motion(dev, 60, 50, axes); + litest_button_click(dev, BTN_STYLUS, true); + litest_button_click(dev, BTN_STYLUS, false); + + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + enum libinput_event_type type; + type = libinput_event_get_type(event); + + if (type >= LIBINPUT_EVENT_TABLET_TOOL_AXIS && + type <= LIBINPUT_EVENT_TABLET_TOOL_BUTTON) { + struct libinput_event_tablet_tool *t; + struct libinput_event *base; + t = libinput_event_get_tablet_tool_event(event); + base = libinput_event_tablet_tool_get_base_event(t); + ck_assert(event == base); + + events++; + + litest_disable_log_handler(li); + ck_assert(libinput_event_get_device_notify_event(event) == NULL); + ck_assert(libinput_event_get_pointer_event(event) == NULL); + ck_assert(libinput_event_get_keyboard_event(event) == NULL); + ck_assert(libinput_event_get_touch_event(event) == NULL); + litest_restore_log_handler(li); + } + libinput_event_destroy(event); + } + + ck_assert_int_gt(events, 0); +} +END_TEST + +START_TEST(bitfield_helpers) +{ + /* This value has a bit set on all of the word boundaries we want to + * test: 0, 1, 7, 8, 31, 32, and 33 + */ + unsigned char read_bitfield[] = { 0x83, 0x1, 0x0, 0x80, 0x3 }; + unsigned char write_bitfield[ARRAY_LENGTH(read_bitfield)] = {0}; + size_t i; + + /* Now check that the bitfield we wrote to came out to be the same as + * the bitfield we were writing from */ + for (i = 0; i < ARRAY_LENGTH(read_bitfield) * 8; i++) { + switch (i) { + case 0: + case 1: + case 7: + case 8: + case 31: + case 32: + case 33: + ck_assert(bit_is_set(read_bitfield, i)); + set_bit(write_bitfield, i); + break; + default: + ck_assert(!bit_is_set(read_bitfield, i)); + clear_bit(write_bitfield, i); + break; + } + } + + ck_assert_int_eq(memcmp(read_bitfield, + write_bitfield, + sizeof(read_bitfield)), + 0); +} +END_TEST + START_TEST(context_ref_counting) { struct libinput *li; @@ -784,6 +873,8 @@ litest_setup_tests(void) litest_add_for_device("events:conversion", event_conversion_key, LITEST_KEYBOARD); litest_add_for_device("events:conversion", event_conversion_touch, LITEST_WACOM_TOUCH); litest_add_for_device("events:conversion", event_conversion_gesture, LITEST_BCM5974); + litest_add_for_device("events:conversion", event_conversion_tablet, LITEST_WACOM_CINTIQ); + litest_add_no_device("bitfield_helpers", bitfield_helpers); litest_add_no_device("context:refcount", context_ref_counting); litest_add_no_device("config:status string", config_status_string); diff --git a/test/pointer.c b/test/pointer.c index ba158158..d043fa8a 100644 --- a/test/pointer.c +++ b/test/pointer.c @@ -1593,14 +1593,14 @@ litest_setup_tests(void) litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD); litest_add_no_device("pointer:button", pointer_button_auto_release); litest_add_no_device("pointer:button", pointer_seat_button_count); - litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_TABLET); litest_add("pointer:scroll", pointer_scroll_button, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_nowheel_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_WHEEL); - litest_add("pointer:scroll", pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_ANY); - litest_add("pointer:scroll", pointer_scroll_natural_enable_config, LITEST_WHEEL, LITEST_ANY); - litest_add("pointer:scroll", pointer_scroll_natural_wheel, LITEST_WHEEL, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_TABLET); + litest_add("pointer:scroll", pointer_scroll_natural_enable_config, LITEST_WHEEL, LITEST_TABLET); + litest_add("pointer:scroll", pointer_scroll_natural_wheel, LITEST_WHEEL, LITEST_TABLET); - litest_add("pointer:calibration", pointer_no_calibration, LITEST_ANY, LITEST_TOUCH|LITEST_SINGLE_TOUCH|LITEST_ABSOLUTE|LITEST_PROTOCOL_A); + litest_add("pointer:calibration", pointer_no_calibration, LITEST_ANY, LITEST_TOUCH|LITEST_SINGLE_TOUCH|LITEST_ABSOLUTE|LITEST_PROTOCOL_A|LITEST_TABLET); /* tests touchpads too */ litest_add("pointer:left-handed", pointer_left_handed_defaults, LITEST_BUTTON, LITEST_ANY); diff --git a/test/tablet.c b/test/tablet.c new file mode 100644 index 00000000..8936d577 --- /dev/null +++ b/test/tablet.c @@ -0,0 +1,3479 @@ +/* + * Copyright © 2014-2015 Red Hat, Inc. + * Copyright © 2014 Stephen Chandler "Lyude" Paul + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> + +#include <check.h> +#include <errno.h> +#include <fcntl.h> +#include <libinput.h> +#include <unistd.h> +#include <stdbool.h> + +#include "libinput-util.h" +#include "evdev-tablet.h" +#include "litest.h" + +START_TEST(tip_down_up) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 10); + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + litest_assert_empty_queue(li); + + litest_assert_empty_queue(li); + +} +END_TEST + +START_TEST(tip_down_prox_in) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 0 }, + { ABS_PRESSURE, 30 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tablet_event), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + libinput_event_destroy(event); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + +} +END_TEST + +START_TEST(tip_up_prox_out) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 0 }, + { ABS_PRESSURE, 30 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 30); + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_tablet_proximity_out(dev); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_proximity_state(tablet_event), + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + +} +END_TEST + +START_TEST(tip_up_btn_change) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 0 }, + { ABS_PRESSURE, 30 }, + { -1, -1 } + }; + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 30); + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 20, axes); + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tablet_event), + BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tablet_event), + LIBINPUT_BUTTON_STATE_PRESSED); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + /* same thing with a release at tip-up */ + litest_axis_set_value(axes, ABS_DISTANCE, 30); + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tablet_event), + BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tablet_event), + LIBINPUT_BUTTON_STATE_RELEASED); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(tip_down_btn_change) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 20, axes); + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tablet_event), + BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tablet_event), + LIBINPUT_BUTTON_STATE_PRESSED); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 30); + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 20, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + litest_drain_events(li); + + /* same thing with a release at tip-down */ + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 20, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_button(tablet_event), + BTN_STYLUS); + ck_assert_int_eq(libinput_event_tablet_tool_get_button_state(tablet_event), + LIBINPUT_BUTTON_STATE_RELEASED); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(tip_down_motion) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double x, y, last_x, last_y; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + libinput_event_destroy(event); + + /* move x/y on tip down, make sure x/y changed */ + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 20); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 70, 70, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + ck_assert(libinput_event_tablet_tool_x_has_changed(tablet_event)); + ck_assert(libinput_event_tablet_tool_y_has_changed(tablet_event)); + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + ck_assert_double_lt(last_x, x); + ck_assert_double_lt(last_y, y); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(tip_up_motion) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 0 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double x, y, last_x, last_y; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_PRESSURE, 20); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 70, 70, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + libinput_event_destroy(event); + + /* move x/y on tip up, make sure x/y changed */ + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 40, 40, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + ck_assert(libinput_event_tablet_tool_x_has_changed(tablet_event)); + ck_assert(libinput_event_tablet_tool_y_has_changed(tablet_event)); + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + ck_assert_double_ne(last_x, x); + ck_assert_double_ne(last_y, y); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(tip_state_proximity) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_axis_set_value(axes, ABS_DISTANCE, 10); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + + litest_drain_events(li); + + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tip_state_axis) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_tablet_motion(dev, 70, 70, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 40, 40, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_tablet_motion(dev, 30, 30, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_axis_set_value(axes, ABS_DISTANCE, 10); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 40, 40, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_tablet_motion(dev, 40, 80, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(tip_state_button) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 40, 40, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_DOWN); + libinput_event_destroy(event); + + litest_axis_set_value(axes, ABS_PRESSURE, 0); + litest_axis_set_value(axes, ABS_DISTANCE, 10); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 40, 40, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 0); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + ck_assert_int_eq(libinput_event_tablet_tool_get_tip_state(tablet_event), + LIBINPUT_TABLET_TOOL_TIP_UP); + libinput_event_destroy(event); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_in_out) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + bool have_tool_update = false, + have_proximity_out = false; + + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + if (libinput_event_get_type(event) == + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) { + struct libinput_tablet_tool * tool; + + have_tool_update++; + tablet_event = libinput_event_get_tablet_tool_event(event); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + ck_assert_int_eq(libinput_tablet_tool_get_type(tool), + LIBINPUT_TABLET_TOOL_TYPE_PEN); + } + libinput_event_destroy(event); + } + ck_assert(have_tool_update); + + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + if (libinput_event_get_type(event) == + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) { + struct libinput_event_tablet_tool *t = + libinput_event_get_tablet_tool_event(event); + + if (libinput_event_tablet_tool_get_proximity_state(t) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) + have_proximity_out = true; + } + + libinput_event_destroy(event); + } + ck_assert(have_proximity_out); + + /* Proximity out must not emit axis events */ + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + enum libinput_event_type type = libinput_event_get_type(event); + + ck_assert(type != LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + libinput_event_destroy(event); + } +} +END_TEST + +START_TEST(proximity_in_button_down) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_pop_event_frame(dev); + libinput_dispatch(li); + + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + litest_assert_tablet_button_event(li, + BTN_STYLUS, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_out_button_up) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_out(dev); + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_pop_event_frame(dev); + libinput_dispatch(li); + + litest_assert_tablet_button_event(li, + BTN_STYLUS, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_out_clear_buttons) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + uint32_t button; + + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + /* Test that proximity out events send button releases for any currently + * pressed stylus buttons + */ + for (button = BTN_TOUCH + 1; button <= BTN_STYLUS2; button++) { + bool button_released = false; + uint32_t event_button; + enum libinput_button_state state; + + if (!libevdev_has_event_code(dev->evdev, EV_KEY, button)) + continue; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, button, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_tablet_proximity_out(dev); + + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + tablet_event = libinput_event_get_tablet_tool_event(event); + + if (libinput_event_get_type(event) == + LIBINPUT_EVENT_TABLET_TOOL_BUTTON) { + + event_button = libinput_event_tablet_tool_get_button(tablet_event); + state = libinput_event_tablet_tool_get_button_state(tablet_event); + + if (event_button == button && + state == LIBINPUT_BUTTON_STATE_RELEASED) + button_released = true; + } + + libinput_event_destroy(event); + } + + ck_assert_msg(button_released, + "Button %s (%d) was not released.", + libevdev_event_code_get_name(EV_KEY, button), + event_button); + } + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_has_axes) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + struct libinput_tablet_tool *tool; + double x, y, + distance; + double last_x, last_y, + last_distance = 0.0, + last_tx = 0.0, last_ty = 0.0; + + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 10 }, + { ABS_TILT_Y, 10 }, + { -1, -1} + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + + ck_assert(libinput_event_tablet_tool_x_has_changed(tablet_event)); + ck_assert(libinput_event_tablet_tool_y_has_changed(tablet_event)); + + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + + litest_assert_double_ne(x, 0); + litest_assert_double_ne(y, 0); + + if (libinput_tablet_tool_has_distance(tool)) { + ck_assert(libinput_event_tablet_tool_distance_has_changed( + tablet_event)); + + distance = libinput_event_tablet_tool_get_distance(tablet_event); + litest_assert_double_ne(distance, 0); + } + + if (libinput_tablet_tool_has_tilt(tool)) { + ck_assert(libinput_event_tablet_tool_tilt_x_has_changed( + tablet_event)); + ck_assert(libinput_event_tablet_tool_tilt_y_has_changed( + tablet_event)); + + x = libinput_event_tablet_tool_get_tilt_x(tablet_event); + y = libinput_event_tablet_tool_get_tilt_y(tablet_event); + + litest_assert_double_ne(x, 0); + litest_assert_double_ne(y, 0); + } + + litest_assert_empty_queue(li); + libinput_event_destroy(event); + + litest_axis_set_value(axes, ABS_DISTANCE, 20); + litest_axis_set_value(axes, ABS_TILT_X, 15); + litest_axis_set_value(axes, ABS_TILT_Y, 25); + litest_tablet_motion(dev, 20, 30, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + if (libinput_tablet_tool_has_distance(tool)) + last_distance = libinput_event_tablet_tool_get_distance( + tablet_event); + if (libinput_tablet_tool_has_tilt(tool)) { + last_tx = libinput_event_tablet_tool_get_tilt_x(tablet_event); + last_ty = libinput_event_tablet_tool_get_tilt_y(tablet_event); + } + + libinput_event_destroy(event); + + /* Make sure that the axes are still present on proximity out */ + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + + ck_assert(!libinput_event_tablet_tool_x_has_changed(tablet_event)); + ck_assert(!libinput_event_tablet_tool_y_has_changed(tablet_event)); + + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + litest_assert_double_eq(x, last_x); + litest_assert_double_eq(y, last_y); + + if (libinput_tablet_tool_has_distance(tool)) { + ck_assert(!libinput_event_tablet_tool_distance_has_changed( + tablet_event)); + + distance = libinput_event_tablet_tool_get_distance( + tablet_event); + litest_assert_double_eq(distance, last_distance); + } + + if (libinput_tablet_tool_has_tilt(tool)) { + ck_assert(!libinput_event_tablet_tool_tilt_x_has_changed( + tablet_event)); + ck_assert(!libinput_event_tablet_tool_tilt_y_has_changed( + tablet_event)); + + x = libinput_event_tablet_tool_get_tilt_x(tablet_event); + y = libinput_event_tablet_tool_get_tilt_y(tablet_event); + + litest_assert_double_eq(x, last_tx); + litest_assert_double_eq(y, last_ty); + } + + litest_assert_empty_queue(li); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(proximity_range_enter) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 90 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_pop_event_frame(dev); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 20); + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + + litest_axis_set_value(axes, ABS_DISTANCE, 90); + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + + litest_tablet_proximity_out(dev); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_range_in_out) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 20 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_pop_event_frame(dev); + libinput_dispatch(li); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + + litest_axis_set_value(axes, ABS_DISTANCE, 90); + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + + litest_tablet_motion(dev, 30, 30, axes); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 20); + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + + litest_tablet_proximity_out(dev); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_range_button_click) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 90 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_tablet_proximity_out(dev); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_range_button_press) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 20 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_assert_tablet_button_event(li, + BTN_STYLUS, + LIBINPUT_BUTTON_STATE_PRESSED); + + litest_axis_set_value(axes, ABS_DISTANCE, 90); + litest_tablet_motion(dev, 15, 15, axes); + libinput_dispatch(li); + + /* expect fake button release */ + litest_assert_tablet_button_event(li, + BTN_STYLUS, + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_tablet_proximity_out(dev); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(proximity_range_button_release) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 90 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_assert_empty_queue(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 20); + litest_tablet_motion(dev, 15, 15, axes); + libinput_dispatch(li); + + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + /* expect fake button press */ + litest_assert_tablet_button_event(li, + BTN_STYLUS, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_empty_queue(li); + + litest_event(dev, EV_KEY, BTN_STYLUS, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + litest_assert_tablet_button_event(li, + BTN_STYLUS, + LIBINPUT_BUTTON_STATE_RELEASED); + + litest_tablet_proximity_out(dev); + litest_assert_tablet_proximity_event(li, + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); +} +END_TEST + +START_TEST(motion) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + int test_x, test_y; + double last_reported_x = 0, last_reported_y = 0; + enum libinput_event_type type; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 5, 100, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + + do { + bool x_changed, y_changed; + double reported_x, reported_y; + + tablet_event = libinput_event_get_tablet_tool_event(event); + ck_assert_int_eq(libinput_event_get_type(event), + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + x_changed = libinput_event_tablet_tool_x_has_changed( + tablet_event); + y_changed = libinput_event_tablet_tool_y_has_changed( + tablet_event); + + ck_assert(x_changed); + ck_assert(y_changed); + + reported_x = libinput_event_tablet_tool_get_x(tablet_event); + reported_y = libinput_event_tablet_tool_get_y(tablet_event); + + litest_assert_double_lt(reported_x, reported_y); + + last_reported_x = reported_x; + last_reported_y = reported_y; + + libinput_event_destroy(event); + event = libinput_get_event(li); + } while (event != NULL); + + for (test_x = 10, test_y = 90; + test_x <= 100; + test_x += 10, test_y -= 10) { + bool x_changed, y_changed; + double reported_x, reported_y; + + litest_tablet_motion(dev, test_x, test_y, axes); + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + tablet_event = libinput_event_get_tablet_tool_event(event); + type = libinput_event_get_type(event); + + if (type == LIBINPUT_EVENT_TABLET_TOOL_AXIS) { + x_changed = libinput_event_tablet_tool_x_has_changed( + tablet_event); + y_changed = libinput_event_tablet_tool_y_has_changed( + tablet_event); + + ck_assert(x_changed); + ck_assert(y_changed); + + reported_x = libinput_event_tablet_tool_get_x( + tablet_event); + reported_y = libinput_event_tablet_tool_get_y( + tablet_event); + + litest_assert_double_gt(reported_x, + last_reported_x); + litest_assert_double_lt(reported_y, + last_reported_y); + + last_reported_x = reported_x; + last_reported_y = reported_y; + } + + libinput_event_destroy(event); + } + } +} +END_TEST + +START_TEST(left_handed) +{ +#if HAVE_LIBWACOM + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + double libinput_max_x, libinput_max_y; + double last_x = -1.0, last_y = -1.0; + double x, y; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + ck_assert(libinput_device_config_left_handed_is_available(dev->libinput_device)); + + libinput_device_get_size (dev->libinput_device, + &libinput_max_x, + &libinput_max_y); + + /* Test that left-handed mode doesn't go into effect until the tool has + * left proximity of the tablet. In order to test this, we have to bring + * the tool into proximity and make sure libinput processes the + * proximity events so that it updates it's internal tablet state, and + * then try setting it to left-handed mode. */ + litest_tablet_proximity_in(dev, 0, 100, axes); + libinput_dispatch(li); + libinput_device_config_left_handed_set(dev->libinput_device, 1); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + + litest_assert_double_eq(last_x, 0); + litest_assert_double_eq(last_y, libinput_max_y); + + libinput_event_destroy(event); + + litest_tablet_motion(dev, 100, 0, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + + litest_assert_double_eq(x, libinput_max_x); + litest_assert_double_eq(y, 0); + + litest_assert_double_gt(x, last_x); + litest_assert_double_lt(y, last_y); + + libinput_event_destroy(event); + + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + /* Since we've drained the events and libinput's aware the tool is out + * of proximity, it should have finally transitioned into left-handed + * mode, so the axes should be inverted once we bring it back into + * proximity */ + litest_tablet_proximity_in(dev, 0, 100, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + + litest_assert_double_eq(last_x, libinput_max_x); + litest_assert_double_eq(last_y, 0); + + libinput_event_destroy(event); + + litest_tablet_motion(dev, 100, 0, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + tablet_event = libinput_event_get_tablet_tool_event(event); + + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + + litest_assert_double_eq(x, 0); + litest_assert_double_eq(y, libinput_max_y); + + litest_assert_double_lt(x, last_x); + litest_assert_double_gt(y, last_y); + + libinput_event_destroy(event); +#endif +} +END_TEST + +START_TEST(no_left_handed) +{ + struct litest_device *dev = litest_current_device(); + + ck_assert(!libinput_device_config_left_handed_is_available(dev->libinput_device)); +} +END_TEST + +START_TEST(left_handed_tilt) +{ +#if HAVE_LIBWACOM + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + enum libinput_config_status status; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 90 }, + { ABS_TILT_Y, 10 }, + { -1, -1 } + }; + double tx, ty; + + status = libinput_device_config_left_handed_set(dev->libinput_device, 1); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tx = libinput_event_tablet_tool_get_tilt_x(tev); + ty = libinput_event_tablet_tool_get_tilt_y(tev); + + ck_assert_double_lt(tx, 0); + ck_assert_double_gt(ty, 0); + + libinput_event_destroy(event); +#endif +} +END_TEST + +START_TEST(motion_event_state) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + int test_x, test_y; + double last_x, last_y; + + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_drain_events(li); + + /* couple of events that go left/bottom to right/top */ + for (test_x = 0, test_y = 100; test_x < 100; test_x += 10, test_y -= 10) + litest_tablet_motion(dev, test_x, test_y, axes); + + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + if (libinput_event_get_type(event) == LIBINPUT_EVENT_TABLET_TOOL_AXIS) + break; + libinput_event_destroy(event); + } + + /* pop the first event off */ + ck_assert_notnull(event); + tablet_event = libinput_event_get_tablet_tool_event(event); + ck_assert_notnull(tablet_event); + + last_x = libinput_event_tablet_tool_get_x(tablet_event); + last_y = libinput_event_tablet_tool_get_y(tablet_event); + + /* mark with a button event, then go back to bottom/left */ + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + for (test_x = 100, test_y = 0; test_x > 0; test_x -= 10, test_y += 10) + litest_tablet_motion(dev, test_x, test_y, axes); + + libinput_event_destroy(event); + libinput_dispatch(li); + ck_assert_int_eq(libinput_next_event_type(li), + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + /* we expect all events up to the button event to go from + bottom/left to top/right */ + while ((event = libinput_get_event(li))) { + double x, y; + + if (libinput_event_get_type(event) != LIBINPUT_EVENT_TABLET_TOOL_AXIS) + break; + + tablet_event = libinput_event_get_tablet_tool_event(event); + ck_assert_notnull(tablet_event); + + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + + ck_assert(x > last_x); + ck_assert(y < last_y); + + last_x = x; + last_y = y; + libinput_event_destroy(event); + } + + ck_assert_int_eq(libinput_event_get_type(event), + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(bad_distance_events) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + const struct input_absinfo *absinfo; + struct axis_replacement axes[] = { + { -1, -1 }, + }; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + absinfo = libevdev_get_abs_info(dev->evdev, ABS_DISTANCE); + ck_assert(absinfo != NULL); + + litest_event(dev, EV_ABS, ABS_DISTANCE, absinfo->maximum); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_ABS, ABS_DISTANCE, absinfo->minimum); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(normalization) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + double pressure, + tilt_vertical, + tilt_horizontal; + const struct input_absinfo *pressure_absinfo, + *tilt_vertical_absinfo, + *tilt_horizontal_absinfo; + + litest_drain_events(li); + + pressure_absinfo = libevdev_get_abs_info(dev->evdev, ABS_PRESSURE); + tilt_vertical_absinfo = libevdev_get_abs_info(dev->evdev, ABS_TILT_X); + tilt_horizontal_absinfo = libevdev_get_abs_info(dev->evdev, ABS_TILT_Y); + + /* Test minimum */ + if (pressure_absinfo != NULL) + litest_event(dev, + EV_ABS, + ABS_PRESSURE, + pressure_absinfo->minimum); + + if (tilt_vertical_absinfo != NULL) + litest_event(dev, + EV_ABS, + ABS_TILT_X, + tilt_vertical_absinfo->minimum); + + if (tilt_horizontal_absinfo != NULL) + litest_event(dev, + EV_ABS, + ABS_TILT_Y, + tilt_horizontal_absinfo->minimum); + + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + if (libinput_event_get_type(event) == LIBINPUT_EVENT_TABLET_TOOL_AXIS) { + tablet_event = libinput_event_get_tablet_tool_event(event); + + if (libinput_event_tablet_tool_pressure_has_changed( + tablet_event)) { + pressure = libinput_event_tablet_tool_get_pressure( + tablet_event); + + litest_assert_double_eq(pressure, 0); + } + + if (libinput_event_tablet_tool_tilt_x_has_changed( + tablet_event)) { + tilt_vertical = + libinput_event_tablet_tool_get_tilt_x( + tablet_event); + + litest_assert_double_eq(tilt_vertical, -1); + } + + if (libinput_event_tablet_tool_tilt_y_has_changed( + tablet_event)) { + tilt_horizontal = + libinput_event_tablet_tool_get_tilt_y( + tablet_event); + + litest_assert_double_eq(tilt_horizontal, -1); + } + } + + libinput_event_destroy(event); + } + + /* Test maximum */ + if (pressure_absinfo != NULL) + litest_event(dev, + EV_ABS, + ABS_PRESSURE, + pressure_absinfo->maximum); + + if (tilt_vertical_absinfo != NULL) + litest_event(dev, + EV_ABS, + ABS_TILT_X, + tilt_vertical_absinfo->maximum); + + if (tilt_horizontal_absinfo != NULL) + litest_event(dev, + EV_ABS, + ABS_TILT_Y, + tilt_horizontal_absinfo->maximum); + + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + while ((event = libinput_get_event(li))) { + if (libinput_event_get_type(event) == LIBINPUT_EVENT_TABLET_TOOL_AXIS) { + tablet_event = libinput_event_get_tablet_tool_event(event); + + if (libinput_event_tablet_tool_pressure_has_changed( + tablet_event)) { + pressure = libinput_event_tablet_tool_get_pressure( + tablet_event); + + litest_assert_double_eq(pressure, 1); + } + + if (libinput_event_tablet_tool_tilt_x_has_changed( + tablet_event)) { + tilt_vertical = + libinput_event_tablet_tool_get_tilt_x( + tablet_event); + + litest_assert_double_eq(tilt_vertical, 1); + } + + if (libinput_event_tablet_tool_tilt_y_has_changed( + tablet_event)) { + tilt_horizontal = + libinput_event_tablet_tool_get_tilt_y( + tablet_event); + + litest_assert_double_eq(tilt_horizontal, 1); + } + } + + libinput_event_destroy(event); + } + +} +END_TEST + +START_TEST(tool_unique) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + ck_assert(libinput_tablet_tool_is_unique(tool)); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tool_serial) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + ck_assert_uint_eq(libinput_tablet_tool_get_serial(tool), 1000); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(serial_changes_tool) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 2000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + + ck_assert_uint_eq(libinput_tablet_tool_get_serial(tool), 2000); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(invalid_serials) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, -1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + while ((event = libinput_get_event(li))) { + if (libinput_event_get_type(event) == + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) { + tablet_event = libinput_event_get_tablet_tool_event(event); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + + ck_assert_uint_eq(libinput_tablet_tool_get_serial(tool), 1000); + } + + libinput_event_destroy(event); + } +} +END_TEST + +START_TEST(tool_ref) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event_tablet_tool *tablet_event; + struct libinput_event *event; + struct libinput_tablet_tool *tool; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tablet_event); + + ck_assert_notnull(tool); + ck_assert(tool == libinput_tablet_tool_ref(tool)); + ck_assert(tool == libinput_tablet_tool_unref(tool)); + ck_assert(libinput_tablet_tool_unref(tool) == NULL); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(pad_buttons_ignored) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + int button; + + litest_drain_events(li); + + for (button = BTN_0; button < BTN_MOUSE; button++) { + litest_event(dev, EV_KEY, button, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, button, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + } + + while ((event = libinput_get_event(li))) { + ck_assert_int_ne(libinput_event_get_type(event), + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + libinput_event_destroy(event); + libinput_dispatch(li); + } + + /* same thing while in prox */ + litest_tablet_proximity_in(dev, 10, 10, axes); + for (button = BTN_0; button < BTN_MOUSE; button++) { + litest_event(dev, EV_KEY, button, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, button, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + } + litest_tablet_proximity_out(dev); + + libinput_dispatch(li); + while ((event = libinput_get_event(li))) { + ck_assert_int_ne(libinput_event_get_type(event), + LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + libinput_event_destroy(event); + libinput_dispatch(li); + } +} +END_TEST + +START_TEST(tools_with_serials) +{ + struct libinput *li = litest_create_context(); + struct litest_device *dev[2]; + struct libinput_tablet_tool *tool[2] = {0}; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + int i; + + for (i = 0; i < 2; i++) { + dev[i] = litest_add_device_with_overrides(li, + LITEST_WACOM_INTUOS, + NULL, + NULL, + NULL, + NULL); + litest_drain_events(li); + + /* WARNING: this test fails if UI_GET_SYSNAME isn't + * available or isn't used by libevdev (1.3, commit 2ff45c73). + * Put a sleep(1) here and that usually fixes it. + */ + + litest_push_event_frame(dev[i]); + litest_tablet_proximity_in(dev[i], 10, 10, NULL); + litest_event(dev[i], EV_MSC, MSC_SERIAL, 100); + litest_pop_event_frame(dev[i]); + + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool[i] = libinput_event_tablet_tool_get_tool(tev); + libinput_event_destroy(event); + } + + /* We should get the same object for both devices */ + ck_assert_notnull(tool[0]); + ck_assert_notnull(tool[1]); + ck_assert_ptr_eq(tool[0], tool[1]); + + litest_delete_device(dev[0]); + litest_delete_device(dev[1]); + libinput_unref(li); +} +END_TEST + +START_TEST(tools_without_serials) +{ + struct libinput *li = litest_create_context(); + struct litest_device *dev[2]; + struct libinput_tablet_tool *tool[2] = {0}; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + int i; + + for (i = 0; i < 2; i++) { + dev[i] = litest_add_device_with_overrides(li, + LITEST_WACOM_ISDV4, + NULL, + NULL, + NULL, + NULL); + + litest_drain_events(li); + + /* WARNING: this test fails if UI_GET_SYSNAME isn't + * available or isn't used by libevdev (1.3, commit 2ff45c73). + * Put a sleep(1) here and that usually fixes it. + */ + + litest_tablet_proximity_in(dev[i], 10, 10, NULL); + + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool[i] = libinput_event_tablet_tool_get_tool(tev); + libinput_event_destroy(event); + } + + /* We should get different tool objects for each device */ + ck_assert_notnull(tool[0]); + ck_assert_notnull(tool[1]); + ck_assert_ptr_ne(tool[0], tool[1]); + + litest_delete_device(dev[0]); + litest_delete_device(dev[1]); + libinput_unref(li); +} +END_TEST + +START_TEST(tool_capabilities) +{ + struct libinput *li = litest_create_context(); + struct litest_device *intuos; + struct litest_device *bamboo; + struct libinput_event *event; + struct libinput_event_tablet_tool *t; + struct libinput_tablet_tool *tool; + + /* The axis capabilities of a tool can differ depending on the type of + * tablet the tool is being used with */ + bamboo = litest_add_device(li, LITEST_WACOM_BAMBOO); + intuos = litest_add_device(li, LITEST_WACOM_INTUOS); + litest_drain_events(li); + + litest_event(bamboo, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(bamboo, EV_SYN, SYN_REPORT, 0); + + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(t); + + ck_assert(libinput_tablet_tool_has_pressure(tool)); + ck_assert(libinput_tablet_tool_has_distance(tool)); + ck_assert(!libinput_tablet_tool_has_tilt(tool)); + + libinput_event_destroy(event); + litest_assert_empty_queue(li); + + litest_event(intuos, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(intuos, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + t = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(t); + tool = libinput_event_tablet_tool_get_tool(t); + + ck_assert(libinput_tablet_tool_has_pressure(tool)); + ck_assert(libinput_tablet_tool_has_distance(tool)); + ck_assert(libinput_tablet_tool_has_tilt(tool)); + + libinput_event_destroy(event); + litest_assert_empty_queue(li); + + litest_delete_device(bamboo); + litest_delete_device(intuos); + libinput_unref(li); +} +END_TEST + +START_TEST(tool_in_prox_before_start) +{ + struct libinput *li; + struct litest_device *dev = litest_current_device(); + struct libinput_event *event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 0 }, + { ABS_TILT_Y, 0 }, + { -1, -1 } + }; + const char *devnode; + + litest_tablet_proximity_in(dev, 10, 10, axes); + + /* for simplicity, we create a new litest context */ + devnode = libevdev_uinput_get_devnode(dev->uinput); + li = litest_create_context(); + libinput_path_add_device(li, devnode); + + litest_wait_for_event_of_type(li, + LIBINPUT_EVENT_DEVICE_ADDED, + -1); + event = libinput_get_event(li); + libinput_event_destroy(event); + + litest_wait_for_event_of_type(li, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, + -1); + event = libinput_get_event(li); + libinput_event_destroy(event); + litest_assert_empty_queue(li); + + litest_tablet_motion(dev, 10, 20, axes); + litest_tablet_motion(dev, 30, 40, axes); + + litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + litest_assert_empty_queue(li); + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_event(dev, EV_KEY, BTN_STYLUS, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_TOOL_BUTTON); + litest_tablet_proximity_out(dev); + + litest_wait_for_event_of_type(li, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY, + -1); + libinput_unref(li); +} +END_TEST + +START_TEST(mouse_tool) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tev); + ck_assert_notnull(tool); + ck_assert_int_eq(libinput_tablet_tool_get_type(tool), + LIBINPUT_TABLET_TOOL_TYPE_MOUSE); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(mouse_buttons) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + int code; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_event(dev, EV_ABS, ABS_MISC, 0x806); /* 5-button mouse tool_id */ + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tev); + ck_assert_notnull(tool); + libinput_tablet_tool_ref(tool); + + libinput_event_destroy(event); + + for (code = BTN_LEFT; code <= BTN_TASK; code++) { + bool has_button = libevdev_has_event_code(dev->evdev, + EV_KEY, + code); + ck_assert_int_eq(!!has_button, + !!libinput_tablet_tool_has_button(tool, code)); + + if (!has_button) + continue; + + litest_event(dev, EV_KEY, code, 1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + litest_event(dev, EV_KEY, code, 0); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + litest_assert_tablet_button_event(li, + code, + LIBINPUT_BUTTON_STATE_PRESSED); + litest_assert_tablet_button_event(li, + code, + LIBINPUT_BUTTON_STATE_RELEASED); + } + + libinput_tablet_tool_unref(tool); +} +END_TEST + +START_TEST(mouse_rotation) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + int angle; + int tilt_center_x, tilt_center_y; + const struct input_absinfo *abs; + double val, old_val = 0; + + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 0 }, + { ABS_TILT_Y, 0 }, + { -1, -1 } + }; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_MOUSE)) + return; + + abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_X); + ck_assert_notnull(abs); + tilt_center_x = (abs->maximum - abs->minimum + 1) / 2; + + abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_Y); + ck_assert_notnull(abs); + tilt_center_y = (abs->maximum - abs->minimum + 1) / 2; + + litest_drain_events(li); + + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_pop_event_frame(dev); + + litest_drain_events(li); + + /* cos/sin are 90 degrees offset from the north-is-zero that + libinput uses. 175 is the CCW offset in the mouse HW */ + for (angle = 5; angle < 360; angle += 5) { + double a = (angle - 90 - 175)/180.0 * M_PI; + int x, y; + + x = cos(a) * 20 + tilt_center_x; + y = sin(a) * 20 + tilt_center_y; + + litest_event(dev, EV_ABS, ABS_TILT_X, x); + litest_event(dev, EV_ABS, ABS_TILT_Y, y); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert(libinput_event_tablet_tool_rotation_has_changed(tev)); + val = libinput_event_tablet_tool_get_rotation(tev); + + /* rounding error galore, we can't test for anything more + precise than these */ + litest_assert_double_lt(val, 360.0); + litest_assert_double_gt(val, old_val); + litest_assert_double_lt(val, angle + 5); + + old_val = val; + libinput_event_destroy(event); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(mouse_wheel) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + const struct input_absinfo *abs; + double val; + int i; + + if (!libevdev_has_event_code(dev->evdev, + EV_REL, + REL_WHEEL)) + return; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1); + litest_event(dev, EV_ABS, ABS_MISC, 0x806); /* 5-button mouse tool_id */ + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tev); + ck_assert_notnull(tool); + libinput_tablet_tool_ref(tool); + + libinput_event_destroy(event); + + ck_assert(libinput_tablet_tool_has_wheel(tool)); + + for (i = 0; i < 3; i++) { + litest_event(dev, EV_REL, REL_WHEEL, -1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert(libinput_event_tablet_tool_wheel_has_changed(tev)); + + val = libinput_event_tablet_tool_get_wheel_delta(tev); + ck_assert_int_eq(val, 15); + + val = libinput_event_tablet_tool_get_wheel_delta_discrete(tev); + ck_assert_int_eq(val, 1); + + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + } + + for (i = 2; i < 5; i++) { + /* send x/y events to make sure we reset the wheel */ + abs = libevdev_get_abs_info(dev->evdev, ABS_X); + litest_event(dev, EV_ABS, ABS_X, (abs->maximum - abs->minimum)/i); + abs = libevdev_get_abs_info(dev->evdev, ABS_Y); + litest_event(dev, EV_ABS, ABS_Y, (abs->maximum - abs->minimum)/i); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert(!libinput_event_tablet_tool_wheel_has_changed(tev)); + + val = libinput_event_tablet_tool_get_wheel_delta(tev); + ck_assert_int_eq(val, 0); + + val = libinput_event_tablet_tool_get_wheel_delta_discrete(tev); + ck_assert_int_eq(val, 0); + + libinput_event_destroy(event); + + litest_assert_empty_queue(li); + } + + libinput_tablet_tool_unref(tool); +} +END_TEST + +START_TEST(airbrush_tool) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_AIRBRUSH)) + return; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_AIRBRUSH, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tev); + + ck_assert_notnull(tool); + ck_assert_int_eq(libinput_tablet_tool_get_type(tool), + LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(airbrush_wheel) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + const struct input_absinfo *abs; + double val; + double scale; + int v; + + if (!libevdev_has_event_code(dev->evdev, + EV_KEY, + BTN_TOOL_AIRBRUSH)) + return; + + litest_drain_events(li); + + abs = libevdev_get_abs_info(dev->evdev, ABS_WHEEL); + ck_assert_notnull(abs); + + litest_event(dev, EV_KEY, BTN_TOOL_AIRBRUSH, 1); + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + /* start with non-zero */ + litest_event(dev, EV_ABS, ABS_WHEEL, 10); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + litest_drain_events(li); + + scale = abs->maximum - abs->minimum; + for (v = abs->minimum; v < abs->maximum; v += 8) { + litest_event(dev, EV_ABS, ABS_WHEEL, v); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert(libinput_event_tablet_tool_slider_has_changed(tev)); + val = libinput_event_tablet_tool_get_slider_position(tev); + + ck_assert_int_eq(val, (v - abs->minimum)/scale); + libinput_event_destroy(event); + litest_assert_empty_queue(li); + } +} +END_TEST + +START_TEST(artpen_tool) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + + if (!libevdev_has_event_code(dev->evdev, + EV_ABS, + ABS_Z)) + return; + + litest_drain_events(li); + + litest_event(dev, EV_KEY, BTN_TOOL_PEN, 1); + litest_event(dev, EV_ABS, ABS_MISC, 0x804); /* Art Pen */ + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + tool = libinput_event_tablet_tool_get_tool(tev); + ck_assert_notnull(tool); + ck_assert_int_eq(libinput_tablet_tool_get_type(tool), + LIBINPUT_TABLET_TOOL_TYPE_PEN); + ck_assert(libinput_tablet_tool_has_rotation(tool)); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(artpen_rotation) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + const struct input_absinfo *abs; + double val; + double scale; + int angle; + + if (!libevdev_has_event_code(dev->evdev, + EV_ABS, + ABS_Z)) + return; + + litest_drain_events(li); + + abs = libevdev_get_abs_info(dev->evdev, ABS_Z); + ck_assert_notnull(abs); + scale = (abs->maximum - abs->minimum + 1)/360.0; + + litest_event(dev, EV_KEY, BTN_TOOL_BRUSH, 1); + litest_event(dev, EV_ABS, ABS_MISC, 0x804); /* Art Pen */ + litest_event(dev, EV_MSC, MSC_SERIAL, 1000); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + litest_event(dev, EV_ABS, ABS_Z, abs->minimum); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + + litest_drain_events(li); + + for (angle = 8; angle < 360; angle += 8) { + int a = angle * scale + abs->minimum; + + litest_event(dev, EV_ABS, ABS_Z, a); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + ck_assert(libinput_event_tablet_tool_rotation_has_changed(tev)); + val = libinput_event_tablet_tool_get_rotation(tev); + + /* artpen has a 90 deg offset cw */ + ck_assert_int_eq(round(val), (angle + 90) % 360); + + libinput_event_destroy(event); + litest_assert_empty_queue(li); + + } +} +END_TEST + +START_TEST(tablet_time_usec) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 5, 100, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + ck_assert_int_eq(libinput_event_tablet_tool_get_time(tev), + libinput_event_tablet_tool_get_time_usec(tev) / 1000); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tablet_pressure_distance_exclusive) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 }, + }; + double pressure, distance; + + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_PRESSURE, 2); + litest_tablet_motion(dev, 70, 70, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + pressure = libinput_event_tablet_tool_get_pressure(tev); + distance = libinput_event_tablet_tool_get_distance(tev); + + ck_assert_double_ne(pressure, 0.0); + ck_assert_double_eq(distance, 0.0); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tablet_calibration_has_matrix) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *d = dev->libinput_device; + enum libinput_config_status status; + int rc; + float calibration[6] = {1, 0, 0, 0, 1, 0}; + int has_calibration; + + has_calibration = libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT); + + rc = libinput_device_config_calibration_has_matrix(d); + ck_assert_int_eq(rc, has_calibration); + rc = libinput_device_config_calibration_get_matrix(d, calibration); + ck_assert_int_eq(rc, 0); + rc = libinput_device_config_calibration_get_default_matrix(d, + calibration); + ck_assert_int_eq(rc, 0); + + status = libinput_device_config_calibration_set_matrix(d, + calibration); + if (has_calibration) + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + else + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); +} +END_TEST + +START_TEST(tablet_calibration_set_matrix_delta) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_device *d = dev->libinput_device; + enum libinput_config_status status; + float calibration[6] = {0.5, 0, 0, 0, 0.5, 0}; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + int has_calibration; + double x, y, dx, dy, mdx, mdy; + + has_calibration = libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT); + if (!has_calibration) + return; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 100, 100, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 80, 80, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + dx = libinput_event_tablet_tool_get_x(tablet_event) - x; + dy = libinput_event_tablet_tool_get_y(tablet_event) - y; + libinput_event_destroy(event); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + status = libinput_device_config_calibration_set_matrix(d, + calibration); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_tablet_proximity_in(dev, 100, 100, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + x = libinput_event_tablet_tool_get_x(tablet_event); + y = libinput_event_tablet_tool_get_y(tablet_event); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 80, 80, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + mdx = libinput_event_tablet_tool_get_x(tablet_event) - x; + mdy = libinput_event_tablet_tool_get_y(tablet_event) - y; + libinput_event_destroy(event); + litest_drain_events(li); + + ck_assert_double_gt(dx, mdx * 2 - 1); + ck_assert_double_lt(dx, mdx * 2 + 1); + ck_assert_double_gt(dy, mdy * 2 - 1); + ck_assert_double_lt(dy, mdy * 2 + 1); +} +END_TEST + +START_TEST(tablet_calibration_set_matrix) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_device *d = dev->libinput_device; + enum libinput_config_status status; + float calibration[6] = {0.5, 0, 0, 0, 1, 0}; + struct libinput_event *event; + struct libinput_event_tablet_tool *tablet_event; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + int has_calibration; + double x, y; + + has_calibration = libevdev_has_property(dev->evdev, INPUT_PROP_DIRECT); + if (!has_calibration) + return; + + litest_drain_events(li); + + status = libinput_device_config_calibration_set_matrix(d, + calibration); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_tablet_proximity_in(dev, 100, 100, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + x = libinput_event_tablet_tool_get_x_transformed(tablet_event, 100); + y = libinput_event_tablet_tool_get_y_transformed(tablet_event, 100); + libinput_event_destroy(event); + + ck_assert_double_gt(x, 49.0); + ck_assert_double_lt(x, 51.0); + ck_assert_double_gt(y, 99.0); + ck_assert_double_lt(y, 100.0); + + litest_tablet_proximity_out(dev); + libinput_dispatch(li); + litest_tablet_proximity_in(dev, 50, 50, axes); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + calibration[0] = 1; + calibration[4] = 0.5; + status = libinput_device_config_calibration_set_matrix(d, + calibration); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_tablet_proximity_in(dev, 100, 100, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tablet_event = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + x = libinput_event_tablet_tool_get_x_transformed(tablet_event, 100); + y = libinput_event_tablet_tool_get_y_transformed(tablet_event, 100); + libinput_event_destroy(event); + + ck_assert_double_gt(x, 99.0); + ck_assert_double_lt(x, 100.0); + ck_assert_double_gt(y, 49.0); + ck_assert_double_lt(y, 51.0); + + litest_tablet_proximity_out(dev); +} +END_TEST + +START_TEST(tablet_pressure_offset) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 70 }, + { ABS_PRESSURE, 20 }, + { -1, -1 }, + }; + double pressure; + + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_drain_events(li); + + /* Put the pen down, with a pressure high enough to meet the + * threshold */ + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 25); + + litest_push_event_frame(dev); + litest_tablet_motion(dev, 70, 70, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + libinput_dispatch(li); + litest_drain_events(li); + + /* Reduce pressure to just a tick over the offset, otherwise we get + * the tip up event again */ + litest_axis_set_value(axes, ABS_PRESSURE, 20.1); + litest_tablet_motion(dev, 70, 70, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + pressure = libinput_event_tablet_tool_get_pressure(tev); + + /* we can't actually get a real 0.0 because that would trigger a tip + * up. but it's close enough to zero that ck_assert_double_eq won't + * notice */ + ck_assert_double_eq(pressure, 0.0); + + libinput_event_destroy(event); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_PRESSURE, 21); + litest_tablet_motion(dev, 70, 70, axes); + + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + pressure = libinput_event_tablet_tool_get_pressure(tev); + + /* can't use the double_eq here, the pressure value is too tiny */ + ck_assert(pressure > 0.0); + ck_assert(pressure < 1.0); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tablet_pressure_offset_decrease) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 70 }, + { ABS_PRESSURE, 20 }, + { -1, -1 }, + }; + double pressure; + + /* offset 20 on prox in */ + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + /* a reduced pressure value must reduce the offset */ + litest_axis_set_value(axes, ABS_PRESSURE, 10); + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + /* a reduced pressure value must reduce the offset */ + litest_tablet_proximity_in(dev, 5, 100, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + pressure = libinput_event_tablet_tool_get_pressure(tev); + ck_assert_double_eq(pressure, 0.0); + + libinput_event_destroy(event); + litest_drain_events(li); + + /* trigger the pressure threshold */ + litest_axis_set_value(axes, ABS_PRESSURE, 15); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 70, 70, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + + pressure = libinput_event_tablet_tool_get_pressure(tev); + + /* can't use the double_eq here, the pressure value is too tiny */ + ck_assert(pressure > 0.0); + ck_assert(pressure < 1.0); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tablet_pressure_offset_increase) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 70 }, + { ABS_PRESSURE, 20 }, + { -1, -1 }, + }; + double pressure; + + /* offset 20 on first prox in */ + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_tablet_proximity_out(dev); + litest_drain_events(li); + + /* offset 30 on second prox in - must not change the offset */ + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 31); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 70, 70, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + libinput_dispatch(li); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_PRESSURE, 30); + litest_tablet_motion(dev, 70, 70, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + pressure = libinput_event_tablet_tool_get_pressure(tev); + /* can't use the double_eq here, the pressure value is too tiny */ + ck_assert(pressure > 0.0); + ck_assert(pressure < 1.0); + libinput_event_destroy(event); + + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_PRESSURE, 20); + litest_tablet_motion(dev, 70, 70, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_TIP); + + pressure = libinput_event_tablet_tool_get_pressure(tev); + + ck_assert_double_eq(pressure, 0.0); + libinput_event_destroy(event); +} +END_TEST + +static void pressure_threshold_warning(struct libinput *libinput, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ + int *warning_triggered = (int*)libinput_get_user_data(libinput); + + if (priority == LIBINPUT_LOG_PRIORITY_ERROR && + strstr(format, "pressure offset greater")) + (*warning_triggered)++; +} + +START_TEST(tablet_pressure_offset_exceed_threshold) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 70 }, + { ABS_PRESSURE, 30 }, + { -1, -1 }, + }; + double pressure; + int warning_triggered = 0; + + litest_drain_events(li); + + libinput_set_user_data(li, &warning_triggered); + + libinput_log_set_handler(li, pressure_threshold_warning); + litest_tablet_proximity_in(dev, 5, 100, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + pressure = libinput_event_tablet_tool_get_pressure(tev); + ck_assert_double_gt(pressure, 0.0); + libinput_event_destroy(event); + + ck_assert_int_eq(warning_triggered, 1); + litest_restore_log_handler(li); +} +END_TEST + +START_TEST(tablet_pressure_offset_none_for_zero_distance) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 0 }, + { ABS_PRESSURE, 20 }, + { -1, -1 }, + }; + double pressure; + + litest_drain_events(li); + + /* we're going straight to touch on proximity, make sure we don't + * offset the pressure here */ + litest_push_event_frame(dev); + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + pressure = libinput_event_tablet_tool_get_pressure(tev); + ck_assert_double_gt(pressure, 0.0); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tablet_pressure_offset_none_for_small_distance) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 20 }, + { ABS_PRESSURE, 20 }, + { -1, -1 }, + }; + double pressure; + + /* stylus too close to the tablet on the proximity in, ignore any + * pressure offset */ + litest_tablet_proximity_in(dev, 5, 100, axes); + litest_drain_events(li); + libinput_dispatch(li); + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 21); + litest_push_event_frame(dev); + litest_tablet_motion(dev, 70, 70, axes); + litest_event(dev, EV_KEY, BTN_TOUCH, 1); + litest_pop_event_frame(dev); + litest_drain_events(li); + + litest_axis_set_value(axes, ABS_PRESSURE, 20); + litest_tablet_motion(dev, 70, 70, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + pressure = libinput_event_tablet_tool_get_pressure(tev); + ck_assert_double_gt(pressure, 0.0); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tilt_available) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 80 }, + { ABS_TILT_Y, 20 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + tool = libinput_event_tablet_tool_get_tool(tev); + ck_assert(libinput_tablet_tool_has_tilt(tool)); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tilt_not_available) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct libinput_tablet_tool *tool; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 80 }, + { ABS_TILT_Y, 20 }, + { -1, -1 } + }; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + tool = libinput_event_tablet_tool_get_tool(tev); + ck_assert(!libinput_tablet_tool_has_tilt(tool)); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(tilt_x) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 90 }, + { ABS_TILT_Y, 0 }, + { -1, -1 } + }; + double tx, ty; + int tilt; + double expected_tx; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + /* 90% of the actual axis but mapped into a [-1, 1] range, so we + * expect a pos. value of 80. Rounding errors in the scaling though, + * we'll get something between 0.79 and 0.80 */ + tx = libinput_event_tablet_tool_get_tilt_x(tev); + ck_assert_double_gt(tx, 0.79); + ck_assert_double_le(tx, 0.80); + + ty = libinput_event_tablet_tool_get_tilt_y(tev); + ck_assert_double_eq(ty, -1); + + libinput_event_destroy(event); + + expected_tx = -1.0; + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 1); + + for (tilt = 0; tilt <= 100; tilt += 5) { + litest_axis_set_value(axes, ABS_TILT_X, tilt); + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + tx = libinput_event_tablet_tool_get_tilt_x(tev); + ck_assert_double_gt(tx, expected_tx - 0.1); + ck_assert_double_lt(tx, expected_tx + 0.1); + + ty = libinput_event_tablet_tool_get_tilt_y(tev); + ck_assert_double_eq(ty, -1); + + libinput_event_destroy(event); + + expected_tx += 0.1; + } + + /* the last event must reach the max */ + ck_assert_double_eq(tx, 1.0); +} +END_TEST + +START_TEST(tilt_y) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { ABS_TILT_X, 0 }, + { ABS_TILT_Y, 90 }, + { -1, -1 } + }; + double tx, ty; + int tilt; + double expected_ty; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + + /* 90% of the actual axis but mapped into a [-1, 1] range, so we + * expect a pos. value of 80. Rounding errors in the scaling though, + * we'll get something between 0.79 and 0.80 */ + ty = libinput_event_tablet_tool_get_tilt_y(tev); + ck_assert_double_gt(ty, 0.79); + ck_assert_double_le(ty, 0.80); + + tx = libinput_event_tablet_tool_get_tilt_x(tev); + ck_assert_double_eq(tx, -1); + + libinput_event_destroy(event); + + expected_ty = -1.0; + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 1); + + for (tilt = 0; tilt <= 100; tilt += 5) { + litest_axis_set_value(axes, ABS_TILT_Y, tilt); + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + ty = libinput_event_tablet_tool_get_tilt_y(tev); + ck_assert_double_gt(ty, expected_ty - 0.1); + ck_assert_double_lt(ty, expected_ty + 0.1); + + tx = libinput_event_tablet_tool_get_tilt_x(tev); + ck_assert_double_eq(tx, -1); + + libinput_event_destroy(event); + + expected_ty += 0.1; + } + + /* the last event must reach the max */ + ck_assert_double_eq(ty, 1.0); +} +END_TEST + +START_TEST(relative_no_profile) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + enum libinput_config_accel_profile profile; + enum libinput_config_status status; + uint32_t profiles; + + ck_assert(libinput_device_config_accel_is_available(device)); + + profile = libinput_device_config_accel_get_default_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + + profiles = libinput_device_config_accel_get_profiles(device); + ck_assert_int_eq(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0); + ck_assert_int_eq(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0); + + status = libinput_device_config_accel_set_profile(device, + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); + + status = libinput_device_config_accel_set_profile(device, + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED); + profile = libinput_device_config_accel_get_profile(device); + ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); +} +END_TEST + +START_TEST(relative_no_delta_prox_in) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double dx, dy; + + litest_drain_events(li); + + litest_tablet_proximity_in(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy == 0.0); + + libinput_event_destroy(event); +} +END_TEST + +START_TEST(relative_delta) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double dx, dy; + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_tablet_motion(dev, 20, 10, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx > 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx < 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 20, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy > 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy < 0.0); + libinput_event_destroy(event); +} +END_TEST + +START_TEST(relative_calibration) +{ + struct litest_device *dev = litest_current_device(); + struct libinput *li = dev->libinput; + struct libinput_event *event; + struct libinput_event_tablet_tool *tev; + struct axis_replacement axes[] = { + { ABS_DISTANCE, 10 }, + { ABS_PRESSURE, 0 }, + { -1, -1 } + }; + double dx, dy; + float calibration[] = { -1, 0, 1, 0, -1, 1 }; + enum libinput_config_status status; + + if (!libinput_device_config_calibration_has_matrix(dev->libinput_device)) + return; + + status = libinput_device_config_calibration_set_matrix( + dev->libinput_device, + calibration); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_tablet_proximity_in(dev, 10, 10, axes); + litest_drain_events(li); + + litest_tablet_motion(dev, 20, 10, axes); + libinput_dispatch(li); + + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx < 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx > 0.0); + ck_assert(dy == 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 20, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy < 0.0); + libinput_event_destroy(event); + + litest_tablet_motion(dev, 10, 10, axes); + libinput_dispatch(li); + event = libinput_get_event(li); + tev = litest_is_tablet_event(event, + LIBINPUT_EVENT_TABLET_TOOL_AXIS); + dx = libinput_event_tablet_tool_get_dx(tev); + dy = libinput_event_tablet_tool_get_dy(tev); + ck_assert(dx == 0.0); + ck_assert(dy > 0.0); + libinput_event_destroy(event); +} +END_TEST + +void +litest_setup_tests(void) +{ + litest_add("tablet:tool", tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); + litest_add_no_device("tablet:tool", tool_capabilities); + litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tool_serial", tool_unique, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); + litest_add("tablet:tool_serial", tool_serial, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); + litest_add("tablet:tool_serial", serial_changes_tool, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); + litest_add("tablet:tool_serial", invalid_serials, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY); + litest_add_no_device("tablet:tool_serial", tools_with_serials); + litest_add_no_device("tablet:tool_serial", tools_without_serials); + litest_add("tablet:proximity", proximity_out_clear_buttons, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:proximity", proximity_in_out, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:proximity", proximity_in_button_down, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:proximity", proximity_out_button_up, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:proximity", proximity_has_axes, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:proximity", bad_distance_events, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:proximity", proximity_range_enter, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:proximity", proximity_range_in_out, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:proximity", proximity_range_button_click, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:proximity", proximity_range_button_press, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:proximity", proximity_range_button_release, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + litest_add("tablet:tip", tip_down_up, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_down_prox_in, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_up_prox_out, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_down_btn_change, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_up_btn_change, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_down_motion, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_up_motion, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_state_proximity, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_state_axis, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tip", tip_state_button, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:motion", motion, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:motion", motion_event_state, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:tilt", tilt_available, LITEST_TABLET|LITEST_TILT, LITEST_ANY); + litest_add("tablet:tilt", tilt_not_available, LITEST_TABLET, LITEST_TILT); + litest_add("tablet:tilt", tilt_x, LITEST_TABLET|LITEST_TILT, LITEST_ANY); + litest_add("tablet:tilt", tilt_y, LITEST_TABLET|LITEST_TILT, LITEST_ANY); + litest_add_for_device("tablet:left_handed", left_handed, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:left_handed", left_handed_tilt, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:left_handed", no_left_handed, LITEST_WACOM_CINTIQ); + litest_add("tablet:normalization", normalization, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:pad", pad_buttons_ignored, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:mouse", mouse_tool, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:mouse", mouse_buttons, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:mouse", mouse_rotation, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:mouse", mouse_wheel, LITEST_TABLET, LITEST_WHEEL); + litest_add("tablet:airbrush", airbrush_tool, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:airbrush", airbrush_wheel, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:artpen", artpen_tool, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:artpen", artpen_rotation, LITEST_TABLET, LITEST_ANY); + + litest_add("tablet:time", tablet_time_usec, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:pressure", tablet_pressure_distance_exclusive, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY); + + litest_add("tablet:calibration", tablet_calibration_has_matrix, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:calibration", tablet_calibration_set_matrix, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:calibration", tablet_calibration_set_matrix_delta, LITEST_TABLET, LITEST_ANY); + + litest_add_for_device("tablet:pressure", tablet_pressure_offset, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:pressure", tablet_pressure_offset_decrease, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:pressure", tablet_pressure_offset_increase, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:pressure", tablet_pressure_offset_exceed_threshold, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_zero_distance, LITEST_WACOM_INTUOS); + litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_small_distance, LITEST_WACOM_INTUOS); + + litest_add("tablet:relative", relative_no_profile, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:relative", relative_no_delta_prox_in, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:relative", relative_delta, LITEST_TABLET, LITEST_ANY); + litest_add("tablet:relative", relative_calibration, LITEST_TABLET, LITEST_ANY); +} diff --git a/test/valgrind.suppressions b/test/valgrind.suppressions index 50b5c58a..b7f43499 100644 --- a/test/valgrind.suppressions +++ b/test/valgrind.suppressions @@ -14,6 +14,24 @@ fun:mtdev_put_event } { + <g_type_register_static> + Memcheck:Leak + ... + fun:g_type_register_static +} +{ + <g_type_register_static> + Memcheck:Leak + ... + fun:g_type_register_fundamental +} +{ + <g_type_register_static> + Memcheck:Leak + ... + fun:g_malloc0 +} +{ libunwind:msync_uninitialized_bytes Memcheck:Param msync(start) diff --git a/tools/event-debug.c b/tools/event-debug.c index ece58888..648111e5 100644 --- a/tools/event-debug.c +++ b/tools/event-debug.c @@ -23,6 +23,7 @@ #define _GNU_SOURCE #include <errno.h> +#include <inttypes.h> #include <fcntl.h> #include <poll.h> #include <stdio.h> @@ -108,9 +109,21 @@ print_event_header(struct libinput_event *ev) case LIBINPUT_EVENT_GESTURE_PINCH_END: type = "GESTURE_PINCH_END"; break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + type = "TABLET_AXIS"; + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + type = "TABLET_PROXIMITY"; + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + type = "TABLET_TIP"; + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + type = "TABLET_BUTTON"; + break; } - printf("%-7s %s ", libinput_device_get_sysname(dev), type); + printf("%-7s %-16s ", libinput_device_get_sysname(dev), type); } static void @@ -156,6 +169,9 @@ print_device_notify(struct libinput_event *ev) if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_GESTURE)) printf("g"); + if (libinput_device_has_capability(dev, + LIBINPUT_DEVICE_CAP_TABLET_TOOL)) + printf("T"); if (libinput_device_get_size(dev, &w, &h) == 0) printf("\tsize %.2f/%.2fmm", w, h); @@ -251,7 +267,7 @@ print_absmotion_event(struct libinput_event *ev) } static void -print_button_event(struct libinput_event *ev) +print_pointer_button_event(struct libinput_event *ev) { struct libinput_event_pointer *p = libinput_event_get_pointer_event(ev); enum libinput_button_state state; @@ -272,7 +288,34 @@ print_button_event(struct libinput_event *ev) } static void -print_axis_event(struct libinput_event *ev) +print_tablet_tip_event(struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *p = libinput_event_get_tablet_tool_event(ev); + enum libinput_tablet_tool_tip_state state; + + print_event_time(libinput_event_tablet_tool_get_time(p)); + + state = libinput_event_tablet_tool_get_tip_state(p); + printf("%s\n", state == LIBINPUT_TABLET_TOOL_TIP_DOWN ? "down" : "up"); +} + +static void +print_tablet_button_event(struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *p = libinput_event_get_tablet_tool_event(ev); + enum libinput_button_state state; + + print_event_time(libinput_event_tablet_tool_get_time(p)); + + state = libinput_event_tablet_tool_get_button_state(p); + printf("%3d %s, seat count: %u\n", + libinput_event_tablet_tool_get_button(p), + state == LIBINPUT_BUTTON_STATE_PRESSED ? "pressed" : "released", + libinput_event_tablet_tool_get_seat_button_count(p)); +} + +static void +print_pointer_axis_event(struct libinput_event *ev) { struct libinput_event_pointer *p = libinput_event_get_pointer_event(ev); double v = 0, h = 0; @@ -296,6 +339,75 @@ print_axis_event(struct libinput_event *ev) } static void +print_tablet_axes(struct libinput_event_tablet_tool *t) +{ + struct libinput_tablet_tool *tool = libinput_event_tablet_tool_get_tool(t); + double x, y; + double dist, pressure; + double rotation, slider, wheel; + double delta; + +#define changed_sym(ev, ax) \ + (libinput_event_tablet_tool_##ax##_has_changed(ev) ? "*" : "") + + x = libinput_event_tablet_tool_get_x(t); + y = libinput_event_tablet_tool_get_x(t); + printf("\t%.2f%s/%.2f%s", + x, changed_sym(t, x), + y, changed_sym(t, y)); + + if (libinput_tablet_tool_has_tilt(tool)) { + x = libinput_event_tablet_tool_get_tilt_x(t); + y = libinput_event_tablet_tool_get_tilt_y(t); + printf("\ttilt: %.2f%s/%.2f%s", + x, changed_sym(t, tilt_x), + y, changed_sym(t, tilt_y)); + } + + if (libinput_tablet_tool_has_distance(tool) || + libinput_tablet_tool_has_pressure(tool)) { + dist = libinput_event_tablet_tool_get_distance(t); + pressure = libinput_event_tablet_tool_get_pressure(t); + if (dist) + printf("\tdistance: %.2f%s", + dist, changed_sym(t, distance)); + else + printf("\tpressure: %.2f%s", + pressure, changed_sym(t, pressure)); + } + + if (libinput_tablet_tool_has_rotation(tool)) { + rotation = libinput_event_tablet_tool_get_rotation(t); + printf("\trotation: %.2f%s", + rotation, changed_sym(t, rotation)); + } + + if (libinput_tablet_tool_has_slider(tool)) { + slider = libinput_event_tablet_tool_get_slider_position(t); + printf("\tslider: %.2f%s", + slider, changed_sym(t, slider)); + } + + if (libinput_tablet_tool_has_wheel(tool)) { + wheel = libinput_event_tablet_tool_get_wheel_delta(t); + delta = libinput_event_tablet_tool_get_wheel_delta_discrete(t); + printf("\twheel: %.2f%s (%d)", + wheel, changed_sym(t, wheel), + (int)delta); + } +} + +static void +print_tablet_axis_event(struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + + print_event_time(libinput_event_tablet_tool_get_time(t)); + print_tablet_axes(t); + printf("\n"); +} + +static void print_touch_event_without_coords(struct libinput_event *ev) { struct libinput_event_touch *t = libinput_event_get_touch_event(ev); @@ -305,6 +417,96 @@ print_touch_event_without_coords(struct libinput_event *ev) } static void +print_proximity_event(struct libinput_event *ev) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + struct libinput_tablet_tool *tool = libinput_event_tablet_tool_get_tool(t); + enum libinput_tablet_tool_proximity_state state; + const char *tool_str, + *state_str; + + switch (libinput_tablet_tool_get_type(tool)) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + tool_str = "pen"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + tool_str = "eraser"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + tool_str = "brush"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + tool_str = "pencil"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + tool_str = "airbrush"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + tool_str = "mouse"; + break; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + tool_str = "lens"; + break; + default: + abort(); + } + + state = libinput_event_tablet_tool_get_proximity_state(t); + + print_event_time(libinput_event_tablet_tool_get_time(t)); + + if (state == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) { + print_tablet_axes(t); + state_str = "proximity-in"; + } else if (state == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { + state_str = "proximity-out"; + printf("\t"); + } else { + abort(); + } + + printf("\t%s (%#" PRIx64 ", id %#" PRIx64 ") %s", + tool_str, + libinput_tablet_tool_get_serial(tool), + libinput_tablet_tool_get_tool_id(tool), + state_str); + + printf("\taxes:"); + if (libinput_tablet_tool_has_distance(tool)) + printf("d"); + if (libinput_tablet_tool_has_pressure(tool)) + printf("p"); + if (libinput_tablet_tool_has_tilt(tool)) + printf("t"); + if (libinput_tablet_tool_has_rotation(tool)) + printf("r"); + if (libinput_tablet_tool_has_slider(tool)) + printf("s"); + if (libinput_tablet_tool_has_wheel(tool)) + printf("w"); + + printf("\tbtn:"); + if (libinput_tablet_tool_has_button(tool, BTN_TOUCH)) + printf("T"); + if (libinput_tablet_tool_has_button(tool, BTN_STYLUS)) + printf("S"); + if (libinput_tablet_tool_has_button(tool, BTN_STYLUS2)) + printf("S2"); + if (libinput_tablet_tool_has_button(tool, BTN_LEFT)) + printf("L"); + if (libinput_tablet_tool_has_button(tool, BTN_MIDDLE)) + printf("M"); + if (libinput_tablet_tool_has_button(tool, BTN_RIGHT)) + printf("R"); + if (libinput_tablet_tool_has_button(tool, BTN_SIDE)) + printf("Sd"); + if (libinput_tablet_tool_has_button(tool, BTN_EXTRA)) + printf("Ex"); + + printf("\n"); +} + +static void print_touch_event_with_coords(struct libinput_event *ev) { struct libinput_event_touch *t = libinput_event_get_touch_event(ev); @@ -395,10 +597,10 @@ handle_and_print_events(struct libinput *li) print_absmotion_event(ev); break; case LIBINPUT_EVENT_POINTER_BUTTON: - print_button_event(ev); + print_pointer_button_event(ev); break; case LIBINPUT_EVENT_POINTER_AXIS: - print_axis_event(ev); + print_pointer_axis_event(ev); break; case LIBINPUT_EVENT_TOUCH_DOWN: print_touch_event_with_coords(ev); @@ -433,6 +635,18 @@ handle_and_print_events(struct libinput *li) case LIBINPUT_EVENT_GESTURE_PINCH_END: print_gesture_event_without_coords(ev); break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + print_tablet_axis_event(ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + print_proximity_event(ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + print_tablet_tip_event(ev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + print_tablet_button_event(ev); + break; } libinput_event_destroy(ev); diff --git a/tools/event-gui.c b/tools/event-gui.c index 0b0e9d71..bb73d781 100644 --- a/tools/event-gui.c +++ b/tools/event-gui.c @@ -51,6 +51,10 @@ struct touch { int x, y; }; +struct point { + double x, y; +}; + struct window { GtkWidget *win; GtkWidget *area; @@ -85,6 +89,22 @@ struct window { double x, y; } pinch; + struct { + double x, y; + double x_in, y_in; + double x_down, y_down; + double x_up, y_up; + double pressure; + double distance; + double tilt_x, tilt_y; + + /* these are for the delta coordinates, but they're not + * deltas, theyconverted into + * abs positions */ + size_t ndeltas; + struct point deltas[64]; + } tool; + struct libinput_device *devices[50]; }; @@ -118,6 +138,8 @@ draw(GtkWidget *widget, cairo_t *cr, gpointer data) struct window *w = data; struct touch *t; int i, offset; + int first, last, mask; + double x, y; cairo_set_source_rgb(cr, 1, 1, 1); cairo_rectangle(cr, 0, 0, w->width, w->height); @@ -216,6 +238,61 @@ draw(GtkWidget *widget, cairo_t *cr, gpointer data) cairo_stroke(cr); cairo_restore(cr); + /* tablet tool, square for prox-in location */ + cairo_save(cr); + cairo_set_source_rgb(cr, .8, .8, .8); + if (w->tool.x_in && w->tool.y_in) { + cairo_rectangle(cr, w->tool.x_in - 15, w->tool.y_in - 15, 30, 30); + cairo_stroke(cr); + cairo_restore(cr); + cairo_save(cr); + } + + if (w->tool.x_down && w->tool.y_down) { + cairo_rectangle(cr, w->tool.x_down - 10, w->tool.y_down - 10, 20, 20); + cairo_stroke(cr); + cairo_restore(cr); + cairo_save(cr); + } + + if (w->tool.x_up && w->tool.y_up) { + cairo_rectangle(cr, w->tool.x_up - 10, w->tool.y_up - 10, 20, 20); + cairo_stroke(cr); + cairo_restore(cr); + cairo_save(cr); + } + + if (w->tool.pressure) + cairo_set_source_rgb(cr, .8, .8, .2); + + cairo_translate(cr, w->tool.x, w->tool.y); + cairo_scale(cr, 1.0 + w->tool.tilt_x, 1.0 + w->tool.tilt_y); + cairo_arc(cr, 0, 0, + 1 + 10 * max(w->tool.pressure, w->tool.distance), + 0, 2 * M_PI); + cairo_fill(cr); + cairo_restore(cr); + + /* tablet deltas */ + mask = ARRAY_LENGTH(w->tool.deltas); + first = max(w->tool.ndeltas + 1, mask) - mask; + last = w->tool.ndeltas; + + cairo_save(cr); + cairo_set_source_rgb(cr, .8, .8, .2); + + x = w->tool.deltas[first % mask].x; + y = w->tool.deltas[first % mask].y; + cairo_move_to(cr, x, y); + + for (i = first + 1; i < last; i++) { + x = w->tool.deltas[i % mask].x; + y = w->tool.deltas[i % mask].y; + cairo_line_to(cr, x, y); + } + + cairo_stroke(cr); + return TRUE; } @@ -551,6 +628,76 @@ handle_event_pinch(struct libinput_event *ev, struct window *w) } } +static void +handle_event_tablet(struct libinput_event *ev, struct window *w) +{ + struct libinput_event_tablet_tool *t = libinput_event_get_tablet_tool_event(ev); + double x, y; + struct point point; + int idx; + const int mask = ARRAY_LENGTH(w->tool.deltas); + + x = libinput_event_tablet_tool_get_x_transformed(t, w->width); + y = libinput_event_tablet_tool_get_y_transformed(t, w->height); + + switch (libinput_event_get_type(ev)) { + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + if (libinput_event_tablet_tool_get_proximity_state(t) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { + w->tool.x_in = 0; + w->tool.y_in = 0; + w->tool.x_down = 0; + w->tool.y_down = 0; + w->tool.x_up = 0; + w->tool.y_up = 0; + } else { + w->tool.x_in = x; + w->tool.y_in = y; + w->tool.ndeltas = 0; + w->tool.deltas[0].x = w->width/2; + w->tool.deltas[0].y = w->height/2; + } + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + w->tool.pressure = libinput_event_tablet_tool_get_pressure(t); + w->tool.distance = libinput_event_tablet_tool_get_distance(t); + w->tool.tilt_x = libinput_event_tablet_tool_get_tilt_x(t); + w->tool.tilt_y = libinput_event_tablet_tool_get_tilt_y(t); + if (libinput_event_tablet_tool_get_tip_state(t) == + LIBINPUT_TABLET_TOOL_TIP_DOWN) { + w->tool.x_down = x; + w->tool.y_down = y; + } else { + w->tool.x_up = x; + w->tool.y_up = y; + } + /* fallthrough */ + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + w->tool.x = x; + w->tool.y = y; + w->tool.pressure = libinput_event_tablet_tool_get_pressure(t); + w->tool.distance = libinput_event_tablet_tool_get_distance(t); + w->tool.tilt_x = libinput_event_tablet_tool_get_tilt_x(t); + w->tool.tilt_y = libinput_event_tablet_tool_get_tilt_y(t); + + /* Add the delta to the last position and store them as abs + * coordinates */ + idx = w->tool.ndeltas % mask; + point = w->tool.deltas[idx]; + + idx = (w->tool.ndeltas + 1) % mask; + point.x += libinput_event_tablet_tool_get_dx(t); + point.y += libinput_event_tablet_tool_get_dy(t); + w->tool.deltas[idx] = point; + w->tool.ndeltas++; + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + break; + default: + abort(); + } +} + static gboolean handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data) { @@ -606,6 +753,12 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data) case LIBINPUT_EVENT_GESTURE_PINCH_END: handle_event_pinch(ev, w); break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + handle_event_tablet(ev, w); + break; } libinput_event_destroy(ev); diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c index 96c5b0d8..30bb2c43 100644 --- a/tools/libinput-list-devices.c +++ b/tools/libinput-list-devices.c @@ -269,6 +269,9 @@ print_device_notify(struct libinput_event *ev) if (libinput_device_has_capability(dev, LIBINPUT_DEVICE_CAP_TOUCH)) printf("touch"); + if (libinput_device_has_capability(dev, + LIBINPUT_DEVICE_CAP_TABLET_TOOL)) + printf("tablet"); printf("\n"); printf("Tap-to-click: %s\n", tap_default(dev)); |