summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2016-01-27 16:02:47 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2016-01-27 16:02:47 +1000
commit4b4172a8e69ee576dd1f8bd6210f5fef2290ef0b (patch)
treec904b715c7277372ba0c2d846205d117f00f5a35
parentcba2278c3ab3479f8805f04dc7e80a8356e1d54d (diff)
parent62a0097d9698aefb12babc8742a389d71a0748ad (diff)
Merge branch 'tablet-support'
-rw-r--r--configure.ac11
-rw-r--r--doc/Makefile.am1
-rw-r--r--doc/absolute-axes.dox18
-rw-r--r--doc/device-configuration-via-udev.dox4
-rw-r--r--doc/normalization-of-relative-motion.dox4
-rw-r--r--doc/pointer-acceleration.dox6
-rw-r--r--doc/svg/tablet-axes.svg563
-rw-r--r--doc/svg/tablet.svg199
-rw-r--r--doc/tablet-support.dox185
-rw-r--r--src/Makefile.am4
-rw-r--r--src/evdev-mt-touchpad.c1
-rw-r--r--src/evdev-tablet.c1655
-rw-r--r--src/evdev-tablet.h204
-rw-r--r--src/evdev.c52
-rw-r--r--src/evdev.h21
-rw-r--r--src/filter.c94
-rw-r--r--src/filter.h3
-rw-r--r--src/libinput-private.h88
-rw-r--r--src/libinput-util.h35
-rw-r--r--src/libinput.c782
-rw-r--r--src/libinput.h916
-rw-r--r--src/libinput.sym49
-rw-r--r--test/Makefile.am11
-rw-r--r--test/device.c39
-rw-r--r--test/litest-device-huion-pentablet.c113
-rw-r--r--test/litest-device-wacom-bamboo-tablet.c119
-rw-r--r--test/litest-device-wacom-cintiq-tablet.c158
-rw-r--r--test/litest-device-wacom-intuos-tablet.c163
-rw-r--r--test/litest-device-wacom-isdv4-tablet.c112
-rw-r--r--test/litest-device-waltop-tablet.c241
-rw-r--r--test/litest-int.h8
-rw-r--r--test/litest.c171
-rw-r--r--test/litest.h128
-rw-r--r--test/misc.c91
-rw-r--r--test/pointer.c10
-rw-r--r--test/tablet.c3479
-rw-r--r--test/valgrind.suppressions18
-rw-r--r--tools/event-debug.c224
-rw-r--r--tools/event-gui.c153
-rw-r--r--tools/libinput-list-devices.c3
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));