libinput 1.5.0
authorJengHyun Kang <jhyuni.kang@samsung.com>
Thu, 24 Nov 2016 05:08:20 +0000 (14:08 +0900)
committerJengHyun Kang <jhyuni.kang@samsung.com>
Thu, 24 Nov 2016 05:08:20 +0000 (14:08 +0900)
Change-Id: Id16a5be2277608d870ec773209c4466c86c80a0d

154 files changed:
.gitignore
.vimdir [new file with mode: 0644]
CODING_STYLE [new file with mode: 0644]
COPYING
README.txt
configure.ac
include/linux/input.h
packaging/libinput.spec
src/Makefile.am
src/evdev-middle-button.c [new file with mode: 0644]
src/evdev-mt-touchpad-buttons.c
src/evdev-mt-touchpad-edge-scroll.c
src/evdev-mt-touchpad-gestures.c [new file with mode: 0644]
src/evdev-mt-touchpad-tap.c
src/evdev-mt-touchpad.c
src/evdev-mt-touchpad.h
src/evdev-tablet-pad-leds.c [new file with mode: 0644]
src/evdev-tablet-pad.c [new file with mode: 0644]
src/evdev-tablet-pad.h [new file with mode: 0644]
src/evdev-tablet.c [new file with mode: 0644]
src/evdev-tablet.h [new file with mode: 0644]
src/evdev.c
src/evdev.h
src/filter-private.h
src/filter.c
src/filter.h
src/libinput-private.h
src/libinput-uninstalled.pc.in [new file with mode: 0644]
src/libinput-util.c
src/libinput-util.h
src/libinput-version.h.in
src/libinput.c
src/libinput.h
src/libinput.sym
src/path.c
src/path.h
src/timer.c
src/timer.h
src/udev-seat.c [changed mode: 0755->0644]
src/udev-seat.h
test/Makefile.am
test/build-pedantic.c
test/device.c
test/gestures.c [new file with mode: 0644]
test/keyboard.c
test/litest-alps-semi-mt.c [deleted file]
test/litest-bcm5974.c [deleted file]
test/litest-device-alps-dualpoint.c [new file with mode: 0644]
test/litest-device-alps-semi-mt.c [new file with mode: 0644]
test/litest-device-anker-mouse-kbd.c [new file with mode: 0644]
test/litest-device-apple-internal-keyboard.c [new file with mode: 0644]
test/litest-device-apple-magicmouse.c [new file with mode: 0644]
test/litest-device-asus-rog-gladius.c [new file with mode: 0644]
test/litest-device-atmel-hover.c [new file with mode: 0644]
test/litest-device-bcm5974.c [new file with mode: 0644]
test/litest-device-cyborg-rat-5.c [new file with mode: 0644]
test/litest-device-elantech-touchpad.c [new file with mode: 0644]
test/litest-device-generic-singletouch.c [new file with mode: 0644]
test/litest-device-huion-pentablet.c [new file with mode: 0644]
test/litest-device-keyboard-all-codes.c [new file with mode: 0644]
test/litest-device-keyboard-razer-blackwidow.c [new file with mode: 0644]
test/litest-device-keyboard.c [new file with mode: 0644]
test/litest-device-logitech-trackball.c [new file with mode: 0644]
test/litest-device-magic-trackpad.c [new file with mode: 0644]
test/litest-device-mouse-low-dpi.c [new file with mode: 0644]
test/litest-device-mouse-roccat.c [new file with mode: 0644]
test/litest-device-mouse-wheel-click-angle.c [new file with mode: 0644]
test/litest-device-mouse.c [new file with mode: 0644]
test/litest-device-ms-surface-cover.c [new file with mode: 0644]
test/litest-device-nexus4-touch-screen.c [new file with mode: 0644]
test/litest-device-protocol-a-touch-screen.c [new file with mode: 0644]
test/litest-device-qemu-usb-tablet.c [new file with mode: 0644]
test/litest-device-synaptics-hover.c [new file with mode: 0644]
test/litest-device-synaptics-i2c.c [new file with mode: 0644]
test/litest-device-synaptics-st.c [new file with mode: 0644]
test/litest-device-synaptics-t440.c [new file with mode: 0644]
test/litest-device-synaptics-x1-carbon-3rd.c [new file with mode: 0644]
test/litest-device-synaptics.c [new file with mode: 0644]
test/litest-device-touch-screen.c [new file with mode: 0644]
test/litest-device-touchscreen-fuzz.c [new file with mode: 0644]
test/litest-device-trackpoint.c [new file with mode: 0644]
test/litest-device-vmware-virtual-usb-mouse.c [new file with mode: 0644]
test/litest-device-wacom-bamboo-tablet.c [new file with mode: 0644]
test/litest-device-wacom-cintiq-13hdt-finger.c [new file with mode: 0644]
test/litest-device-wacom-cintiq-13hdt-pad.c [new file with mode: 0644]
test/litest-device-wacom-cintiq-13hdt-pen.c [new file with mode: 0644]
test/litest-device-wacom-cintiq-24hd.c [new file with mode: 0644]
test/litest-device-wacom-cintiq-24hdt-pad.c [new file with mode: 0644]
test/litest-device-wacom-cintiq-tablet.c [new file with mode: 0644]
test/litest-device-wacom-ekr.c [new file with mode: 0644]
test/litest-device-wacom-hid4800-pen.c [new file with mode: 0644]
test/litest-device-wacom-intuos-finger.c [new file with mode: 0644]
test/litest-device-wacom-intuos-tablet.c [new file with mode: 0644]
test/litest-device-wacom-intuos3-pad.c [new file with mode: 0644]
test/litest-device-wacom-intuos5-pad.c [new file with mode: 0644]
test/litest-device-wacom-isdv4-tablet.c [new file with mode: 0644]
test/litest-device-wacom-touch.c [new file with mode: 0644]
test/litest-device-waltop-tablet.c [new file with mode: 0644]
test/litest-device-wheel-only.c [new file with mode: 0644]
test/litest-device-xen-virtual-pointer.c [new file with mode: 0644]
test/litest-device-yubikey.c [new file with mode: 0644]
test/litest-generic-singletouch.c [deleted file]
test/litest-int.h
test/litest-keyboard.c [deleted file]
test/litest-mouse.c [deleted file]
test/litest-ms-surface-cover.c [deleted file]
test/litest-qemu-usb-tablet.c [deleted file]
test/litest-selftest.c [new file with mode: 0644]
test/litest-synaptics-hover.c [deleted file]
test/litest-synaptics-st.c [deleted file]
test/litest-synaptics-t440.c [deleted file]
test/litest-synaptics-x1-carbon-3rd.c [deleted file]
test/litest-synaptics.c [deleted file]
test/litest-trackpoint.c [deleted file]
test/litest-vmware-virtual-usb-mouse.c [deleted file]
test/litest-wacom-touch.c [deleted file]
test/litest-xen-virtual-pointer.c [deleted file]
test/litest.c
test/litest.h
test/log.c
test/misc.c
test/pad.c [new file with mode: 0644]
test/path.c
test/pointer.c
test/tablet.c [new file with mode: 0644]
test/touch.c
test/touchpad-buttons.c [new file with mode: 0644]
test/touchpad-tap.c [new file with mode: 0644]
test/touchpad.c
test/trackball.c [new file with mode: 0644]
test/trackpoint.c
test/udev.c
test/valgrind.suppressions
tools/.gitignore
tools/Makefile.am
tools/event-debug.c
tools/event-gui.c
tools/libinput-debug-events.man [new file with mode: 0644]
tools/libinput-list-devices.c [new file with mode: 0644]
tools/libinput-list-devices.man [new file with mode: 0644]
tools/make-ptraccel-graphs.sh [new file with mode: 0755]
tools/ptraccel-debug.c [new file with mode: 0644]
tools/publish-doc
tools/shared.c
tools/shared.h
udev/.gitignore
udev/80-libinput-device-groups.rules [deleted file]
udev/80-libinput-device-groups.rules.in [new file with mode: 0644]
udev/80-libinput-test-device.rules [new file with mode: 0644]
udev/90-libinput-model-quirks.hwdb [new file with mode: 0644]
udev/90-libinput-model-quirks.rules.in [new file with mode: 0644]
udev/Makefile.am
udev/libinput-device-group.c
udev/libinput-model-quirks.c [new file with mode: 0644]

index 2253d4546ec7da41bb42db4173194f3054a5e5ec..ab5f9933a11ee34cb9caf53964ff34c17b60cfe4 100644 (file)
@@ -3,10 +3,20 @@
 *.la
 *.lo
 *.swp
+*~
+*.sig
+*.tar.*
+*.announce
+*.patch
+*.rej
+*.trs
+*.gcda
+*.gcno
 Makefile
 Makefile.in
 aclocal.m4
 autom4te.cache/
+compile
 config.guess
 config.h
 config.h.in
diff --git a/.vimdir b/.vimdir
new file mode 100644 (file)
index 0000000..e3ef2d8
--- /dev/null
+++ b/.vimdir
@@ -0,0 +1 @@
+set noexpandtab shiftwidth=8 cinoptions=:0,+0,(0
diff --git a/CODING_STYLE b/CODING_STYLE
new file mode 100644 (file)
index 0000000..c5336cc
--- /dev/null
@@ -0,0 +1,116 @@
+- Indentation in tabs, 8 characters wide, spaces after the tabs where
+  vertical alignment is required (see below)
+
+- Max line width 80ch, do not break up printed strings though
+
+- Break up long lines at logical groupings, one line for each logical group
+
+  int a = somelongname() +
+         someotherlongname();
+
+  if (a < 0 &&
+      (b > 20 & d < 10) &&
+      d != 0.0)
+
+
+  somelongfunctioncall(arg1,
+                      arg2,
+                      arg3);
+
+- Function declarations: return type on separate line, {} on separate line,
+  arguments broken up as above.
+
+  static inline int
+  foobar(int a, int b)
+  {
+
+  }
+
+  void
+  somenamethatiswaytoolong(int a,
+                          int b,
+                          int c)
+  {
+  }
+
+- /* comments only */, no // comments
+
+- variable_name, not VariableName or variableName. same for functions.
+
+- no typedefs of structs, enums, unions
+
+- if it generates a compiler warning, it needs to be fixed
+- if it generates a static checker warning, it needs to be fixed or
+  commented
+
+- declare variables at the top, try to keep them as local as possible.
+  Exception: if the same variable is re-used in multiple blocks, declare it
+  at the top.
+
+  int a;
+  int c;
+
+  if (foo) {
+       int b;
+
+       c = get_value();
+       usevalue(c);
+  }
+
+  if (bar) {
+       c = get_value();
+       useit(c);
+  }
+
+- do not mix function invocations and variable definitions.
+
+  wrong:
+
+  {
+         int a = foo();
+         int b = 7;
+  }
+
+  right:
+  {
+         int a;
+         int b = 7;
+
+         a = foo();
+  }
+
+  There are exceptions here, e.g. tp_libinput_context(),
+  litest_current_device()
+
+- if/else: { on the same line, no curly braces if both blocks are a single
+  statement. If either if or else block are multiple statements, both must
+  have curly braces.
+
+  if (foo) {
+       blah();
+       bar();
+  } else {
+       a = 10;
+  }
+
+- public functions MUST be doxygen-commented, use doxygen's @foo rather than
+  \foo notation
+
+- include "config.h" comes first, followed by system headers, followed by
+  external library headers, followed by internal headers.
+  sort alphabetically where it makes sense (specifically system headers)
+
+  #include "config.h"
+
+  #include <stdio.h>
+  #include <string.h>
+
+  #include <libevdev/libevdev.h>
+
+  #include "libinput-private.h"
+
+- goto jumps only to the end of the function, and only for good reasons
+  (usually cleanup). goto never jumps backwards
+
+- Use stdbool.h's bool for booleans within the library (instead of 'int').
+  Exception: the public API uses int, not bool.
diff --git a/COPYING b/COPYING
index 8bbb3c38574d8931a8dcaf1776c061288796128d..706317fbfc16047428de453809cce43e9fc09987 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -1,24 +1,34 @@
+Copyright © 2006-2009 Simon Thum
 Copyright © 2008-2012 Kristian Høgsberg
 Copyright © 2010-2012 Intel Corporation
 Copyright © 2010-2011 Benjamin Franzke
 Copyright © 2011-2012 Collabora, Ltd.
 Copyright © 2013-2014 Jonas Ådahl
-Copyright © 2013-2014 Red Hat, Inc.
+Copyright © 2013-2015 Red Hat, Inc.
 
-Permission to use, copy, modify, distribute, and sell this software and its
-documentation for any purpose is hereby granted without fee, provided that
-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.
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
 
-THE 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.
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+libinput ships a copy of the GPL-licensed Linux kernel's linux/input.h
+header file. [1] This does not make libinput GPL.
+This copy is provided to provide consistent behavior regardless which kernel
+version libinput is compiled against. The header is used during compilation
+only, libinput does not link against GPL libraries.
+
+[1] http://cgit.freedesktop.org/wayland/libinput/tree/include/linux/input.h
index c5dc61e53974d50c45e8b9ed61425008a176e483..1a7ffc5f5d6d6bff49d981d1afb247165dfe672e 100644 (file)
@@ -9,22 +9,90 @@ applications that need to directly deal with input devices.
 It provides device detection, device handling, input device event processing
 and abstraction so minimize the amount of custom input code the user of
 libinput need to provide the common set of functionality that users expect.
-
 Input event processing includes scaling touch coordinates, generating
 pointer events from touchpads, pointer acceleration, etc.
 
-libinput originates from weston, the Wayland reference compositor.
+libinput originates from
+[weston](http://cgit.freedesktop.org/wayland/weston/), the Wayland reference
+compositor.
+
+Architecture
+------------
+
+libinput is not used directly by applications, rather it is used by the
+xf86-input-libinput X.Org driver or wayland compositors. The typical
+software stack for a system running Wayland is:
+
+@dotfile libinput-stack-wayland.gv
+
+Where the Wayland compositor may be Weston, mutter, KWin, etc. Note that
+Wayland encourages the use of toolkits, so the Wayland client (your
+application) does not usually talk directly to the compositor but rather
+employs a toolkit (e.g. GTK) to do so.
+
+The simplified software stack for a system running X.Org is:
+
+@dotfile libinput-stack-xorg.gv
+
+Again, on a modern system the application does not usually talk directly to
+the X server using Xlib but rather employs a toolkit to do so.
+
+Source code
+-----------
 
 The source code of libinput can be found at:
 http://cgit.freedesktop.org/wayland/libinput
 
-For more information, visit:
+For a list of current and past releases visit:
 http://www.freedesktop.org/wiki/Software/libinput/
 
+Build instructions:
+http://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html
+
+Reporting Bugs
+--------------
+
 Bugs can be filed in the libinput component of Wayland:
 https://bugs.freedesktop.org/enter_bug.cgi?product=Wayland&component=libinput
 
-Online API documentation:
+Where possible, please provide an
+[evemu](http://www.freedesktop.org/wiki/Evemu/) recording of the input
+device and/or the event sequence in question.
+
+See @ref reporting_bugs for more info.
+
+Documentation
+-------------
+
+Developer API documentation:
 http://wayland.freedesktop.org/libinput/doc/latest/modules.html
 
+High-level documentation about libinput's features:
+http://wayland.freedesktop.org/libinput/doc/latest/pages.html
+
+Examples of how to use libinput are the debugging tools in the libinput
+repository. Developers are encouraged to look at those tools for a
+real-world (yet simple) example on how to use libinput.
+
+- A commandline debugging tool: https://cgit.freedesktop.org/wayland/libinput/tree/tools/event-debug.c
+- A GTK application that draws cursor/touch/tablet positions: https://cgit.freedesktop.org/wayland/libinput/tree/tools/event-gui.c
+
+Build instructions:
+http://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html
+
+License
+-------
+
+libinput is licensed under the MIT license.
+
+> Permission is hereby granted, free of charge, to any person obtaining a
+> copy of this software and associated documentation files (the "Software"),
+> to deal in the Software without restriction, including without limitation
+> the rights to use, copy, modify, merge, publish, distribute, sublicense,
+> and/or sell copies of the Software, and to permit persons to whom the
+> Software is furnished to do so, subject to the following conditions: [...]
+
+See the [COPYING](http://cgit.freedesktop.org/wayland/libinput/tree/COPYING)
+file for the full license information.
+
 */
index 3a94f2491437d54d98884e68e9be766465040157..47e15944c0329788e13aa67790dce40544aa952d 100644 (file)
@@ -1,7 +1,7 @@
 AC_PREREQ([2.64])
 
-m4_define([libinput_major_version], [0])
-m4_define([libinput_minor_version], [11])
+m4_define([libinput_major_version], [1])
+m4_define([libinput_minor_version], [5])
 m4_define([libinput_micro_version], [0])
 m4_define([libinput_version],
           [libinput_major_version.libinput_minor_version.libinput_micro_version])
@@ -17,6 +17,10 @@ AC_SUBST([LIBINPUT_VERSION_MINOR], [libinput_minor_version])
 AC_SUBST([LIBINPUT_VERSION_MICRO], [libinput_micro_version])
 AC_SUBST([LIBINPUT_VERSION], [libinput_version])
 
+AC_DEFINE([LIBINPUT_VERSION_MAJOR], [libinput_major_version], "libinput major version number")
+AC_DEFINE([LIBINPUT_VERSION_MINOR], [libinput_minor_version], "libinput minor version number")
+AC_DEFINE([LIBINPUT_VERSION_MICRO], [libinput_micro_version], "libinput micro version number")
+
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4])
 
@@ -25,12 +29,13 @@ AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz])
 # Before making a release, the LIBINPUT_LT_VERSION string should be
 # modified.
 # The string is of the form C:R:A.
-# - If interfaces have been changed or added, but binary compatibility has
-#   been preserved, change to C+1:0:A+1
-# - If binary compatibility has been broken (eg removed or changed interfaces)
-#   change to C+1:0:0
-# - If the interface is the same as the previous version, change to C:R+1:A
-LIBINPUT_LT_VERSION=9:0:2
+# a) If binary compatibility has been broken (eg removed or changed interfaces)
+#    change to C+1:0:0. DO NOT DO THIS! Use symbol versioning instead and
+#    do b) instead.
+# b) If interfaces have been changed or added, but binary compatibility has
+#    been preserved, change to C+1:0:A+1
+# c) If the interface is the same as the previous version, change to C:R+1:A
+LIBINPUT_LT_VERSION=20:1:10
 AC_SUBST(LIBINPUT_LT_VERSION)
 
 AM_SILENT_RULES([yes])
@@ -54,11 +59,16 @@ AC_CHECK_DECL(TFD_CLOEXEC,[],
 AC_CHECK_DECL(CLOCK_MONOTONIC,[],
              [AC_MSG_ERROR("CLOCK_MONOTONIC is needed to compile libinput")],
              [[#include <time.h>]])
+AC_CHECK_DECL(static_assert, [],
+             [AC_DEFINE(static_assert(...), [/* */], [noop static_assert() replacement]),
+              AC_MSG_RESULT([no])],
+             [[#include <assert.h>]])
 
 PKG_PROG_PKG_CONFIG()
 PKG_CHECK_MODULES(MTDEV, [mtdev >= 1.1.0])
-PKG_CHECK_MODULES(LIBUDEV, [libudev], [HAVE_UDEV="yes"], [HAVE_UDEV="no"])
+PKG_CHECK_MODULES(LIBUDEV, [libudev])
 PKG_CHECK_MODULES(LIBEVDEV, [libevdev >= 0.4])
+
 AC_CHECK_LIB([m], [atan2])
 AC_CHECK_LIB([rt], [clock_gettime])
 
@@ -163,42 +173,81 @@ if test "x$build_tests" = "xyes"; then
        fi
 
        AC_PATH_PROG(VALGRIND, [valgrind])
-fi
 
-# Check for ttrace header files
-PKG_CHECK_MODULES(TTRACE,
-               [ttrace],
-               [have_ttrace="yes"], [have_ttrace="no"])
+       AC_ARG_WITH(libunwind,
+                   AS_HELP_STRING([--without-libunwind],[Do not use libunwind]))
 
-if test "x$have_ttrace" = "xyes"; then
-       AC_DEFINE(ENABLE_TTRACE, 1, [ttrace available])
-fi
+       AS_IF([test "x$with_libunwind" != "xno"],
+               [PKG_CHECK_MODULES(LIBUNWIND,
+                         [libunwind],
+                         [HAVE_LIBUNWIND=yes],
+                         [HAVE_LIBUNWIND=no])],
+               [HAVE_LIBUNWIND=no])
 
-# Check for udev property
-if test "x$HAVE_UDEV" = "xyes"; then
-       AC_SEARCH_LIBS([input_set_default_property], [udev],
-               [have_udev_property="yes"],
-               [have_udev_property="no"])
+       AS_IF([test "x$HAVE_LIBUNWIND" = "xyes"],
+               [AC_DEFINE(HAVE_LIBUNWIND, 1, [Have libunwind support])],
+               [AS_IF([test "x$with_libunwind" = "xyes"],
+                       [AC_MSG_ERROR([libunwind requested but not found])])])
 
-       if test "x$have_udev_property" = "xyes"; then
-               AC_DEFINE(HAVE_INPUT_SET_DEFAULT_PROPERTY, 1,
-                       [have input_set_default_property function])
+       AC_PATH_PROG(ADDR2LINE, [addr2line])
+       if test "x$ADDR2LINE" != "x"; then
+               AC_DEFINE_UNQUOTED(HAVE_ADDR2LINE, 1, [addr2line found])
+               AC_DEFINE_UNQUOTED(ADDR2LINE, ["$ADDR2LINE"], [Path to addr2line])
        fi
 fi
+AM_CONDITIONAL(HAVE_LIBUNWIND, [test "x$HAVE_LIBUNWIND" = xyes])
+
+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])
+
+       OLD_LIBS=$LIBS
+       OLD_CFLAGS=$CFLAGS
+       LIBS="$LIBS $LIBWACOM_LIBS"
+       CFLAGS="$CFLAGS $LIBWACOM_CFLAGS"
+       AC_MSG_CHECKING([if libwacom_get_paired_device is available])
+       AC_LINK_IFELSE(
+                      [AC_LANG_PROGRAM([[#include <libwacom/libwacom.h>]],
+                                       [[libwacom_get_paired_device(NULL)]])],
+                      [AC_MSG_RESULT([yes])
+                       AC_DEFINE(HAVE_LIBWACOM_GET_PAIRED_DEVICE, [1],
+                                 [libwacom_get_paired_device() is available])
+                       [libwacom_have_get_paired_device=yes]],
+                      [AC_MSG_RESULT([no])
+                       [libwacom_have_get_paired_device=no]])
+       LIBS=$OLD_LIBS
+       CFLAGS=$OLD_CFLAGS
+fi
+AM_CONDITIONAL(HAVE_LIBWACOM_GET_PAIRED_DEVICE,
+              [test "x$libwacom_have_get_paired_device" == "xyes"])
 
 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"])
 
+# Used by the udev rules so we can use callouts during testing without
+# installing everything first. Default is the empty string so the installed
+# rule will use udev's default path. Override is in udev/Makefile.am
+AC_SUBST(UDEV_TEST_PATH, "")
+AC_PATH_PROG(SED, [sed])
+
 AC_CONFIG_FILES([Makefile
                 doc/Makefile
                 doc/libinput.doxygen
                 src/Makefile
                 src/libinput.pc
+                src/libinput-uninstalled.pc
                 src/libinput-version.h
                 test/Makefile
                 tools/Makefile
-                udev/Makefile])
+                udev/Makefile
+                udev/80-libinput-device-groups.rules
+                udev/90-libinput-model-quirks.rules])
 AC_CONFIG_FILES([test/symbols-leak-test],
                [chmod +x test/symbols-leak-test])
 AC_OUTPUT
@@ -207,8 +256,10 @@ 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}
+       Tests use libunwind     ${HAVE_LIBUNWIND}
        Build GUI event tool    ${build_eventgui}
        ])
index 39b550b5cdce5795d9b3e24d466f8fefa54e525d..4bf3d6d4391f3b756916bf977e664910644c5f96 100644 (file)
@@ -8,13 +8,11 @@
 #ifndef _INPUT_H
 #define _INPUT_H
 
-
 #include <sys/time.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <linux/types.h>
 
-
 /*
  * The event structure itself
  */
@@ -164,6 +162,7 @@ struct input_keymap_entry {
 #define INPUT_PROP_SEMI_MT             0x03    /* touch rectangle only */
 #define INPUT_PROP_TOPBUTTONPAD                0x04    /* softbuttons at top of pad */
 #define INPUT_PROP_POINTING_STICK      0x05    /* is a pointing stick */
+#define INPUT_PROP_ACCELEROMETER       0x06    /* has accelerometer */
 
 #define INPUT_PROP_MAX                 0x1f
 #define INPUT_PROP_CNT                 (INPUT_PROP_MAX + 1)
@@ -461,7 +460,10 @@ struct input_keymap_entry {
 #define KEY_VIDEO_NEXT         241     /* drive next video source */
 #define KEY_VIDEO_PREV         242     /* drive previous video source */
 #define KEY_BRIGHTNESS_CYCLE   243     /* brightness up, after max is min */
-#define KEY_BRIGHTNESS_ZERO    244     /* brightness off, use ambient */
+#define KEY_BRIGHTNESS_AUTO    244     /* Set Auto Brightness: manual
+                                         brightness control is off,
+                                         rely on ambient */
+#define KEY_BRIGHTNESS_ZERO    KEY_BRIGHTNESS_AUTO
 #define KEY_DISPLAY_OFF                245     /* display device to off state */
 
 #define KEY_WWAN               246     /* Wireless WAN (LTE, UMTS, GSM, etc.) */
@@ -631,6 +633,7 @@ struct input_keymap_entry {
 #define KEY_ADDRESSBOOK                0x1ad   /* AL Contacts/Address Book */
 #define KEY_MESSENGER          0x1ae   /* AL Instant Messaging */
 #define KEY_DISPLAYTOGGLE      0x1af   /* Turn display (LCD) on and off */
+#define KEY_BRIGHTNESS_TOGGLE  KEY_DISPLAYTOGGLE
 #define KEY_SPELLCHECK         0x1b0   /* AL Spell Check */
 #define KEY_LOGOFF             0x1b1   /* AL Logoff */
 
@@ -722,6 +725,24 @@ struct input_keymap_entry {
 
 #define KEY_ALS_TOGGLE         0x230   /* Ambient light sensor */
 
+#define KEY_BUTTONCONFIG               0x240   /* AL Button Configuration */
+#define KEY_TASKMANAGER                0x241   /* AL Task/Project Manager */
+#define KEY_JOURNAL            0x242   /* AL Log/Journal/Timecard */
+#define KEY_CONTROLPANEL               0x243   /* AL Control Panel */
+#define KEY_APPSELECT          0x244   /* AL Select Task/Application */
+#define KEY_SCREENSAVER                0x245   /* AL Screen Saver */
+#define KEY_VOICECOMMAND               0x246   /* Listening Voice Command */
+
+#define KEY_BRIGHTNESS_MIN             0x250   /* Set Brightness to Minimum */
+#define KEY_BRIGHTNESS_MAX             0x251   /* Set Brightness to Maximum */
+
+#define KEY_KBDINPUTASSIST_PREV                0x260
+#define KEY_KBDINPUTASSIST_NEXT                0x261
+#define KEY_KBDINPUTASSIST_PREVGROUP           0x262
+#define KEY_KBDINPUTASSIST_NEXTGROUP           0x263
+#define KEY_KBDINPUTASSIST_ACCEPT              0x264
+#define KEY_KBDINPUTASSIST_CANCEL              0x265
+
 #define BTN_TRIGGER_HAPPY              0x2c0
 #define BTN_TRIGGER_HAPPY1             0x2c0
 #define BTN_TRIGGER_HAPPY2             0x2c1
@@ -835,7 +856,6 @@ struct input_keymap_entry {
 #define ABS_MT_TOOL_X          0x3c    /* Center X tool position */
 #define ABS_MT_TOOL_Y          0x3d    /* Center Y tool position */
 
-
 #define ABS_MAX                        0x3f
 #define ABS_CNT                        (ABS_MAX+1)
 
@@ -948,7 +968,8 @@ struct input_keymap_entry {
  */
 #define MT_TOOL_FINGER         0
 #define MT_TOOL_PEN            1
-#define MT_TOOL_MAX            1
+#define MT_TOOL_PALM           2
+#define MT_TOOL_MAX            2
 
 /*
  * Values describing the status of a force-feedback effect
index 63e53e17264fbc98f1793da8472c4083af63fb4c..f55de8d106521157f70b1c6e8f8adf0e1989b642 100644 (file)
@@ -17,6 +17,7 @@ BuildRequires:  pkgconfig(libevent)
 BuildRequires:  pkgconfig(libudev)
 BuildRequires:  pkgconfig(mtdev)
 BuildRequires:  pkgconfig(ttrace)
+#BuildRequires:  pkgconfig(libwacom)
 
 %global TZ_SYS_RO_SHARE  %{?TZ_SYS_RO_SHARE:%TZ_SYS_RO_SHARE}%{!?TZ_SYS_RO_SHARE:/usr/share}
 
@@ -50,7 +51,7 @@ functionality that users expect.
 %setup -q
 cp %{SOURCE1001} .
 
-%autogen --with-udev-dir=%{udev_dir}
+%autogen --with-udev-dir=%{udev_dir} --disable-libwacom
 
 %build
 %__make %{?_smp_mflags}
@@ -74,6 +75,9 @@ cp -a %{_builddir}/%{buildsubdir}/COPYING %{buildroot}/%{TZ_SYS_RO_SHARE}/licens
 %{_libdir}/*.so.*
 %{udev_dir}/%{name}*
 %{udev_dir}/rules.d/*%{name}*
+/usr/bin/*
+/usr/share/man/*
+/usr/lib/udev/hwdb.d/90-libinput-model-quirks.hwdb
 
 %files devel
 %manifest %{name}.manifest
index 31df128f5d9c332a815325f5c3ade4447d94400b..2f5f14b0a39705adc343daafff5c0bc9ed0ef38c 100644 (file)
@@ -1,5 +1,6 @@
 lib_LTLIBRARIES = libinput.la
-noinst_LTLIBRARIES = libinput-util.la
+noinst_LTLIBRARIES = libinput-util.la \
+                    libfilter.la
 
 include_HEADERS =                      \
        libinput.h
@@ -10,11 +11,18 @@ libinput_la_SOURCES =                       \
        libinput-private.h              \
        evdev.c                         \
        evdev.h                         \
+       evdev-middle-button.c           \
        evdev-mt-touchpad.c             \
        evdev-mt-touchpad.h             \
        evdev-mt-touchpad-tap.c         \
        evdev-mt-touchpad-buttons.c     \
        evdev-mt-touchpad-edge-scroll.c \
+       evdev-mt-touchpad-gestures.c    \
+       evdev-tablet.c                  \
+       evdev-tablet.h                  \
+       evdev-tablet-pad.c              \
+       evdev-tablet-pad.h              \
+       evdev-tablet-pad-leds.c         \
        filter.c                        \
        filter.h                        \
        filter-private.h                \
@@ -30,6 +38,7 @@ libinput_la_LIBADD = $(MTDEV_LIBS) \
                     $(LIBUDEV_LIBS) \
                     $(LIBEVDEV_LIBS) \
                     $(TTRACE_LIBS) \
+                    $(LIBWACOM_LIBS) \
                     libinput-util.la
 
 libinput_la_CFLAGS = -I$(top_srcdir)/include \
@@ -37,6 +46,7 @@ libinput_la_CFLAGS = -I$(top_srcdir)/include \
                     $(LIBUDEV_CFLAGS)  \
                     $(LIBEVDEV_CFLAGS) \
                     $(TTRACE_CFLAGS)   \
+                    $(LIBWACOM_CFLAGS) \
                     $(GCC_CFLAGS)
 EXTRA_libinput_la_DEPENDENCIES = $(srcdir)/libinput.sym
 
@@ -49,6 +59,13 @@ libinput_util_la_CFLAGS = -I$(top_srcdir)/include \
                          $(LIBUDEV_CFLAGS) \
                          $(GCC_CFLAGS)
 
+libfilter_la_SOURCES = \
+       filter.c \
+       filter.h \
+       filter-private.h
+libfilter_la_LIBADD =
+libfilter_la_CFLAGS =
+
 libinput_la_LDFLAGS = -version-info $(LIBINPUT_LT_VERSION) -shared \
                      -Wl,--version-script=$(srcdir)/libinput.sym
 
diff --git a/src/evdev-middle-button.c b/src/evdev-middle-button.c
new file mode 100644 (file)
index 0000000..09f77de
--- /dev/null
@@ -0,0 +1,715 @@
+/*
+ * Copyright © 2014-2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+
+#include "evdev.h"
+
+#define MIDDLEBUTTON_TIMEOUT ms2us(50)
+
+/*****************************************
+ * BEFORE YOU EDIT THIS FILE, look at the state diagram in
+ * doc/middle-button-emulation-state-machine.svg, or online at
+ * https://drive.google.com/file/d/0B1NwWmji69nodUJncXRMc1FvY1k/view?usp=sharing
+ * (it's a http://draw.io diagram)
+ *
+ * Any changes in this file must be represented in the diagram.
+ *
+ * Note in regards to the state machine: it only handles left, right and
+ * emulated middle button clicks, all other button events are passed
+ * through. When in the PASSTHROUGH state, all events are passed through
+ * as-is.
+ */
+
+static inline const char*
+middlebutton_state_to_str(enum evdev_middlebutton_state state)
+{
+       switch (state) {
+       CASE_RETURN_STRING(MIDDLEBUTTON_IDLE);
+       CASE_RETURN_STRING(MIDDLEBUTTON_LEFT_DOWN);
+       CASE_RETURN_STRING(MIDDLEBUTTON_RIGHT_DOWN);
+       CASE_RETURN_STRING(MIDDLEBUTTON_MIDDLE);
+       CASE_RETURN_STRING(MIDDLEBUTTON_LEFT_UP_PENDING);
+       CASE_RETURN_STRING(MIDDLEBUTTON_RIGHT_UP_PENDING);
+       CASE_RETURN_STRING(MIDDLEBUTTON_PASSTHROUGH);
+       CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_LR);
+       CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_L);
+       CASE_RETURN_STRING(MIDDLEBUTTON_IGNORE_R);
+       }
+
+       return NULL;
+}
+
+static inline const char*
+middlebutton_event_to_str(enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_L_DOWN);
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_R_DOWN);
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_OTHER);
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_L_UP);
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_R_UP);
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_TIMEOUT);
+       CASE_RETURN_STRING(MIDDLEBUTTON_EVENT_ALL_UP);
+       }
+
+       return NULL;
+}
+
+static void
+middlebutton_state_error(struct evdev_device *device,
+                        enum evdev_middlebutton_event event)
+{
+       log_bug_libinput(evdev_libinput_context(device),
+                        "Invalid event %s in middle btn state %s\n",
+                        middlebutton_event_to_str(event),
+                        middlebutton_state_to_str(device->middlebutton.state));
+}
+
+static void
+middlebutton_timer_set(struct evdev_device *device, uint64_t now)
+{
+       libinput_timer_set(&device->middlebutton.timer,
+                          now + MIDDLEBUTTON_TIMEOUT);
+}
+
+static void
+middlebutton_timer_cancel(struct evdev_device *device)
+{
+       libinput_timer_cancel(&device->middlebutton.timer);
+}
+
+static inline void
+middlebutton_set_state(struct evdev_device *device,
+                      enum evdev_middlebutton_state state,
+                      uint64_t now)
+{
+       switch (state) {
+       case MIDDLEBUTTON_LEFT_DOWN:
+       case MIDDLEBUTTON_RIGHT_DOWN:
+               middlebutton_timer_set(device, now);
+               device->middlebutton.first_event_time = now;
+               break;
+       case MIDDLEBUTTON_IDLE:
+       case MIDDLEBUTTON_MIDDLE:
+       case MIDDLEBUTTON_LEFT_UP_PENDING:
+       case MIDDLEBUTTON_RIGHT_UP_PENDING:
+       case MIDDLEBUTTON_PASSTHROUGH:
+       case MIDDLEBUTTON_IGNORE_LR:
+       case MIDDLEBUTTON_IGNORE_L:
+       case MIDDLEBUTTON_IGNORE_R:
+               middlebutton_timer_cancel(device);
+               break;
+       }
+
+       device->middlebutton.state = state;
+}
+
+static void
+middlebutton_post_event(struct evdev_device *device,
+                       uint64_t now,
+                       int button,
+                       enum libinput_button_state state)
+{
+       evdev_pointer_notify_button(device,
+                                   now,
+                                   button,
+                                   state);
+}
+
+static int
+evdev_middlebutton_idle_handle_event(struct evdev_device *device,
+                                    uint64_t time,
+                                    enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               middlebutton_set_state(device, MIDDLEBUTTON_LEFT_DOWN, time);
+               break;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_set_state(device, MIDDLEBUTTON_RIGHT_DOWN, time);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+       case MIDDLEBUTTON_EVENT_L_UP:
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_ldown_handle_event(struct evdev_device *device,
+                                     uint64_t time,
+                                     enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               middlebutton_post_event(device, time,
+                                       BTN_LEFT,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_PASSTHROUGH,
+                                      time);
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_post_event(device,
+                                       device->middlebutton.first_event_time,
+                                       BTN_LEFT,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_post_event(device, time,
+                                       BTN_LEFT,
+                                       LIBINPUT_BUTTON_STATE_RELEASED);
+               middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_post_event(device,
+                                       device->middlebutton.first_event_time,
+                                       BTN_LEFT,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_PASSTHROUGH,
+                                      time);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_rdown_handle_event(struct evdev_device *device,
+                                     uint64_t time,
+                                     enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               middlebutton_post_event(device,
+                                       device->middlebutton.first_event_time,
+                                       BTN_RIGHT,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_PASSTHROUGH,
+                                      time);
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_post_event(device,
+                                       device->middlebutton.first_event_time,
+                                       BTN_RIGHT,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_post_event(device, time,
+                                       BTN_RIGHT,
+                                       LIBINPUT_BUTTON_STATE_RELEASED);
+               middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_post_event(device,
+                                       device->middlebutton.first_event_time,
+                                       BTN_RIGHT,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_PASSTHROUGH,
+                                      time);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_middle_handle_event(struct evdev_device *device,
+                                      uint64_t time,
+                                      enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_RELEASED);
+               middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_LR, time);
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_RELEASED);
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_LEFT_UP_PENDING,
+                                      time);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_RELEASED);
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_RIGHT_UP_PENDING,
+                                      time);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_lup_pending_handle_event(struct evdev_device *device,
+                                           uint64_t time,
+                                           enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_L, time);
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_rup_pending_handle_event(struct evdev_device *device,
+                                           uint64_t time,
+                                           enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               middlebutton_post_event(device, time,
+                                       BTN_MIDDLE,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+               middlebutton_set_state(device, MIDDLEBUTTON_MIDDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_R, time);
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_passthrough_handle_event(struct evdev_device *device,
+                                           uint64_t time,
+                                           enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+       case MIDDLEBUTTON_EVENT_OTHER:
+       case MIDDLEBUTTON_EVENT_R_UP:
+       case MIDDLEBUTTON_EVENT_L_UP:
+               return 0;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_set_state(device, MIDDLEBUTTON_IDLE, time);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_ignore_lr_handle_event(struct evdev_device *device,
+                                         uint64_t time,
+                                         enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_L, time);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_set_state(device, MIDDLEBUTTON_IGNORE_R, time);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_ignore_l_handle_event(struct evdev_device *device,
+                                        uint64_t time,
+                                        enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               return 0;
+       case MIDDLEBUTTON_EVENT_OTHER:
+       case MIDDLEBUTTON_EVENT_R_UP:
+               return 0;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_PASSTHROUGH,
+                                      time);
+               break;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               middlebutton_state_error(device, event);
+               break;
+       }
+
+       return 1;
+}
+static int
+evdev_middlebutton_ignore_r_handle_event(struct evdev_device *device,
+                                        uint64_t time,
+                                        enum evdev_middlebutton_event event)
+{
+       switch (event) {
+       case MIDDLEBUTTON_EVENT_L_DOWN:
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_DOWN:
+               middlebutton_state_error(device, event);
+               break;
+       case MIDDLEBUTTON_EVENT_OTHER:
+               return 0;
+       case MIDDLEBUTTON_EVENT_R_UP:
+               middlebutton_set_state(device,
+                                      MIDDLEBUTTON_PASSTHROUGH,
+                                      time);
+               break;
+       case MIDDLEBUTTON_EVENT_L_UP:
+               return 0;
+       case MIDDLEBUTTON_EVENT_TIMEOUT:
+       case MIDDLEBUTTON_EVENT_ALL_UP:
+               break;
+       }
+
+       return 1;
+}
+
+static int
+evdev_middlebutton_handle_event(struct evdev_device *device,
+                               uint64_t time,
+                               enum evdev_middlebutton_event event)
+{
+       int rc;
+       enum evdev_middlebutton_state current;
+
+       current = device->middlebutton.state;
+
+       switch (current) {
+       case MIDDLEBUTTON_IDLE:
+               rc = evdev_middlebutton_idle_handle_event(device, time, event);
+               break;
+       case MIDDLEBUTTON_LEFT_DOWN:
+               rc = evdev_middlebutton_ldown_handle_event(device, time, event);
+               break;
+       case MIDDLEBUTTON_RIGHT_DOWN:
+               rc = evdev_middlebutton_rdown_handle_event(device, time, event);
+               break;
+       case MIDDLEBUTTON_MIDDLE:
+               rc = evdev_middlebutton_middle_handle_event(device, time, event);
+               break;
+       case MIDDLEBUTTON_LEFT_UP_PENDING:
+               rc = evdev_middlebutton_lup_pending_handle_event(device,
+                                                                time,
+                                                                event);
+               break;
+       case MIDDLEBUTTON_RIGHT_UP_PENDING:
+               rc = evdev_middlebutton_rup_pending_handle_event(device,
+                                                                time,
+                                                                event);
+               break;
+       case MIDDLEBUTTON_PASSTHROUGH:
+               rc = evdev_middlebutton_passthrough_handle_event(device,
+                                                                time,
+                                                                event);
+               break;
+       case MIDDLEBUTTON_IGNORE_LR:
+               rc = evdev_middlebutton_ignore_lr_handle_event(device,
+                                                              time,
+                                                              event);
+               break;
+       case MIDDLEBUTTON_IGNORE_L:
+               rc = evdev_middlebutton_ignore_l_handle_event(device,
+                                                             time,
+                                                             event);
+               break;
+       case MIDDLEBUTTON_IGNORE_R:
+               rc = evdev_middlebutton_ignore_r_handle_event(device,
+                                                             time,
+                                                             event);
+               break;
+       }
+
+       log_debug(evdev_libinput_context(device),
+                 "middlebuttonstate: %s → %s → %s, rc %d\n",
+                 middlebutton_state_to_str(current),
+                 middlebutton_event_to_str(event),
+                 middlebutton_state_to_str(device->middlebutton.state),
+                 rc);
+
+       return rc;
+}
+
+static inline void
+evdev_middlebutton_apply_config(struct evdev_device *device)
+{
+       if (device->middlebutton.want_enabled ==
+           device->middlebutton.enabled)
+               return;
+
+       if (device->middlebutton.button_mask != 0)
+               return;
+
+       device->middlebutton.enabled = device->middlebutton.want_enabled;
+}
+
+bool
+evdev_middlebutton_filter_button(struct evdev_device *device,
+                                uint64_t time,
+                                int button,
+                                enum libinput_button_state state)
+{
+       enum evdev_middlebutton_event event;
+       bool is_press = state == LIBINPUT_BUTTON_STATE_PRESSED;
+       int rc;
+       unsigned int bit = (button - BTN_LEFT);
+       uint32_t old_mask = 0;
+
+       if (!device->middlebutton.enabled)
+               return false;
+
+       switch (button) {
+       case BTN_LEFT:
+               if (is_press)
+                       event = MIDDLEBUTTON_EVENT_L_DOWN;
+               else
+                       event = MIDDLEBUTTON_EVENT_L_UP;
+               break;
+       case BTN_RIGHT:
+               if (is_press)
+                       event = MIDDLEBUTTON_EVENT_R_DOWN;
+               else
+                       event = MIDDLEBUTTON_EVENT_R_UP;
+               break;
+
+       /* BTN_MIDDLE counts as "other" and resets middle button
+        * emulation */
+       case BTN_MIDDLE:
+       default:
+               event = MIDDLEBUTTON_EVENT_OTHER;
+               break;
+       }
+
+       if (button < BTN_LEFT ||
+           bit >= sizeof(device->middlebutton.button_mask) * 8) {
+               log_bug_libinput(evdev_libinput_context(device),
+                                "Button mask too small for %s\n",
+                                libevdev_event_code_get_name(EV_KEY,
+                                                             button));
+               return true;
+       }
+
+       rc = evdev_middlebutton_handle_event(device, time, event);
+
+       old_mask = device->middlebutton.button_mask;
+       if (is_press)
+               device->middlebutton.button_mask |= 1 << bit;
+       else
+               device->middlebutton.button_mask &= ~(1 << bit);
+
+       if (old_mask != device->middlebutton.button_mask &&
+           device->middlebutton.button_mask == 0) {
+               evdev_middlebutton_handle_event(device,
+                                               time,
+                                               MIDDLEBUTTON_EVENT_ALL_UP);
+               evdev_middlebutton_apply_config(device);
+       }
+
+       return rc;
+}
+
+static void
+evdev_middlebutton_handle_timeout(uint64_t now, void *data)
+{
+       struct evdev_device *device = (struct evdev_device*)data;
+
+       evdev_middlebutton_handle_event(device, now, MIDDLEBUTTON_EVENT_TIMEOUT);
+}
+
+int
+evdev_middlebutton_is_available(struct libinput_device *device)
+{
+       return 1;
+}
+
+static enum libinput_config_status
+evdev_middlebutton_set(struct libinput_device *device,
+                      enum libinput_config_middle_emulation_state enable)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+
+       switch (enable) {
+       case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED:
+               evdev->middlebutton.want_enabled = true;
+               break;
+       case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED:
+               evdev->middlebutton.want_enabled = false;
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+       }
+
+       evdev_middlebutton_apply_config(evdev);
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+enum libinput_config_middle_emulation_state
+evdev_middlebutton_get(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+
+       return evdev->middlebutton.want_enabled ?
+                       LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED :
+                       LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
+}
+
+enum libinput_config_middle_emulation_state
+evdev_middlebutton_get_default(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+
+       return evdev->middlebutton.enabled_default ?
+                       LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED :
+                       LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
+}
+
+void
+evdev_init_middlebutton(struct evdev_device *device,
+                       bool enable,
+                       bool want_config)
+{
+       libinput_timer_init(&device->middlebutton.timer,
+                           evdev_libinput_context(device),
+                           evdev_middlebutton_handle_timeout,
+                           device);
+       device->middlebutton.enabled_default = enable;
+       device->middlebutton.want_enabled = enable;
+       device->middlebutton.enabled = enable;
+
+       if (!want_config)
+               return;
+
+       device->middlebutton.config.available = evdev_middlebutton_is_available;
+       device->middlebutton.config.set = evdev_middlebutton_set;
+       device->middlebutton.config.get = evdev_middlebutton_get;
+       device->middlebutton.config.get_default = evdev_middlebutton_get_default;
+       device->base.config.middle_emulation = &device->middlebutton.config;
+}
index 9dbb51377f91c69b498e031d5c82273c187973e3..b59cf13a6cd15fe090033aad288f074b92aafede 100644 (file)
@@ -1,25 +1,28 @@
 /*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
+#include "config.h"
+
 #include <errno.h>
 #include <limits.h>
 #include <math.h>
@@ -29,9 +32,8 @@
 
 #include "evdev-mt-touchpad.h"
 
-#define DEFAULT_BUTTON_MOTION_THRESHOLD 0.02 /* 2% of size */
-#define DEFAULT_BUTTON_ENTER_TIMEOUT 100 /* ms */
-#define DEFAULT_BUTTON_LEAVE_TIMEOUT 300 /* ms */
+#define DEFAULT_BUTTON_ENTER_TIMEOUT ms2us(100)
+#define DEFAULT_BUTTON_LEAVE_TIMEOUT ms2us(300)
 
 /*****************************************
  * BEFORE YOU EDIT THIS FILE, look at the state diagram in
@@ -44,8 +46,6 @@
  * The state machine only affects the soft button area code.
  */
 
-#define CASE_RETURN_STRING(a) case a: return #a;
-
 static inline const char*
 button_state_to_str(enum button_state state) {
        switch(state) {
@@ -64,6 +64,7 @@ static inline const char*
 button_event_to_str(enum button_event event) {
        switch(event) {
        CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_R);
+       CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_M);
        CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_L);
        CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_R);
        CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_M);
@@ -78,51 +79,68 @@ button_event_to_str(enum button_event event) {
 }
 
 static inline bool
-is_inside_bottom_button_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_bottom_button_area(const struct tp_dispatch *tp,
+                            const struct tp_touch *t)
+{
+       return t->point.y >= tp->buttons.bottom_area.top_edge;
+}
+
+static inline bool
+is_inside_bottom_right_area(const struct tp_dispatch *tp,
+                           const struct tp_touch *t)
 {
-       return t->y >= tp->buttons.bottom_area.top_edge;
+       return is_inside_bottom_button_area(tp, t) &&
+              t->point.x > tp->buttons.bottom_area.rightbutton_left_edge;
 }
 
 static inline bool
-is_inside_bottom_right_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_bottom_middle_area(const struct tp_dispatch *tp,
+                          const struct tp_touch *t)
 {
        return is_inside_bottom_button_area(tp, t) &&
-              t->x > tp->buttons.bottom_area.rightbutton_left_edge;
+              !is_inside_bottom_right_area(tp, t) &&
+              t->point.x > tp->buttons.bottom_area.middlebutton_left_edge;
 }
 
 static inline bool
-is_inside_bottom_left_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_bottom_left_area(const struct tp_dispatch *tp,
+                          const struct tp_touch *t)
 {
        return is_inside_bottom_button_area(tp, t) &&
+              !is_inside_bottom_middle_area(tp, t) &&
               !is_inside_bottom_right_area(tp, t);
 }
 
 static inline bool
-is_inside_top_button_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_top_button_area(const struct tp_dispatch *tp,
+                         const struct tp_touch *t)
 {
-       return t->y <= tp->buttons.top_area.bottom_edge;
+       return t->point.y <= tp->buttons.top_area.bottom_edge;
 }
 
 static inline bool
-is_inside_top_right_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_top_right_area(const struct tp_dispatch *tp,
+                        const struct tp_touch *t)
 {
        return is_inside_top_button_area(tp, t) &&
-              t->x > tp->buttons.top_area.rightbutton_left_edge;
+              t->point.x > tp->buttons.top_area.rightbutton_left_edge;
 }
 
 static inline bool
-is_inside_top_left_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_top_left_area(const struct tp_dispatch *tp,
+                       const struct tp_touch *t)
 {
        return is_inside_top_button_area(tp, t) &&
-              t->x < tp->buttons.top_area.leftbutton_right_edge;
+              t->point.x < tp->buttons.top_area.leftbutton_right_edge;
 }
 
 static inline bool
-is_inside_top_middle_area(struct tp_dispatch *tp, struct tp_touch *t)
+is_inside_top_middle_area(const struct tp_dispatch *tp,
+                         const struct tp_touch *t)
 {
        return is_inside_top_button_area(tp, t) &&
-              t->x >= tp->buttons.top_area.leftbutton_right_edge &&
-              t->x <= tp->buttons.top_area.rightbutton_left_edge;
+              t->point.x >= tp->buttons.top_area.leftbutton_right_edge &&
+              t->point.x <= tp->buttons.top_area.rightbutton_left_edge;
 }
 
 static void
@@ -144,19 +162,21 @@ tp_button_set_leave_timer(struct tp_dispatch *tp, struct tp_touch *t)
  * as described in the state machine diagram.
  */
 static void
-tp_button_set_state(struct tp_dispatch *tp, struct tp_touch *t,
-                   enum button_state new_state, enum button_event event)
+tp_button_set_state(struct tp_dispatch *tp,
+                   struct tp_touch *t,
+                   enum button_state new_state,
+                   enum button_event event)
 {
        libinput_timer_cancel(&t->button.timer);
 
        t->button.state = new_state;
+
        switch (t->button.state) {
        case BUTTON_STATE_NONE:
                t->button.curr = 0;
                break;
        case BUTTON_STATE_AREA:
                t->button.curr = BUTTON_EVENT_IN_AREA;
-               tp_set_pointer(tp, t);
                break;
        case BUTTON_STATE_BOTTOM:
                t->button.curr = event;
@@ -183,6 +203,7 @@ tp_button_none_handle_event(struct tp_dispatch *tp,
 {
        switch (event) {
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
                tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, event);
                break;
@@ -211,6 +232,7 @@ tp_button_area_handle_event(struct tp_dispatch *tp,
 {
        switch (event) {
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
        case BUTTON_EVENT_IN_TOP_R:
        case BUTTON_EVENT_IN_TOP_M:
@@ -234,9 +256,12 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp,
 {
        switch (event) {
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
                if (event != t->button.curr)
-                       tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM,
+                       tp_button_set_state(tp,
+                                           t,
+                                           BUTTON_STATE_BOTTOM,
                                            event);
                break;
        case BUTTON_EVENT_IN_TOP_R:
@@ -257,11 +282,12 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp,
 
 static void
 tp_button_top_handle_event(struct tp_dispatch *tp,
-                           struct tp_touch *t,
-                           enum button_event event)
+                          struct tp_touch *t,
+                          enum button_event event)
 {
        switch (event) {
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
                tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, event);
                break;
@@ -269,7 +295,9 @@ tp_button_top_handle_event(struct tp_dispatch *tp,
        case BUTTON_EVENT_IN_TOP_M:
        case BUTTON_EVENT_IN_TOP_L:
                if (event != t->button.curr)
-                       tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW,
+                       tp_button_set_state(tp,
+                                           t,
+                                           BUTTON_STATE_TOP_NEW,
                                            event);
                break;
        case BUTTON_EVENT_IN_AREA:
@@ -287,11 +315,12 @@ tp_button_top_handle_event(struct tp_dispatch *tp,
 
 static void
 tp_button_top_new_handle_event(struct tp_dispatch *tp,
-                               struct tp_touch *t,
-                               enum button_event event)
+                              struct tp_touch *t,
+                              enum button_event event)
 {
        switch(event) {
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
                tp_button_set_state(tp, t, BUTTON_STATE_AREA, event);
                break;
@@ -299,7 +328,9 @@ tp_button_top_new_handle_event(struct tp_dispatch *tp,
        case BUTTON_EVENT_IN_TOP_M:
        case BUTTON_EVENT_IN_TOP_L:
                if (event != t->button.curr)
-                       tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW,
+                       tp_button_set_state(tp,
+                                           t,
+                                           BUTTON_STATE_TOP_NEW,
                                            event);
                break;
        case BUTTON_EVENT_IN_AREA:
@@ -321,21 +352,26 @@ tp_button_top_new_handle_event(struct tp_dispatch *tp,
 
 static void
 tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp,
-                                     struct tp_touch *t,
-                                     enum button_event event)
+                                    struct tp_touch *t,
+                                    enum button_event event)
 {
        switch(event) {
        case BUTTON_EVENT_IN_TOP_R:
        case BUTTON_EVENT_IN_TOP_M:
        case BUTTON_EVENT_IN_TOP_L:
                if (event == t->button.curr)
-                       tp_button_set_state(tp, t, BUTTON_STATE_TOP,
+                       tp_button_set_state(tp,
+                                           t,
+                                           BUTTON_STATE_TOP,
                                            event);
                else
-                       tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW,
+                       tp_button_set_state(tp,
+                                           t,
+                                           BUTTON_STATE_TOP_NEW,
                                            event);
                break;
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
        case BUTTON_EVENT_IN_AREA:
                break;
@@ -353,11 +389,12 @@ tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp,
 
 static void
 tp_button_ignore_handle_event(struct tp_dispatch *tp,
-                           struct tp_touch *t,
-                           enum button_event event)
+                             struct tp_touch *t,
+                             enum button_event event)
 {
        switch (event) {
        case BUTTON_EVENT_IN_BOTTOM_R:
+       case BUTTON_EVENT_IN_BOTTOM_M:
        case BUTTON_EVENT_IN_BOTTOM_L:
        case BUTTON_EVENT_IN_TOP_R:
        case BUTTON_EVENT_IN_TOP_M:
@@ -380,7 +417,7 @@ tp_button_handle_event(struct tp_dispatch *tp,
                       enum button_event event,
                       uint64_t time)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
        enum button_state current = t->button.state;
 
        switch(t->button.state) {
@@ -415,7 +452,7 @@ tp_button_handle_event(struct tp_dispatch *tp,
                          button_state_to_str(t->button.state));
 }
 
-int
+void
 tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
 {
        struct tp_touch *t;
@@ -427,26 +464,30 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time)
                if (t->state == TOUCH_END) {
                        tp_button_handle_event(tp, t, BUTTON_EVENT_UP, time);
                } else if (t->dirty) {
+                       enum button_event event;
+
                        if (is_inside_bottom_right_area(tp, t))
-                               tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_R, time);
+                               event = BUTTON_EVENT_IN_BOTTOM_R;
+                       else if (is_inside_bottom_middle_area(tp, t))
+                               event = BUTTON_EVENT_IN_BOTTOM_M;
                        else if (is_inside_bottom_left_area(tp, t))
-                               tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_L, time);
+                               event = BUTTON_EVENT_IN_BOTTOM_L;
                        else if (is_inside_top_right_area(tp, t))
-                               tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_R, time);
+                               event = BUTTON_EVENT_IN_TOP_R;
                        else if (is_inside_top_middle_area(tp, t))
-                               tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_M, time);
+                               event = BUTTON_EVENT_IN_TOP_M;
                        else if (is_inside_top_left_area(tp, t))
-                               tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_L, time);
+                               event = BUTTON_EVENT_IN_TOP_L;
                        else
-                               tp_button_handle_event(tp, t, BUTTON_EVENT_IN_AREA, time);
+                               event = BUTTON_EVENT_IN_AREA;
+
+                       tp_button_handle_event(tp, t, event, time);
                }
                if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE)
                        tp_button_handle_event(tp, t, BUTTON_EVENT_RELEASE, time);
                if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
                        tp_button_handle_event(tp, t, BUTTON_EVENT_PRESS, time);
        }
-
-       return 0;
 }
 
 static void
@@ -457,12 +498,12 @@ tp_button_handle_timeout(uint64_t now, void *data)
        tp_button_handle_event(t->tp, t, BUTTON_EVENT_TIMEOUT, now);
 }
 
-int
+void
 tp_process_button(struct tp_dispatch *tp,
                  const struct input_event *e,
                  uint64_t time)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
        uint32_t mask = 1 << (e->code - BTN_LEFT);
 
        /* Ignore other buttons on clickpads */
@@ -470,7 +511,7 @@ tp_process_button(struct tp_dispatch *tp,
                log_bug_kernel(libinput,
                               "received %s button event on a clickpad\n",
                               libevdev_event_code_get_name(EV_KEY, e->code));
-               return 0;
+               return;
        }
 
        if (e->value) {
@@ -480,8 +521,6 @@ tp_process_button(struct tp_dispatch *tp,
                tp->buttons.state &= ~mask;
                tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE;
        }
-
-       return 0;
 }
 
 void
@@ -498,30 +537,61 @@ static void
 tp_init_softbuttons(struct tp_dispatch *tp,
                    struct evdev_device *device)
 {
-       int width, height;
-       const struct input_absinfo *absinfo_x, *absinfo_y;
-       int xoffset, yoffset;
-       int yres;
-
-       absinfo_x = device->abs.absinfo_x;
-       absinfo_y = device->abs.absinfo_y;
+       double width, height;
+       struct device_coords edges;
+       int mb_le, mb_re; /* middle button left/right edge */
+       struct phys_coords mm = { 0.0, 0.0 };
 
-       xoffset = absinfo_x->minimum,
-       yoffset = absinfo_y->minimum;
-       yres = absinfo_y->resolution;
-       width = abs(absinfo_x->maximum - absinfo_x->minimum);
-       height = abs(absinfo_y->maximum - absinfo_y->minimum);
+       evdev_device_get_size(device, &width, &height);
 
-       /* button height: 10mm or 15% of the touchpad height,
+       /* button height: 10mm or 15% or the touchpad height,
           whichever is smaller */
-       if (yres > 1 && (height * 0.15/yres) > 10) {
-               tp->buttons.bottom_area.top_edge =
-               absinfo_y->maximum - 10 * yres;
+       if (height * 0.15 > 10)
+               mm.y = height - 10;
+       else
+               mm.y = height * 0.85;
+
+       mm.x = width * 0.5;
+       edges = evdev_device_mm_to_units(device, &mm);
+       tp->buttons.bottom_area.top_edge = edges.y;
+       tp->buttons.bottom_area.rightbutton_left_edge = edges.x;
+
+       tp->buttons.bottom_area.middlebutton_left_edge = INT_MAX;
+
+       /* if middlebutton emulation is enabled, don't init a software area */
+       if (device->middlebutton.want_enabled)
+               return;
+
+       /* The middle button is 25% of the touchpad and centered. Many
+        * touchpads don't have markings for the middle button at all so we
+        * need to make it big enough to reliably hit it but not too big so
+        * it takes away all the space.
+        *
+        * On touchpads with visible markings we reduce the size of the
+        * middle button since users have a visual guide.
+        *
+        * All Dell touchpads appear to have a middle marker.
+        */
+       if (tp->device->model_flags & EVDEV_MODEL_DELL_TOUCHPAD) {
+               mm.x = width/2 - 5; /* 10mm wide */
+               edges = evdev_device_mm_to_units(device, &mm);
+               mb_le = edges.x;
+
+               mm.x = width/2 + 5; /* 10mm wide */
+               edges = evdev_device_mm_to_units(device, &mm);
+               mb_re = edges.x;
        } else {
-               tp->buttons.bottom_area.top_edge = height * .85 + yoffset;
+               mm.x = width * 0.375;
+               edges = evdev_device_mm_to_units(device, &mm);
+               mb_le = edges.x;
+
+               mm.x = width * 0.625;
+               edges = evdev_device_mm_to_units(device, &mm);
+               mb_re = edges.x;
        }
 
-       tp->buttons.bottom_area.rightbutton_left_edge = width/2 + xoffset;
+       tp->buttons.bottom_area.middlebutton_left_edge = mb_le;
+       tp->buttons.bottom_area.rightbutton_left_edge = mb_re;
 }
 
 void
@@ -529,19 +599,7 @@ tp_init_top_softbuttons(struct tp_dispatch *tp,
                        struct evdev_device *device,
                        double topbutton_size_mult)
 {
-       int width, height;
-       const struct input_absinfo *absinfo_x, *absinfo_y;
-       int xoffset, yoffset;
-       int yres;
-
-       absinfo_x = device->abs.absinfo_x;
-       absinfo_y = device->abs.absinfo_y;
-
-       xoffset = absinfo_x->minimum,
-       yoffset = absinfo_y->minimum;
-       yres = absinfo_y->resolution;
-       width = abs(absinfo_x->maximum - absinfo_x->minimum);
-       height = abs(absinfo_y->maximum - absinfo_y->minimum);
+       struct device_coords edges;
 
        if (tp->buttons.has_topbuttons) {
                /* T440s has the top button line 5mm from the top, event
@@ -549,16 +607,20 @@ tp_init_top_softbuttons(struct tp_dispatch *tp,
                   top - which maps to 15%.  We allow the caller to enlarge the
                   area using a multiplier for the touchpad disabled case. */
                double topsize_mm = 10 * topbutton_size_mult;
-               double topsize_pct = .15 * topbutton_size_mult;
+               struct phys_coords mm;
+               double width, height;
 
-               if (yres > 1) {
-                       tp->buttons.top_area.bottom_edge =
-                       yoffset + topsize_mm * yres;
-               } else {
-                       tp->buttons.top_area.bottom_edge = height * topsize_pct + yoffset;
-               }
-               tp->buttons.top_area.rightbutton_left_edge = width * .58 + xoffset;
-               tp->buttons.top_area.leftbutton_right_edge = width * .42 + xoffset;
+               evdev_device_get_size(device, &width, &height);
+
+               mm.x = width * 0.58;
+               mm.y = topsize_mm;
+               edges = evdev_device_mm_to_units(device, &mm);
+               tp->buttons.top_area.bottom_edge = edges.y;
+               tp->buttons.top_area.rightbutton_left_edge = edges.x;
+
+               mm.x = width * 0.42;
+               edges = evdev_device_mm_to_units(device, &mm);
+               tp->buttons.top_area.leftbutton_right_edge = edges.x;
        } else {
                tp->buttons.top_area.bottom_edge = INT_MIN;
        }
@@ -630,12 +692,22 @@ tp_button_config_click_get_method(struct libinput_device *device)
 static enum libinput_config_click_method
 tp_click_get_default_method(struct tp_dispatch *tp)
 {
+       struct evdev_device *device = tp->device;
+       uint32_t clickfinger_models = EVDEV_MODEL_CHROMEBOOK |
+                                     EVDEV_MODEL_SYSTEM76_BONOBO |
+                                     EVDEV_MODEL_SYSTEM76_GALAGO |
+                                     EVDEV_MODEL_SYSTEM76_KUDU |
+                                     EVDEV_MODEL_CLEVO_W740SU;
+
        if (!tp->buttons.is_clickpad)
                return LIBINPUT_CONFIG_CLICK_METHOD_NONE;
        else if (libevdev_get_id_vendor(tp->device->evdev) == VENDOR_ID_APPLE)
                return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
-       else
-               return LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
+
+       if (device->model_flags & clickfinger_models)
+               return LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
+
+       return LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
 }
 
 static enum libinput_config_click_method
@@ -647,14 +719,119 @@ tp_button_config_click_get_default_method(struct libinput_device *device)
        return tp_click_get_default_method(tp);
 }
 
-int
+void
+tp_clickpad_middlebutton_apply_config(struct evdev_device *device)
+{
+       struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch;
+
+       if (!tp->buttons.is_clickpad ||
+           tp->buttons.state != 0)
+               return;
+
+       if (device->middlebutton.want_enabled ==
+           device->middlebutton.enabled)
+               return;
+
+       device->middlebutton.enabled = device->middlebutton.want_enabled;
+       if (tp->buttons.click_method ==
+           LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
+               tp_init_softbuttons(tp, device);
+}
+
+static int
+tp_clickpad_middlebutton_is_available(struct libinput_device *device)
+{
+       return evdev_middlebutton_is_available(device);
+}
+
+static enum libinput_config_status
+tp_clickpad_middlebutton_set(struct libinput_device *device,
+                    enum libinput_config_middle_emulation_state enable)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+
+       switch (enable) {
+       case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED:
+               evdev->middlebutton.want_enabled = true;
+               break;
+       case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED:
+               evdev->middlebutton.want_enabled = false;
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+       }
+
+       tp_clickpad_middlebutton_apply_config(evdev);
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_middle_emulation_state
+tp_clickpad_middlebutton_get(struct libinput_device *device)
+{
+       return evdev_middlebutton_get(device);
+}
+
+static enum libinput_config_middle_emulation_state
+tp_clickpad_middlebutton_get_default(struct libinput_device *device)
+{
+       return evdev_middlebutton_get_default(device);
+}
+
+static inline void
+tp_init_clickpad_middlebutton_emulation(struct tp_dispatch *tp,
+                                       struct evdev_device *device)
+{
+       device->middlebutton.enabled_default = false;
+       device->middlebutton.want_enabled = false;
+       device->middlebutton.enabled = false;
+
+       device->middlebutton.config.available = tp_clickpad_middlebutton_is_available;
+       device->middlebutton.config.set = tp_clickpad_middlebutton_set;
+       device->middlebutton.config.get = tp_clickpad_middlebutton_get;
+       device->middlebutton.config.get_default = tp_clickpad_middlebutton_get_default;
+       device->base.config.middle_emulation = &device->middlebutton.config;
+}
+
+static inline void
+tp_init_middlebutton_emulation(struct tp_dispatch *tp,
+                              struct evdev_device *device)
+{
+       bool enable_by_default,
+            want_config_option;
+
+       /* On clickpads we provide the config option but disable by default.
+          When enabled, the middle software button disappears */
+       if (tp->buttons.is_clickpad) {
+               tp_init_clickpad_middlebutton_emulation(tp, device);
+               return;
+       }
+
+       /* init middle button emulation on non-clickpads, but only if we
+        * don't have a middle button. Exception: ALPS touchpads don't know
+        * if they have a middle button, so we always want the option there
+        * and enabled by default.
+        */
+       if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE)) {
+               enable_by_default = true;
+               want_config_option = false;
+       } else if (device->model_flags & EVDEV_MODEL_ALPS_TOUCHPAD) {
+               enable_by_default = true;
+               want_config_option = true;
+       } else
+               return;
+
+       evdev_init_middlebutton(tp->device,
+                               enable_by_default,
+                               want_config_option);
+}
+
+void
 tp_init_buttons(struct tp_dispatch *tp,
                struct evdev_device *device)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
        struct tp_touch *t;
-       int width, height;
-       double diagonal;
        const struct input_absinfo *absinfo_x, *absinfo_y;
 
        tp->buttons.is_clickpad = libevdev_has_property(device->evdev,
@@ -668,8 +845,9 @@ tp_init_buttons(struct tp_dispatch *tp,
                        log_bug_kernel(libinput,
                                       "%s: clickpad advertising right button\n",
                                       device->devname);
-       } else {
-               if (!tp->buttons.is_clickpad)
+       } else if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_LEFT) &&
+                  !tp->buttons.is_clickpad &&
+                  libevdev_get_id_vendor(device->evdev) != VENDOR_ID_APPLE) {
                        log_bug_kernel(libinput,
                                       "%s: non clickpad without right button?\n",
                                       device->devname);
@@ -678,11 +856,9 @@ tp_init_buttons(struct tp_dispatch *tp,
        absinfo_x = device->abs.absinfo_x;
        absinfo_y = device->abs.absinfo_y;
 
-       width = abs(absinfo_x->maximum - absinfo_x->minimum);
-       height = abs(absinfo_y->maximum - absinfo_y->minimum);
-       diagonal = sqrt(width*width + height*height);
-
-       tp->buttons.motion_dist = diagonal * DEFAULT_BUTTON_MOTION_THRESHOLD;
+       /* pinned-finger motion threshold, see tp_unpin_finger. */
+       tp->buttons.motion_dist.x_scale_coeff = 1.0/absinfo_x->resolution;
+       tp->buttons.motion_dist.y_scale_coeff = 1.0/absinfo_y->resolution;
 
        tp->buttons.config_method.get_methods = tp_button_config_click_get_methods;
        tp->buttons.config_method.set_method = tp_button_config_click_set_method;
@@ -695,14 +871,14 @@ tp_init_buttons(struct tp_dispatch *tp,
 
        tp_init_top_softbuttons(tp, device, 1.0);
 
+       tp_init_middlebutton_emulation(tp, device);
+
        tp_for_each_touch(tp, t) {
                t->button.state = BUTTON_STATE_NONE;
                libinput_timer_init(&t->button.timer,
-                                   tp->device->base.seat->libinput,
+                                   tp_libinput_context(tp),
                                    tp_button_handle_timeout, t);
        }
-
-       return 0;
 }
 
 void
@@ -735,10 +911,10 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
                                state = LIBINPUT_BUTTON_STATE_RELEASED;
 
                        b = evdev_to_left_handed(tp->device, button);
-                       evdev_pointer_notify_button(tp->device,
-                                                   time,
-                                                   b,
-                                                   state);
+                       evdev_pointer_notify_physical_button(tp->device,
+                                                            time,
+                                                            b,
+                                                            state);
                }
 
                button++;
@@ -749,6 +925,112 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time)
        return 0;
 }
 
+static inline bool
+tp_clickfinger_within_distance(struct tp_dispatch *tp,
+                              struct tp_touch *t1,
+                              struct tp_touch *t2)
+{
+       double x, y;
+       bool within_distance = false;
+       int xres, yres;
+       int bottom_threshold;
+
+       if (!t1 || !t2)
+               return 0;
+
+       if (t1->thumb.state == THUMB_STATE_YES ||
+           t2->thumb.state == THUMB_STATE_YES)
+               return 0;
+
+       x = abs(t1->point.x - t2->point.x);
+       y = abs(t1->point.y - t2->point.y);
+
+       xres = tp->device->abs.absinfo_x->resolution;
+       yres = tp->device->abs.absinfo_y->resolution;
+       x /= xres;
+       y /= yres;
+
+       /* maximum horiz spread is 40mm horiz, 30mm vert, anything wider
+        * than that is probably a gesture. */
+       if (x > 40 || y > 30)
+               goto out;
+
+       within_distance = true;
+
+       /* if y spread is <= 20mm, they're definitely together. */
+       if (y <= 20)
+               goto out;
+
+       /* if they're vertically spread between 20-40mm, they're not
+        * together if:
+        * - the touchpad's vertical size is >50mm, anything smaller is
+        *   unlikely to have a thumb resting on it
+        * - and one of the touches is in the bottom 20mm of the touchpad
+        *   and the other one isn't
+        */
+
+       if (tp->device->abs.dimensions.y/yres < 50)
+               goto out;
+
+       bottom_threshold = tp->device->abs.absinfo_y->maximum - 20 * yres;
+       if ((t1->point.y > bottom_threshold) !=
+                   (t2->point.y > bottom_threshold))
+               within_distance = 0;
+
+out:
+       return within_distance;
+}
+
+static uint32_t
+tp_clickfinger_set_button(struct tp_dispatch *tp)
+{
+       uint32_t button;
+       unsigned int nfingers = tp->nfingers_down;
+       struct tp_touch *t;
+       struct tp_touch *first = NULL,
+                       *second = NULL;
+
+       if (nfingers != 2)
+               goto out;
+
+       /* two fingers down on the touchpad. Check for distance
+        * between the fingers. */
+       tp_for_each_touch(tp, t) {
+               if (t->state != TOUCH_BEGIN && t->state != TOUCH_UPDATE)
+                       continue;
+
+               if (t->thumb.state == THUMB_STATE_YES)
+                       continue;
+
+               if (!first)
+                       first = t;
+               else if (!second)
+                       second = t;
+       }
+
+       if (!first || !second) {
+               nfingers = 1;
+               goto out;
+       }
+
+       if (tp_clickfinger_within_distance(tp, first, second))
+               nfingers = 2;
+       else
+               nfingers = 1;
+
+out:
+       switch (nfingers) {
+       case 0:
+       case 1: button = BTN_LEFT; break;
+       case 2: button = BTN_RIGHT; break;
+       default:
+               button = BTN_MIDDLE; break;
+               break;
+       }
+
+       return button;
+}
+
 static int
 tp_notify_clickpadbutton(struct tp_dispatch *tp,
                         uint64_t time,
@@ -761,33 +1043,33 @@ tp_notify_clickpadbutton(struct tp_dispatch *tp,
                struct evdev_dispatch *dispatch = tp->buttons.trackpoint->dispatch;
                struct input_event event;
 
-               event.time.tv_sec = time/1000;
-               event.time.tv_usec = (time % 1000) * 1000;
+               event.time.tv_sec = time / ms2us(1000);
+               event.time.tv_usec = time % ms2us(1000);
                event.type = EV_KEY;
                event.code = button;
                event.value = (state == LIBINPUT_BUTTON_STATE_PRESSED) ? 1 : 0;
-               dispatch->interface->process(dispatch, tp->buttons.trackpoint,
-                                            &event, time);
+               dispatch->interface->process(dispatch,
+                                            tp->buttons.trackpoint,
+                                            &event,
+                                            time);
                return 1;
        }
 
        /* Ignore button events not for the trackpoint while suspended */
-       if (tp->device->suspended)
+       if (tp->device->is_suspended)
                return 0;
 
+       /* A button click always terminates edge scrolling, even if we
+        * don't end up sending a button event. */
+       tp_edge_scroll_stop_events(tp, time);
+
        /*
         * If the user has requested clickfinger replace the button chosen
         * by the softbutton code with one based on the number of fingers.
         */
        if (tp->buttons.click_method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER &&
-                       state == LIBINPUT_BUTTON_STATE_PRESSED) {
-               switch (tp->nfingers_down) {
-               case 1: button = BTN_LEFT; break;
-               case 2: button = BTN_RIGHT; break;
-               case 3: button = BTN_MIDDLE; break;
-               default:
-                       button = 0;
-               }
+           state == LIBINPUT_BUTTON_STATE_PRESSED) {
+               button = tp_clickfinger_set_button(tp);
                tp->buttons.active = button;
 
                if (!button)
@@ -804,10 +1086,10 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time)
        uint32_t current, old, button, is_top;
        enum libinput_button_state state;
        enum { AREA = 0x01, LEFT = 0x02, MIDDLE = 0x04, RIGHT = 0x08 };
+       bool want_left_handed = true;
 
        current = tp->buttons.state;
        old = tp->buttons.old_state;
-       button = 0;
        is_top = 0;
 
        if (!tp->buttons.click_pending && current == old)
@@ -815,47 +1097,62 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time)
 
        if (current) {
                struct tp_touch *t;
+               uint32_t area = 0;
 
                tp_for_each_touch(tp, t) {
                        switch (t->button.curr) {
                        case BUTTON_EVENT_IN_AREA:
-                               button |= AREA;
+                               area |= AREA;
                                break;
                        case BUTTON_EVENT_IN_TOP_L:
                                is_top = 1;
                                /* fallthrough */
                        case BUTTON_EVENT_IN_BOTTOM_L:
-                               button |= LEFT;
+                               area |= LEFT;
                                break;
                        case BUTTON_EVENT_IN_TOP_M:
                                is_top = 1;
-                               button |= MIDDLE;
+                               /* fallthrough */
+                       case BUTTON_EVENT_IN_BOTTOM_M:
+                               area |= MIDDLE;
                                break;
                        case BUTTON_EVENT_IN_TOP_R:
                                is_top = 1;
                                /* fallthrough */
                        case BUTTON_EVENT_IN_BOTTOM_R:
-                               button |= RIGHT;
+                               area |= RIGHT;
                                break;
                        default:
                                break;
                        }
                }
 
-               if (button == 0) {
+               if (area == 0 &&
+                   tp->buttons.click_method != LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) {
                        /* No touches, wait for a touch before processing */
                        tp->buttons.click_pending = true;
                        return 0;
                }
 
-               if ((button & MIDDLE) || ((button & LEFT) && (button & RIGHT)))
-                       button = evdev_to_left_handed(tp->device, BTN_MIDDLE);
-               else if (button & RIGHT)
-                       button = evdev_to_left_handed(tp->device, BTN_RIGHT);
-               else if (button & LEFT)
-                       button = evdev_to_left_handed(tp->device, BTN_LEFT);
-               else /* main area is always BTN_LEFT */
+               if ((tp->device->middlebutton.enabled || is_top) &&
+                   (area & LEFT) && (area & RIGHT)) {
+                       button = BTN_MIDDLE;
+               } else if (area & MIDDLE) {
+                       button = BTN_MIDDLE;
+               } else if (area & RIGHT) {
+                       button = BTN_RIGHT;
+               } else if (area & LEFT) {
+                       button = BTN_LEFT;
+               } else { /* main or no area (for clickfinger) is always BTN_LEFT */
                        button = BTN_LEFT;
+                       want_left_handed = false;
+               }
+
+               if (is_top)
+                       want_left_handed = false;
+
+               if (want_left_handed)
+                       button = evdev_to_left_handed(tp->device, button);
 
                tp->buttons.active = button;
                tp->buttons.active_is_topbutton = is_top;
@@ -871,8 +1168,11 @@ tp_post_clickpadbutton_buttons(struct tp_dispatch *tp, uint64_t time)
        tp->buttons.click_pending = false;
 
        if (button)
-               return tp_notify_clickpadbutton(tp, time, button, is_top, state);
-
+               return tp_notify_clickpadbutton(tp,
+                                               time,
+                                               button,
+                                               is_top,
+                                               state);
        return 0;
 }
 
@@ -885,14 +1185,17 @@ tp_post_button_events(struct tp_dispatch *tp, uint64_t time)
                return tp_post_physical_buttons(tp, time);
 }
 
-int
-tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
+bool
+tp_button_touch_active(const struct tp_dispatch *tp,
+                      const struct tp_touch *t)
 {
        return t->button.state == BUTTON_STATE_AREA;
 }
 
 bool
-tp_button_is_inside_softbutton_area(struct tp_dispatch *tp, struct tp_touch *t)
+tp_button_is_inside_softbutton_area(const struct tp_dispatch *tp,
+                                   const struct tp_touch *t)
 {
-       return is_inside_top_button_area(tp, t) || is_inside_bottom_button_area(tp, t);
+       return is_inside_top_button_area(tp, t) ||
+              is_inside_bottom_button_area(tp, t);
 }
index 8d0a13e37da44baaec93df47430456358ab53f81..1d30bcab20697eb303c6d458deede10734a13224 100644 (file)
@@ -1,25 +1,28 @@
 /*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
+#include "config.h"
+
 #include <errno.h>
 #include <limits.h>
 #include <math.h>
 
 #include "evdev-mt-touchpad.h"
 
-#define DEFAULT_SCROLL_LOCK_TIMEOUT 300 /* ms */
 /* Use a reasonably large threshold until locked into scrolling mode, to
    avoid accidentally locking in scrolling mode when trying to use the entire
    touchpad to move the pointer. The user can wait for the timeout to trigger
    to do a small scroll. */
-/* In mm for touchpads with valid resolution, see tp_init_accel() */
-#define DEFAULT_SCROLL_THRESHOLD 10.0
+#define DEFAULT_SCROLL_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3)
 
 enum scroll_event {
        SCROLL_EVENT_TOUCH,
@@ -45,23 +46,66 @@ enum scroll_event {
        SCROLL_EVENT_POSTED,
 };
 
-static uint32_t
-tp_touch_get_edge(struct tp_dispatch *tp, struct tp_touch *touch)
+static inline const char*
+edge_state_to_str(enum tp_edge_scroll_touch_state state)
+{
+
+       switch (state) {
+       CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_NONE);
+       CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_EDGE_NEW);
+       CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_EDGE);
+       CASE_RETURN_STRING(EDGE_SCROLL_TOUCH_STATE_AREA);
+       }
+       return NULL;
+}
+
+static inline const char*
+edge_event_to_str(enum scroll_event event)
+{
+       switch (event) {
+       CASE_RETURN_STRING(SCROLL_EVENT_TOUCH);
+       CASE_RETURN_STRING(SCROLL_EVENT_MOTION);
+       CASE_RETURN_STRING(SCROLL_EVENT_RELEASE);
+       CASE_RETURN_STRING(SCROLL_EVENT_TIMEOUT);
+       CASE_RETURN_STRING(SCROLL_EVENT_POSTED);
+       }
+       return NULL;
+}
+
+uint32_t
+tp_touch_get_edge(const struct tp_dispatch *tp, const struct tp_touch *t)
 {
        uint32_t edge = EDGE_NONE;
 
        if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE)
                return EDGE_NONE;
 
-       if (touch->x > tp->scroll.right_edge)
+       if (t->point.x > tp->scroll.right_edge)
                edge |= EDGE_RIGHT;
 
-       if (touch->y > tp->scroll.bottom_edge)
+       if (t->point.y > tp->scroll.bottom_edge)
                edge |= EDGE_BOTTOM;
 
        return edge;
 }
 
+static inline void
+tp_edge_scroll_set_timer(struct tp_dispatch *tp,
+                        struct tp_touch *t)
+{
+       const int DEFAULT_SCROLL_LOCK_TIMEOUT = ms2us(300);
+       /* if we use software buttons, we disable timeout-based
+        * edge scrolling. A finger resting on the button areas is
+        * likely there to trigger a button event.
+        */
+       if (tp->buttons.click_method ==
+           LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
+               return;
+
+       libinput_timer_set(&t->scroll.timer,
+                          t->millis + DEFAULT_SCROLL_LOCK_TIMEOUT);
+}
+
 static void
 tp_edge_scroll_set_state(struct tp_dispatch *tp,
                         struct tp_touch *t,
@@ -74,19 +118,16 @@ tp_edge_scroll_set_state(struct tp_dispatch *tp,
        switch (state) {
        case EDGE_SCROLL_TOUCH_STATE_NONE:
                t->scroll.edge = EDGE_NONE;
-               t->scroll.threshold = DEFAULT_SCROLL_THRESHOLD;
                break;
        case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
                t->scroll.edge = tp_touch_get_edge(tp, t);
-               libinput_timer_set(&t->scroll.timer,
-                                  t->millis + DEFAULT_SCROLL_LOCK_TIMEOUT);
+               t->scroll.initial = t->point;
+               tp_edge_scroll_set_timer(tp, t);
                break;
        case EDGE_SCROLL_TOUCH_STATE_EDGE:
-               t->scroll.threshold = 0.01; /* Do not allow 0.0 events */
                break;
        case EDGE_SCROLL_TOUCH_STATE_AREA:
                t->scroll.edge = EDGE_NONE;
-               tp_set_pointer(tp, t);
                break;
        }
 }
@@ -96,7 +137,7 @@ tp_edge_scroll_handle_none(struct tp_dispatch *tp,
                           struct tp_touch *t,
                           enum scroll_event event)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
 
        switch (event) {
        case SCROLL_EVENT_TOUCH:
@@ -124,7 +165,7 @@ tp_edge_scroll_handle_edge_new(struct tp_dispatch *tp,
                               struct tp_touch *t,
                               enum scroll_event event)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
 
        switch (event) {
        case SCROLL_EVENT_TOUCH:
@@ -153,7 +194,7 @@ tp_edge_scroll_handle_edge(struct tp_dispatch *tp,
                           struct tp_touch *t,
                           enum scroll_event event)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
 
        switch (event) {
        case SCROLL_EVENT_TOUCH:
@@ -184,7 +225,7 @@ tp_edge_scroll_handle_area(struct tp_dispatch *tp,
                           struct tp_touch *t,
                           enum scroll_event event)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
 
        switch (event) {
        case SCROLL_EVENT_TOUCH:
@@ -207,7 +248,10 @@ tp_edge_scroll_handle_event(struct tp_dispatch *tp,
                            struct tp_touch *t,
                            enum scroll_event event)
 {
-       switch (t->scroll.edge_state) {
+       struct libinput *libinput = tp_libinput_context(tp);
+       enum tp_edge_scroll_touch_state current = t->scroll.edge_state;
+
+       switch (current) {
        case EDGE_SCROLL_TOUCH_STATE_NONE:
                tp_edge_scroll_handle_none(tp, t, event);
                break;
@@ -221,6 +265,12 @@ tp_edge_scroll_handle_event(struct tp_dispatch *tp,
                tp_edge_scroll_handle_area(tp, t, event);
                break;
        }
+
+       log_debug(libinput,
+                 "edge state: %s → %s → %s\n",
+                 edge_state_to_str(current),
+                 edge_event_to_str(event),
+                 edge_state_to_str(t->scroll.edge_state));
 }
 
 static void
@@ -231,47 +281,42 @@ tp_edge_scroll_handle_timeout(uint64_t now, void *data)
        tp_edge_scroll_handle_event(t->tp, t, SCROLL_EVENT_TIMEOUT);
 }
 
-int
+void
 tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device)
 {
        struct tp_touch *t;
-       int width, height;
-       int edge_width, edge_height;
-
-       width = device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum;
-       height = device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum;
-
-       switch (tp->model) {
-       case MODEL_ALPS:
-               edge_width = width * .15;
-               edge_height = height * .15;
-               break;
-       case MODEL_APPLETOUCH: /* unibody are all clickpads, so N/A */
-               edge_width = width * .085;
-               edge_height = height * .085;
-               break;
-       default:
-               /* For elantech and synaptics, note for lenovo #40 series,
-                * e.g. the T440s min/max are the absolute edges, not the
-                * recommended ones as usual with synaptics. But these are
-                * clickpads, so N/A.
-                */
-               edge_width = width * .04;
-               edge_height = height * .054;
-       }
-
-       tp->scroll.right_edge = device->abs.absinfo_x->maximum - edge_width;
-       tp->scroll.bottom_edge = device->abs.absinfo_y->maximum - edge_height;
+       double width, height;
+       bool want_horiz_scroll = true;
+       struct device_coords edges;
+       struct phys_coords mm = { 0.0, 0.0 };
+
+       evdev_device_get_size(device, &width, &height);
+       /* Touchpads smaller than 50mm are not tall enough to have a
+          horizontal scroll area, it takes too much space away. But
+          clickpads have enough space here anyway because of the
+          software button area (and all these tiny clickpads were built
+          when software buttons were a thing, e.g. Lenovo *20 series)
+        */
+       if (!tp->buttons.is_clickpad)
+           want_horiz_scroll = (height >= 50);
+
+       /* 7mm edge size */
+       mm.x = width - 7;
+       mm.y = height - 7;
+       edges = evdev_device_mm_to_units(device, &mm);
+
+       tp->scroll.right_edge = edges.x;
+       if (want_horiz_scroll)
+               tp->scroll.bottom_edge = edges.y;
+       else
+               tp->scroll.bottom_edge = INT_MAX;
 
        tp_for_each_touch(tp, t) {
                t->scroll.direction = -1;
-               t->scroll.threshold = DEFAULT_SCROLL_THRESHOLD;
                libinput_timer_init(&t->scroll.timer,
-                                   device->base.seat->libinput,
+                                   tp_libinput_context(tp),
                                    tp_edge_scroll_handle_timeout, t);
        }
-
-       return 0;
 }
 
 void
@@ -288,6 +333,18 @@ tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
 {
        struct tp_touch *t;
 
+       if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE) {
+               tp_for_each_touch(tp, t) {
+                       if (t->state == TOUCH_BEGIN)
+                               t->scroll.edge_state =
+                                       EDGE_SCROLL_TOUCH_STATE_AREA;
+                       else if (t->state == TOUCH_END)
+                               t->scroll.edge_state =
+                                       EDGE_SCROLL_TOUCH_STATE_NONE;
+               }
+               return;
+       }
+
        tp_for_each_touch(tp, t) {
                if (!t->dirty)
                        continue;
@@ -312,50 +369,83 @@ tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
 int
 tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
 {
-       struct libinput_device *device = &tp->device->base;
+       struct evdev_device *device = tp->device;
        struct tp_touch *t;
        enum libinput_pointer_axis axis;
-       double dx, dy, *delta;
+       double *delta;
+       struct normalized_coords normalized, tmp;
+       const struct normalized_coords zero = { 0.0, 0.0 };
+       const struct discrete_coords zero_discrete = { 0.0, 0.0 };
 
        tp_for_each_touch(tp, t) {
                if (!t->dirty)
                        continue;
 
+               if (t->palm.state != PALM_NONE)
+                       continue;
+
+               /* only scroll with the finger in the previous edge */
+               if (t->scroll.edge &&
+                   (tp_touch_get_edge(tp, t) & t->scroll.edge) == 0)
+                       continue;
+
                switch (t->scroll.edge) {
                        case EDGE_NONE:
                                if (t->scroll.direction != -1) {
                                        /* Send stop scroll event */
-                                       pointer_notify_axis(device, time,
+                                       evdev_notify_axis(device, time,
                                                AS_MASK(t->scroll.direction),
                                                LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
-                                               0.0, 0.0,
-                                               0, 0);
+                                               &zero,
+                                               &zero_discrete);
                                        t->scroll.direction = -1;
                                }
                                continue;
                        case EDGE_RIGHT:
                                axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
-                               delta = &dy;
+                               delta = &normalized.y;
                                break;
                        case EDGE_BOTTOM:
                                axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
-                               delta = &dx;
+                               delta = &normalized.x;
                                break;
                        default: /* EDGE_RIGHT | EDGE_BOTTOM */
                                continue; /* Don't know direction yet, skip */
                }
 
-               tp_get_delta(t, &dx, &dy);
-               tp_filter_motion(tp, &dx, &dy, NULL, NULL, time);
+               normalized = tp_get_delta(t);
+               /* scroll is not accelerated */
+               normalized = tp_filter_motion_unaccelerated(tp, &normalized, time);
+
+               switch (t->scroll.edge_state) {
+               case EDGE_SCROLL_TOUCH_STATE_NONE:
+               case EDGE_SCROLL_TOUCH_STATE_AREA:
+                       log_bug_libinput(tp_libinput_context(tp),
+                                        "unexpected scroll state %d\n",
+                                        t->scroll.edge_state);
+                       break;
+               case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
+                       tmp = normalized;
+                       normalized = tp_normalize_delta(tp,
+                                       device_delta(t->point,
+                                                    t->scroll.initial));
+                       if (fabs(*delta) < DEFAULT_SCROLL_THRESHOLD)
+                               normalized = zero;
+                       else
+                               normalized = tmp;
+                       break;
+               case EDGE_SCROLL_TOUCH_STATE_EDGE:
+                       break;
+               }
 
-               if (fabs(*delta) < t->scroll.threshold)
+               if (*delta == 0.0)
                        continue;
 
-               pointer_notify_axis(device, time,
-                                   AS_MASK(axis),
-                                   LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
-                                   dx, dy,
-                                   0, 0);
+               evdev_notify_axis(device, time,
+                                 AS_MASK(axis),
+                                 LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
+                                 &normalized,
+                                 &zero_discrete);
                t->scroll.direction = axis;
 
                tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_POSTED);
@@ -367,23 +457,30 @@ tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
 void
 tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time)
 {
-       struct libinput_device *device = &tp->device->base;
+       struct evdev_device *device = tp->device;
        struct tp_touch *t;
+       const struct normalized_coords zero = { 0.0, 0.0 };
+       const struct discrete_coords zero_discrete = { 0.0, 0.0 };
 
        tp_for_each_touch(tp, t) {
                if (t->scroll.direction != -1) {
-                       pointer_notify_axis(device, time,
+                       evdev_notify_axis(device, time,
                                            AS_MASK(t->scroll.direction),
                                            LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
-                                           0.0, 0.0,
-                                           0.0, 0.0);
+                                           &zero,
+                                           &zero_discrete);
                        t->scroll.direction = -1;
+                       /* reset touch to area state, avoids loading the
+                        * state machine with special case handling */
+                       t->scroll.edge = EDGE_NONE;
+                       t->scroll.edge_state = EDGE_SCROLL_TOUCH_STATE_AREA;
                }
        }
 }
 
 int
-tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
+tp_edge_scroll_touch_active(const struct tp_dispatch *tp,
+                           const struct tp_touch *t)
 {
        return t->scroll.edge_state == EDGE_SCROLL_TOUCH_STATE_AREA;
 }
diff --git a/src/evdev-mt-touchpad-gestures.c b/src/evdev-mt-touchpad-gestures.c
new file mode 100644 (file)
index 0000000..8b854b3
--- /dev/null
@@ -0,0 +1,643 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include "evdev-mt-touchpad.h"
+
+#define DEFAULT_GESTURE_SWITCH_TIMEOUT ms2us(100)
+#define DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT ms2us(150)
+
+static inline const char*
+gesture_state_to_str(enum tp_gesture_state state)
+{
+       switch (state) {
+       CASE_RETURN_STRING(GESTURE_STATE_NONE);
+       CASE_RETURN_STRING(GESTURE_STATE_UNKNOWN);
+       CASE_RETURN_STRING(GESTURE_STATE_SCROLL);
+       CASE_RETURN_STRING(GESTURE_STATE_PINCH);
+       CASE_RETURN_STRING(GESTURE_STATE_SWIPE);
+       }
+       return NULL;
+}
+
+static struct normalized_coords
+tp_get_touches_delta(struct tp_dispatch *tp, bool average)
+{
+       struct tp_touch *t;
+       unsigned int i, nactive = 0;
+       struct normalized_coords normalized;
+       struct normalized_coords delta = {0.0, 0.0};
+
+       for (i = 0; i < tp->num_slots; i++) {
+               t = &tp->touches[i];
+
+               if (!tp_touch_active(tp, t))
+                       continue;
+
+               nactive++;
+
+               if (t->dirty) {
+                       normalized = tp_get_delta(t);
+
+                       delta.x += normalized.x;
+                       delta.y += normalized.y;
+               }
+       }
+
+       if (!average || nactive == 0)
+               return delta;
+
+       delta.x /= nactive;
+       delta.y /= nactive;
+
+       return delta;
+}
+
+static inline struct normalized_coords
+tp_get_combined_touches_delta(struct tp_dispatch *tp)
+{
+       return tp_get_touches_delta(tp, false);
+}
+
+static inline struct normalized_coords
+tp_get_average_touches_delta(struct tp_dispatch *tp)
+{
+       return tp_get_touches_delta(tp, true);
+}
+
+static void
+tp_gesture_start(struct tp_dispatch *tp, uint64_t time)
+{
+       struct libinput *libinput = tp_libinput_context(tp);
+       const struct normalized_coords zero = { 0.0, 0.0 };
+
+       if (tp->gesture.started)
+               return;
+
+       switch (tp->gesture.state) {
+       case GESTURE_STATE_NONE:
+       case GESTURE_STATE_UNKNOWN:
+               log_bug_libinput(libinput,
+                                "%s in unknown gesture mode\n",
+                                __func__);
+               break;
+       case GESTURE_STATE_SCROLL:
+               /* NOP */
+               break;
+       case GESTURE_STATE_PINCH:
+               gesture_notify_pinch(&tp->device->base, time,
+                                   LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                   tp->gesture.finger_count,
+                                   &zero, &zero, 1.0, 0.0);
+               break;
+       case GESTURE_STATE_SWIPE:
+               gesture_notify_swipe(&tp->device->base, time,
+                                    LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                                    tp->gesture.finger_count,
+                                    &zero, &zero);
+               break;
+       }
+
+       tp->gesture.started = true;
+}
+
+static void
+tp_gesture_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
+{
+       struct normalized_coords delta, unaccel;
+       struct device_float_coords raw;
+
+       /* When a clickpad is clicked, combine motion of all active touches */
+       if (tp->buttons.is_clickpad && tp->buttons.state)
+               unaccel = tp_get_combined_touches_delta(tp);
+       else
+               unaccel = tp_get_average_touches_delta(tp);
+
+       delta = tp_filter_motion(tp, &unaccel, time);
+
+       if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) {
+               raw = tp_unnormalize_for_xaxis(tp, unaccel);
+               pointer_notify_motion(&tp->device->base,
+                                     time,
+                                     &delta,
+                                     &raw);
+       }
+}
+
+static unsigned int
+tp_gesture_get_active_touches(const struct tp_dispatch *tp,
+                             struct tp_touch **touches,
+                             unsigned int count)
+{
+       unsigned int i, n = 0;
+       struct tp_touch *t;
+
+       memset(touches, 0, count * sizeof(struct tp_touch *));
+
+       for (i = 0; i < tp->ntouches; i++) {
+               t = &tp->touches[i];
+               if (tp_touch_active(tp, t)) {
+                       touches[n++] = t;
+                       if (n == count)
+                               return count;
+               }
+       }
+
+       /*
+        * This can happen when the user does .e.g:
+        * 1) Put down 1st finger in center (so active)
+        * 2) Put down 2nd finger in a button area (so inactive)
+        * 3) Put down 3th finger somewhere, gets reported as a fake finger,
+        *    so gets same coordinates as 1st -> active
+        *
+        * We could avoid this by looking at all touches, be we really only
+        * want to look at real touches.
+        */
+       return n;
+}
+
+static int
+tp_gesture_get_direction(struct tp_dispatch *tp, struct tp_touch *touch,
+                        unsigned int nfingers)
+{
+       struct normalized_coords normalized;
+       struct device_float_coords delta;
+       double move_threshold = TP_MM_TO_DPI_NORMALIZED(1);
+
+       move_threshold *= (nfingers - 1);
+
+       delta = device_delta(touch->point, touch->gesture.initial);
+
+       normalized = tp_normalize_delta(tp, delta);
+
+       if (normalized_length(normalized) < move_threshold)
+               return UNDEFINED_DIRECTION;
+
+       return normalized_get_direction(normalized);
+}
+
+static void
+tp_gesture_get_pinch_info(struct tp_dispatch *tp,
+                         double *distance,
+                         double *angle,
+                         struct device_float_coords *center)
+{
+       struct normalized_coords normalized;
+       struct device_float_coords delta;
+       struct tp_touch *first = tp->gesture.touches[0],
+                       *second = tp->gesture.touches[1];
+
+       delta = device_delta(first->point, second->point);
+       normalized = tp_normalize_delta(tp, delta);
+       *distance = normalized_length(normalized);
+       *angle = atan2(normalized.y, normalized.x) * 180.0 / M_PI;
+
+       *center = device_average(first->point, second->point);
+}
+
+static void
+tp_gesture_set_scroll_buildup(struct tp_dispatch *tp)
+{
+       struct device_float_coords d0, d1;
+       struct device_float_coords average;
+       struct tp_touch *first = tp->gesture.touches[0],
+                       *second = tp->gesture.touches[1];
+
+       d0 = device_delta(first->point, first->gesture.initial);
+       d1 = device_delta(second->point, second->gesture.initial);
+
+       average = device_float_average(d0, d1);
+       tp->device->scroll.buildup = tp_normalize_delta(tp, average);
+}
+
+static enum tp_gesture_state
+tp_gesture_handle_state_none(struct tp_dispatch *tp, uint64_t time)
+{
+       struct tp_touch *first, *second;
+       struct tp_touch *touches[4];
+       unsigned int ntouches;
+       unsigned int i;
+
+       ntouches = tp_gesture_get_active_touches(tp, touches, 4);
+       if (ntouches < 2)
+               return GESTURE_STATE_NONE;
+
+       if (!tp->gesture.enabled) {
+               if (ntouches == 2)
+                       return GESTURE_STATE_SCROLL;
+               else
+                       return GESTURE_STATE_NONE;
+       }
+
+       first = touches[0];
+       second = touches[1];
+
+       /* For 3+ finger gestures we cheat. A human hand's finger
+        * arrangement means that for a 3 or 4 finger swipe gesture, the
+        * fingers are roughly arranged in a horizontal line.
+        * They will all move in the same direction, so we can simply look
+        * at the left and right-most ones only. If we have fake touches, we
+        * just take the left/right-most real touch position, since the fake
+        * touch has the same location as one of those.
+        *
+        * For a 3 or 4 finger pinch gesture, 2 or 3 fingers are roughly in
+        * a horizontal line, with the thumb below and left (right-handed
+        * users) or right (left-handed users). Again, the row of non-thumb
+        * fingers moves identically so we can look at the left and
+        * right-most only and then treat it like a two-finger
+        * gesture.
+        */
+       if (ntouches > 2) {
+               second = touches[0];
+
+               for (i = 1; i < ntouches && i < tp->num_slots; i++) {
+                       if (touches[i]->point.x < first->point.x)
+                               first = touches[i];
+                       else if (touches[i]->point.x > second->point.x)
+                               second = touches[i];
+               }
+
+               if (first == second)
+                       return GESTURE_STATE_NONE;
+
+       }
+
+       tp->gesture.initial_time = time;
+       first->gesture.initial = first->point;
+       second->gesture.initial = second->point;
+       tp->gesture.touches[0] = first;
+       tp->gesture.touches[1] = second;
+
+       return GESTURE_STATE_UNKNOWN;
+}
+
+static inline int
+tp_gesture_same_directions(int dir1, int dir2)
+{
+       /*
+        * In some cases (semi-mt touchpads) we may seen one finger move
+        * e.g. N/NE and the other W/NW so we not only check for overlapping
+        * directions, but also for neighboring bits being set.
+        * The ((dira & 0x80) && (dirb & 0x01)) checks are to check for bit 0
+        * and 7 being set as they also represent neighboring directions.
+        */
+       return ((dir1 | (dir1 >> 1)) & dir2) ||
+               ((dir2 | (dir2 >> 1)) & dir1) ||
+               ((dir1 & 0x80) && (dir2 & 0x01)) ||
+               ((dir2 & 0x80) && (dir1 & 0x01));
+}
+
+static inline void
+tp_gesture_init_pinch( struct tp_dispatch *tp)
+{
+       tp_gesture_get_pinch_info(tp,
+                                 &tp->gesture.initial_distance,
+                                 &tp->gesture.angle,
+                                 &tp->gesture.center);
+       tp->gesture.prev_scale = 1.0;
+}
+
+static enum tp_gesture_state
+tp_gesture_handle_state_unknown(struct tp_dispatch *tp, uint64_t time)
+{
+       struct tp_touch *first = tp->gesture.touches[0],
+                       *second = tp->gesture.touches[1];
+       int dir1, dir2;
+       int yres = tp->device->abs.absinfo_y->resolution;
+       int vert_distance;
+
+       /* for two-finger gestures, if the fingers stay unmoving for a
+        * while, assume (slow) scroll */
+       if (tp->gesture.finger_count == 2) {
+               if (time > (tp->gesture.initial_time + DEFAULT_GESTURE_2FG_SCROLL_TIMEOUT)) {
+                       tp_gesture_set_scroll_buildup(tp);
+                       return GESTURE_STATE_SCROLL;
+               }
+
+               /* Else check if one finger is > 20mm below the others */
+               vert_distance = abs(first->point.y - second->point.y);
+               if (vert_distance > 20 * yres &&
+                   tp->gesture.finger_count > 2 &&
+                   tp->gesture.enabled) {
+                       tp_gesture_init_pinch(tp);
+                       return GESTURE_STATE_PINCH;
+               }
+       }
+
+       /* Else wait for both fingers to have moved */
+       dir1 = tp_gesture_get_direction(tp, first, tp->gesture.finger_count);
+       dir2 = tp_gesture_get_direction(tp, second, tp->gesture.finger_count);
+       if (dir1 == UNDEFINED_DIRECTION || dir2 == UNDEFINED_DIRECTION)
+               return GESTURE_STATE_UNKNOWN;
+
+       /* If both touches are moving in the same direction assume
+        * scroll or swipe */
+       if (tp_gesture_same_directions(dir1, dir2)) {
+               if (tp->gesture.finger_count == 2) {
+                       tp_gesture_set_scroll_buildup(tp);
+                       return GESTURE_STATE_SCROLL;
+               } else if (tp->gesture.enabled) {
+                       return GESTURE_STATE_SWIPE;
+               }
+       } else {
+               tp_gesture_init_pinch(tp);
+               return GESTURE_STATE_PINCH;
+       }
+
+       return GESTURE_STATE_UNKNOWN;
+}
+
+static enum tp_gesture_state
+tp_gesture_handle_state_scroll(struct tp_dispatch *tp, uint64_t time)
+{
+       struct normalized_coords delta;
+
+       if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
+               return GESTURE_STATE_SCROLL;
+
+       delta = tp_get_average_touches_delta(tp);
+
+       /* scroll is not accelerated */
+       delta = tp_filter_motion_unaccelerated(tp, &delta, time);
+
+       if (normalized_is_zero(delta))
+               return GESTURE_STATE_SCROLL;
+
+       tp_gesture_start(tp, time);
+       evdev_post_scroll(tp->device,
+                         time,
+                         LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
+                         &delta);
+
+       return GESTURE_STATE_SCROLL;
+}
+
+static enum tp_gesture_state
+tp_gesture_handle_state_swipe(struct tp_dispatch *tp, uint64_t time)
+{
+       struct normalized_coords delta, unaccel;
+
+       unaccel = tp_get_average_touches_delta(tp);
+       delta = tp_filter_motion(tp, &unaccel, time);
+
+       if (!normalized_is_zero(delta) || !normalized_is_zero(unaccel)) {
+               tp_gesture_start(tp, time);
+               gesture_notify_swipe(&tp->device->base, time,
+                                    LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                                    tp->gesture.finger_count,
+                                    &delta, &unaccel);
+       }
+
+       return GESTURE_STATE_SWIPE;
+}
+
+static enum tp_gesture_state
+tp_gesture_handle_state_pinch(struct tp_dispatch *tp, uint64_t time)
+{
+       double angle, angle_delta, distance, scale;
+       struct device_float_coords center, fdelta;
+       struct normalized_coords delta, unaccel;
+
+       tp_gesture_get_pinch_info(tp, &distance, &angle, &center);
+
+       scale = distance / tp->gesture.initial_distance;
+
+       angle_delta = angle - tp->gesture.angle;
+       tp->gesture.angle = angle;
+       if (angle_delta > 180.0)
+               angle_delta -= 360.0;
+       else if (angle_delta < -180.0)
+               angle_delta += 360.0;
+
+       fdelta = device_float_delta(center, tp->gesture.center);
+       tp->gesture.center = center;
+       unaccel = tp_normalize_delta(tp, fdelta);
+       delta = tp_filter_motion(tp, &unaccel, time);
+
+       if (normalized_is_zero(delta) && normalized_is_zero(unaccel) &&
+           scale == tp->gesture.prev_scale && angle_delta == 0.0)
+               return GESTURE_STATE_PINCH;
+
+       tp_gesture_start(tp, time);
+       gesture_notify_pinch(&tp->device->base, time,
+                            LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                            tp->gesture.finger_count,
+                            &delta, &unaccel, scale, angle_delta);
+
+       tp->gesture.prev_scale = scale;
+
+       return GESTURE_STATE_PINCH;
+}
+
+static void
+tp_gesture_post_gesture(struct tp_dispatch *tp, uint64_t time)
+{
+       enum tp_gesture_state oldstate = tp->gesture.state;
+
+       if (tp->gesture.state == GESTURE_STATE_NONE)
+               tp->gesture.state =
+                       tp_gesture_handle_state_none(tp, time);
+
+       if (tp->gesture.state == GESTURE_STATE_UNKNOWN)
+               tp->gesture.state =
+                       tp_gesture_handle_state_unknown(tp, time);
+
+       if (tp->gesture.state == GESTURE_STATE_SCROLL)
+               tp->gesture.state =
+                       tp_gesture_handle_state_scroll(tp, time);
+
+       if (tp->gesture.state == GESTURE_STATE_SWIPE)
+               tp->gesture.state =
+                       tp_gesture_handle_state_swipe(tp, time);
+
+       if (tp->gesture.state == GESTURE_STATE_PINCH)
+               tp->gesture.state =
+                       tp_gesture_handle_state_pinch(tp, time);
+
+       log_debug(tp_libinput_context(tp),
+                 "gesture state: %s → %s\n",
+                 gesture_state_to_str(oldstate),
+                 gesture_state_to_str(tp->gesture.state));
+}
+
+void
+tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time)
+{
+       if (tp->gesture.finger_count == 0)
+               return;
+
+       /* When tap-and-dragging, or a clickpad is clicked force 1fg mode */
+       if (tp_tap_dragging(tp) || (tp->buttons.is_clickpad && tp->buttons.state)) {
+               tp_gesture_cancel(tp, time);
+               tp->gesture.finger_count = 1;
+               tp->gesture.finger_count_pending = 0;
+       }
+
+       /* Don't send events when we're unsure in which mode we are */
+       if (tp->gesture.finger_count_pending)
+               return;
+
+       switch (tp->gesture.finger_count) {
+       case 1:
+               if (tp->queued & TOUCHPAD_EVENT_MOTION)
+                       tp_gesture_post_pointer_motion(tp, time);
+               break;
+       case 2:
+       case 3:
+       case 4:
+               tp_gesture_post_gesture(tp, time);
+               break;
+       }
+}
+
+void
+tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
+{
+       if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG)
+               return;
+
+       evdev_stop_scroll(tp->device,
+                         time,
+                         LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+}
+
+static void
+tp_gesture_end(struct tp_dispatch *tp, uint64_t time, bool cancelled)
+{
+       struct libinput *libinput = tp_libinput_context(tp);
+       enum tp_gesture_state state = tp->gesture.state;
+
+       tp->gesture.state = GESTURE_STATE_NONE;
+
+       if (!tp->gesture.started)
+               return;
+
+       switch (state) {
+       case GESTURE_STATE_NONE:
+       case GESTURE_STATE_UNKNOWN:
+               log_bug_libinput(libinput,
+                                "%s in unknown gesture mode\n",
+                                __func__);
+               break;
+       case GESTURE_STATE_SCROLL:
+               tp_gesture_stop_twofinger_scroll(tp, time);
+               break;
+       case GESTURE_STATE_PINCH:
+               gesture_notify_pinch_end(&tp->device->base, time,
+                                        tp->gesture.finger_count,
+                                        tp->gesture.prev_scale,
+                                        cancelled);
+               break;
+       case GESTURE_STATE_SWIPE:
+               gesture_notify_swipe_end(&tp->device->base,
+                                        time,
+                                        tp->gesture.finger_count,
+                                        cancelled);
+               break;
+       }
+
+       tp->gesture.started = false;
+}
+
+void
+tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time)
+{
+       tp_gesture_end(tp, time, true);
+}
+
+void
+tp_gesture_stop(struct tp_dispatch *tp, uint64_t time)
+{
+       tp_gesture_end(tp, time, false);
+}
+
+static void
+tp_gesture_finger_count_switch_timeout(uint64_t now, void *data)
+{
+       struct tp_dispatch *tp = data;
+
+       if (!tp->gesture.finger_count_pending)
+               return;
+
+       tp_gesture_cancel(tp, now); /* End current gesture */
+       tp->gesture.finger_count = tp->gesture.finger_count_pending;
+       tp->gesture.finger_count_pending = 0;
+}
+
+void
+tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time)
+{
+       unsigned int active_touches = 0;
+       struct tp_touch *t;
+
+       tp_for_each_touch(tp, t) {
+               if (tp_touch_active(tp, t))
+                       active_touches++;
+       }
+
+       if (active_touches != tp->gesture.finger_count) {
+               /* If all fingers are lifted immediately end the gesture */
+               if (active_touches == 0) {
+                       tp_gesture_stop(tp, time);
+                       tp->gesture.finger_count = 0;
+                       tp->gesture.finger_count_pending = 0;
+               /* Immediately switch to new mode to avoid initial latency */
+               } else if (!tp->gesture.started) {
+                       tp->gesture.finger_count = active_touches;
+                       tp->gesture.finger_count_pending = 0;
+               /* Else debounce finger changes */
+               } else if (active_touches != tp->gesture.finger_count_pending) {
+                       tp->gesture.finger_count_pending = active_touches;
+                       libinput_timer_set(&tp->gesture.finger_count_switch_timer,
+                               time + DEFAULT_GESTURE_SWITCH_TIMEOUT);
+               }
+       } else {
+                tp->gesture.finger_count_pending = 0;
+       }
+}
+
+void
+tp_init_gesture(struct tp_dispatch *tp)
+{
+       /* two-finger scrolling is always enabled, this flag just
+        * decides whether we detect pinch. semi-mt devices are too
+        * unreliable to do pinch gestures. */
+       tp->gesture.enabled = !tp->semi_mt && tp->num_slots > 1;
+
+       tp->gesture.state = GESTURE_STATE_NONE;
+
+       libinput_timer_init(&tp->gesture.finger_count_switch_timer,
+                           tp_libinput_context(tp),
+                           tp_gesture_finger_count_switch_timeout, tp);
+}
+
+void
+tp_remove_gesture(struct tp_dispatch *tp)
+{
+       libinput_timer_cancel(&tp->gesture.finger_count_switch_timer);
+}
index 33c6da648bc9431a0c0b58a2226681610c8f675f..12639aa3e1d2c8ac44372f24a0b941f0eb323466 100644 (file)
@@ -1,23 +1,24 @@
 /*
- * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software
- * and its documentation for any purpose is hereby granted without
- * fee, provided 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 Red Hat
- * not be used in advertising or publicity pertaining to distribution
- * of the software without specific, written prior permission.  Red
- * Hat makes no representations about the suitability of this software
- * for any purpose.  It is provided "as is" without express or implied
- * warranty.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
- * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
- * NO EVENT SHALL THE AUTHORS 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
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
 
 #include "evdev-mt-touchpad.h"
 
-#define CASE_RETURN_STRING(a) case a: return #a;
-
-#define DEFAULT_TAP_TIMEOUT_PERIOD 180
-#define DEFAULT_TAP_MOVE_THRESHOLD 30
+#define DEFAULT_TAP_TIMEOUT_PERIOD ms2us(180)
+#define DEFAULT_DRAG_TIMEOUT_PERIOD ms2us(300)
+#define DEFAULT_TAP_MOVE_THRESHOLD TP_MM_TO_DPI_NORMALIZED(3)
 
 enum tap_event {
        TAP_EVENT_TOUCH = 12,
@@ -45,6 +45,7 @@ enum tap_event {
        TAP_EVENT_RELEASE,
        TAP_EVENT_BUTTON,
        TAP_EVENT_TIMEOUT,
+       TAP_EVENT_THUMB,
 };
 
 /*****************************************
@@ -59,8 +60,8 @@ enum tap_event {
  */
 
 static inline const char*
-tap_state_to_str(enum tp_tap_state state) {
-
+tap_state_to_str(enum tp_tap_state state)
+{
        switch(state) {
        CASE_RETURN_STRING(TAP_STATE_IDLE);
        CASE_RETURN_STRING(TAP_STATE_HOLD);
@@ -68,30 +69,34 @@ tap_state_to_str(enum tp_tap_state state) {
        CASE_RETURN_STRING(TAP_STATE_TAPPED);
        CASE_RETURN_STRING(TAP_STATE_TOUCH_2);
        CASE_RETURN_STRING(TAP_STATE_TOUCH_2_HOLD);
+       CASE_RETURN_STRING(TAP_STATE_TOUCH_2_RELEASE);
        CASE_RETURN_STRING(TAP_STATE_TOUCH_3);
        CASE_RETURN_STRING(TAP_STATE_TOUCH_3_HOLD);
        CASE_RETURN_STRING(TAP_STATE_DRAGGING);
        CASE_RETURN_STRING(TAP_STATE_DRAGGING_WAIT);
        CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_DOUBLETAP);
+       CASE_RETURN_STRING(TAP_STATE_DRAGGING_OR_TAP);
        CASE_RETURN_STRING(TAP_STATE_DRAGGING_2);
+       CASE_RETURN_STRING(TAP_STATE_MULTITAP);
+       CASE_RETURN_STRING(TAP_STATE_MULTITAP_DOWN);
        CASE_RETURN_STRING(TAP_STATE_DEAD);
        }
        return NULL;
 }
 
 static inline const char*
-tap_event_to_str(enum tap_event event) {
-
+tap_event_to_str(enum tap_event event)
+{
        switch(event) {
        CASE_RETURN_STRING(TAP_EVENT_TOUCH);
        CASE_RETURN_STRING(TAP_EVENT_MOTION);
        CASE_RETURN_STRING(TAP_EVENT_RELEASE);
        CASE_RETURN_STRING(TAP_EVENT_TIMEOUT);
        CASE_RETURN_STRING(TAP_EVENT_BUTTON);
+       CASE_RETURN_STRING(TAP_EVENT_THUMB);
        }
        return NULL;
 }
-#undef CASE_RETURN_STRING
 
 static void
 tp_tap_notify(struct tp_dispatch *tp,
@@ -100,14 +105,17 @@ tp_tap_notify(struct tp_dispatch *tp,
              enum libinput_button_state state)
 {
        int32_t button;
+       int32_t button_map[2][3] = {
+               { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE },
+               { BTN_LEFT, BTN_MIDDLE, BTN_RIGHT },
+       };
 
-       switch (nfingers) {
-       case 1: button = BTN_LEFT; break;
-       case 2: button = BTN_RIGHT; break;
-       case 3: button = BTN_MIDDLE; break;
-       default:
+       assert(tp->tap.map < ARRAY_LENGTH(button_map));
+
+       if (nfingers > 3)
                return;
-       }
+
+       button = button_map[tp->tap.map][nfingers - 1];
 
        if (state == LIBINPUT_BUTTON_STATE_PRESSED)
                tp->tap.buttons_pressed |= (1 << nfingers);
@@ -126,6 +134,12 @@ tp_tap_set_timer(struct tp_dispatch *tp, uint64_t time)
        libinput_timer_set(&tp->tap.timer, time + DEFAULT_TAP_TIMEOUT_PERIOD);
 }
 
+static void
+tp_tap_set_drag_timer(struct tp_dispatch *tp, uint64_t time)
+{
+       libinput_timer_set(&tp->tap.timer, time + DEFAULT_DRAG_TIMEOUT_PERIOD);
+}
+
 static void
 tp_tap_clear_timer(struct tp_dispatch *tp)
 {
@@ -137,23 +151,29 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp,
                         struct tp_touch *t,
                         enum tap_event event, uint64_t time)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
 
        switch (event) {
        case TAP_EVENT_TOUCH:
                tp->tap.state = TAP_STATE_TOUCH;
+               tp->tap.first_press_time = time;
                tp_tap_set_timer(tp, time);
                break;
        case TAP_EVENT_RELEASE:
+               break;
        case TAP_EVENT_MOTION:
                log_bug_libinput(libinput,
-                                "invalid event, no fingers are down\n");
+                                "invalid tap event, no fingers are down\n");
                break;
        case TAP_EVENT_TIMEOUT:
                break;
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               log_bug_libinput(libinput,
+                                "invalid tap event, no fingers down, no thumb\n");
+               break;
        }
 }
 
@@ -169,9 +189,20 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
                tp_tap_set_timer(tp, time);
                break;
        case TAP_EVENT_RELEASE:
-               tp->tap.state = TAP_STATE_TAPPED;
-               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
-               tp_tap_set_timer(tp, time);
+               tp_tap_notify(tp,
+                             tp->tap.first_press_time,
+                             1,
+                             LIBINPUT_BUTTON_STATE_PRESSED);
+               if (tp->tap.drag_enabled) {
+                       tp->tap.state = TAP_STATE_TAPPED;
+                       tp_tap_set_timer(tp, time);
+               } else {
+                       tp_tap_notify(tp,
+                                     time,
+                                     1,
+                                     LIBINPUT_BUTTON_STATE_RELEASED);
+                       tp->tap.state = TAP_STATE_IDLE;
+               }
                break;
        case TAP_EVENT_TIMEOUT:
        case TAP_EVENT_MOTION:
@@ -181,6 +212,12 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               tp->tap.state = TAP_STATE_IDLE;
+               t->tap.is_thumb = true;
+               t->tap.state = TAP_TOUCH_STATE_DEAD;
+               tp_tap_clear_timer(tp);
+               break;
        }
 }
 
@@ -204,6 +241,11 @@ tp_tap_hold_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               tp->tap.state = TAP_STATE_IDLE;
+               t->tap.is_thumb = true;
+               t->tap.state = TAP_TOUCH_STATE_DEAD;
+               break;
        }
 }
 
@@ -212,16 +254,17 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp,
                           struct tp_touch *t,
                           enum tap_event event, uint64_t time)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
 
        switch (event) {
        case TAP_EVENT_MOTION:
        case TAP_EVENT_RELEASE:
                log_bug_libinput(libinput,
-                                "invalid event when fingers are up\n");
+                                "invalid tap event when fingers are up\n");
                break;
        case TAP_EVENT_TOUCH:
                tp->tap.state = TAP_STATE_DRAGGING_OR_DOUBLETAP;
+               tp_tap_set_timer(tp, time);
                break;
        case TAP_EVENT_TIMEOUT:
                tp->tap.state = TAP_STATE_IDLE;
@@ -231,6 +274,8 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DEAD;
                tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -246,12 +291,8 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp,
                tp_tap_set_timer(tp, time);
                break;
        case TAP_EVENT_RELEASE:
-               tp->tap.state = TAP_STATE_HOLD;
-               if (t->tap.state == TAP_TOUCH_STATE_TOUCH) {
-                       tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_PRESSED);
-                       tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_RELEASED);
-               }
-               tp_tap_clear_timer(tp);
+               tp->tap.state = TAP_STATE_TOUCH_2_RELEASE;
+               tp_tap_set_timer(tp, time);
                break;
        case TAP_EVENT_MOTION:
                tp_tap_clear_timer(tp);
@@ -262,6 +303,8 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -286,6 +329,37 @@ tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               break;
+       }
+}
+
+static void
+tp_tap_touch2_release_handle_event(struct tp_dispatch *tp,
+                                  struct tp_touch *t,
+                                  enum tap_event event, uint64_t time)
+{
+
+       switch (event) {
+       case TAP_EVENT_TOUCH:
+               tp->tap.state = TAP_STATE_TOUCH_2_HOLD;
+               t->tap.state = TAP_TOUCH_STATE_DEAD;
+               tp_tap_clear_timer(tp);
+               break;
+       case TAP_EVENT_RELEASE:
+               tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_PRESSED);
+               tp_tap_notify(tp, time, 2, LIBINPUT_BUTTON_STATE_RELEASED);
+               tp->tap.state = TAP_STATE_IDLE;
+               break;
+       case TAP_EVENT_MOTION:
+       case TAP_EVENT_TIMEOUT:
+               tp->tap.state = TAP_STATE_HOLD;
+               break;
+       case TAP_EVENT_BUTTON:
+               tp->tap.state = TAP_STATE_DEAD;
+               break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -315,6 +389,8 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -338,6 +414,8 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_BUTTON:
                tp->tap.state = TAP_STATE_DEAD;
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -351,11 +429,8 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DRAGGING_2;
                break;
        case TAP_EVENT_RELEASE:
-               tp->tap.state = TAP_STATE_IDLE;
-               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
-               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
+               tp->tap.state = TAP_STATE_MULTITAP;
                tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
-               tp_tap_clear_timer(tp);
                break;
        case TAP_EVENT_MOTION:
        case TAP_EVENT_TIMEOUT:
@@ -365,6 +440,8 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DEAD;
                tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -379,8 +456,16 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DRAGGING_2;
                break;
        case TAP_EVENT_RELEASE:
-               tp->tap.state = TAP_STATE_DRAGGING_WAIT;
-               tp_tap_set_timer(tp, time);
+               if (tp->tap.drag_lock_enabled) {
+                       tp->tap.state = TAP_STATE_DRAGGING_WAIT;
+                       tp_tap_set_drag_timer(tp, time);
+               } else {
+                       tp_tap_notify(tp,
+                                     time,
+                                     1,
+                                     LIBINPUT_BUTTON_STATE_RELEASED);
+                       tp->tap.state = TAP_STATE_IDLE;
+               }
                break;
        case TAP_EVENT_MOTION:
        case TAP_EVENT_TIMEOUT:
@@ -390,6 +475,8 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DEAD;
                tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -401,8 +488,8 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp,
 
        switch (event) {
        case TAP_EVENT_TOUCH:
-               tp->tap.state = TAP_STATE_DRAGGING;
-               tp_tap_clear_timer(tp);
+               tp->tap.state = TAP_STATE_DRAGGING_OR_TAP;
+               tp_tap_set_timer(tp, time);
                break;
        case TAP_EVENT_RELEASE:
        case TAP_EVENT_MOTION:
@@ -415,6 +502,36 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DEAD;
                tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
                break;
+       case TAP_EVENT_THUMB:
+               break;
+       }
+}
+
+static void
+tp_tap_dragging_tap_handle_event(struct tp_dispatch *tp,
+                                 struct tp_touch *t,
+                                 enum tap_event event, uint64_t time)
+{
+
+       switch (event) {
+       case TAP_EVENT_TOUCH:
+               tp->tap.state = TAP_STATE_DRAGGING_2;
+               tp_tap_clear_timer(tp);
+               break;
+       case TAP_EVENT_RELEASE:
+               tp->tap.state = TAP_STATE_IDLE;
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
+               break;
+       case TAP_EVENT_MOTION:
+       case TAP_EVENT_TIMEOUT:
+               tp->tap.state = TAP_STATE_DRAGGING;
+               break;
+       case TAP_EVENT_BUTTON:
+               tp->tap.state = TAP_STATE_DEAD;
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
+               break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -440,6 +557,73 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp,
                tp->tap.state = TAP_STATE_DEAD;
                tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
                break;
+       case TAP_EVENT_THUMB:
+               break;
+       }
+}
+
+static void
+tp_tap_multitap_handle_event(struct tp_dispatch *tp,
+                             struct tp_touch *t,
+                             enum tap_event event, uint64_t time)
+{
+       struct libinput *libinput = tp_libinput_context(tp);
+
+       switch (event) {
+       case TAP_EVENT_RELEASE:
+               log_bug_libinput(libinput,
+                                "invalid tap event, no fingers are down\n");
+               break;
+       case TAP_EVENT_TOUCH:
+               tp->tap.state = TAP_STATE_MULTITAP_DOWN;
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
+               tp_tap_set_timer(tp, time);
+               break;
+       case TAP_EVENT_MOTION:
+               log_bug_libinput(libinput,
+                                "invalid tap event, no fingers are down\n");
+               break;
+       case TAP_EVENT_TIMEOUT:
+               tp->tap.state = TAP_STATE_IDLE;
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_PRESSED);
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
+               break;
+       case TAP_EVENT_BUTTON:
+               tp->tap.state = TAP_STATE_IDLE;
+               tp_tap_clear_timer(tp);
+               break;
+       case TAP_EVENT_THUMB:
+               break;
+       }
+}
+
+static void
+tp_tap_multitap_down_handle_event(struct tp_dispatch *tp,
+                                 struct tp_touch *t,
+                                 enum tap_event event,
+                                 uint64_t time)
+{
+       switch (event) {
+       case TAP_EVENT_RELEASE:
+               tp->tap.state = TAP_STATE_MULTITAP;
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
+               break;
+       case TAP_EVENT_TOUCH:
+               tp->tap.state = TAP_STATE_DRAGGING_2;
+               tp_tap_clear_timer(tp);
+               break;
+       case TAP_EVENT_MOTION:
+       case TAP_EVENT_TIMEOUT:
+               tp->tap.state = TAP_STATE_DRAGGING;
+               tp_tap_clear_timer(tp);
+               break;
+       case TAP_EVENT_BUTTON:
+               tp->tap.state = TAP_STATE_DEAD;
+               tp_tap_notify(tp, time, 1, LIBINPUT_BUTTON_STATE_RELEASED);
+               tp_tap_clear_timer(tp);
+               break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -460,6 +644,8 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp,
        case TAP_EVENT_TIMEOUT:
        case TAP_EVENT_BUTTON:
                break;
+       case TAP_EVENT_THUMB:
+               break;
        }
 }
 
@@ -469,7 +655,7 @@ tp_tap_handle_event(struct tp_dispatch *tp,
                    enum tap_event event,
                    uint64_t time)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       struct libinput *libinput = tp_libinput_context(tp);
        enum tp_tap_state current;
 
        current = tp->tap.state;
@@ -493,6 +679,9 @@ tp_tap_handle_event(struct tp_dispatch *tp,
        case TAP_STATE_TOUCH_2_HOLD:
                tp_tap_touch2_hold_handle_event(tp, t, event, time);
                break;
+       case TAP_STATE_TOUCH_2_RELEASE:
+               tp_tap_touch2_release_handle_event(tp, t, event, time);
+               break;
        case TAP_STATE_TOUCH_3:
                tp_tap_touch3_handle_event(tp, t, event, time);
                break;
@@ -508,9 +697,18 @@ tp_tap_handle_event(struct tp_dispatch *tp,
        case TAP_STATE_DRAGGING_WAIT:
                tp_tap_dragging_wait_handle_event(tp, t, event, time);
                break;
+       case TAP_STATE_DRAGGING_OR_TAP:
+               tp_tap_dragging_tap_handle_event(tp, t, event, time);
+               break;
        case TAP_STATE_DRAGGING_2:
                tp_tap_dragging2_handle_event(tp, t, event, time);
                break;
+       case TAP_STATE_MULTITAP:
+               tp_tap_multitap_handle_event(tp, t, event, time);
+               break;
+       case TAP_STATE_MULTITAP_DOWN:
+               tp_tap_multitap_down_handle_event(tp, t, event, time);
+               break;
        case TAP_STATE_DEAD:
                tp_tap_dead_handle_event(tp, t, event, time);
                break;
@@ -527,14 +725,14 @@ tp_tap_handle_event(struct tp_dispatch *tp,
 }
 
 static bool
-tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t)
+tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp,
+                               struct tp_touch *t)
 {
-       int threshold = DEFAULT_TAP_MOVE_THRESHOLD;
-       double dx, dy;
+       struct normalized_coords norm =
+               tp_normalize_delta(tp, device_delta(t->point,
+                                                   t->tap.initial));
 
-       tp_get_delta(t, &dx, &dy);
-
-       return dx * dx + dy * dy > threshold * threshold;
+       return normalized_length(norm) > DEFAULT_TAP_MOVE_THRESHOLD;
 }
 
 static bool
@@ -566,9 +764,31 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
                    tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS)
                        t->tap.state = TAP_TOUCH_STATE_DEAD;
 
+               /* If a touch was considered thumb for tapping once, we
+                * ignore it for the rest of lifetime */
+               if (t->tap.is_thumb)
+                       continue;
+
                if (t->state == TOUCH_BEGIN) {
+                       /* The simple version: if a touch is a thumb on
+                        * begin we ignore it. All other thumb touches
+                        * follow the normal tap state for now */
+                       if (t->thumb.state == THUMB_STATE_YES) {
+                               t->tap.is_thumb = true;
+                               continue;
+                       }
+
                        t->tap.state = TAP_TOUCH_STATE_TOUCH;
+                       t->tap.initial = t->point;
                        tp_tap_handle_event(tp, t, TAP_EVENT_TOUCH, time);
+
+                       /* If we think this is a palm, pretend there's a
+                        * motion event which will prevent tap clicks
+                        * without requiring extra states in the FSM.
+                        */
+                       if (tp_palm_tap_is_palm(tp, t))
+                               tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time);
+
                } else if (t->state == TOUCH_END) {
                        tp_tap_handle_event(tp, t, TAP_EVENT_RELEASE, time);
                        t->tap.state = TAP_TOUCH_STATE_IDLE;
@@ -584,6 +804,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
                        }
 
                        tp_tap_handle_event(tp, t, TAP_EVENT_MOTION, time);
+               } else if (tp->tap.state != TAP_STATE_IDLE &&
+                          t->thumb.state == THUMB_STATE_YES &&
+                          !t->tap.is_thumb) {
+                       tp_tap_handle_event(tp, t, TAP_EVENT_THUMB, time);
                }
        }
 
@@ -597,8 +821,10 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
        case TAP_STATE_TOUCH:
        case TAP_STATE_TAPPED:
        case TAP_STATE_DRAGGING_OR_DOUBLETAP:
+       case TAP_STATE_DRAGGING_OR_TAP:
        case TAP_STATE_TOUCH_2:
        case TAP_STATE_TOUCH_3:
+       case TAP_STATE_MULTITAP_DOWN:
                filter_motion = 1;
                break;
 
@@ -610,6 +836,22 @@ tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time)
        return filter_motion;
 }
 
+static inline void
+tp_tap_update_map(struct tp_dispatch *tp)
+{
+       if (tp->tap.state != TAP_STATE_IDLE)
+               return;
+
+       if (tp->tap.map != tp->tap.want_map)
+               tp->tap.map = tp->tap.want_map;
+}
+
+void
+tp_tap_post_process_state(struct tp_dispatch *tp)
+{
+       tp_tap_update_map(tp);
+}
+
 static void
 tp_tap_handle_timeout(uint64_t time, void *data)
 {
@@ -688,8 +930,15 @@ tp_tap_config_is_enabled(struct libinput_device *device)
 }
 
 static enum libinput_config_tap_state
-tp_tap_config_get_default(struct libinput_device *device)
+tp_tap_default(struct evdev_device *evdev)
 {
+       /**
+        * If we don't have a left button we must have tapping enabled by
+        * default.
+        */
+       if (!libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_LEFT))
+               return LIBINPUT_CONFIG_TAP_ENABLED;
+
        /**
         * Tapping is disabled by default for two reasons:
         * * if you don't know that tapping is a thing (or enabled by
@@ -702,22 +951,150 @@ tp_tap_config_get_default(struct libinput_device *device)
        return LIBINPUT_CONFIG_TAP_DISABLED;
 }
 
-int
+static enum libinput_config_tap_state
+tp_tap_config_get_default(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device *)device;
+
+       return tp_tap_default(evdev);
+}
+
+static enum libinput_config_status
+tp_tap_config_set_map(struct libinput_device *device,
+                     enum libinput_config_tap_button_map map)
+{
+       struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch;
+       struct tp_dispatch *tp = NULL;
+
+       tp = container_of(dispatch, tp, base);
+       tp->tap.want_map = map;
+
+       tp_tap_update_map(tp);
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_tap_button_map
+tp_tap_config_get_map(struct libinput_device *device)
+{
+       struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch;
+       struct tp_dispatch *tp = NULL;
+
+       tp = container_of(dispatch, tp, base);
+
+       return tp->tap.want_map;
+}
+
+static enum libinput_config_tap_button_map
+tp_tap_config_get_default_map(struct libinput_device *device)
+{
+       return LIBINPUT_CONFIG_TAP_MAP_LRM;
+}
+
+static enum libinput_config_status
+tp_tap_config_set_drag_enabled(struct libinput_device *device,
+                              enum libinput_config_drag_state enabled)
+{
+       struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch;
+       struct tp_dispatch *tp = NULL;
+
+       tp = container_of(dispatch, tp, base);
+       tp->tap.drag_enabled = enabled;
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_drag_state
+tp_tap_config_get_drag_enabled(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device *)device;
+       struct tp_dispatch *tp = NULL;
+
+       tp = container_of(evdev->dispatch, tp, base);
+
+       return tp->tap.drag_enabled;
+}
+
+static inline enum libinput_config_drag_state
+tp_drag_default(struct evdev_device *device)
+{
+       return LIBINPUT_CONFIG_DRAG_ENABLED;
+}
+
+static enum libinput_config_drag_state
+tp_tap_config_get_default_drag_enabled(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device *)device;
+
+       return tp_drag_default(evdev);
+}
+
+static enum libinput_config_status
+tp_tap_config_set_draglock_enabled(struct libinput_device *device,
+                                  enum libinput_config_drag_lock_state enabled)
+{
+       struct evdev_dispatch *dispatch = ((struct evdev_device *) device)->dispatch;
+       struct tp_dispatch *tp = NULL;
+
+       tp = container_of(dispatch, tp, base);
+       tp->tap.drag_lock_enabled = enabled;
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_drag_lock_state
+tp_tap_config_get_draglock_enabled(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device *)device;
+       struct tp_dispatch *tp = NULL;
+
+       tp = container_of(evdev->dispatch, tp, base);
+
+       return tp->tap.drag_lock_enabled;
+}
+
+static inline enum libinput_config_drag_lock_state
+tp_drag_lock_default(struct evdev_device *device)
+{
+       return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
+}
+
+static enum libinput_config_drag_lock_state
+tp_tap_config_get_default_draglock_enabled(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device *)device;
+
+       return tp_drag_lock_default(evdev);
+}
+
+void
 tp_init_tap(struct tp_dispatch *tp)
 {
        tp->tap.config.count = tp_tap_config_count;
        tp->tap.config.set_enabled = tp_tap_config_set_enabled;
        tp->tap.config.get_enabled = tp_tap_config_is_enabled;
        tp->tap.config.get_default = tp_tap_config_get_default;
+       tp->tap.config.set_map = tp_tap_config_set_map;
+       tp->tap.config.get_map = tp_tap_config_get_map;
+       tp->tap.config.get_default_map = tp_tap_config_get_default_map;
+       tp->tap.config.set_drag_enabled = tp_tap_config_set_drag_enabled;
+       tp->tap.config.get_drag_enabled = tp_tap_config_get_drag_enabled;
+       tp->tap.config.get_default_drag_enabled = tp_tap_config_get_default_drag_enabled;
+       tp->tap.config.set_draglock_enabled = tp_tap_config_set_draglock_enabled;
+       tp->tap.config.get_draglock_enabled = tp_tap_config_get_draglock_enabled;
+       tp->tap.config.get_default_draglock_enabled = tp_tap_config_get_default_draglock_enabled;
        tp->device->base.config.tap = &tp->tap.config;
 
        tp->tap.state = TAP_STATE_IDLE;
+       tp->tap.enabled = tp_tap_default(tp->device);
+       tp->tap.map = LIBINPUT_CONFIG_TAP_MAP_LRM;
+       tp->tap.want_map = tp->tap.map;
+       tp->tap.drag_enabled = tp_drag_default(tp->device);
+       tp->tap.drag_lock_enabled = tp_drag_lock_default(tp->device);
 
        libinput_timer_init(&tp->tap.timer,
-                           tp->device->base.seat->libinput,
+                           tp_libinput_context(tp),
                            tp_tap_handle_timeout, tp);
-
-       return 0;
 }
 
 void
@@ -752,12 +1129,13 @@ tp_tap_resume(struct tp_dispatch *tp, uint64_t time)
 }
 
 bool
-tp_tap_dragging(struct tp_dispatch *tp)
+tp_tap_dragging(const struct tp_dispatch *tp)
 {
        switch (tp->tap.state) {
        case TAP_STATE_DRAGGING:
        case TAP_STATE_DRAGGING_2:
        case TAP_STATE_DRAGGING_WAIT:
+       case TAP_STATE_DRAGGING_OR_TAP:
                return true;
        default:
                return false;
index ae37ab17d81ce631f4433e18a9cfae8126df50f5..38b638b536450999dbb3a45d4902a27a3f30b3cf 100644 (file)
@@ -1,23 +1,24 @@
 /*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
 
 #include "evdev-mt-touchpad.h"
 
-#define DEFAULT_ACCEL_NUMERATOR 1200.0
-#define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0
-#define DEFAULT_TRACKPOINT_ACTIVITY_TIMEOUT 500 /* ms */
-
-static inline int
-tp_hysteresis(int in, int center, int margin)
-{
-       int diff = in - center;
-       if (abs(diff) <= margin)
-               return center;
-
-       if (diff > margin)
-               return center + diff - margin;
-       else
-               return center + diff + margin;
-}
+#define DEFAULT_TRACKPOINT_ACTIVITY_TIMEOUT ms2us(300)
+#define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1 ms2us(200)
+#define DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2 ms2us(500)
+#define THUMB_MOVE_TIMEOUT ms2us(300)
+#define FAKE_FINGER_OVERFLOW (1 << 7)
 
-static inline struct tp_motion *
+static inline struct device_coords *
 tp_motion_history_offset(struct tp_touch *t, int offset)
 {
        int offset_index =
@@ -56,27 +46,28 @@ tp_motion_history_offset(struct tp_touch *t, int offset)
        return &t->history.samples[offset_index];
 }
 
-void
+struct normalized_coords
 tp_filter_motion(struct tp_dispatch *tp,
-                double *dx, double *dy,
-                double *dx_unaccel, double *dy_unaccel,
+                const struct normalized_coords *unaccelerated,
                 uint64_t time)
 {
-       struct motion_params motion;
+       if (normalized_is_zero(*unaccelerated))
+               return *unaccelerated;
 
-       motion.dx = *dx * tp->accel.x_scale_coeff;
-       motion.dy = *dy * tp->accel.y_scale_coeff;
-
-       if (dx_unaccel)
-               *dx_unaccel = motion.dx;
-       if (dy_unaccel)
-               *dy_unaccel = motion.dy;
+       return filter_dispatch(tp->device->pointer.filter,
+                              unaccelerated, tp, time);
+}
 
-       if (motion.dx != 0.0 || motion.dy != 0.0)
-               filter_dispatch(tp->device->pointer.filter, &motion, tp, time);
+struct normalized_coords
+tp_filter_motion_unaccelerated(struct tp_dispatch *tp,
+                              const struct normalized_coords *unaccelerated,
+                              uint64_t time)
+{
+       if (normalized_is_zero(*unaccelerated))
+               return *unaccelerated;
 
-       *dx = motion.dx;
-       *dy = motion.dy;
+       return filter_dispatch_constant(tp->device->pointer.filter,
+                                       unaccelerated, tp, time);
 }
 
 static inline void
@@ -87,8 +78,7 @@ tp_motion_history_push(struct tp_touch *t)
        if (t->history.count < TOUCHPAD_HISTORY_LENGTH)
                t->history.count++;
 
-       t->history.samples[motion_index].x = t->x;
-       t->history.samples[motion_index].y = t->y;
+       t->history.samples[motion_index] = t->point;
        t->history.index = motion_index;
 }
 
@@ -96,23 +86,22 @@ static inline void
 tp_motion_hysteresis(struct tp_dispatch *tp,
                     struct tp_touch *t)
 {
-       int x = t->x,
-           y = t->y;
+       int x = t->point.x,
+           y = t->point.y;
 
        if (t->history.count == 0) {
-               t->hysteresis.center_x = t->x;
-               t->hysteresis.center_y = t->y;
+               t->hysteresis_center = t->point;
        } else {
-               x = tp_hysteresis(x,
-                                 t->hysteresis.center_x,
-                                 tp->hysteresis.margin_x);
-               y = tp_hysteresis(y,
-                                 t->hysteresis.center_y,
-                                 tp->hysteresis.margin_y);
-               t->hysteresis.center_x = x;
-               t->hysteresis.center_y = y;
-               t->x = x;
-               t->y = y;
+               x = evdev_hysteresis(x,
+                                    t->hysteresis_center.x,
+                                    tp->hysteresis_margin.x);
+               y = evdev_hysteresis(y,
+                                    t->hysteresis_center.y,
+                                    tp->hysteresis_margin.y);
+               t->hysteresis_center.x = x;
+               t->hysteresis_center.y = y;
+               t->point.x = x;
+               t->point.y = y;
        }
 }
 
@@ -138,8 +127,18 @@ tp_get_touch(struct tp_dispatch *tp, unsigned int slot)
 static inline unsigned int
 tp_fake_finger_count(struct tp_dispatch *tp)
 {
-       /* don't count BTN_TOUCH */
-       return ffs(tp->fake_touches >> 1);
+       /* Only one of BTN_TOOL_DOUBLETAP/TRIPLETAP/... may be set at any
+        * time */
+       if (__builtin_popcount(
+                      tp->fake_touches & ~(FAKE_FINGER_OVERFLOW|0x1)) > 1)
+           log_bug_kernel(tp_libinput_context(tp),
+                          "Invalid fake finger state %#x\n",
+                          tp->fake_touches);
+
+       if (tp->fake_touches & FAKE_FINGER_OVERFLOW)
+               return FAKE_FINGER_OVERFLOW;
+       else /* don't count BTN_TOUCH */
+               return ffs(tp->fake_touches >> 1);
 }
 
 static inline bool
@@ -157,6 +156,8 @@ tp_fake_finger_set(struct tp_dispatch *tp,
 
        switch (code) {
        case BTN_TOUCH:
+               if (!is_press)
+                       tp->fake_touches &= ~FAKE_FINGER_OVERFLOW;
                shift = 0;
                break;
        case BTN_TOOL_FINGER:
@@ -167,14 +168,24 @@ tp_fake_finger_set(struct tp_dispatch *tp,
        case BTN_TOOL_QUADTAP:
                shift = code - BTN_TOOL_DOUBLETAP + 2;
                break;
+       /* when QUINTTAP is released we're either switching to 6 fingers
+          (flag stays in place until BTN_TOUCH is released) or
+          one of DOUBLE/TRIPLE/QUADTAP (will clear the flag on press) */
+       case BTN_TOOL_QUINTTAP:
+               if (is_press)
+                       tp->fake_touches |= FAKE_FINGER_OVERFLOW;
+               return;
        default:
                return;
        }
 
-       if (is_press)
+       if (is_press) {
+               tp->fake_touches &= ~FAKE_FINGER_OVERFLOW;
                tp->fake_touches |= 1 << shift;
-       else
+
+       } else {
                tp->fake_touches &= ~(0x1 << shift);
+       }
 }
 
 static inline void
@@ -204,6 +215,10 @@ tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
        t->state = TOUCH_BEGIN;
        t->millis = time;
        tp->nfingers_down++;
+       t->palm.time = time;
+       t->thumb.state = THUMB_STATE_MAYBE;
+       t->thumb.first_touch_time = time;
+       t->tap.is_thumb = false;
        assert(tp->nfingers_down >= 1);
 }
 
@@ -227,11 +242,11 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
        }
 
        t->dirty = true;
-       t->is_pointer = false;
-       t->palm.is_palm = false;
+       t->palm.state = PALM_NONE;
        t->state = TOUCH_END;
        t->pinned.is_pinned = false;
        t->millis = time;
+       t->palm.time = 0;
        assert(tp->nfingers_down >= 1);
        tp->nfingers_down--;
        tp->queued |= TOUCHPAD_EVENT_MOTION;
@@ -253,23 +268,58 @@ tp_estimate_delta(int x0, int x1, int x2, int x3)
        return (x0 + x1 - x2 - x3) / 4.0;
 }
 
-void
-tp_get_delta(struct tp_touch *t, double *dx, double *dy)
+struct normalized_coords
+tp_get_delta(struct tp_touch *t)
 {
-       if (t->history.count < TOUCHPAD_MIN_SAMPLES) {
-               *dx = 0;
-               *dy = 0;
+       struct device_float_coords delta;
+       const struct normalized_coords zero = { 0.0, 0.0 };
+
+       if (t->history.count < TOUCHPAD_MIN_SAMPLES)
+               return zero;
+
+       delta.x = tp_estimate_delta(tp_motion_history_offset(t, 0)->x,
+                                   tp_motion_history_offset(t, 1)->x,
+                                   tp_motion_history_offset(t, 2)->x,
+                                   tp_motion_history_offset(t, 3)->x);
+       delta.y = tp_estimate_delta(tp_motion_history_offset(t, 0)->y,
+                                   tp_motion_history_offset(t, 1)->y,
+                                   tp_motion_history_offset(t, 2)->y,
+                                   tp_motion_history_offset(t, 3)->y);
+
+       return tp_normalize_delta(t->tp, delta);
+}
+
+static inline void
+tp_check_axis_range(struct tp_dispatch *tp,
+                   unsigned int code,
+                   int value)
+{
+       int min, max;
+
+       switch(code) {
+       case ABS_X:
+       case ABS_MT_POSITION_X:
+               min = tp->warning_range.min.x;
+               max = tp->warning_range.max.x;
+               break;
+       case ABS_Y:
+       case ABS_MT_POSITION_Y:
+               min = tp->warning_range.min.y;
+               max = tp->warning_range.max.y;
+               break;
+       default:
                return;
        }
 
-       *dx = tp_estimate_delta(tp_motion_history_offset(t, 0)->x,
-                               tp_motion_history_offset(t, 1)->x,
-                               tp_motion_history_offset(t, 2)->x,
-                               tp_motion_history_offset(t, 3)->x);
-       *dy = tp_estimate_delta(tp_motion_history_offset(t, 0)->y,
-                               tp_motion_history_offset(t, 1)->y,
-                               tp_motion_history_offset(t, 2)->y,
-                               tp_motion_history_offset(t, 3)->y);
+       if (value < min || value > max) {
+               log_info_ratelimit(tp_libinput_context(tp),
+                                  &tp->warning_range.range_warn_limit,
+                                  "Axis %#x value %d is outside expected range [%d, %d]\n"
+                                  "See %s/absolute_coordinate_ranges.html for details\n",
+                                  code, value, min, max,
+                                  HTTP_DOC_LINK);
+
+       }
 }
 
 static void
@@ -281,13 +331,15 @@ tp_process_absolute(struct tp_dispatch *tp,
 
        switch(e->code) {
        case ABS_MT_POSITION_X:
-               t->x = e->value;
+               tp_check_axis_range(tp, e->code, e->value);
+               t->point.x = e->value;
                t->millis = time;
                t->dirty = true;
                tp->queued |= TOUCHPAD_EVENT_MOTION;
                break;
        case ABS_MT_POSITION_Y:
-               t->y = e->value;
+               tp_check_axis_range(tp, e->code, e->value);
+               t->point.y = e->value;
                t->millis = time;
                t->dirty = true;
                tp->queued |= TOUCHPAD_EVENT_MOTION;
@@ -295,11 +347,20 @@ tp_process_absolute(struct tp_dispatch *tp,
        case ABS_MT_SLOT:
                tp->slot = e->value;
                break;
+       case ABS_MT_DISTANCE:
+               t->distance = e->value;
+               break;
        case ABS_MT_TRACKING_ID:
                if (e->value != -1)
                        tp_new_touch(tp, t, time);
                else
                        tp_end_sequence(tp, t, time);
+               break;
+       case ABS_MT_PRESSURE:
+               t->pressure = e->value;
+               t->dirty = true;
+               tp->queued |= TOUCHPAD_EVENT_OTHERAXIS;
+               break;
        }
 }
 
@@ -312,13 +373,15 @@ tp_process_absolute_st(struct tp_dispatch *tp,
 
        switch(e->code) {
        case ABS_X:
-               t->x = e->value;
+               tp_check_axis_range(tp, e->code, e->value);
+               t->point.x = e->value;
                t->millis = time;
                t->dirty = true;
                tp->queued |= TOUCHPAD_EVENT_MOTION;
                break;
        case ABS_Y:
-               t->y = e->value;
+               tp_check_axis_range(tp, e->code, e->value);
+               t->point.y = e->value;
                t->millis = time;
                t->dirty = true;
                tp->queued |= TOUCHPAD_EVENT_MOTION;
@@ -326,20 +389,57 @@ tp_process_absolute_st(struct tp_dispatch *tp,
        }
 }
 
+static inline void
+tp_restore_synaptics_touches(struct tp_dispatch *tp,
+                            uint64_t time)
+{
+       unsigned int i;
+       unsigned int nfake_touches;
+
+       nfake_touches = tp_fake_finger_count(tp);
+       if (nfake_touches < 3)
+               return;
+
+       if (tp->nfingers_down >= nfake_touches ||
+           tp->nfingers_down == tp->num_slots)
+               return;
+
+       /* Synaptics devices may end touch 2 on BTN_TOOL_TRIPLETAP
+        * and start it again on the next frame with different coordinates
+        * (#91352). We search the touches we have, if there is one that has
+        * just ended despite us being on tripletap, we move it back to
+        * update.
+        */
+       for (i = 0; i < tp->num_slots; i++) {
+               struct tp_touch *t = tp_get_touch(tp, i);
+
+               if (t->state != TOUCH_END)
+                       continue;
+
+               /* new touch, move it through begin to update immediately */
+               tp_new_touch(tp, t, time);
+               tp_begin_touch(tp, t, time);
+               t->state = TOUCH_UPDATE;
+       }
+}
+
 static void
-tp_process_fake_touch(struct tp_dispatch *tp,
-                     const struct input_event *e,
-                     uint64_t time)
+tp_process_fake_touches(struct tp_dispatch *tp,
+                       uint64_t time)
 {
        struct tp_touch *t;
        unsigned int nfake_touches;
        unsigned int i, start;
 
-       tp_fake_finger_set(tp, e->code, e->value != 0);
-
        nfake_touches = tp_fake_finger_count(tp);
+       if (nfake_touches == FAKE_FINGER_OVERFLOW)
+               return;
+
+       if (tp->device->model_flags &
+           EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD)
+               tp_restore_synaptics_touches(tp, time);
 
-       start = tp->has_mt ? tp->real_touches : 0;
+       start = tp->has_mt ? tp->num_slots : 0;
        for (i = start; i < tp->ntouches; i++) {
                t = tp_get_touch(tp, i);
                if (i < nfake_touches)
@@ -357,8 +457,7 @@ tp_process_trackpoint_button(struct tp_dispatch *tp,
        struct evdev_dispatch *dispatch;
        struct input_event event;
 
-       if (!tp->buttons.trackpoint ||
-           (tp->device->tags & EVDEV_TAG_TOUCHPAD_TRACKPOINT) == 0)
+       if (!tp->buttons.trackpoint)
                return;
 
        dispatch = tp->buttons.trackpoint->dispatch;
@@ -400,7 +499,8 @@ tp_process_key(struct tp_dispatch *tp,
                case BTN_TOOL_DOUBLETAP:
                case BTN_TOOL_TRIPLETAP:
                case BTN_TOOL_QUADTAP:
-                       tp_process_fake_touch(tp, e, time);
+               case BTN_TOOL_QUINTTAP:
+                       tp_fake_finger_set(tp, e->code, !!e->value);
                        break;
                case BTN_0:
                case BTN_1:
@@ -413,24 +513,21 @@ tp_process_key(struct tp_dispatch *tp,
 static void
 tp_unpin_finger(struct tp_dispatch *tp, struct tp_touch *t)
 {
-       unsigned int xdist, ydist;
+       double xdist, ydist;
 
        if (!t->pinned.is_pinned)
                return;
 
-       xdist = abs(t->x - t->pinned.center_x);
-       ydist = abs(t->y - t->pinned.center_y);
+       xdist = abs(t->point.x - t->pinned.center.x);
+       xdist *= tp->buttons.motion_dist.x_scale_coeff;
+       ydist = abs(t->point.y - t->pinned.center.y);
+       ydist *= tp->buttons.motion_dist.y_scale_coeff;
 
-       if (xdist * xdist + ydist * ydist >=
-                       tp->buttons.motion_dist * tp->buttons.motion_dist) {
+       /* 1.5mm movement -> unpin */
+       if (hypot(xdist, ydist) >= 1.5) {
                t->pinned.is_pinned = false;
-               tp_set_pointer(tp, t);
                return;
        }
-
-       /* The finger may slowly drift, adjust the center */
-       t->pinned.center_x = t->x + t->pinned.center_x / 2;
-       t->pinned.center_y = t->y + t->pinned.center_y / 2;
 }
 
 static void
@@ -439,228 +536,312 @@ tp_pin_fingers(struct tp_dispatch *tp)
        struct tp_touch *t;
 
        tp_for_each_touch(tp, t) {
-               t->is_pointer = false;
                t->pinned.is_pinned = true;
-               t->pinned.center_x = t->x;
-               t->pinned.center_y = t->y;
+               t->pinned.center = t->point;
        }
 }
 
-static int
-tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
+bool
+tp_touch_active(const struct tp_dispatch *tp, const struct tp_touch *t)
 {
        return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) &&
-               !t->palm.is_palm &&
+               t->palm.state == PALM_NONE &&
                !t->pinned.is_pinned &&
+               t->thumb.state != THUMB_STATE_YES &&
                tp_button_touch_active(tp, t) &&
                tp_edge_scroll_touch_active(tp, t);
 }
 
-void
-tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t)
+bool
+tp_palm_tap_is_palm(const struct tp_dispatch *tp, const struct tp_touch *t)
 {
-       struct tp_touch *tmp = NULL;
+       if (t->state != TOUCH_BEGIN)
+               return false;
 
-       /* Only set the touch as pointer if we don't have one yet */
-       tp_for_each_touch(tp, tmp) {
-               if (tmp->is_pointer)
-                       return;
+       if (t->point.x > tp->palm.left_edge &&
+           t->point.x < tp->palm.right_edge)
+               return false;
+
+       /* We're inside the left/right palm edge and not in one of the
+        * software button areas */
+       if (t->point.y < tp->buttons.bottom_area.top_edge) {
+               log_debug(tp_libinput_context(tp),
+                         "palm: palm-tap detected\n");
+               return true;
        }
 
-       if (tp_touch_active(tp, t))
-               t->is_pointer = true;
+       return false;
 }
 
-static void
-tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
+static bool
+tp_palm_detect_dwt_triggered(struct tp_dispatch *tp,
+                            struct tp_touch *t,
+                            uint64_t time)
 {
-       const int PALM_TIMEOUT = 200; /* ms */
-       const int DIRECTIONS = NE|E|SE|SW|W|NW;
-
-       /* If labelled a touch as palm, we unlabel as palm when
-          we move out of the palm edge zone within the timeout, provided
-          the direction is within 45 degrees of the horizontal.
-        */
-       if (t->palm.is_palm) {
-               if (time < t->palm.time + PALM_TIMEOUT &&
-                   (t->x > tp->palm.left_edge && t->x < tp->palm.right_edge)) {
-                       int dirs = vector_get_direction(t->x - t->palm.x, t->y - t->palm.y);
-                       if ((dirs & DIRECTIONS) && !(dirs & ~DIRECTIONS)) {
-                               t->palm.is_palm = false;
-                               tp_set_pointer(tp, t);
-                       }
+       if (tp->dwt.dwt_enabled &&
+           tp->dwt.keyboard_active &&
+           t->state == TOUCH_BEGIN) {
+               t->palm.state = PALM_TYPING;
+               t->palm.first = t->point;
+               return true;
+       } else if (!tp->dwt.keyboard_active &&
+                  t->state == TOUCH_UPDATE &&
+                  t->palm.state == PALM_TYPING) {
+               /* If a touch has started before the first or after the last
+                  key press, release it on timeout. Benefit: a palm rested
+                  while typing on the touchpad will be ignored, but a touch
+                  started once we stop typing will be able to control the
+                  pointer (alas not tap, etc.).
+                  */
+               if (t->palm.time == 0 ||
+                   t->palm.time > tp->dwt.keyboard_last_press_time) {
+                       t->palm.state = PALM_NONE;
+                       log_debug(tp_libinput_context(tp),
+                                 "palm: touch released, timeout after typing\n");
                }
-               return;
        }
 
-       /* palm must start in exclusion zone, it's ok to move into
-          the zone without being a palm */
-       if (t->state != TOUCH_BEGIN ||
-           (t->x > tp->palm.left_edge && t->x < tp->palm.right_edge))
-               return;
-
-       /* don't detect palm in software button areas, it's
-          likely that legitimate touches start in the area
-          covered by the exclusion zone */
-       if (tp->buttons.is_clickpad &&
-           tp_button_is_inside_softbutton_area(tp, t))
-               return;
-
-       t->palm.is_palm = true;
-       t->palm.time = time;
-       t->palm.x = t->x;
-       t->palm.y = t->y;
+       return false;
 }
 
-static void
-tp_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
+static bool
+tp_palm_detect_trackpoint_triggered(struct tp_dispatch *tp,
+                                   struct tp_touch *t,
+                                   uint64_t time)
 {
-       struct tp_touch *t;
-       int nchanged = 0;
-       double dx = 0, dy =0;
-       double tmpx, tmpy;
+       if (!tp->palm.monitor_trackpoint)
+               return false;
 
-       tp_for_each_touch(tp, t) {
-               if (tp_touch_active(tp, t) && t->dirty) {
-                       nchanged++;
-                       tp_get_delta(t, &tmpx, &tmpy);
+       if (t->palm.state == PALM_NONE &&
+           t->state == TOUCH_BEGIN &&
+           tp->palm.trackpoint_active) {
+               t->palm.state = PALM_TRACKPOINT;
+               return true;
+       } else if (t->palm.state == PALM_TRACKPOINT &&
+                  t->state == TOUCH_UPDATE &&
+                  !tp->palm.trackpoint_active) {
 
-                       dx += tmpx;
-                       dy += tmpy;
+               if (t->palm.time == 0 ||
+                   t->palm.time > tp->palm.trackpoint_last_event_time) {
+                       t->palm.state = PALM_NONE;
+                       log_debug(tp_libinput_context(tp),
+                                 "palm: touch released, timeout after trackpoint\n");
                }
-               /* Stop spurious MOTION events at the end of scrolling */
-               t->is_pointer = false;
        }
 
-       if (nchanged == 0)
-               return;
-
-       dx /= nchanged;
-       dy /= nchanged;
+       return false;
+}
 
-       tp_filter_motion(tp, &dx, &dy, NULL, NULL, time);
+static inline bool
+tp_palm_detect_move_out_of_edge(struct tp_dispatch *tp,
+                               struct tp_touch *t,
+                               uint64_t time)
+{
+       const int PALM_TIMEOUT = ms2us(200);
+       const int DIRECTIONS = NE|E|SE|SW|W|NW;
+       struct device_float_coords delta;
+       int dirs;
+
+       if (time < t->palm.time + PALM_TIMEOUT &&
+           (t->point.x > tp->palm.left_edge && t->point.x < tp->palm.right_edge)) {
+               delta = device_delta(t->point, t->palm.first);
+               dirs = normalized_get_direction(tp_normalize_delta(tp, delta));
+               if ((dirs & DIRECTIONS) && !(dirs & ~DIRECTIONS))
+                       return true;
+       }
 
-       evdev_post_scroll(tp->device,
-                         time,
-                         LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
-                         dx, dy);
-       tp->scroll.twofinger_state = TWOFINGER_SCROLL_STATE_ACTIVE;
+       return false;
 }
 
-static void
-tp_twofinger_stop_scroll(struct tp_dispatch *tp, uint64_t time)
-{
-       struct tp_touch *t, *ptr = NULL;
-       int nfingers_down = 0;
-
-       evdev_stop_scroll(tp->device,
-                         time,
-                         LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
-
-       /* If we were scrolling and now there's exactly 1 active finger,
-          switch back to pointer movement */
-       if (tp->scroll.twofinger_state == TWOFINGER_SCROLL_STATE_ACTIVE) {
-               tp_for_each_touch(tp, t) {
-                       if (tp_touch_active(tp, t)) {
-                               nfingers_down++;
-                               if (ptr == NULL)
-                                       ptr = t;
-                       }
-               }
+static inline bool
+tp_palm_detect_multifinger(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
+{
+       struct tp_touch *other;
+
+       if (tp->nfingers_down < 2)
+               return false;
+
+       /* If we have at least one other active non-palm touch make this
+        * touch non-palm too. This avoids palm detection during two-finger
+        * scrolling.
+        *
+        * Note: if both touches start in the palm zone within the same
+        * frame the second touch will still be PALM_NONE and thus detected
+        * here as non-palm touch. This is too niche to worry about for now.
+        */
+       tp_for_each_touch(tp, other) {
+               if (other == t)
+                       continue;
 
-               if (nfingers_down == 1)
-                       tp_set_pointer(tp, ptr);
+               if (tp_touch_active(tp, other) &&
+                   other->palm.state == PALM_NONE) {
+                       return true;
+               }
        }
 
-       tp->scroll.twofinger_state = TWOFINGER_SCROLL_STATE_NONE;
+       return false;
 }
 
-static int
-tp_twofinger_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
+static void
+tp_palm_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 {
-       struct tp_touch *t;
-       int nfingers_down = 0;
 
-       /* No 2fg scrolling during tap-n-drag */
-       if (tp_tap_dragging(tp))
-               return 0;
+       if (tp_palm_detect_dwt_triggered(tp, t, time))
+               goto out;
 
-       /* No 2fg scrolling while a clickpad is clicked */
-       if (tp->buttons.is_clickpad && tp->buttons.state)
-               return 0;
+       if (tp_palm_detect_trackpoint_triggered(tp, t, time))
+               goto out;
 
-       /* Only count active touches for 2 finger scrolling */
-       tp_for_each_touch(tp, t) {
-               if (tp_touch_active(tp, t))
-                       nfingers_down++;
-       }
+       if (t->palm.state == PALM_EDGE) {
+               if (tp_palm_detect_multifinger(tp, t, time)) {
+                       t->palm.state = PALM_NONE;
+                       log_debug(tp_libinput_context(tp),
+                                 "palm: touch released, multiple fingers\n");
 
-       if (nfingers_down == 2) {
-               tp_post_twofinger_scroll(tp, time);
-               return 1;
+               /* If labelled a touch as palm, we unlabel as palm when
+                  we move out of the palm edge zone within the timeout, provided
+                  the direction is within 45 degrees of the horizontal.
+                */
+               } else if (tp_palm_detect_move_out_of_edge(tp, t, time)) {
+                       t->palm.state = PALM_NONE;
+                       log_debug(tp_libinput_context(tp),
+                                 "palm: touch released, out of edge zone\n");
+               }
+               return;
+       } else if (tp_palm_detect_multifinger(tp, t, time)) {
+               return;
        }
 
-       tp_twofinger_stop_scroll(tp, time);
+       /* palm must start in exclusion zone, it's ok to move into
+          the zone without being a palm */
+       if (t->state != TOUCH_BEGIN ||
+           (t->point.x > tp->palm.left_edge && t->point.x < tp->palm.right_edge))
+               return;
 
-       return 0;
-}
+       /* don't detect palm in software button areas, it's
+          likely that legitimate touches start in the area
+          covered by the exclusion zone */
+       if (tp->buttons.is_clickpad &&
+           tp_button_is_inside_softbutton_area(tp, t))
+               return;
 
-static void
-tp_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
-{
-       /* Note this must be always called, so that it knows the state of
-        * touches when the scroll-mode changes.
-        */
-       tp_edge_scroll_handle_state(tp, time);
+       if (tp_touch_get_edge(tp, t) & EDGE_RIGHT)
+               return;
+
+       t->palm.state = PALM_EDGE;
+       t->palm.time = time;
+       t->palm.first = t->point;
+
+out:
+       log_debug(tp_libinput_context(tp),
+                 "palm: palm detected (%s)\n",
+                 t->palm.state == PALM_EDGE ? "edge" :
+                 t->palm.state == PALM_TYPING ? "typing" : "trackpoint");
 }
 
-static int
-tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
+static inline const char*
+thumb_state_to_str(enum tp_thumb_state state)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
-
-       switch (tp->scroll.method) {
-       case LIBINPUT_CONFIG_SCROLL_NO_SCROLL:
-               break;
-       case LIBINPUT_CONFIG_SCROLL_2FG:
-               return tp_twofinger_scroll_post_events(tp, time);
-       case LIBINPUT_CONFIG_SCROLL_EDGE:
-               return tp_edge_scroll_post_events(tp, time);
-       case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN:
-               log_bug_libinput(libinput, "Unexpected scroll mode\n");
-               break;
+       switch(state){
+       CASE_RETURN_STRING(THUMB_STATE_NO);
+       CASE_RETURN_STRING(THUMB_STATE_YES);
+       CASE_RETURN_STRING(THUMB_STATE_MAYBE);
        }
-       return 0;
+
+       return NULL;
 }
 
 static void
-tp_stop_scroll_events(struct tp_dispatch *tp, uint64_t time)
+tp_thumb_detect(struct tp_dispatch *tp, struct tp_touch *t, uint64_t time)
 {
-       struct libinput *libinput = tp->device->base.seat->libinput;
+       enum tp_thumb_state state = t->thumb.state;
 
-       switch (tp->scroll.method) {
-       case LIBINPUT_CONFIG_SCROLL_NO_SCROLL:
-               break;
-       case LIBINPUT_CONFIG_SCROLL_2FG:
-               tp_twofinger_stop_scroll(tp, time);
-               break;
-       case LIBINPUT_CONFIG_SCROLL_EDGE:
-               tp_edge_scroll_stop_events(tp, time);
-               break;
-       case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN:
-               log_bug_libinput(libinput, "Unexpected scroll mode\n");
-               break;
+       /* once a thumb, always a thumb, once ruled out always ruled out */
+       if (!tp->thumb.detect_thumbs ||
+           t->thumb.state != THUMB_STATE_MAYBE)
+               return;
+
+       if (t->point.y < tp->thumb.upper_thumb_line) {
+               /* if a potential thumb is above the line, it won't ever
+                * label as thumb */
+               t->thumb.state = THUMB_STATE_NO;
+               goto out;
+       }
+
+       /* If the thumb moves by more than 7mm, it's not a resting thumb */
+       if (t->state == TOUCH_BEGIN)
+               t->thumb.initial = t->point;
+       else if (t->state == TOUCH_UPDATE) {
+               struct device_float_coords delta;
+               struct normalized_coords normalized;
+
+               delta = device_delta(t->point, t->thumb.initial);
+               normalized = tp_normalize_delta(tp, delta);
+               if (normalized_length(normalized) >
+                       TP_MM_TO_DPI_NORMALIZED(7)) {
+                       t->thumb.state = THUMB_STATE_NO;
+                       goto out;
+               }
        }
+
+       /* Note: a thumb at the edge of the touchpad won't trigger the
+        * threshold, the surface area is usually too small. So we have a
+        * two-stage detection: pressure and time within the area.
+        * A finger that remains at the very bottom of the touchpad becomes
+        * a thumb.
+        */
+       if (t->pressure > tp->thumb.threshold)
+               t->thumb.state = THUMB_STATE_YES;
+       else if (t->point.y > tp->thumb.lower_thumb_line &&
+                tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE &&
+                t->thumb.first_touch_time + THUMB_MOVE_TIMEOUT < time)
+               t->thumb.state = THUMB_STATE_YES;
+
+       /* now what? we marked it as thumb, so:
+        *
+        * - pointer motion must ignore this touch
+        * - clickfinger must ignore this touch for finger count
+        * - software buttons are unaffected
+        * - edge scrolling unaffected
+        * - gestures: unaffected
+        * - tapping: honour thumb on begin, ignore it otherwise for now,
+        *   this gets a tad complicated otherwise
+        */
+out:
+       if (t->thumb.state != state)
+               log_debug(tp_libinput_context(tp),
+                         "thumb state: %s → %s\n",
+                         thumb_state_to_str(state),
+                         thumb_state_to_str(t->thumb.state));
 }
 
 static void
-tp_remove_scroll(struct tp_dispatch *tp)
+tp_unhover_abs_distance(struct tp_dispatch *tp, uint64_t time)
 {
-       tp_remove_edge_scroll(tp);
+       struct tp_touch *t;
+       unsigned int i;
+
+       for (i = 0; i < tp->ntouches; i++) {
+               t = tp_get_touch(tp, i);
+
+               if (!t->dirty)
+                       continue;
+
+               if (t->state == TOUCH_HOVERING) {
+                       if (t->distance == 0) {
+                               /* avoid jumps when landing a finger */
+                               tp_motion_history_reset(t);
+                               tp_begin_touch(tp, t, time);
+                       }
+               } else {
+                       if (t->distance > 0)
+                               tp_end_touch(tp, t, time);
+               }
+       }
 }
 
 static void
-tp_unhover_touches(struct tp_dispatch *tp, uint64_t time)
+tp_unhover_fake_touches(struct tp_dispatch *tp, uint64_t time)
 {
        struct tp_touch *t;
        unsigned int nfake_touches;
@@ -670,6 +851,9 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time)
                return;
 
        nfake_touches = tp_fake_finger_count(tp);
+       if (nfake_touches == FAKE_FINGER_OVERFLOW)
+               return;
+
        if (tp->nfingers_down == nfake_touches &&
            ((tp->nfingers_down == 0 && !tp_fake_finger_is_touching(tp)) ||
             (tp->nfingers_down > 0 && tp_fake_finger_is_touching(tp))))
@@ -702,7 +886,8 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time)
                for (i = tp->ntouches - 1; i >= 0; i--) {
                        t = tp_get_touch(tp, i);
 
-                       if (t->state == TOUCH_HOVERING)
+                       if (t->state == TOUCH_HOVERING ||
+                           t->state == TOUCH_NONE)
                                continue;
 
                        tp_end_touch(tp, t, time);
@@ -714,42 +899,180 @@ tp_unhover_touches(struct tp_dispatch *tp, uint64_t time)
        }
 }
 
+static void
+tp_unhover_touches(struct tp_dispatch *tp, uint64_t time)
+{
+       if (tp->reports_distance)
+               tp_unhover_abs_distance(tp, time);
+       else
+               tp_unhover_fake_touches(tp, time);
+
+}
+
+static inline void
+tp_position_fake_touches(struct tp_dispatch *tp)
+{
+       struct tp_touch *t;
+       struct tp_touch *topmost = NULL;
+       unsigned int start, i;
+
+       if (tp_fake_finger_count(tp) <= tp->num_slots ||
+           tp->nfingers_down == 0)
+               return;
+
+       /* We have at least one fake touch down. Find the top-most real
+        * touch and copy its coordinates over to to all fake touches.
+        * This is more reliable than just taking the first touch.
+        */
+       for (i = 0; i < tp->num_slots; i++) {
+               t = tp_get_touch(tp, i);
+               if (t->state == TOUCH_END ||
+                   t->state == TOUCH_NONE)
+                       continue;
+
+               if (topmost == NULL || t->point.y < topmost->point.y)
+                       topmost = t;
+       }
+
+       if (!topmost) {
+               log_bug_libinput(tp_libinput_context(tp),
+                                "Unable to find topmost touch\n");
+               return;
+       }
+
+       start = tp->has_mt ? tp->num_slots : 1;
+       for (i = start; i < tp->ntouches; i++) {
+               t = tp_get_touch(tp, i);
+               if (t->state == TOUCH_NONE)
+                       continue;
+
+               t->point = topmost->point;
+               if (!t->dirty)
+                       t->dirty = topmost->dirty;
+       }
+}
+
+static inline bool
+tp_need_motion_history_reset(struct tp_dispatch *tp)
+{
+       bool rc = false;
+
+       /* Changing the numbers of fingers can cause a jump in the
+        * coordinates, always reset the motion history for all touches when
+        * that happens.
+        */
+       if (tp->nfingers_down != tp->old_nfingers_down)
+               return true;
+
+       /* if we're transitioning between slots and fake touches in either
+        * direction, we may get a coordinate jump
+        */
+       if (tp->nfingers_down != tp->old_nfingers_down &&
+                (tp->nfingers_down > tp->num_slots ||
+                tp->old_nfingers_down > tp->num_slots))
+               return true;
+
+       /* Quirk: if we had multiple events without x/y axis
+          information, the next x/y event is going to be a jump. So we
+          reset that touch to non-dirty effectively swallowing that event
+          and restarting with the next event again.
+        */
+       if (tp->device->model_flags & EVDEV_MODEL_LENOVO_T450_TOUCHPAD) {
+               if (tp->queued & TOUCHPAD_EVENT_MOTION) {
+                       if (tp->quirks.nonmotion_event_count > 10) {
+                               tp->queued &= ~TOUCHPAD_EVENT_MOTION;
+                               rc = true;
+                       }
+                       tp->quirks.nonmotion_event_count = 0;
+               }
+
+               if ((tp->queued & (TOUCHPAD_EVENT_OTHERAXIS|TOUCHPAD_EVENT_MOTION)) ==
+                   TOUCHPAD_EVENT_OTHERAXIS)
+                       tp->quirks.nonmotion_event_count++;
+       }
+
+       return rc;
+}
+
+static bool
+tp_detect_jumps(const struct tp_dispatch *tp, struct tp_touch *t)
+{
+       struct device_coords *last;
+       double dx, dy;
+       const int JUMP_THRESHOLD_MM = 20;
+
+       /* We haven't seen pointer jumps on Wacom tablets yet, so exclude
+        * those.
+        */
+       if (tp->device->model_flags & EVDEV_MODEL_WACOM_TOUCHPAD)
+               return false;
+
+       if (t->history.count == 0)
+               return false;
+
+       /* called before tp_motion_history_push, so offset 0 is the most
+        * recent coordinate */
+       last = tp_motion_history_offset(t, 0);
+       dx = fabs(t->point.x - last->x) / tp->device->abs.absinfo_x->resolution;
+       dy = fabs(t->point.y - last->y) / tp->device->abs.absinfo_y->resolution;
+
+       return hypot(dx, dy) > JUMP_THRESHOLD_MM;
+}
+
 static void
 tp_process_state(struct tp_dispatch *tp, uint64_t time)
 {
        struct tp_touch *t;
-       struct tp_touch *first = tp_get_touch(tp, 0);
        unsigned int i;
+       bool restart_filter = false;
+       bool want_motion_reset;
 
+       tp_process_fake_touches(tp, time);
        tp_unhover_touches(tp, time);
+       tp_position_fake_touches(tp);
+
+       want_motion_reset = tp_need_motion_history_reset(tp);
 
        for (i = 0; i < tp->ntouches; i++) {
                t = tp_get_touch(tp, i);
 
-               /* semi-mt finger postions may "jump" when nfingers changes */
-               if (tp->semi_mt && tp->nfingers_down != tp->old_nfingers_down)
+               if (want_motion_reset) {
                        tp_motion_history_reset(t);
-
-               if (i >= tp->real_touches && t->state != TOUCH_NONE) {
-                       t->x = first->x;
-                       t->y = first->y;
-                       if (!t->dirty)
-                               t->dirty = first->dirty;
+                       t->quirks.reset_motion_history = true;
+               } else if (t->quirks.reset_motion_history) {
+                       tp_motion_history_reset(t);
+                       t->quirks.reset_motion_history = false;
                }
 
                if (!t->dirty)
                        continue;
 
+               if (tp_detect_jumps(tp, t)) {
+                       if (!tp->semi_mt)
+                               log_bug_kernel(tp_libinput_context(tp),
+                                              "Touch jump detected and discarded.\n"
+                                              "See %stouchpad_jumping_cursor.html for details\n",
+                                              HTTP_DOC_LINK);
+                       tp_motion_history_reset(t);
+               }
+
+               tp_thumb_detect(tp, t, time);
                tp_palm_detect(tp, t, time);
 
                tp_motion_hysteresis(tp, t);
                tp_motion_history_push(t);
 
                tp_unpin_finger(tp, t);
+
+               if (t->state == TOUCH_BEGIN)
+                       restart_filter = true;
        }
 
+       if (restart_filter)
+               filter_restart(tp->device->pointer.filter, tp, time);
+
        tp_button_handle_state(tp, time);
-       tp_scroll_handle_state(tp, time);
+       tp_edge_scroll_handle_state(tp, time);
 
        /*
         * We have a physical button down event on a clickpad. To avoid
@@ -760,6 +1083,8 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
        if ((tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) &&
            tp->buttons.is_clickpad)
                tp_pin_fingers(tp);
+
+       tp_gesture_handle_state(tp, time);
 }
 
 static void
@@ -788,108 +1113,61 @@ tp_post_process_state(struct tp_dispatch *tp, uint64_t time)
        tp->buttons.old_state = tp->buttons.state;
 
        tp->queued = TOUCHPAD_EVENT_NONE;
+
+       tp_tap_post_process_state(tp);
 }
 
 static void
-tp_get_pointer_delta(struct tp_dispatch *tp, double *dx, double *dy)
+tp_post_events(struct tp_dispatch *tp, uint64_t time)
 {
-       struct tp_touch *t = tp_current_touch(tp);
+       int filter_motion = 0;
 
-       if (!t->is_pointer) {
-               tp_for_each_touch(tp, t) {
-                       if (t->is_pointer)
-                               break;
-               }
+       /* Only post (top) button events while suspended */
+       if (tp->device->is_suspended) {
+               tp_post_button_events(tp, time);
+               return;
        }
 
-       if (!t->is_pointer || !t->dirty)
+       filter_motion |= tp_tap_handle_state(tp, time);
+       filter_motion |= tp_post_button_events(tp, time);
+
+       if (filter_motion ||
+           tp->palm.trackpoint_active ||
+           tp->dwt.keyboard_active) {
+               tp_edge_scroll_stop_events(tp, time);
+               tp_gesture_cancel(tp, time);
+               return;
+       }
+
+       if (tp_edge_scroll_post_events(tp, time) != 0)
                return;
 
-       tp_get_delta(t, dx, dy);
+       tp_gesture_post_events(tp, time);
 }
 
 static void
-tp_get_active_touches_delta(struct tp_dispatch *tp, double *dx, double *dy)
+tp_handle_state(struct tp_dispatch *tp,
+               uint64_t time)
 {
-       struct tp_touch *t;
-       double tdx, tdy;
-       unsigned int i;
+       tp_process_state(tp, time);
+       tp_post_events(tp, time);
+       tp_post_process_state(tp, time);
 
-       for (i = 0; i < tp->real_touches; i++) {
-               t = tp_get_touch(tp, i);
-
-               if (!tp_touch_active(tp, t) || !t->dirty)
-                       continue;
-
-               tp_get_delta(t, &tdx, &tdy);
-               *dx += tdx;
-               *dy += tdy;
-       }
-}
-
-static void
-tp_post_pointer_motion(struct tp_dispatch *tp, uint64_t time)
-{
-       double dx = 0.0, dy = 0.0;
-       double dx_unaccel, dy_unaccel;
-
-       /* When a clickpad is clicked, combine motion of all active touches */
-       if (tp->buttons.is_clickpad && tp->buttons.state)
-               tp_get_active_touches_delta(tp, &dx, &dy);
-       else
-               tp_get_pointer_delta(tp, &dx, &dy);
-
-       tp_filter_motion(tp, &dx, &dy, &dx_unaccel, &dy_unaccel, time);
-
-       if (dx != 0.0 || dy != 0.0 || dx_unaccel != 0.0 || dy_unaccel != 0.0) {
-               pointer_notify_motion(&tp->device->base, time,
-                                     dx, dy, dx_unaccel, dy_unaccel);
-       }
-}
-
-static void
-tp_post_events(struct tp_dispatch *tp, uint64_t time)
-{
-       int filter_motion = 0;
-
-       /* Only post (top) button events while suspended */
-       if (tp->device->suspended) {
-               tp_post_button_events(tp, time);
-               return;
-       }
-
-       filter_motion |= tp_tap_handle_state(tp, time);
-       filter_motion |= tp_post_button_events(tp, time);
-
-       if (filter_motion || tp->sendevents.trackpoint_active) {
-               tp_stop_scroll_events(tp, time);
-               return;
-       }
-
-       if (tp_post_scroll_events(tp, time) != 0)
-               return;
-
-       tp_post_pointer_motion(tp, time);
-}
+       tp_clickpad_middlebutton_apply_config(tp->device);
+}
 
 static void
-tp_handle_state(struct tp_dispatch *tp,
-               uint64_t time)
-{
-       tp_process_state(tp, time);
-       tp_post_events(tp, time);
-       tp_post_process_state(tp, time);
-}
-
-static void
-tp_process(struct evdev_dispatch *dispatch,
-          struct evdev_device *device,
-          struct input_event *e,
-          uint64_t time)
+tp_interface_process(struct evdev_dispatch *dispatch,
+                    struct evdev_device *device,
+                    struct input_event *e,
+                    uint64_t time)
 {
        struct tp_dispatch *tp =
                (struct tp_dispatch *)dispatch;
 
+       if (tp->ignore_events)
+               return;
+
        switch (e->type) {
        case EV_ABS:
                if (tp->has_mt)
@@ -909,15 +1187,21 @@ tp_process(struct evdev_dispatch *dispatch,
 static void
 tp_remove_sendevents(struct tp_dispatch *tp)
 {
-       libinput_timer_cancel(&tp->sendevents.trackpoint_timer);
+       libinput_timer_cancel(&tp->palm.trackpoint_timer);
+       libinput_timer_cancel(&tp->dwt.keyboard_timer);
+
+       if (tp->buttons.trackpoint &&
+           tp->palm.monitor_trackpoint)
+               libinput_device_remove_event_listener(
+                                       &tp->palm.trackpoint_listener);
 
-       if (tp->buttons.trackpoint)
+       if (tp->dwt.keyboard)
                libinput_device_remove_event_listener(
-                                       &tp->sendevents.trackpoint_listener);
+                                       &tp->dwt.keyboard_listener);
 }
 
 static void
-tp_remove(struct evdev_dispatch *dispatch)
+tp_interface_remove(struct evdev_dispatch *dispatch)
 {
        struct tp_dispatch *tp =
                (struct tp_dispatch*)dispatch;
@@ -925,24 +1209,30 @@ tp_remove(struct evdev_dispatch *dispatch)
        tp_remove_tap(tp);
        tp_remove_buttons(tp);
        tp_remove_sendevents(tp);
-       tp_remove_scroll(tp);
+       tp_remove_edge_scroll(tp);
+       tp_remove_gesture(tp);
 }
 
 static void
-tp_destroy(struct evdev_dispatch *dispatch)
+tp_interface_destroy(struct evdev_dispatch *dispatch)
 {
        struct tp_dispatch *tp =
                (struct tp_dispatch*)dispatch;
 
-
        free(tp->touches);
        free(tp);
 }
 
+static void
+tp_release_fake_touches(struct tp_dispatch *tp)
+{
+       tp->fake_touches = 0;
+}
+
 static void
 tp_clear_state(struct tp_dispatch *tp)
 {
-       uint64_t now = libinput_now(tp->device->base.seat->libinput);
+       uint64_t now = libinput_now(tp_libinput_context(tp));
        struct tp_touch *t;
 
        /* Unroll the touchpad state.
@@ -962,6 +1252,7 @@ tp_clear_state(struct tp_dispatch *tp)
        tp_for_each_touch(tp, t) {
                tp_end_sequence(tp, t, now);
        }
+       tp_release_fake_touches(tp);
 
        tp_handle_state(tp, now);
 }
@@ -978,12 +1269,21 @@ tp_suspend(struct tp_dispatch *tp, struct evdev_device *device)
        if (tp->buttons.has_topbuttons) {
                evdev_notify_suspended_device(device);
                /* Enlarge topbutton area while suspended */
-               tp_init_top_softbuttons(tp, device, 1.5);
+               tp_init_top_softbuttons(tp, device, 3.0);
        } else {
                evdev_device_suspend(device);
        }
 }
 
+static void
+tp_interface_suspend(struct evdev_dispatch *dispatch,
+                    struct evdev_device *device)
+{
+       struct tp_dispatch *tp = (struct tp_dispatch *)dispatch;
+
+       tp_clear_state(tp);
+}
+
 static void
 tp_resume(struct tp_dispatch *tp, struct evdev_device *device)
 {
@@ -1004,7 +1304,8 @@ tp_trackpoint_timeout(uint64_t now, void *data)
        struct tp_dispatch *tp = data;
 
        tp_tap_resume(tp, now);
-       tp->sendevents.trackpoint_active = false;
+       tp->palm.trackpoint_active = false;
+       tp->palm.trackpoint_event_count = 0;
 }
 
 static void
@@ -1017,34 +1318,250 @@ tp_trackpoint_event(uint64_t time, struct libinput_event *event, void *data)
        if (event->type == LIBINPUT_EVENT_POINTER_BUTTON)
                return;
 
-       if (!tp->sendevents.trackpoint_active) {
-               evdev_stop_scroll(tp->device,
-                                 time,
-                                 LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+       tp->palm.trackpoint_last_event_time = time;
+       tp->palm.trackpoint_event_count++;
+
+       /* Require at least three events before enabling palm detection */
+       if (tp->palm.trackpoint_event_count < 3)
+               return;
+
+       if (!tp->palm.trackpoint_active) {
+               tp_edge_scroll_stop_events(tp, time);
+               tp_gesture_cancel(tp, time);
                tp_tap_suspend(tp, time);
-               tp->sendevents.trackpoint_active = true;
+               tp->palm.trackpoint_active = true;
        }
 
-       libinput_timer_set(&tp->sendevents.trackpoint_timer,
+       libinput_timer_set(&tp->palm.trackpoint_timer,
                           time + DEFAULT_TRACKPOINT_ACTIVITY_TIMEOUT);
 }
 
 static void
-tp_device_added(struct evdev_device *device,
-               struct evdev_device *added_device)
+tp_keyboard_timeout(uint64_t now, void *data)
+{
+       struct tp_dispatch *tp = data;
+
+       if (tp->dwt.dwt_enabled &&
+           long_any_bit_set(tp->dwt.key_mask,
+                            ARRAY_LENGTH(tp->dwt.key_mask))) {
+               libinput_timer_set(&tp->dwt.keyboard_timer,
+                                  now + DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2);
+               tp->dwt.keyboard_last_press_time = now;
+               log_debug(tp_libinput_context(tp), "palm: keyboard timeout refresh\n");
+               return;
+       }
+
+       tp_tap_resume(tp, now);
+
+       tp->dwt.keyboard_active = false;
+
+       log_debug(tp_libinput_context(tp), "palm: keyboard timeout\n");
+}
+
+static inline bool
+tp_key_is_modifier(unsigned int keycode)
+{
+       switch (keycode) {
+       /* Ignore modifiers to be responsive to ctrl-click, alt-tab, etc. */
+       case KEY_LEFTCTRL:
+       case KEY_RIGHTCTRL:
+       case KEY_LEFTALT:
+       case KEY_RIGHTALT:
+       case KEY_LEFTSHIFT:
+       case KEY_RIGHTSHIFT:
+       case KEY_FN:
+       case KEY_CAPSLOCK:
+       case KEY_TAB:
+       case KEY_COMPOSE:
+       case KEY_RIGHTMETA:
+       case KEY_LEFTMETA:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static inline bool
+tp_key_ignore_for_dwt(unsigned int keycode)
+{
+       /* Ignore keys not part of the "typewriter set", i.e. F-keys,
+        * multimedia keys, numpad, etc.
+        */
+
+       if (tp_key_is_modifier(keycode))
+               return false;
+
+       return keycode >= KEY_F1;
+}
+
+static void
+tp_keyboard_event(uint64_t time, struct libinput_event *event, void *data)
+{
+       struct tp_dispatch *tp = data;
+       struct libinput_event_keyboard *kbdev;
+       unsigned int timeout;
+       unsigned int key;
+       bool is_modifier;
+
+       if (event->type != LIBINPUT_EVENT_KEYBOARD_KEY)
+               return;
+
+       kbdev = libinput_event_get_keyboard_event(event);
+       key = libinput_event_keyboard_get_key(kbdev);
+
+       /* Only trigger the timer on key down. */
+       if (libinput_event_keyboard_get_key_state(kbdev) !=
+           LIBINPUT_KEY_STATE_PRESSED) {
+               long_clear_bit(tp->dwt.key_mask, key);
+               long_clear_bit(tp->dwt.mod_mask, key);
+               return;
+       }
+
+       if (!tp->dwt.dwt_enabled)
+               return;
+
+       if (tp_key_ignore_for_dwt(key))
+               return;
+
+       /* modifier keys don't trigger disable-while-typing so things like
+        * ctrl+zoom or ctrl+click are possible */
+       is_modifier = tp_key_is_modifier(key);
+       if (is_modifier) {
+               long_set_bit(tp->dwt.mod_mask, key);
+               return;
+       }
+
+       if (!tp->dwt.keyboard_active) {
+               /* This is the first non-modifier key press. Check if the
+                * modifier mask is set. If any modifier is down we don't
+                * trigger dwt because it's likely to be combination like
+                * Ctrl+S or similar */
+
+               if (long_any_bit_set(tp->dwt.mod_mask,
+                                    ARRAY_LENGTH(tp->dwt.mod_mask)))
+                   return;
+
+               tp_edge_scroll_stop_events(tp, time);
+               tp_gesture_cancel(tp, time);
+               tp_tap_suspend(tp, time);
+               tp->dwt.keyboard_active = true;
+               timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_1;
+       } else {
+               timeout = DEFAULT_KEYBOARD_ACTIVITY_TIMEOUT_2;
+       }
+
+       tp->dwt.keyboard_last_press_time = time;
+       long_set_bit(tp->dwt.key_mask, key);
+       libinput_timer_set(&tp->dwt.keyboard_timer,
+                          time + timeout);
+}
+
+static bool
+tp_dwt_device_is_blacklisted(struct evdev_device *device)
+{
+       unsigned int bus = libevdev_get_id_bustype(device->evdev);
+
+       /* evemu will set the right bus type */
+       if (bus == BUS_VIRTUAL)
+               return true;
+
+       if (device->tags & EVDEV_TAG_EXTERNAL_TOUCHPAD)
+               return true;
+
+       return false;
+}
+
+static bool
+tp_want_dwt(struct evdev_device *touchpad,
+           struct evdev_device *keyboard)
+{
+       unsigned int bus_tp = libevdev_get_id_bustype(touchpad->evdev),
+                    bus_kbd = libevdev_get_id_bustype(keyboard->evdev);
+       unsigned int vendor_tp = evdev_device_get_id_vendor(touchpad);
+       unsigned int vendor_kbd = evdev_device_get_id_vendor(keyboard);
+
+       if (tp_dwt_device_is_blacklisted(touchpad) ||
+           tp_dwt_device_is_blacklisted(keyboard))
+               return false;
+
+       /* If the touchpad is on serio, the keyboard is too, so ignore any
+          other devices */
+       if (bus_tp == BUS_I8042 && bus_kbd != bus_tp)
+               return false;
+
+       /* For Apple touchpads, always use its internal keyboard */
+       if (vendor_tp == VENDOR_ID_APPLE) {
+               return vendor_kbd == vendor_tp &&
+                      keyboard->model_flags &
+                               EVDEV_MODEL_APPLE_INTERNAL_KEYBOARD;
+       }
+
+       /* everything else we don't really know, so we have to assume
+          they go together */
+
+       return true;
+}
+
+static void
+tp_dwt_pair_keyboard(struct evdev_device *touchpad,
+                    struct evdev_device *keyboard)
+{
+       struct tp_dispatch *tp = (struct tp_dispatch*)touchpad->dispatch;
+       unsigned int bus_kbd = libevdev_get_id_bustype(keyboard->evdev);
+
+       if (!tp_want_dwt(touchpad, keyboard))
+               return;
+
+       /* If we already have a keyboard paired, override it if the new one
+        * is a serio device. Otherwise keep the current one */
+       if (tp->dwt.keyboard) {
+               if (bus_kbd != BUS_I8042)
+                       return;
+
+               memset(tp->dwt.key_mask, 0, sizeof(tp->dwt.key_mask));
+               memset(tp->dwt.mod_mask, 0, sizeof(tp->dwt.mod_mask));
+               libinput_device_remove_event_listener(&tp->dwt.keyboard_listener);
+       }
+
+       libinput_device_add_event_listener(&keyboard->base,
+                               &tp->dwt.keyboard_listener,
+                               tp_keyboard_event, tp);
+       tp->dwt.keyboard = keyboard;
+       tp->dwt.keyboard_active = false;
+
+       log_debug(touchpad->base.seat->libinput,
+                 "palm: dwt activated with %s<->%s\n",
+                 touchpad->devname,
+                 keyboard->devname);
+}
+
+static void
+tp_interface_device_added(struct evdev_device *device,
+                         struct evdev_device *added_device)
 {
        struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch;
+       unsigned int bus_tp = libevdev_get_id_bustype(device->evdev),
+                    bus_trp = libevdev_get_id_bustype(added_device->evdev);
+       bool tp_is_internal, trp_is_internal;
+
+       tp_is_internal = bus_tp != BUS_USB && bus_tp != BUS_BLUETOOTH;
+       trp_is_internal = bus_trp != BUS_USB && bus_trp != BUS_BLUETOOTH;
 
        if (tp->buttons.trackpoint == NULL &&
-           (added_device->tags & EVDEV_TAG_TRACKPOINT)) {
+           (added_device->tags & EVDEV_TAG_TRACKPOINT) &&
+           tp_is_internal && trp_is_internal) {
                /* Don't send any pending releases to the new trackpoint */
                tp->buttons.active_is_topbutton = false;
                tp->buttons.trackpoint = added_device;
-               libinput_device_add_event_listener(&added_device->base,
-                                       &tp->sendevents.trackpoint_listener,
-                                       tp_trackpoint_event, tp);
+               if (tp->palm.monitor_trackpoint)
+                       libinput_device_add_event_listener(&added_device->base,
+                                               &tp->palm.trackpoint_listener,
+                                               tp_trackpoint_event, tp);
        }
 
+       if (added_device->tags & EVDEV_TAG_KEYBOARD)
+           tp_dwt_pair_keyboard(device, added_device);
+
        if (tp->sendevents.current_mode !=
            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE)
                return;
@@ -1054,8 +1571,8 @@ tp_device_added(struct evdev_device *device,
 }
 
 static void
-tp_device_removed(struct evdev_device *device,
-                 struct evdev_device *removed_device)
+tp_interface_device_removed(struct evdev_device *device,
+                           struct evdev_device *removed_device)
 {
        struct tp_dispatch *tp = (struct tp_dispatch*)device->dispatch;
        struct libinput_device *dev;
@@ -1066,11 +1583,18 @@ tp_device_removed(struct evdev_device *device,
                        tp->buttons.active = 0;
                        tp->buttons.active_is_topbutton = false;
                }
-               libinput_device_remove_event_listener(
-                                       &tp->sendevents.trackpoint_listener);
+               if (tp->palm.monitor_trackpoint)
+                       libinput_device_remove_event_listener(
+                                               &tp->palm.trackpoint_listener);
                tp->buttons.trackpoint = NULL;
        }
 
+       if (removed_device == tp->dwt.keyboard) {
+               libinput_device_remove_event_listener(
+                                       &tp->dwt.keyboard_listener);
+               tp->dwt.keyboard = NULL;
+       }
+
        if (tp->sendevents.current_mode !=
            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE)
                return;
@@ -1086,11 +1610,43 @@ tp_device_removed(struct evdev_device *device,
        tp_resume(tp, device);
 }
 
+static inline void
+evdev_tag_touchpad_internal(struct evdev_device *device)
+{
+       device->tags |= EVDEV_TAG_INTERNAL_TOUCHPAD;
+       device->tags &= ~EVDEV_TAG_EXTERNAL_TOUCHPAD;
+}
+
+static inline void
+evdev_tag_touchpad_external(struct evdev_device *device)
+{
+       device->tags |= EVDEV_TAG_EXTERNAL_TOUCHPAD;
+       device->tags &= ~EVDEV_TAG_INTERNAL_TOUCHPAD;
+}
+
 static void
-tp_tag_device(struct evdev_device *device,
-             struct udev_device *udev_device)
+evdev_tag_touchpad(struct evdev_device *device,
+                  struct udev_device *udev_device)
 {
-       int bustype;
+       int bustype, vendor;
+       const char *prop;
+
+       prop = udev_device_get_property_value(udev_device,
+                                             "ID_INPUT_TOUCHPAD_INTEGRATION");
+       if (prop) {
+               if (streq(prop, "internal")) {
+                       evdev_tag_touchpad_internal(device);
+                       return;
+               } else if (streq(prop, "external")) {
+                       evdev_tag_touchpad_external(device);
+                       return;
+               } else {
+                       log_info(evdev_libinput_context(device),
+                                "%s: tagged as unknown value %s\n",
+                                device->devname,
+                                prop);
+               }
+       }
 
        /* simple approach: touchpads on USB or Bluetooth are considered
         * external, anything else is internal. Exception is Apple -
@@ -1098,26 +1654,69 @@ tp_tag_device(struct evdev_device *device,
         * external USB touchpads anyway.
         */
        bustype = libevdev_get_id_bustype(device->evdev);
-       if (bustype == BUS_USB) {
-                if (libevdev_get_id_vendor(device->evdev) == VENDOR_ID_APPLE)
-                        device->tags |= EVDEV_TAG_INTERNAL_TOUCHPAD;
-       } else if (bustype != BUS_BLUETOOTH)
-               device->tags |= EVDEV_TAG_INTERNAL_TOUCHPAD;
+       vendor = libevdev_get_id_vendor(device->evdev);
+
+       switch (bustype) {
+       case BUS_USB:
+               if (device->model_flags & EVDEV_MODEL_APPLE_TOUCHPAD)
+                        evdev_tag_touchpad_internal(device);
+               break;
+       case BUS_BLUETOOTH:
+               evdev_tag_touchpad_external(device);
+               break;
+       default:
+               evdev_tag_touchpad_internal(device);
+               break;
+       }
+
+       switch (vendor) {
+       /* Logitech does not have internal touchpads */
+       case VENDOR_ID_LOGITECH:
+               evdev_tag_touchpad_external(device);
+               break;
+       }
+
+       /* Wacom makes touchpads, but not internal ones */
+       if (device->model_flags & EVDEV_MODEL_WACOM_TOUCHPAD)
+               evdev_tag_touchpad_external(device);
+
+       if ((device->tags &
+           (EVDEV_TAG_EXTERNAL_TOUCHPAD|EVDEV_TAG_INTERNAL_TOUCHPAD)) == 0) {
+               log_bug_libinput(evdev_libinput_context(device),
+                                "%s: Internal or external? Please file a bug.\n",
+                                device->devname);
+               evdev_tag_touchpad_external(device);
+       }
+}
+
+static void
+tp_interface_toggle_touch(struct evdev_dispatch *dispatch,
+                         struct evdev_device *device,
+                         bool enable)
+{
+       struct tp_dispatch *tp = (struct tp_dispatch*)dispatch;
+       bool ignore_events = !enable;
+
+       if (ignore_events == tp->ignore_events)
+               return;
+
+       if (ignore_events)
+               tp_clear_state(tp);
 
-       if (udev_device_get_property_value(udev_device,
-                                          "TOUCHPAD_HAS_TRACKPOINT_BUTTONS"))
-               device->tags |= EVDEV_TAG_TOUCHPAD_TRACKPOINT;
+       tp->ignore_events = ignore_events;
 }
 
 static struct evdev_dispatch_interface tp_interface = {
-       tp_process,
-       tp_remove,
-       tp_destroy,
-       tp_device_added,
-       tp_device_removed,
-       tp_device_removed, /* device_suspended, treat as remove */
-       tp_device_added,   /* device_resumed, treat as add */
-       tp_tag_device,
+       tp_interface_process,
+       tp_interface_suspend,
+       tp_interface_remove,
+       tp_interface_destroy,
+       tp_interface_device_added,
+       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 */
+       tp_interface_toggle_touch,
 };
 
 static void
@@ -1128,7 +1727,29 @@ tp_init_touch(struct tp_dispatch *tp,
        t->has_ended = true;
 }
 
-static int
+static void
+tp_sync_touch(struct tp_dispatch *tp,
+             struct evdev_device *device,
+             struct tp_touch *t,
+             int slot)
+{
+       struct libevdev *evdev = device->evdev;
+
+       if (!libevdev_fetch_slot_value(evdev,
+                                      slot,
+                                      ABS_MT_POSITION_X,
+                                      &t->point.x))
+               t->point.x = libevdev_get_event_value(evdev, EV_ABS, ABS_X);
+       if (!libevdev_fetch_slot_value(evdev,
+                                      slot,
+                                      ABS_MT_POSITION_Y,
+                                      &t->point.y))
+               t->point.y = libevdev_get_event_value(evdev, EV_ABS, ABS_Y);
+
+       libevdev_fetch_slot_value(evdev, slot, ABS_MT_DISTANCE, &t->distance);
+}
+
+static bool
 tp_init_slots(struct tp_dispatch *tp,
              struct evdev_device *device)
 {
@@ -1147,17 +1768,38 @@ tp_init_slots(struct tp_dispatch *tp,
 
        absinfo = libevdev_get_abs_info(device->evdev, ABS_MT_SLOT);
        if (absinfo) {
-               tp->real_touches = absinfo->maximum + 1;
+               tp->num_slots = absinfo->maximum + 1;
                tp->slot = absinfo->value;
                tp->has_mt = true;
        } else {
-               tp->real_touches = 1;
+               tp->num_slots = 1;
                tp->slot = 0;
                tp->has_mt = false;
        }
 
        tp->semi_mt = libevdev_has_property(device->evdev, INPUT_PROP_SEMI_MT);
 
+       /* Semi-mt devices are not reliable for true multitouch data, so we
+        * simply pretend they're single touch touchpads with BTN_TOOL bits.
+        * Synaptics:
+        * Terrible resolution when two fingers are down,
+        * causing scroll jumps. The single-touch emulation ABS_X/Y is
+        * accurate but the ABS_MT_POSITION touchpoints report the bounding
+        * box and that causes jumps. See https://bugzilla.redhat.com/1235175
+        * Elantech:
+        * On three-finger taps/clicks, one slot doesn't get a coordinate
+        * assigned. See https://bugs.freedesktop.org/show_bug.cgi?id=93583
+        * Alps:
+        * If three fingers are set down in the same frame, one slot has the
+        * coordinates 0/0 and may not get updated for several frames.
+        * See https://bugzilla.redhat.com/show_bug.cgi?id=1295073
+        */
+       if (tp->semi_mt) {
+               tp->num_slots = 1;
+               tp->slot = 0;
+               tp->has_mt = false;
+       }
+
        ARRAY_FOR_EACH(max_touches, m) {
                if (libevdev_has_event_code(device->evdev,
                                            EV_KEY,
@@ -1167,33 +1809,57 @@ tp_init_slots(struct tp_dispatch *tp,
                }
        }
 
-       tp->ntouches = max(tp->real_touches, n_btn_tool_touches);
+       tp->ntouches = max(tp->num_slots, n_btn_tool_touches);
        tp->touches = calloc(tp->ntouches, sizeof(struct tp_touch));
        if (!tp->touches)
-               return -1;
+               return false;
 
        for (i = 0; i < tp->ntouches; i++)
                tp_init_touch(tp, &tp->touches[i]);
 
-       return 0;
+       /* Always sync the first touch so we get ABS_X/Y synced on
+        * single-touch touchpads */
+       tp_sync_touch(tp, device, &tp->touches[0], 0);
+       for (i = 1; i < tp->num_slots; i++)
+               tp_sync_touch(tp, device, &tp->touches[i], i);
+
+       return true;
 }
 
-static int
-tp_init_accel(struct tp_dispatch *tp, double diagonal)
+static uint32_t
+tp_accel_config_get_profiles(struct libinput_device *libinput_device)
+{
+       return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+}
+
+static enum libinput_config_status
+tp_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
+tp_accel_config_get_profile(struct libinput_device *libinput_device)
+{
+       return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+}
+
+static enum libinput_config_accel_profile
+tp_accel_config_get_default_profile(struct libinput_device *libinput_device)
+{
+       return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+}
+
+static bool
+tp_init_accel(struct tp_dispatch *tp)
 {
+       struct evdev_device *device = tp->device;
        int res_x, res_y;
+       struct motion_filter *filter;
 
-       if (tp->has_mt) {
-               res_x = libevdev_get_abs_resolution(tp->device->evdev,
-                                                   ABS_MT_POSITION_X);
-               res_y = libevdev_get_abs_resolution(tp->device->evdev,
-                                                   ABS_MT_POSITION_Y);
-       } else {
-               res_x = libevdev_get_abs_resolution(tp->device->evdev,
-                                                   ABS_X);
-               res_y = libevdev_get_abs_resolution(tp->device->evdev,
-                                                   ABS_Y);
-       }
+       res_x = tp->device->abs.absinfo_x->resolution;
+       res_y = tp->device->abs.absinfo_y->resolution;
 
        /*
         * Not all touchpads report the same amount of units/mm (resolution).
@@ -1202,64 +1868,64 @@ tp_init_accel(struct tp_dispatch *tp, double diagonal)
         * and y resolution, so that a circle on the
         * touchpad does not turn into an elipse on the screen.
         */
-       if (res_x > 1 && res_y > 1) {
-               tp->accel.x_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_x;
-               tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y;
-
-               /* FIXME: once normalized, touchpads see the same
-                  acceleration as mice. that is technically correct but
-                  subjectively wrong, we expect a touchpad to be a lot
-                  slower than a mouse.
-                  For now, apply a magic factor here until this is
-                  fixed in the actual filter code.
-                */
-               {
-                       const double MAGIC = 0.4;
-                       tp->accel.x_scale_coeff *= MAGIC;
-                       tp->accel.y_scale_coeff *= MAGIC;
-               }
-       } else {
-       /*
-        * For touchpads where the driver does not provide resolution, fall
-        * back to scaling motion events based on the diagonal size in units.
-        */
-               tp->accel.x_scale_coeff = DEFAULT_ACCEL_NUMERATOR / diagonal;
-               tp->accel.y_scale_coeff = DEFAULT_ACCEL_NUMERATOR / diagonal;
-       }
+       tp->accel.x_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_x;
+       tp->accel.y_scale_coeff = (DEFAULT_MOUSE_DPI/25.4) / res_y;
+
+       if (tp->device->model_flags & EVDEV_MODEL_LENOVO_X230 ||
+           tp->device->model_flags & EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81)
+               filter = create_pointer_accelerator_filter_lenovo_x230(tp->device->dpi);
+       else
+               filter = create_pointer_accelerator_filter_touchpad(tp->device->dpi);
 
-       if (evdev_device_init_pointer_acceleration(tp->device) == -1)
-               return -1;
+       if (!filter)
+               return false;
 
-       return 0;
+       evdev_device_init_pointer_acceleration(tp->device, filter);
+
+       /* we override the profile hooks for accel configuration with hooks
+        * that don't allow selection of profiles */
+       device->pointer.config.get_profiles = tp_accel_config_get_profiles;
+       device->pointer.config.set_profile = tp_accel_config_set_profile;
+       device->pointer.config.get_profile = tp_accel_config_get_profile;
+       device->pointer.config.get_default_profile = tp_accel_config_get_default_profile;
+
+       return true;
 }
 
 static uint32_t
-tp_scroll_config_scroll_method_get_methods(struct libinput_device *device)
+tp_scroll_get_methods(struct tp_dispatch *tp)
 {
-       struct evdev_device *evdev = (struct evdev_device*)device;
-       struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
-       uint32_t methods = LIBINPUT_CONFIG_SCROLL_NO_SCROLL;
+       uint32_t methods = LIBINPUT_CONFIG_SCROLL_EDGE;
 
        if (tp->ntouches >= 2)
                methods |= LIBINPUT_CONFIG_SCROLL_2FG;
 
-       if (!tp->buttons.is_clickpad)
-               methods |= LIBINPUT_CONFIG_SCROLL_EDGE;
-
        return methods;
 }
 
+static uint32_t
+tp_scroll_config_scroll_method_get_methods(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+       struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+
+       return tp_scroll_get_methods(tp);
+}
+
 static enum libinput_config_status
 tp_scroll_config_scroll_method_set_method(struct libinput_device *device,
                        enum libinput_config_scroll_method method)
 {
        struct evdev_device *evdev = (struct evdev_device*)device;
        struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+       uint64_t time = libinput_now(tp_libinput_context(tp));
 
        if (method == tp->scroll.method)
                return LIBINPUT_CONFIG_STATUS_SUCCESS;
 
-       tp_stop_scroll_events(tp, libinput_now(device->seat->libinput));
+       tp_edge_scroll_stop_events(tp, time);
+       tp_gesture_stop_twofinger_scroll(tp, time);
+
        tp->scroll.method = method;
 
        return LIBINPUT_CONFIG_STATUS_SUCCESS;
@@ -1277,10 +1943,21 @@ tp_scroll_config_scroll_method_get_method(struct libinput_device *device)
 static enum libinput_config_scroll_method
 tp_scroll_get_default_method(struct tp_dispatch *tp)
 {
-       if (tp->ntouches >= 2)
-               return LIBINPUT_CONFIG_SCROLL_2FG;
+       uint32_t methods;
+       enum libinput_config_scroll_method method;
+
+       methods = tp_scroll_get_methods(tp);
+
+       if (methods & LIBINPUT_CONFIG_SCROLL_2FG)
+               method = LIBINPUT_CONFIG_SCROLL_2FG;
        else
-               return LIBINPUT_CONFIG_SCROLL_EDGE;
+               method = LIBINPUT_CONFIG_SCROLL_EDGE;
+
+       if ((methods & method) == 0)
+               log_bug_libinput(tp_libinput_context(tp),
+                                "Invalid default scroll method %d\n",
+                                method);
+       return method;
 }
 
 static enum libinput_config_scroll_method
@@ -1292,11 +1969,10 @@ tp_scroll_config_scroll_method_get_default_method(struct libinput_device *device
        return tp_scroll_get_default_method(tp);
 }
 
-static int
+static void
 tp_init_scroll(struct tp_dispatch *tp, struct evdev_device *device)
 {
-       if (tp_edge_scroll_init(tp, device) != 0)
-               return -1;
+       tp_edge_scroll_init(tp, device);
 
        evdev_init_natural_scroll(device);
 
@@ -1307,51 +1983,280 @@ tp_init_scroll(struct tp_dispatch *tp, struct evdev_device *device)
        tp->scroll.method = tp_scroll_get_default_method(tp);
        tp->device->base.config.scroll_method = &tp->scroll.config_method;
 
-       /* In mm for touchpads with valid resolution, see tp_init_accel() */
-       tp->device->scroll.threshold = 5.0;
-
-       return 0;
+        /* In mm for touchpads with valid resolution, see tp_init_accel() */
+       tp->device->scroll.threshold = 0.0;
+       tp->device->scroll.direction_lock_threshold = 5.0;
 }
 
 static int
+tp_dwt_config_is_available(struct libinput_device *device)
+{
+       return 1;
+}
+
+static enum libinput_config_status
+tp_dwt_config_set(struct libinput_device *device,
+          enum libinput_config_dwt_state enable)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+       struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+
+       switch(enable) {
+       case LIBINPUT_CONFIG_DWT_ENABLED:
+       case LIBINPUT_CONFIG_DWT_DISABLED:
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+       }
+
+       tp->dwt.dwt_enabled = (enable == LIBINPUT_CONFIG_DWT_ENABLED);
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_dwt_state
+tp_dwt_config_get(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+       struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+
+       return tp->dwt.dwt_enabled ?
+               LIBINPUT_CONFIG_DWT_ENABLED :
+               LIBINPUT_CONFIG_DWT_DISABLED;
+}
+
+static bool
+tp_dwt_default_enabled(struct tp_dispatch *tp)
+{
+       return true;
+}
+
+static enum libinput_config_dwt_state
+tp_dwt_config_get_default(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+       struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+
+       return tp_dwt_default_enabled(tp) ?
+               LIBINPUT_CONFIG_DWT_ENABLED :
+               LIBINPUT_CONFIG_DWT_DISABLED;
+}
+
+static void
+tp_init_dwt(struct tp_dispatch *tp,
+           struct evdev_device *device)
+{
+       if (tp_dwt_device_is_blacklisted(device))
+               return;
+
+       tp->dwt.config.is_available = tp_dwt_config_is_available;
+       tp->dwt.config.set_enabled = tp_dwt_config_set;
+       tp->dwt.config.get_enabled = tp_dwt_config_get;
+       tp->dwt.config.get_default_enabled = tp_dwt_config_get_default;
+       tp->dwt.dwt_enabled = tp_dwt_default_enabled(tp);
+       device->base.config.dwt = &tp->dwt.config;
+
+       return;
+}
+
+static void
 tp_init_palmdetect(struct tp_dispatch *tp,
                   struct evdev_device *device)
 {
-       int width;
+       double width, height;
+       struct phys_coords mm = { 0.0, 0.0 };
+       struct device_coords edges;
 
        tp->palm.right_edge = INT_MAX;
        tp->palm.left_edge = INT_MIN;
 
-       width = abs(device->abs.absinfo_x->maximum -
-                   device->abs.absinfo_x->minimum);
+       /* Wacom doesn't have internal touchpads */
+       if (device->model_flags & EVDEV_MODEL_WACOM_TOUCHPAD)
+               return;
 
-       /* Apple touchpads are always big enough to warrant palm detection */
-       if (evdev_device_get_id_vendor(device) != VENDOR_ID_APPLE) {
-               /* We don't know how big the touchpad is */
-               if (device->abs.absinfo_x->resolution == 1)
-                       return 0;
+       evdev_device_get_size(device, &width, &height);
 
-               /* Enable palm detection on touchpads >= 80 mm. Anything smaller
-                  probably won't need it, until we find out it does */
-               if (width/device->abs.absinfo_x->resolution < 80)
-                       return 0;
-       }
+       /* Enable palm detection on touchpads >= 70 mm. Anything smaller
+          probably won't need it, until we find out it does */
+       if (width < 70.0)
+               return;
 
        /* palm edges are 5% of the width on each side */
-       tp->palm.right_edge = device->abs.absinfo_x->maximum - width * 0.05;
-       tp->palm.left_edge = device->abs.absinfo_x->minimum + width * 0.05;
+       mm.x = width * 0.05;
+       edges = evdev_device_mm_to_units(device, &mm);
+       tp->palm.left_edge = edges.x;
 
-       return 0;
+       mm.x = width * 0.95;
+       edges = evdev_device_mm_to_units(device, &mm);
+       tp->palm.right_edge = edges.x;
+
+       tp->palm.monitor_trackpoint = true;
 }
 
-static int
+static void
 tp_init_sendevents(struct tp_dispatch *tp,
                   struct evdev_device *device)
 {
-       libinput_timer_init(&tp->sendevents.trackpoint_timer,
-                           tp->device->base.seat->libinput,
+       libinput_timer_init(&tp->palm.trackpoint_timer,
+                           tp_libinput_context(tp),
                            tp_trackpoint_timeout, tp);
-       return 0;
+
+       libinput_timer_init(&tp->dwt.keyboard_timer,
+                           tp_libinput_context(tp),
+                           tp_keyboard_timeout, tp);
+}
+
+static void
+tp_init_thumb(struct tp_dispatch *tp)
+{
+       struct evdev_device *device = tp->device;
+       const struct input_absinfo *abs;
+       double w = 0.0, h = 0.0;
+       struct device_coords edges;
+       struct phys_coords mm = { 0.0, 0.0 };
+       int xres, yres;
+       double threshold;
+
+       if (!tp->buttons.is_clickpad)
+               return;
+
+       /* if the touchpad is less than 50mm high, skip thumb detection.
+        * it's too small to meaningfully interact with a thumb on the
+        * touchpad */
+       evdev_device_get_size(device, &w, &h);
+       if (h < 50)
+               return;
+
+       tp->thumb.detect_thumbs = true;
+       tp->thumb.threshold = INT_MAX;
+
+       /* detect thumbs by pressure in the bottom 15mm, detect thumbs by
+        * lingering in the bottom 8mm */
+       mm.y = h * 0.85;
+       edges = evdev_device_mm_to_units(device, &mm);
+       tp->thumb.upper_thumb_line = edges.y;
+
+       mm.y = h * 0.92;
+       edges = evdev_device_mm_to_units(device, &mm);
+       tp->thumb.lower_thumb_line = edges.y;
+
+       abs = libevdev_get_abs_info(device->evdev, ABS_MT_PRESSURE);
+       if (!abs)
+               goto out;
+
+       if (abs->maximum - abs->minimum < 255)
+               goto out;
+
+       /* Our reference touchpad is the T440s with 42x42 resolution.
+        * Higher-res touchpads exhibit higher pressure for the same
+        * interaction. On the T440s, the threshold value is 100, you don't
+        * reach that with a normal finger interaction.
+        * Note: "thumb" means massive touch that should not interact, not
+        * "using the tip of my thumb for a pinch gestures".
+        */
+       xres = tp->device->abs.absinfo_x->resolution;
+       yres = tp->device->abs.absinfo_y->resolution;
+       threshold = 100.0 * hypot(xres, yres)/hypot(42, 42);
+       tp->thumb.threshold = max(100, threshold);
+
+out:
+       log_debug(tp_libinput_context(tp),
+                 "thumb: enabled thumb detection%s on '%s'\n",
+                 tp->thumb.threshold != INT_MAX ? " (+pressure)" : "",
+                 device->devname);
+}
+
+static bool
+tp_pass_sanity_check(struct tp_dispatch *tp,
+                    struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+       struct libinput *libinput = tp_libinput_context(tp);
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X))
+               goto error;
+
+       if (!libevdev_has_event_code(evdev, EV_KEY, BTN_TOUCH))
+               goto error;
+
+       if (!libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_FINGER))
+               goto error;
+
+       return true;
+
+error:
+       log_bug_kernel(libinput,
+                      "device %s failed touchpad sanity checks\n",
+                      device->devname);
+       return false;
+}
+
+static void
+tp_init_default_resolution(struct tp_dispatch *tp,
+                          struct evdev_device *device)
+{
+       const int touchpad_width_mm = 69, /* 1 under palm detection */
+                 touchpad_height_mm = 50;
+       int xres, yres;
+
+       if (!device->abs.is_fake_resolution)
+               return;
+
+       /* we only get here if
+        * - the touchpad provides no resolution
+        * - the udev hwdb didn't override the resoluion
+        * - no ATTR_SIZE_HINT is set
+        *
+        * The majority of touchpads that triggers all these conditions
+        * are old ones, so let's assume a small touchpad size and assume
+        * that.
+        */
+       log_info(tp_libinput_context(tp),
+                "%s: no resolution or size hints, assuming a size of %dx%dmm\n",
+                device->devname,
+                touchpad_width_mm,
+                touchpad_height_mm);
+
+       xres = device->abs.dimensions.x/touchpad_width_mm;
+       yres = device->abs.dimensions.y/touchpad_height_mm;
+       libevdev_set_abs_resolution(device->evdev, ABS_X, xres);
+       libevdev_set_abs_resolution(device->evdev, ABS_Y, yres);
+       libevdev_set_abs_resolution(device->evdev, ABS_MT_POSITION_X, xres);
+       libevdev_set_abs_resolution(device->evdev, ABS_MT_POSITION_Y, yres);
+       device->abs.is_fake_resolution = false;
+}
+
+static inline void
+tp_init_hysteresis(struct tp_dispatch *tp)
+{
+       int res_x, res_y;
+
+       res_x = tp->device->abs.absinfo_x->resolution;
+       res_y = tp->device->abs.absinfo_y->resolution;
+       tp->hysteresis_margin.x = res_x/2;
+       tp->hysteresis_margin.y = res_y/2;
+
+       return;
+}
+
+static void
+tp_init_range_warnings(struct tp_dispatch *tp,
+                      struct evdev_device *device,
+                      int width,
+                      int height)
+{
+       const struct input_absinfo *x, *y;
+
+       x = device->abs.absinfo_x;
+       y = device->abs.absinfo_y;
+
+       tp->warning_range.min.x = x->minimum - 0.05 * width;
+       tp->warning_range.min.y = y->minimum - 0.05 * height;
+       tp->warning_range.max.x = x->maximum + 0.05 * width;
+       tp->warning_range.max.y = y->maximum + 0.05 * height;
+
+       /* One warning every 5 min is enough */
+       ratelimit_init(&tp->warning_range.range_warn_limit, s2us(3000), 1);
 }
 
 static int
@@ -1359,46 +2264,46 @@ tp_init(struct tp_dispatch *tp,
        struct evdev_device *device)
 {
        int width, height;
-       double diagonal;
 
        tp->base.interface = &tp_interface;
        tp->device = device;
 
-       if (tp_init_slots(tp, device) != 0)
-               return -1;
+       if (!tp_pass_sanity_check(tp, device))
+               return false;
 
-       width = abs(device->abs.absinfo_x->maximum -
-                   device->abs.absinfo_x->minimum);
-       height = abs(device->abs.absinfo_y->maximum -
-                    device->abs.absinfo_y->minimum);
-       diagonal = sqrt(width*width + height*height);
+       tp_init_default_resolution(tp, device);
 
-       tp->hysteresis.margin_x =
-               diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
-       tp->hysteresis.margin_y =
-               diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR;
+       if (!tp_init_slots(tp, device))
+               return false;
 
-       if (tp_init_accel(tp, diagonal) != 0)
-               return -1;
+       width = device->abs.dimensions.x;
+       height = device->abs.dimensions.y;
 
-       if (tp_init_tap(tp) != 0)
-               return -1;
+       tp_init_range_warnings(tp, device, width, height);
 
-       if (tp_init_buttons(tp, device) != 0)
-               return -1;
+       tp->reports_distance = libevdev_has_event_code(device->evdev,
+                                                      EV_ABS,
+                                                      ABS_MT_DISTANCE);
 
-       if (tp_init_palmdetect(tp, device) != 0)
-               return -1;
+       tp_init_hysteresis(tp);
 
-       if (tp_init_sendevents(tp, device) != 0)
-               return -1;
+       if (!tp_init_accel(tp))
+               return false;
 
-       if (tp_init_scroll(tp, device) != 0)
-               return -1;
+       tp_init_tap(tp);
+       tp_init_buttons(tp, device);
+       tp_init_dwt(tp, device);
+       tp_init_palmdetect(tp, device);
+       tp_init_sendevents(tp, device);
+       tp_init_scroll(tp, device);
+       tp_init_gesture(tp);
+       tp_init_thumb(tp);
 
        device->seat_caps |= EVDEV_DEVICE_POINTER;
+       if (tp->gesture.enabled)
+               device->seat_caps |= EVDEV_DEVICE_GESTURE;
 
-       return 0;
+       return true;
 }
 
 static uint32_t
@@ -1494,53 +2399,19 @@ tp_change_to_left_handed(struct evdev_device *device)
        device->left_handed.enabled = device->left_handed.want_enabled;
 }
 
-struct model_lookup_t {
-       uint16_t vendor;
-       uint16_t product_start;
-       uint16_t product_end;
-       enum touchpad_model model;
-};
-
-static struct model_lookup_t model_lookup_table[] = {
-       { 0x0002, 0x0007, 0x0007, MODEL_SYNAPTICS },
-       { 0x0002, 0x0008, 0x0008, MODEL_ALPS },
-       { 0x0002, 0x000e, 0x000e, MODEL_ELANTECH },
-       { 0x05ac,      0, 0x0222, MODEL_APPLETOUCH },
-       { 0x05ac, 0x0223, 0x0228, MODEL_UNIBODY_MACBOOK },
-       { 0x05ac, 0x0229, 0x022b, MODEL_APPLETOUCH },
-       { 0x05ac, 0x022c, 0xffff, MODEL_UNIBODY_MACBOOK },
-       { 0, 0, 0, 0 }
-};
-
-static enum touchpad_model
-tp_get_model(struct evdev_device *device)
-{
-       struct model_lookup_t *lookup;
-       uint16_t vendor  = libevdev_get_id_vendor(device->evdev);
-       uint16_t product = libevdev_get_id_product(device->evdev);
-
-       for (lookup = model_lookup_table; lookup->vendor; lookup++) {
-               if (lookup->vendor == vendor &&
-                   lookup->product_start <= product &&
-                   product <= lookup->product_end)
-                       return lookup->model;
-       }
-       return MODEL_UNKNOWN;
-}
-
 struct evdev_dispatch *
 evdev_mt_touchpad_create(struct evdev_device *device)
 {
        struct tp_dispatch *tp;
 
+       evdev_tag_touchpad(device, device->udev_device);
+
        tp = zalloc(sizeof *tp);
        if (!tp)
                return NULL;
 
-       tp->model = tp_get_model(device);
-
-       if (tp_init(tp, device) != 0) {
-               tp_destroy(&tp->base);
+       if (!tp_init(tp, device)) {
+               tp_interface_destroy(&tp->base);
                return NULL;
        }
 
@@ -1554,5 +2425,5 @@ evdev_mt_touchpad_create(struct evdev_device *device)
 
        evdev_init_left_handed(device, tp_change_to_left_handed);
 
-       return  &tp->base;
+       return &tp->base;
 }
index 3479e5eafa6014ea1a07f8c0d0861f8bae7487e9..f4ad0907bf88eeebf8cf808dc961bea4147400ce 100644 (file)
@@ -1,26 +1,26 @@
 /*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
-
 #ifndef EVDEV_MT_TOUCHPAD_H
 #define EVDEV_MT_TOUCHPAD_H
 
 #define TOUCHPAD_HISTORY_LENGTH 4
 #define TOUCHPAD_MIN_SAMPLES 4
 
-#define VENDOR_ID_APPLE 0x5ac
+/* Convert mm to a distance normalized to DEFAULT_MOUSE_DPI */
+#define TP_MM_TO_DPI_NORMALIZED(mm) (DEFAULT_MOUSE_DPI/25.4 * mm)
 
 enum touchpad_event {
        TOUCHPAD_EVENT_NONE             = 0,
        TOUCHPAD_EVENT_MOTION           = (1 << 0),
        TOUCHPAD_EVENT_BUTTON_PRESS     = (1 << 1),
        TOUCHPAD_EVENT_BUTTON_RELEASE   = (1 << 2),
+       TOUCHPAD_EVENT_OTHERAXIS        = (1 << 3),
 };
 
 enum touchpad_model {
@@ -59,8 +61,16 @@ enum touch_state {
        TOUCH_END
 };
 
+enum touch_palm_state {
+       PALM_NONE = 0,
+       PALM_EDGE,
+       PALM_TYPING,
+       PALM_TRACKPOINT,
+};
+
 enum button_event {
        BUTTON_EVENT_IN_BOTTOM_R = 30,
+       BUTTON_EVENT_IN_BOTTOM_M,
        BUTTON_EVENT_IN_BOTTOM_L,
        BUTTON_EVENT_IN_TOP_R,
        BUTTON_EVENT_IN_TOP_M,
@@ -89,12 +99,16 @@ enum tp_tap_state {
        TAP_STATE_TAPPED,
        TAP_STATE_TOUCH_2,
        TAP_STATE_TOUCH_2_HOLD,
+       TAP_STATE_TOUCH_2_RELEASE,
        TAP_STATE_TOUCH_3,
        TAP_STATE_TOUCH_3_HOLD,
        TAP_STATE_DRAGGING_OR_DOUBLETAP,
+       TAP_STATE_DRAGGING_OR_TAP,
        TAP_STATE_DRAGGING,
        TAP_STATE_DRAGGING_WAIT,
        TAP_STATE_DRAGGING_2,
+       TAP_STATE_MULTITAP,
+       TAP_STATE_MULTITAP_DOWN,
        TAP_STATE_DEAD, /**< finger count exceeded */
 };
 
@@ -118,14 +132,18 @@ enum tp_edge_scroll_touch_state {
        EDGE_SCROLL_TOUCH_STATE_AREA,
 };
 
-enum tp_twofinger_scroll_state {
-       TWOFINGER_SCROLL_STATE_NONE,
-       TWOFINGER_SCROLL_STATE_ACTIVE,
+enum tp_gesture_state {
+       GESTURE_STATE_NONE,
+       GESTURE_STATE_UNKNOWN,
+       GESTURE_STATE_SCROLL,
+       GESTURE_STATE_PINCH,
+       GESTURE_STATE_SWIPE,
 };
 
-struct tp_motion {
-       int32_t x;
-       int32_t y;
+enum tp_thumb_state {
+       THUMB_STATE_NO,
+       THUMB_STATE_YES,
+       THUMB_STATE_MAYBE,
 };
 
 struct tp_touch {
@@ -133,21 +151,27 @@ struct tp_touch {
        enum touch_state state;
        bool has_ended;                         /* TRACKING_ID == -1 */
        bool dirty;
-       bool is_pointer;                        /* the pointer-controlling touch */
-       int32_t x;
-       int32_t y;
+       struct device_coords point;
        uint64_t millis;
+       int distance;                           /* distance == 0 means touch */
+       int pressure;
+
+       struct {
+               /* A quirk mostly used on Synaptics touchpads. In a
+                  transition to/from fake touches > num_slots, the current
+                  event data is likely garbage and the subsequent event
+                  is likely too. This marker tells us to reset the motion
+                  history again -> this effectively swallows any motion */
+               bool reset_motion_history;
+       } quirks;
 
        struct {
-               struct tp_motion samples[TOUCHPAD_HISTORY_LENGTH];
+               struct device_coords samples[TOUCHPAD_HISTORY_LENGTH];
                unsigned int index;
                unsigned int count;
        } history;
 
-       struct {
-               int32_t center_x;
-               int32_t center_y;
-       } hysteresis;
+       struct device_coords hysteresis_center;
 
        /* A pinned touchpoint is the one that pressed the physical button
         * on a clickpad. After the release, it won't move until the center
@@ -155,8 +179,7 @@ struct tp_touch {
         */
        struct {
                bool is_pinned;
-               int32_t center_x;
-               int32_t center_y;
+               struct device_coords center;
        } pinned;
 
        /* Software-button state and timeout if applicable */
@@ -169,21 +192,33 @@ struct tp_touch {
 
        struct {
                enum tp_tap_touch_state state;
+               struct device_coords initial;
+               bool is_thumb;
        } tap;
 
        struct {
                enum tp_edge_scroll_touch_state edge_state;
                uint32_t edge;
                int direction;
-               double threshold;
                struct libinput_timer timer;
+               struct device_coords initial;
        } scroll;
 
        struct {
-               bool is_palm;
-               int32_t x, y;  /* first coordinates if is_palm == true */
-               uint32_t time; /* first timestamp if is_palm == true */
+               enum touch_palm_state state;
+               struct device_coords first; /* first coordinates if is_palm == true */
+               uint64_t time; /* first timestamp if is_palm == true */
        } palm;
+
+       struct {
+               struct device_coords initial;
+       } gesture;
+
+       struct {
+               enum tp_thumb_state state;
+               uint64_t first_touch_time;
+               struct device_coords initial;
+       } thumb;
 };
 
 struct tp_dispatch {
@@ -194,9 +229,13 @@ struct tp_dispatch {
        unsigned int slot;                      /* current slot */
        bool has_mt;
        bool semi_mt;
-       enum touchpad_model model;
+       bool reports_distance;                  /* does the device support true hovering */
 
-       unsigned int real_touches;              /* number of slots */
+       /* true if we're reading events (i.e. not suspended) but we're
+        * ignoring them */
+       bool ignore_events;
+
+       unsigned int num_slots;                 /* number of slots */
        unsigned int ntouches;                  /* no slots inc. fakes */
        struct tp_touch *touches;               /* len == ntouches */
        /* bit 0: BTN_TOUCH
@@ -206,16 +245,33 @@ struct tp_dispatch {
         */
        unsigned int fake_touches;
 
+       struct device_coords hysteresis_margin;
+
        struct {
-               int32_t margin_x;
-               int32_t margin_y;
-       } hysteresis;
+               struct device_coords min, max;
+               struct ratelimit range_warn_limit;
+       } warning_range;
 
        struct {
                double x_scale_coeff;
                double y_scale_coeff;
        } accel;
 
+       struct {
+               bool enabled;
+               bool started;
+               unsigned int finger_count;
+               unsigned int finger_count_pending;
+               struct libinput_timer finger_count_switch_timer;
+               enum tp_gesture_state state;
+               struct tp_touch *touches[2];
+               uint64_t initial_time;
+               double initial_distance;
+               double prev_scale;
+               double angle;
+               struct device_float_coords center;
+       } gesture;
+
        struct {
                bool is_clickpad;               /* true for clickpads */
                bool has_topbuttons;
@@ -223,7 +279,10 @@ struct tp_dispatch {
                bool click_pending;
                uint32_t state;
                uint32_t old_state;
-               uint32_t motion_dist;           /* for pinned touches */
+               struct {
+                       double x_scale_coeff;
+                       double y_scale_coeff;
+               } motion_dist;                  /* for pinned touches */
                unsigned int active;            /* currently active button, for release event */
                bool active_is_topbutton;       /* is active a top button? */
 
@@ -232,14 +291,15 @@ struct tp_dispatch {
                 * The buttons are split according to the edge settings.
                 */
                struct {
-                       int32_t top_edge;
-                       int32_t rightbutton_left_edge;
+                       int32_t top_edge;       /* in device coordinates */
+                       int32_t rightbutton_left_edge; /* in device coordinates */
+                       int32_t middlebutton_left_edge; /* in device coordinates */
                } bottom_area;
 
                struct {
-                       int32_t bottom_edge;
-                       int32_t rightbutton_left_edge;
-                       int32_t leftbutton_right_edge;
+                       int32_t bottom_edge;    /* in device coordinates */
+                       int32_t rightbutton_left_edge; /* in device coordinates */
+                       int32_t leftbutton_right_edge; /* in device coordinates */
                } top_area;
 
                struct evdev_device *trackpoint;
@@ -251,9 +311,8 @@ struct tp_dispatch {
        struct {
                struct libinput_device_config_scroll_method config_method;
                enum libinput_config_scroll_method method;
-               int32_t right_edge;
-               int32_t bottom_edge;
-               enum tp_twofinger_scroll_state twofinger_state;
+               int32_t right_edge;             /* in device coordinates */
+               int32_t bottom_edge;            /* in device coordinates */
        } scroll;
 
        enum touchpad_event queued;
@@ -265,47 +324,129 @@ struct tp_dispatch {
                struct libinput_timer timer;
                enum tp_tap_state state;
                uint32_t buttons_pressed;
+               uint64_t first_press_time;
+
+               enum libinput_config_tap_button_map map;
+               enum libinput_config_tap_button_map want_map;
+
+               bool drag_enabled;
+               bool drag_lock_enabled;
        } tap;
 
        struct {
-               int32_t right_edge;
-               int32_t left_edge;
+               int32_t right_edge;             /* in device coordinates */
+               int32_t left_edge;              /* in device coordinates */
+
+               bool trackpoint_active;
+               struct libinput_event_listener trackpoint_listener;
+               struct libinput_timer trackpoint_timer;
+               uint64_t trackpoint_last_event_time;
+               uint32_t trackpoint_event_count;
+               bool monitor_trackpoint;
        } palm;
 
        struct {
                struct libinput_device_config_send_events config;
                enum libinput_config_send_events_mode current_mode;
-               bool trackpoint_active;
-               struct libinput_event_listener trackpoint_listener;
-               struct libinput_timer trackpoint_timer;
        } sendevents;
+
+       struct {
+               struct libinput_device_config_dwt config;
+               bool dwt_enabled;
+
+               bool keyboard_active;
+               struct libinput_event_listener keyboard_listener;
+               struct libinput_timer keyboard_timer;
+               struct evdev_device *keyboard;
+               unsigned long key_mask[NLONGS(KEY_CNT)];
+               unsigned long mod_mask[NLONGS(KEY_CNT)];
+
+               uint64_t keyboard_last_press_time;
+       } dwt;
+
+       struct {
+               bool detect_thumbs;
+               int threshold;
+               int upper_thumb_line;
+               int lower_thumb_line;
+       } thumb;
+
+       struct {
+               /* A quirk used on the T450 series Synaptics hardware.
+                * Slowly moving the finger causes multiple events with only
+                * ABS_MT_PRESSURE but no x/y information. When the x/y
+                * event comes, it will be a jump of ~20 units. We use the
+                * below to count non-motion events to discard that first
+                * event with the jump.
+                */
+               unsigned int nonmotion_event_count;
+       } quirks;
 };
 
 #define tp_for_each_touch(_tp, _t) \
        for (unsigned int _i = 0; _i < (_tp)->ntouches && (_t = &(_tp)->touches[_i]); _i++)
 
-void
-tp_get_delta(struct tp_touch *t, double *dx, double *dy);
+static inline struct libinput*
+tp_libinput_context(const struct tp_dispatch *tp)
+{
+       return evdev_libinput_context(tp->device);
+}
 
-void
-tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t);
+static inline struct normalized_coords
+tp_normalize_delta(struct tp_dispatch *tp, struct device_float_coords delta)
+{
+       struct normalized_coords normalized;
 
-void
+       normalized.x = delta.x * tp->accel.x_scale_coeff;
+       normalized.y = delta.y * tp->accel.y_scale_coeff;
+
+       return normalized;
+}
+
+/**
+ * Takes a dpi-normalized set of coordinates, returns a set of coordinates
+ * in the x-axis' coordinate space.
+ */
+static inline struct device_float_coords
+tp_unnormalize_for_xaxis(struct tp_dispatch *tp, struct normalized_coords delta)
+{
+       struct device_float_coords raw;
+
+       raw.x = delta.x / tp->accel.x_scale_coeff;
+       raw.y = delta.y / tp->accel.x_scale_coeff; /* <--- not a typo */
+
+       return raw;
+}
+
+struct normalized_coords
+tp_get_delta(struct tp_touch *t);
+
+struct normalized_coords
 tp_filter_motion(struct tp_dispatch *tp,
-                double *dx, double *dy,
-                double *dx_unaccel, double *dy_unaccel,
+                const struct normalized_coords *unaccelerated,
                 uint64_t time);
 
+struct normalized_coords
+tp_filter_motion_unaccelerated(struct tp_dispatch *tp,
+                              const struct normalized_coords *unaccelerated,
+                              uint64_t time);
+
+bool
+tp_touch_active(const struct tp_dispatch *tp, const struct tp_touch *t);
+
 int
 tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time);
 
-int
+void
+tp_tap_post_process_state(struct tp_dispatch *tp);
+
+void
 tp_init_tap(struct tp_dispatch *tp);
 
 void
 tp_remove_tap(struct tp_dispatch *tp);
 
-int
+void
 tp_init_buttons(struct tp_dispatch *tp, struct evdev_device *device);
 
 void
@@ -316,7 +457,7 @@ tp_init_top_softbuttons(struct tp_dispatch *tp,
 void
 tp_remove_buttons(struct tp_dispatch *tp);
 
-int
+void
 tp_process_button(struct tp_dispatch *tp,
                  const struct input_event *e,
                  uint64_t time);
@@ -328,14 +469,16 @@ tp_release_all_buttons(struct tp_dispatch *tp,
 int
 tp_post_button_events(struct tp_dispatch *tp, uint64_t time);
 
-int
+void
 tp_button_handle_state(struct tp_dispatch *tp, uint64_t time);
 
-int
-tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
+bool
+tp_button_touch_active(const struct tp_dispatch *tp,
+                      const struct tp_touch *t);
 
 bool
-tp_button_is_inside_softbutton_area(struct tp_dispatch *tp, struct tp_touch *t);
+tp_button_is_inside_softbutton_area(const struct tp_dispatch *tp,
+                                   const struct tp_touch *t);
 
 void
 tp_release_all_taps(struct tp_dispatch *tp,
@@ -348,9 +491,9 @@ void
 tp_tap_resume(struct tp_dispatch *tp, uint64_t time);
 
 bool
-tp_tap_dragging(struct tp_dispatch *tp);
+tp_tap_dragging(const struct tp_dispatch *tp);
 
-int
+void
 tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device);
 
 void
@@ -366,6 +509,37 @@ void
 tp_edge_scroll_stop_events(struct tp_dispatch *tp, uint64_t time);
 
 int
-tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
+tp_edge_scroll_touch_active(const struct tp_dispatch *tp,
+                           const struct tp_touch *t);
+
+uint32_t
+tp_touch_get_edge(const struct tp_dispatch *tp, const struct tp_touch *t);
+
+void
+tp_init_gesture(struct tp_dispatch *tp);
+
+void
+tp_remove_gesture(struct tp_dispatch *tp);
+
+void
+tp_gesture_stop(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_cancel(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_handle_state(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_post_events(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_gesture_stop_twofinger_scroll(struct tp_dispatch *tp, uint64_t time);
+
+bool
+tp_palm_tap_is_palm(const struct tp_dispatch *tp, const struct tp_touch *t);
+
+void
+tp_clickpad_middlebutton_apply_config(struct evdev_device *device);
 
 #endif
diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c
new file mode 100644 (file)
index 0000000..8b162a6
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright © 2016 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.
+ */
+
+#include "config.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "evdev-tablet-pad.h"
+
+struct pad_led_group {
+       struct libinput_tablet_pad_mode_group base;
+};
+
+static void
+pad_led_group_destroy(struct libinput_tablet_pad_mode_group *g)
+{
+       struct pad_led_group *group = (struct pad_led_group *)g;
+
+       free(group);
+}
+
+static struct pad_led_group *
+pad_group_new_basic(struct pad_dispatch *pad,
+                   unsigned int group_index,
+                   int nleds)
+{
+       struct pad_led_group *group;
+
+       group = zalloc(sizeof *group);
+       if (!group)
+               return NULL;
+
+       group->base.device = &pad->device->base;
+       group->base.refcount = 1;
+       group->base.index = group_index;
+       group->base.current_mode = 0;
+       group->base.num_modes = nleds;
+       group->base.destroy = pad_led_group_destroy;
+
+       return group;
+}
+
+static inline struct libinput_tablet_pad_mode_group *
+pad_get_mode_group(struct pad_dispatch *pad, unsigned int index)
+{
+       struct libinput_tablet_pad_mode_group *group;
+
+       list_for_each(group, &pad->modes.mode_group_list, link) {
+               if (group->index == index)
+                       return group;
+       }
+
+       return NULL;
+}
+
+static int
+pad_init_fallback_group(struct pad_dispatch *pad)
+{
+       struct pad_led_group *group;
+
+       group = pad_group_new_basic(pad, 0, 1);
+       if (!group)
+               return 1;
+
+       /* If we only have one group, all buttons/strips/rings are part of
+        * that group. We rely on the other layers to filter out invalid
+        * indices */
+       group->base.button_mask = -1;
+       group->base.strip_mask = -1;
+       group->base.ring_mask = -1;
+       group->base.toggle_button_mask = 0;
+
+       list_insert(&pad->modes.mode_group_list, &group->base.link);
+
+       return 0;
+}
+
+int
+pad_init_leds(struct pad_dispatch *pad,
+             struct evdev_device *device)
+{
+       int rc = 1;
+
+       list_init(&pad->modes.mode_group_list);
+
+       if (pad->nbuttons > 32) {
+               log_bug_libinput(device->base.seat->libinput,
+                                "Too many pad buttons for modes %d\n",
+                                pad->nbuttons);
+               return rc;
+       }
+
+       /* Eventually we slot the libwacom-based led detection in here. That
+        * requires getting the kernel ready first. For now we just init the
+        * fallback single-mode group.
+        */
+       rc = pad_init_fallback_group(pad);
+
+       return rc;
+}
+
+void
+pad_destroy_leds(struct pad_dispatch *pad)
+{
+       struct libinput_tablet_pad_mode_group *group, *tmpgrp;
+
+       list_for_each_safe(group, tmpgrp, &pad->modes.mode_group_list, link)
+               libinput_tablet_pad_mode_group_unref(group);
+}
+
+void
+pad_button_update_mode(struct libinput_tablet_pad_mode_group *g,
+                      unsigned int button_index,
+                      enum libinput_button_state state)
+{
+       struct pad_led_group *group = (struct pad_led_group*)g;
+
+       if (state != LIBINPUT_BUTTON_STATE_PRESSED)
+               return;
+
+       if (!libinput_tablet_pad_mode_group_button_is_toggle(g, button_index))
+               return;
+
+       log_bug_libinput(group->base.device->seat->libinput,
+                        "Button %d should not be a toggle button",
+                        button_index);
+}
+
+int
+evdev_device_tablet_pad_get_num_mode_groups(struct evdev_device *device)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
+       struct libinput_tablet_pad_mode_group *group;
+       int num_groups = 0;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+               return -1;
+
+       list_for_each(group, &pad->modes.mode_group_list, link)
+               num_groups++;
+
+       return num_groups;
+}
+
+struct libinput_tablet_pad_mode_group *
+evdev_device_tablet_pad_get_mode_group(struct evdev_device *device,
+                                      unsigned int index)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+               return NULL;
+
+       return pad_get_mode_group(pad, index);
+}
diff --git a/src/evdev-tablet-pad.c b/src/evdev-tablet-pad.c
new file mode 100644 (file)
index 0000000..82542bc
--- /dev/null
@@ -0,0 +1,689 @@
+/*
+ * Copyright © 2016 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.
+ */
+
+#include "config.h"
+#include "evdev-tablet-pad.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define pad_set_status(pad_,s_) (pad_)->status |= (s_)
+#define pad_unset_status(pad_,s_) (pad_)->status &= ~(s_)
+#define pad_has_status(pad_,s_) (!!((pad_)->status & (s_)))
+
+static void
+pad_get_buttons_pressed(struct pad_dispatch *pad,
+                       struct button_state *buttons)
+{
+       struct button_state *state = &pad->button_state;
+       struct button_state *prev_state = &pad->prev_button_state;
+       unsigned int i;
+
+       for (i = 0; i < sizeof(buttons->bits); i++)
+               buttons->bits[i] = state->bits[i] & ~(prev_state->bits[i]);
+}
+
+static void
+pad_get_buttons_released(struct pad_dispatch *pad,
+                        struct button_state *buttons)
+{
+       struct button_state *state = &pad->button_state;
+       struct button_state *prev_state = &pad->prev_button_state;
+       unsigned int i;
+
+       for (i = 0; i < sizeof(buttons->bits); i++)
+               buttons->bits[i] = prev_state->bits[i] & ~(state->bits[i]);
+}
+
+static inline bool
+pad_button_is_down(const struct pad_dispatch *pad,
+                  uint32_t button)
+{
+       return bit_is_set(pad->button_state.bits, button);
+}
+
+static inline bool
+pad_any_button_down(const struct pad_dispatch *pad)
+{
+       const struct button_state *state = &pad->button_state;
+       unsigned int i;
+
+       for (i = 0; i < sizeof(state->bits); i++)
+               if (state->bits[i] != 0)
+                       return true;
+
+       return false;
+}
+
+static inline void
+pad_button_set_down(struct pad_dispatch *pad,
+                   uint32_t button,
+                   bool is_down)
+{
+       struct button_state *state = &pad->button_state;
+
+       if (is_down) {
+               set_bit(state->bits, button);
+               pad_set_status(pad, PAD_BUTTONS_PRESSED);
+       } else {
+               clear_bit(state->bits, button);
+               pad_set_status(pad, PAD_BUTTONS_RELEASED);
+       }
+}
+
+static void
+pad_process_absolute(struct pad_dispatch *pad,
+                    struct evdev_device *device,
+                    struct input_event *e,
+                    uint64_t time)
+{
+       switch (e->code) {
+       case ABS_WHEEL:
+               pad->changed_axes |= PAD_AXIS_RING1;
+               pad_set_status(pad, PAD_AXES_UPDATED);
+               break;
+       case ABS_THROTTLE:
+               pad->changed_axes |= PAD_AXIS_RING2;
+               pad_set_status(pad, PAD_AXES_UPDATED);
+               break;
+       case ABS_RX:
+               pad->changed_axes |= PAD_AXIS_STRIP1;
+               pad_set_status(pad, PAD_AXES_UPDATED);
+               break;
+       case ABS_RY:
+               pad->changed_axes |= PAD_AXIS_STRIP2;
+               pad_set_status(pad, PAD_AXES_UPDATED);
+               break;
+       case ABS_MISC:
+               /* The wacom driver always sends a 0 axis event on finger
+                  up, but we also get an ABS_MISC 15 on touch down and
+                  ABS_MISC 0 on touch up, on top of the actual event. This
+                  is kernel behavior for xf86-input-wacom backwards
+                  compatibility after the 3.17 wacom HID move.
+
+                  We use that event to tell when we truly went a full
+                  rotation around the wheel vs. a finger release.
+
+                  FIXME: On the Intuos5 and later the kernel merges all
+                  states into that event, so if any finger is down on any
+                  button, the wheel release won't trigger the ABS_MISC 0
+                  but still send a 0 event. We can't currently detect this.
+                */
+               pad->have_abs_misc_terminator = true;
+               break;
+       default:
+               log_info(pad_libinput_context(pad),
+                        "Unhandled EV_ABS event code %#x\n", e->code);
+               break;
+       }
+}
+
+static inline double
+normalize_ring(const struct input_absinfo *absinfo)
+{
+       /* libinput has 0 as the ring's northernmost point in the device's
+          current logical rotation, increasing clockwise to 1. Wacom has
+          0 on the left-most wheel position.
+        */
+       double range = absinfo->maximum - absinfo->minimum + 1;
+       double value = (absinfo->value - absinfo->minimum) / range - 0.25;
+
+       if (value < 0.0)
+               value += 1.0;
+
+       return value;
+}
+
+static inline double
+normalize_strip(const struct input_absinfo *absinfo)
+{
+       /* strip axes don't use a proper value, they just shift the bit left
+        * for each position. 0 isn't a real value either, it's only sent on
+        * finger release */
+       double min = 0,
+              max = log2(absinfo->maximum);
+       double range = max - min;
+       double value = (log2(absinfo->value) - min) / range;
+
+       return value;
+}
+
+static inline double
+pad_handle_ring(struct pad_dispatch *pad,
+               struct evdev_device *device,
+               unsigned int code)
+{
+       const struct input_absinfo *absinfo;
+       double degrees;
+
+       absinfo = libevdev_get_abs_info(device->evdev, code);
+       assert(absinfo);
+
+       degrees = normalize_ring(absinfo) * 360;
+
+       if (device->left_handed.enabled)
+               degrees = fmod(degrees + 180, 360);
+
+       return degrees;
+}
+
+static inline double
+pad_handle_strip(struct pad_dispatch *pad,
+                struct evdev_device *device,
+                unsigned int code)
+{
+       const struct input_absinfo *absinfo;
+       double pos;
+
+       absinfo = libevdev_get_abs_info(device->evdev, code);
+       assert(absinfo);
+
+       if (absinfo->value == 0)
+               return 0.0;
+
+       pos = normalize_strip(absinfo);
+
+       if (device->left_handed.enabled)
+               pos = 1.0 - pos;
+
+       return pos;
+}
+
+static inline struct libinput_tablet_pad_mode_group *
+pad_ring_get_mode_group(struct pad_dispatch *pad,
+                       unsigned int ring)
+{
+       struct libinput_tablet_pad_mode_group *group;
+
+       list_for_each(group, &pad->modes.mode_group_list, link) {
+               if (libinput_tablet_pad_mode_group_has_ring(group, ring))
+                       return group;
+       }
+
+       assert(!"Unable to find ring mode group");
+
+       return NULL;
+}
+
+static inline struct libinput_tablet_pad_mode_group *
+pad_strip_get_mode_group(struct pad_dispatch *pad,
+                       unsigned int strip)
+{
+       struct libinput_tablet_pad_mode_group *group;
+
+       list_for_each(group, &pad->modes.mode_group_list, link) {
+               if (libinput_tablet_pad_mode_group_has_strip(group, strip))
+                       return group;
+       }
+
+       assert(!"Unable to find strip mode group");
+
+       return NULL;
+}
+
+static void
+pad_check_notify_axes(struct pad_dispatch *pad,
+                     struct evdev_device *device,
+                     uint64_t time)
+{
+       struct libinput_device *base = &device->base;
+       struct libinput_tablet_pad_mode_group *group;
+       double value;
+       bool send_finger_up = false;
+
+       /* Suppress the reset to 0 on finger up. See the
+          comment in pad_process_absolute */
+       if (pad->have_abs_misc_terminator &&
+           libevdev_get_event_value(device->evdev, EV_ABS, ABS_MISC) == 0)
+               send_finger_up = true;
+
+       if (pad->changed_axes & PAD_AXIS_RING1) {
+               value = pad_handle_ring(pad, device, ABS_WHEEL);
+               if (send_finger_up)
+                       value = -1.0;
+
+               group = pad_ring_get_mode_group(pad, 0);
+               tablet_pad_notify_ring(base,
+                                      time,
+                                      0,
+                                      value,
+                                      LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
+                                      group);
+       }
+
+       if (pad->changed_axes & PAD_AXIS_RING2) {
+               value = pad_handle_ring(pad, device, ABS_THROTTLE);
+               if (send_finger_up)
+                       value = -1.0;
+
+               group = pad_ring_get_mode_group(pad, 1);
+               tablet_pad_notify_ring(base,
+                                      time,
+                                      1,
+                                      value,
+                                      LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
+                                      group);
+       }
+
+       if (pad->changed_axes & PAD_AXIS_STRIP1) {
+               value = pad_handle_strip(pad, device, ABS_RX);
+               if (send_finger_up)
+                       value = -1.0;
+
+               group = pad_strip_get_mode_group(pad, 0);
+               tablet_pad_notify_strip(base,
+                                       time,
+                                       0,
+                                       value,
+                                       LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
+                                       group);
+       }
+
+       if (pad->changed_axes & PAD_AXIS_STRIP2) {
+               value = pad_handle_strip(pad, device, ABS_RY);
+               if (send_finger_up)
+                       value = -1.0;
+
+               group = pad_strip_get_mode_group(pad, 1);
+               tablet_pad_notify_strip(base,
+                                       time,
+                                       1,
+                                       value,
+                                       LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
+                                       group);
+       }
+
+       pad->changed_axes = PAD_AXIS_NONE;
+       pad->have_abs_misc_terminator = false;
+}
+
+static void
+pad_process_key(struct pad_dispatch *pad,
+               struct evdev_device *device,
+               struct input_event *e,
+               uint64_t time)
+{
+       uint32_t button = e->code;
+       uint32_t is_press = e->value != 0;
+
+       pad_button_set_down(pad, button, is_press);
+}
+
+static inline struct libinput_tablet_pad_mode_group *
+pad_button_get_mode_group(struct pad_dispatch *pad,
+                         unsigned int button)
+{
+       struct libinput_tablet_pad_mode_group *group;
+
+       list_for_each(group, &pad->modes.mode_group_list, link) {
+               if (libinput_tablet_pad_mode_group_has_button(group, button))
+                       return group;
+       }
+
+       assert(!"Unable to find button mode group\n");
+
+       return NULL;
+}
+
+static void
+pad_notify_button_mask(struct pad_dispatch *pad,
+                      struct evdev_device *device,
+                      uint64_t time,
+                      const struct button_state *buttons,
+                      enum libinput_button_state state)
+{
+       struct libinput_device *base = &device->base;
+       struct libinput_tablet_pad_mode_group *group;
+       int32_t code;
+       unsigned int i;
+
+       for (i = 0; i < sizeof(buttons->bits); i++) {
+               unsigned char buttons_slice = buttons->bits[i];
+
+               code = i * 8;
+               while (buttons_slice) {
+                       int enabled;
+                       char map;
+
+                       code++;
+                       enabled = (buttons_slice & 1);
+                       buttons_slice >>= 1;
+
+                       if (!enabled)
+                               continue;
+
+                       map = pad->button_map[code - 1];
+                       if (map != -1) {
+                               group = pad_button_get_mode_group(pad, map);
+                               pad_button_update_mode(group, map, state);
+                               tablet_pad_notify_button(base, time, map, state, group);
+                       }
+               }
+       }
+}
+
+static void
+pad_notify_buttons(struct pad_dispatch *pad,
+                  struct evdev_device *device,
+                  uint64_t time,
+                  enum libinput_button_state state)
+{
+       struct button_state buttons;
+
+       if (state == LIBINPUT_BUTTON_STATE_PRESSED)
+               pad_get_buttons_pressed(pad, &buttons);
+       else
+               pad_get_buttons_released(pad, &buttons);
+
+       pad_notify_button_mask(pad, device, time, &buttons, state);
+}
+
+static void
+pad_change_to_left_handed(struct evdev_device *device)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
+
+       if (device->left_handed.enabled == device->left_handed.want_enabled)
+               return;
+
+       if (pad_any_button_down(pad))
+               return;
+
+       device->left_handed.enabled = device->left_handed.want_enabled;
+}
+
+static void
+pad_flush(struct pad_dispatch *pad,
+         struct evdev_device *device,
+         uint64_t time)
+{
+       if (pad_has_status(pad, PAD_AXES_UPDATED)) {
+               pad_check_notify_axes(pad, device, time);
+               pad_unset_status(pad, PAD_AXES_UPDATED);
+       }
+
+       if (pad_has_status(pad, PAD_BUTTONS_RELEASED)) {
+               pad_notify_buttons(pad,
+                                  device,
+                                  time,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+               pad_unset_status(pad, PAD_BUTTONS_RELEASED);
+
+               pad_change_to_left_handed(device);
+       }
+
+       if (pad_has_status(pad, PAD_BUTTONS_PRESSED)) {
+               pad_notify_buttons(pad,
+                                  device,
+                                  time,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+               pad_unset_status(pad, PAD_BUTTONS_PRESSED);
+       }
+
+       /* Update state */
+       memcpy(&pad->prev_button_state,
+              &pad->button_state,
+              sizeof(pad->button_state));
+}
+
+static void
+pad_process(struct evdev_dispatch *dispatch,
+           struct evdev_device *device,
+           struct input_event *e,
+           uint64_t time)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch *)dispatch;
+
+       switch (e->type) {
+       case EV_ABS:
+               pad_process_absolute(pad, device, e, time);
+               break;
+       case EV_KEY:
+               pad_process_key(pad, device, e, time);
+               break;
+       case EV_SYN:
+               pad_flush(pad, device, time);
+               break;
+       case EV_MSC:
+               /* The EKR sends the serial as MSC_SERIAL, ignore this for
+                * now */
+               break;
+       default:
+               log_error(pad_libinput_context(pad),
+                         "Unexpected event type %s (%#x)\n",
+                         libevdev_event_type_get_name(e->type),
+                         e->type);
+               break;
+       }
+}
+
+static void
+pad_suspend(struct evdev_dispatch *dispatch,
+           struct evdev_device *device)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch *)dispatch;
+       struct libinput *libinput = pad_libinput_context(pad);
+       unsigned int code;
+
+       for (code = KEY_ESC; code < KEY_CNT; code++) {
+               if (pad_button_is_down(pad, code))
+                       pad_button_set_down(pad, code, false);
+       }
+
+       pad_flush(pad, device, libinput_now(libinput));
+}
+
+static void
+pad_destroy(struct evdev_dispatch *dispatch)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch*)dispatch;
+
+       pad_destroy_leds(pad);
+       free(pad);
+}
+
+static struct evdev_dispatch_interface pad_interface = {
+       pad_process,
+       pad_suspend, /* suspend */
+       NULL, /* remove */
+       pad_destroy,
+       NULL, /* device_added */
+       NULL, /* device_removed */
+       NULL, /* device_suspended */
+       NULL, /* device_resumed */
+       NULL, /* post_added */
+       NULL, /* toggle_touch */
+};
+
+static void
+pad_init_buttons(struct pad_dispatch *pad,
+                struct evdev_device *device)
+{
+       unsigned int code;
+       size_t i;
+       int map = 0;
+
+       for (i = 0; i < ARRAY_LENGTH(pad->button_map); i++)
+               pad->button_map[i] = -1;
+
+       /* we match wacom_report_numbered_buttons() from the kernel */
+       for (code = BTN_0; code < BTN_0 + 10; code++) {
+               if (libevdev_has_event_code(device->evdev, EV_KEY, code))
+                       pad->button_map[code] = map++;
+       }
+
+       for (code = BTN_BASE; code < BTN_BASE + 2; code++) {
+               if (libevdev_has_event_code(device->evdev, EV_KEY, code))
+                       pad->button_map[code] = map++;
+       }
+
+       for (code = BTN_A; code < BTN_A + 6; code++) {
+               if (libevdev_has_event_code(device->evdev, EV_KEY, code))
+                       pad->button_map[code] = map++;
+       }
+
+       pad->nbuttons = map;
+}
+
+static void
+pad_init_left_handed(struct evdev_device *device)
+{
+       if (evdev_tablet_has_left_handed(device))
+               evdev_init_left_handed(device,
+                                      pad_change_to_left_handed);
+}
+
+static int
+pad_init(struct pad_dispatch *pad, struct evdev_device *device)
+{
+       pad->base.interface = &pad_interface;
+       pad->device = device;
+       pad->status = PAD_NONE;
+       pad->changed_axes = PAD_AXIS_NONE;
+
+       pad_init_buttons(pad, device);
+       pad_init_left_handed(device);
+       if (pad_init_leds(pad, device) != 0)
+               return 1;
+
+       return 0;
+}
+
+static uint32_t
+pad_sendevents_get_modes(struct libinput_device *device)
+{
+       return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
+}
+
+static enum libinput_config_status
+pad_sendevents_set_mode(struct libinput_device *device,
+                       enum libinput_config_send_events_mode mode)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+       struct pad_dispatch *pad = (struct pad_dispatch*)evdev->dispatch;
+
+       if (mode == pad->sendevents.current_mode)
+               return LIBINPUT_CONFIG_STATUS_SUCCESS;
+
+       switch(mode) {
+       case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED:
+               break;
+       case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED:
+               pad_suspend(evdev->dispatch, evdev);
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+       }
+
+       pad->sendevents.current_mode = mode;
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_send_events_mode
+pad_sendevents_get_mode(struct libinput_device *device)
+{
+       struct evdev_device *evdev = (struct evdev_device*)device;
+       struct pad_dispatch *dispatch = (struct pad_dispatch*)evdev->dispatch;
+
+       return dispatch->sendevents.current_mode;
+}
+
+static enum libinput_config_send_events_mode
+pad_sendevents_get_default_mode(struct libinput_device *device)
+{
+       return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
+}
+
+struct evdev_dispatch *
+evdev_tablet_pad_create(struct evdev_device *device)
+{
+       struct pad_dispatch *pad;
+
+       pad = zalloc(sizeof *pad);
+       if (!pad)
+               return NULL;
+
+       if (pad_init(pad, device) != 0) {
+               pad_destroy(&pad->base);
+               return NULL;
+       }
+
+       device->base.config.sendevents = &pad->sendevents.config;
+       pad->sendevents.current_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
+       pad->sendevents.config.get_modes = pad_sendevents_get_modes;
+       pad->sendevents.config.set_mode = pad_sendevents_set_mode;
+       pad->sendevents.config.get_mode = pad_sendevents_get_mode;
+       pad->sendevents.config.get_default_mode = pad_sendevents_get_default_mode;
+
+       return &pad->base;
+}
+
+int
+evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device)
+{
+       struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+               return -1;
+
+       return pad->nbuttons;
+}
+
+int
+evdev_device_tablet_pad_get_num_rings(struct evdev_device *device)
+{
+       int nrings = 0;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+               return -1;
+
+       if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_WHEEL)) {
+               nrings++;
+               if (libevdev_has_event_code(device->evdev,
+                                           EV_ABS,
+                                           ABS_THROTTLE))
+                       nrings++;
+       }
+
+       return nrings;
+}
+
+int
+evdev_device_tablet_pad_get_num_strips(struct evdev_device *device)
+{
+       int nstrips = 0;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+               return -1;
+
+       if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_RX)) {
+               nstrips++;
+               if (libevdev_has_event_code(device->evdev,
+                                           EV_ABS,
+                                           ABS_RY))
+                       nstrips++;
+       }
+
+       return nstrips;
+}
diff --git a/src/evdev-tablet-pad.h b/src/evdev-tablet-pad.h
new file mode 100644 (file)
index 0000000..9002fca
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#ifndef EVDEV_BUTTONSET_WACOM_H
+#define EVDEV_BUTTONSET_WACOM_H
+
+#include "evdev.h"
+
+#define LIBINPUT_BUTTONSET_AXIS_NONE 0
+
+enum pad_status {
+       PAD_NONE = 0,
+       PAD_AXES_UPDATED = 1 << 0,
+       PAD_BUTTONS_PRESSED = 1 << 1,
+       PAD_BUTTONS_RELEASED = 1 << 2,
+};
+
+enum pad_axes {
+       PAD_AXIS_NONE = 0,
+       PAD_AXIS_RING1 = 1 << 0,
+       PAD_AXIS_RING2 = 1 << 1,
+       PAD_AXIS_STRIP1 = 1 << 2,
+       PAD_AXIS_STRIP2 = 1 << 3,
+};
+
+struct button_state {
+       unsigned char bits[NCHARS(KEY_CNT)];
+};
+
+struct pad_dispatch {
+       struct evdev_dispatch base;
+       struct evdev_device *device;
+       unsigned char status;
+       uint32_t changed_axes;
+
+       struct button_state button_state;
+       struct button_state prev_button_state;
+
+       char button_map[KEY_CNT];
+       unsigned int nbuttons;
+
+       bool have_abs_misc_terminator;
+
+       struct {
+               struct libinput_device_config_send_events config;
+               enum libinput_config_send_events_mode current_mode;
+       } sendevents;
+
+       struct {
+               struct list mode_group_list;
+       } modes;
+};
+
+static inline struct libinput *
+pad_libinput_context(const struct pad_dispatch *pad)
+{
+       return evdev_libinput_context(pad->device);
+}
+
+int
+pad_init_leds(struct pad_dispatch *pad, struct evdev_device *device);
+void
+pad_destroy_leds(struct pad_dispatch *pad);
+void
+pad_button_update_mode(struct libinput_tablet_pad_mode_group *g,
+                      unsigned int pressed_button,
+                      enum libinput_button_state state);
+#endif
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
new file mode 100644 (file)
index 0000000..14023b6
--- /dev/null
@@ -0,0 +1,1786 @@
+/*
+ * 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,
+                          struct button_state *buttons)
+{
+       size_t i;
+       const struct button_state *state = &tablet->button_state,
+                                 *prev_state = &tablet->prev_button_state;
+
+       for (i = 0; i < sizeof(buttons->bits); i++)
+               buttons->bits[i] = state->bits[i] & ~(prev_state->bits[i]);
+}
+
+static inline void
+tablet_get_released_buttons(struct tablet_dispatch *tablet,
+                           struct button_state *buttons)
+{
+       size_t i;
+       const struct button_state *state = &tablet->button_state,
+                                 *prev_state = &tablet->prev_button_state;
+
+       for (i = 0; i < sizeof(buttons->bits); i++)
+               buttons->bits[i] = prev_state->bits[i] &
+                                       ~(state->bits[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->bits); i++) {
+               state->bits[i] = state->bits[i] | prev_state->bits[i];
+               prev_state->bits[i] = 0;
+       }
+}
+
+static bool
+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 inline bool
+tablet_filter_axis_fuzz(const struct tablet_dispatch *tablet,
+                       const struct evdev_device *device,
+                       const struct input_event *e,
+                       enum libinput_tablet_tool_axis axis)
+{
+       int delta, fuzz;
+       int current, previous;
+
+       previous = tablet->prev_value[axis];
+       current = e->value;
+       delta = previous - current;
+
+       fuzz = libevdev_get_abs_fuzz(device->evdev, e->code);
+
+       /* ABS_DISTANCE doesn't have have fuzz set and causes continuous
+        * updates for the cursor/lens tools. Add a minimum fuzz of 2, same
+        * as the xf86-input-wacom driver
+        */
+       switch (e->code) {
+       case ABS_DISTANCE:
+               fuzz = max(2, fuzz);
+               break;
+       default:
+               break;
+       }
+
+       return abs(delta) <= fuzz;
+}
+
+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(tablet_libinput_context(tablet),
+                                        "Invalid ABS event code %#x\n",
+                                        e->code);
+                       break;
+               }
+
+               tablet->prev_value[axis] = tablet->current_value[axis];
+               if (tablet_filter_axis_fuzz(tablet, device, e, axis))
+                       break;
+
+               tablet->current_value[axis] = e->value;
+               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(tablet_libinput_context(tablet),
+                        "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_slider(const struct input_absinfo *absinfo)
+{
+       double range = absinfo->maximum - absinfo->minimum;
+       double value = (absinfo->value - absinfo->minimum) / range;
+
+       return value * 2 - 1;
+}
+
+static inline double
+normalize_distance(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
+adjust_tilt(const struct input_absinfo *absinfo)
+{
+       double range = absinfo->maximum - absinfo->minimum;
+       double value = (absinfo->value - absinfo->minimum) / range;
+       const int WACOM_MAX_DEGREES = 64;
+
+       /* If resolution is nonzero, it's in units/radian. But require
+        * a min/max less/greater than zero so we can assume 0 is the
+        * center */
+       if (absinfo->resolution != 0 &&
+           absinfo->maximum > 0 &&
+           absinfo->minimum < 0) {
+               value = 180.0/M_PI * absinfo->value/absinfo->resolution;
+       } else {
+               /* Wacom supports physical [-64, 64] degrees, so map to that by
+                * default. If other tablets have a different physical range or
+                * nonzero physical offsets, they need extra treatment
+                * here.
+                */
+               /* Map to the (-1, 1) range */
+               value = (value * 2) - 1;
+               value *= WACOM_MAX_DEGREES;
+       }
+
+       return value;
+}
+
+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;
+
+       /* 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.x;
+}
+
+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
+tool_process_delta(struct libinput_tablet_tool *tool,
+                  const struct evdev_device *device,
+                  const struct device_coords *delta,
+                  uint64_t time)
+{
+       struct normalized_coords accel;
+
+       accel.x = 1.0 * delta->x;
+       accel.y = 1.0 * delta->y;
+
+       if (normalized_is_zero(accel))
+               return accel;
+
+       return filter_dispatch(device->pointer.filter,
+                              &accel,
+                              tool,
+                              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_distance(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_slider(absinfo);
+       }
+
+       return tablet->axes.slider;
+}
+
+static inline struct tilt_degrees
+tablet_handle_tilt(struct tablet_dispatch *tablet,
+                  struct evdev_device *device)
+{
+       struct tilt_degrees 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 = adjust_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 = adjust_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 = tool_process_delta(tool, 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);
+               clear_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_TILT_X);
+               clear_bit(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_TILT_Y);
+               axes.tilt.x = 0;
+               axes.tilt.y = 0;
+
+               /* tilt is already converted to left-handed, so mouse
+                * rotation is converted to left-handed automatically */
+       } else {
+               axes.rotation = tablet_handle_artpen_rotation(tablet, device);
+               if (device->left_handed.enabled)
+                       axes.rotation = fmod(180 + axes.rotation, 360);
+       }
+
+       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_libinput_context(tablet),
+                        "Unhandled button %s (%#x)\n",
+                        libevdev_event_code_get_name(EV_KEY, evcode), evcode);
+               return;
+       }
+
+       if (enable) {
+               set_bit(tablet->button_state.bits, evcode);
+               tablet_set_status(tablet, TABLET_BUTTONS_PRESSED);
+       } else {
+               clear_bit(tablet->button_state.bits, 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(tablet_libinput_context(tablet),
+                                "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(tablet_libinput_context(tablet),
+                                        "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_libinput_context(tablet),
+                        "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(tablet_libinput_context(tablet),
+                        "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_libinput_context(tablet);
+       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 *libinput = tablet_libinput_context(tablet);
+       struct libinput_tablet_tool *tool = NULL, *t;
+       struct list *tool_list;
+
+       if (serial) {
+               tool_list = &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;
+                       }
+               }
+       }
+
+       /* If we get a tool with a delayed serial number, we already created
+        * a 0-serial number tool for it earlier. Re-use that, even though
+        * it means we can't distinguish this tool from others.
+        * https://bugs.freedesktop.org/show_bug.cgi?id=97526
+        */
+       if (!tool) {
+               tool_list = &tablet->tool_list;
+               /* 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
+                * 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;
+                       }
+               }
+
+               /* Didn't find the tool but we have a serial. Switch
+                * tool_list back so we create in the correct list */
+               if (!tool && serial)
+                       tool_list = &libinput->tool_list;
+       }
+
+       /* 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 struct button_state *buttons,
+                         enum libinput_button_state state)
+{
+       struct libinput_device *base = &device->base;
+       size_t i;
+       size_t nbits = 8 * sizeof(buttons->bits);
+       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->bits, 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)
+{
+       struct button_state buttons;
+
+       if (state == LIBINPUT_BUTTON_STATE_PRESSED)
+               tablet_get_pressed_buttons(tablet, &buttons);
+       else
+               tablet_get_released_buttons(tablet, &buttons);
+
+       tablet_notify_button_mask(tablet,
+                                 device,
+                                 time,
+                                 tool,
+                                 &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(tablet_libinput_context(tablet),
+                        "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(tablet_libinput_context(tablet),
+                "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(tablet_libinput_context(tablet),
+                                "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(tablet_libinput_context(tablet),
+                                "Invalid status: leaving contact\n");
+
+       p = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE);
+       if (!p) {
+               log_bug_libinput(tablet_libinput_context(tablet),
+                                "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.bits,
+                      0,
+                      sizeof(tablet->button_state.bits));
+               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_set_touch_device_enabled(struct evdev_device *touch_device,
+                               bool enable)
+{
+       struct evdev_dispatch *dispatch;
+
+       if (touch_device == NULL)
+               return;
+
+       dispatch = touch_device->dispatch;
+       if (dispatch->interface->toggle_touch)
+               dispatch->interface->toggle_touch(dispatch,
+                                                 touch_device,
+                                                 enable);
+}
+
+static inline void
+tablet_toggle_touch_device(struct tablet_dispatch *tablet,
+                          struct evdev_device *tablet_device)
+{
+       bool enable_events;
+
+       enable_events = tablet_has_status(tablet,
+                                         TABLET_TOOL_OUT_OF_RANGE) ||
+                       tablet_has_status(tablet, TABLET_NONE) ||
+                       tablet_has_status(tablet,
+                                         TABLET_TOOL_LEAVING_PROXIMITY) ||
+                       tablet_has_status(tablet,
+                                         TABLET_TOOL_OUT_OF_PROXIMITY);
+
+       tablet_set_touch_device_enabled(tablet->touch_device, enable_events);
+}
+
+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_toggle_touch_device(tablet, device);
+               tablet_reset_state(tablet);
+               break;
+       default:
+               log_error(tablet_libinput_context(tablet),
+                         "Unexpected event type %s (%#x)\n",
+                         libevdev_event_type_get_name(e->type),
+                         e->type);
+               break;
+       }
+}
+
+static void
+tablet_suspend(struct evdev_dispatch *dispatch,
+              struct evdev_device *device)
+{
+       struct tablet_dispatch *tablet =
+               (struct tablet_dispatch *)dispatch;
+
+       tablet_set_touch_device_enabled(tablet->touch_device, true);
+}
+
+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_device_added(struct evdev_device *device,
+                   struct evdev_device *added_device)
+{
+       struct tablet_dispatch *tablet =
+               (struct tablet_dispatch*)device->dispatch;
+
+       if (libinput_device_get_device_group(&device->base) !=
+           libinput_device_get_device_group(&added_device->base))
+               return;
+
+       /* Touch screens or external touchpads only */
+       if (evdev_device_has_capability(added_device, LIBINPUT_DEVICE_CAP_TOUCH) ||
+           (evdev_device_has_capability(added_device, LIBINPUT_DEVICE_CAP_POINTER) &&
+            (added_device->tags & EVDEV_TAG_EXTERNAL_TOUCHPAD)))
+           tablet->touch_device = added_device;
+}
+
+static void
+tablet_device_removed(struct evdev_device *device,
+                     struct evdev_device *removed_device)
+{
+       struct tablet_dispatch *tablet =
+               (struct tablet_dispatch*)device->dispatch;
+
+       if (tablet->touch_device == removed_device)
+               tablet->touch_device = NULL;
+}
+
+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);
+
+       /* we can't fetch MSC_SERIAL from the kernel, so we set the serial
+        * to 0 for now. On the first real event from the device we get the
+        * serial (if any) and that event will be converted into a proximity
+        * event */
+       tablet->current_tool_serial = 0;
+}
+
+static struct evdev_dispatch_interface tablet_interface = {
+       tablet_process,
+       tablet_suspend,
+       NULL, /* remove */
+       tablet_destroy,
+       tablet_device_added,
+       tablet_device_removed,
+       NULL, /* device_suspended */
+       NULL, /* device_resumed */
+       tablet_check_initial_proximity,
+       NULL, /* toggle_touch */
+};
+
+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->calibration);
+}
+
+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;
+
+       x = device->abs.absinfo_x;
+       y = device->abs.absinfo_y;
+
+       filter = create_pointer_accelerator_filter_tablet(x->resolution,
+                                                         y->resolution);
+       if (!filter)
+               return -1;
+
+       evdev_device_init_pointer_acceleration(device, filter);
+
+       /* 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 void
+tablet_init_left_handed(struct evdev_device *device)
+{
+       if (evdev_tablet_has_left_handed(device))
+                   evdev_init_left_handed(device,
+                                          tablet_change_to_left_handed);
+}
+
+static int
+tablet_reject_device(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+       int rc = -1;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
+               goto out;
+
+       if (!libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_PEN))
+               goto out;
+
+       rc = 0;
+
+out:
+       if (rc) {
+               log_bug_libinput(evdev_libinput_context(device),
+                                "Device '%s' does not meet tablet criteria. "
+                                "Ignoring this device.\n",
+                                device->devname);
+       }
+       return rc;
+}
+
+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);
+
+       if (tablet_reject_device(device))
+               return -1;
+
+       tablet_init_calibration(tablet, device);
+       tablet_init_proximity_threshold(tablet, device);
+       rc = tablet_init_accel(tablet, device);
+       if (rc != 0)
+               return rc;
+
+       tablet_init_left_handed(device);
+
+       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;
+}
+
+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;
+       }
+
+       return &tablet->base;
+}
diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h
new file mode 100644 (file)
index 0000000..2279e03
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * 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 bits[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)];
+       int current_value[LIBINPUT_TABLET_TOOL_AXIS_MAX + 1];
+       int prev_value[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;
+
+       struct libinput_device_config_calibration calibration;
+
+       /* The paired touch device on devices with both pen & touch */
+       struct evdev_device *touch_device;
+};
+
+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;
+}
+
+static inline struct libinput *
+tablet_libinput_context(const struct tablet_dispatch *tablet)
+{
+       return evdev_libinput_context(tablet->device);
+}
+
+#endif
index 951b76827adaa108814203b688571cfc001e45a9..f7f72305954917990681989d4d00e5d85b624a7c 100644 (file)
@@ -1,24 +1,26 @@
 /*
  * Copyright © 2010 Intel Corporation
  * Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
@@ -27,6 +29,7 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include "linux/input.h"
 #include <unistd.h>
 #include <fcntl.h>
 #include "evdev.h"
 #include "filter.h"
 #include "libinput-private.h"
-#include "stdio.h"
+
+#if HAVE_LIBWACOM
+#include <libwacom/libwacom.h>
+#endif
 
 #define DEFAULT_WHEEL_CLICK_ANGLE 15
-#define DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT 200
-#define DEFAULT_TOUCH_PRESSURE 1.0
-#define DEFAULT_TOUCH_ORIENTATION 0.0
-#define DEFAULT_TOUCH_MAJOR 0.0
-#define DEFAULT_TOUCH_MINOR 0.0
+#define DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT ms2us(200)
 
 enum evdev_key_type {
        EVDEV_KEY_TYPE_NONE,
@@ -63,6 +65,9 @@ enum evdev_device_udev_tags {
         EVDEV_UDEV_TAG_TABLET = (1 << 5),
         EVDEV_UDEV_TAG_JOYSTICK = (1 << 6),
         EVDEV_UDEV_TAG_ACCELEROMETER = (1 << 7),
+        EVDEV_UDEV_TAG_TABLET_PAD = (1 << 8),
+        EVDEV_UDEV_TAG_POINTINGSTICK = (1 << 9),
+        EVDEV_UDEV_TAG_TRACKBALL = (1 << 10),
 };
 
 struct evdev_udev_tag_match {
@@ -78,23 +83,26 @@ static const struct evdev_udev_tag_match evdev_udev_tag_matches[] = {
        {"ID_INPUT_TOUCHPAD",           EVDEV_UDEV_TAG_TOUCHPAD},
        {"ID_INPUT_TOUCHSCREEN",        EVDEV_UDEV_TAG_TOUCHSCREEN},
        {"ID_INPUT_TABLET",             EVDEV_UDEV_TAG_TABLET},
+       {"ID_INPUT_TABLET_PAD",         EVDEV_UDEV_TAG_TABLET_PAD},
        {"ID_INPUT_JOYSTICK",           EVDEV_UDEV_TAG_JOYSTICK},
        {"ID_INPUT_ACCELEROMETER",      EVDEV_UDEV_TAG_ACCELEROMETER},
+       {"ID_INPUT_POINTINGSTICK",      EVDEV_UDEV_TAG_POINTINGSTICK},
+       {"ID_INPUT_TRACKBALL",          EVDEV_UDEV_TAG_TRACKBALL},
 
        /* sentinel value */
        { 0 },
 };
 
 static void
-hw_set_key_down(struct evdev_device *device, int code, int pressed)
+hw_set_key_down(struct fallback_dispatch *dispatch, int code, int pressed)
 {
-       long_set_bit_state(device->hw_key_mask, code, pressed);
+       long_set_bit_state(dispatch->hw_key_mask, code, pressed);
 }
 
-static int
-hw_is_key_down(struct evdev_device *device, int code)
+static bool
+hw_is_key_down(struct fallback_dispatch *dispatch, int code)
 {
-       return long_bit_is_set(device->hw_key_mask, code);
+       return long_bit_is_set(dispatch->hw_key_mask, code);
 }
 
 static int
@@ -117,7 +125,7 @@ update_key_down_count(struct evdev_device *device, int code, int pressed)
        }
 
        if (key_count > 32) {
-               log_bug_libinput(device->base.seat->libinput,
+               log_bug_libinput(evdev_libinput_context(device),
                                 "Key count for %s reached abnormal values\n",
                                 libevdev_event_code_get_name(EV_KEY, code));
        }
@@ -125,11 +133,12 @@ update_key_down_count(struct evdev_device *device, int code, int pressed)
        return key_count;
 }
 
-void
-evdev_keyboard_notify_key(struct evdev_device *device,
-                         uint32_t time,
-                         int key,
-                         enum libinput_key_state state)
+static void
+fallback_keyboard_notify_key(struct fallback_dispatch *dispatch,
+                            struct evdev_device *device,
+                            uint64_t time,
+                            int key,
+                            enum libinput_key_state state)
 {
        int down_count;
 
@@ -141,10 +150,28 @@ evdev_keyboard_notify_key(struct evdev_device *device,
 }
 
 void
-evdev_pointer_notify_button(struct evdev_device *device,
-                           uint32_t time,
-                           int button,
-                           enum libinput_button_state state)
+evdev_pointer_notify_physical_button(struct evdev_device *device,
+                                    uint64_t time,
+                                    int button,
+                                    enum libinput_button_state state)
+{
+       if (evdev_middlebutton_filter_button(device,
+                                            time,
+                                            button,
+                                            state))
+                       return;
+
+       evdev_pointer_notify_button(device,
+                                   time,
+                                   (unsigned int)button,
+                                   state);
+}
+
+static void
+evdev_pointer_post_button(struct evdev_device *device,
+                         uint64_t time,
+                         unsigned int button,
+                         enum libinput_button_state state)
 {
        int down_count;
 
@@ -154,17 +181,70 @@ evdev_pointer_notify_button(struct evdev_device *device,
            (state == LIBINPUT_BUTTON_STATE_RELEASED && down_count == 0)) {
                pointer_notify_button(&device->base, time, button, state);
 
-               if (state == LIBINPUT_BUTTON_STATE_RELEASED &&
-                   device->left_handed.change_to_enabled)
-                       device->left_handed.change_to_enabled(device);
+               if (state == LIBINPUT_BUTTON_STATE_RELEASED) {
+                       if (device->left_handed.change_to_enabled)
+                               device->left_handed.change_to_enabled(device);
 
-               if (state == LIBINPUT_BUTTON_STATE_RELEASED &&
-                   device->scroll.change_scroll_method)
-                       device->scroll.change_scroll_method(device);
+                       if (device->scroll.change_scroll_method)
+                               device->scroll.change_scroll_method(device);
+               }
        }
 
 }
 
+static void
+evdev_button_scroll_timeout(uint64_t time, void *data)
+{
+       struct evdev_device *device = data;
+
+       device->scroll.button_scroll_active = true;
+}
+
+static void
+evdev_button_scroll_button(struct evdev_device *device,
+                          uint64_t time, int is_press)
+{
+       device->scroll.button_scroll_btn_pressed = is_press;
+
+       if (is_press) {
+               libinput_timer_set(&device->scroll.timer,
+                                  time + DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT);
+               device->scroll.button_down_time = time;
+       } else {
+               libinput_timer_cancel(&device->scroll.timer);
+               if (device->scroll.button_scroll_active) {
+                       evdev_stop_scroll(device, time,
+                                         LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
+                       device->scroll.button_scroll_active = false;
+               } else {
+                       /* If the button is released quickly enough emit the
+                        * button press/release events. */
+                       evdev_pointer_post_button(device,
+                                       device->scroll.button_down_time,
+                                       device->scroll.button,
+                                       LIBINPUT_BUTTON_STATE_PRESSED);
+                       evdev_pointer_post_button(device, time,
+                                       device->scroll.button,
+                                       LIBINPUT_BUTTON_STATE_RELEASED);
+               }
+       }
+}
+
+void
+evdev_pointer_notify_button(struct evdev_device *device,
+                           uint64_t time,
+                           unsigned int button,
+                           enum libinput_button_state state)
+{
+       if (device->scroll.method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN &&
+           button == device->scroll.button) {
+               evdev_button_scroll_button(device, time, state);
+               return;
+       }
+
+       evdev_pointer_post_button(device, time, button, state);
+}
+
 void
 evdev_device_led_update(struct evdev_device *device, enum libinput_led leds)
 {
@@ -195,13 +275,27 @@ 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, int32_t *x, int32_t *y)
+void
+evdev_transform_absolute(struct evdev_device *device,
+                        struct device_coords *point)
+{
+       if (!device->abs.apply_calibration)
+               return;
+
+       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_mult_vec(&device->abs.calibration, x, y);
+       matrix_to_relative(&rel_matrix, &device->abs.calibration);
+       matrix_mult_vec(&rel_matrix, &point->x, &point->y);
 }
 
 static inline double
@@ -227,281 +321,416 @@ evdev_device_transform_y(struct evdev_device *device,
        return scale_axis(device->abs.absinfo_y, y, height);
 }
 
-double
-evdev_device_transform_ellipse_diameter_to_mm(struct evdev_device *device,
-                                             int diameter,
-                                             double axis_angle)
+static inline void
+normalize_delta(struct evdev_device *device,
+               const struct device_coords *delta,
+               struct normalized_coords *normalized)
 {
-       double x_res = device->abs.absinfo_x->resolution;
-       double y_res = device->abs.absinfo_y->resolution;
+       normalized->x = delta->x * DEFAULT_MOUSE_DPI / (double)device->dpi;
+       normalized->y = delta->y * DEFAULT_MOUSE_DPI / (double)device->dpi;
+}
 
-       if (x_res == y_res)
-               return diameter / (x_res ? x_res : 1);
+static inline bool
+evdev_post_trackpoint_scroll(struct evdev_device *device,
+                            struct normalized_coords unaccel,
+                            uint64_t time)
+{
+       if (device->scroll.method != LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN ||
+           !device->scroll.button_scroll_btn_pressed)
+               return false;
 
-       /* resolution differs but no orientation available
-        * -> estimate resolution using the average */
-       if (device->abs.absinfo_orientation == NULL) {
-               return diameter * 2.0 / (x_res + y_res);
-       } else {
-               /* Why scale x using sine of angle?
-                * axis_angle = 0 indicates that the given diameter
-                * is aligned with the y-axis. */
-               double x_scaling_ratio = fabs(sin(deg2rad(axis_angle)));
-               double y_scaling_ratio = fabs(cos(deg2rad(axis_angle)));
+       if (device->scroll.button_scroll_active)
+               evdev_post_scroll(device, time,
+                                 LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
+                                 &unaccel);
+       /* if the button is down but scroll is not active, we're within the
+          timeout where swallow motion events but don't post scroll buttons */
 
-               return diameter / hypotf(y_res * y_scaling_ratio,
-                                        x_res * x_scaling_ratio);
-       }
+       return true;
 }
 
-double
-evdev_device_transform_ellipse_diameter(struct evdev_device *device,
-                                       int diameter,
-                                       double axis_angle,
-                                       uint32_t width,
-                                       uint32_t height)
-{
-       double x_res = device->abs.absinfo_x->resolution;
-       double y_res = device->abs.absinfo_y->resolution;
-       double x_scale = width / (device->abs.x + 1.0);
-       double y_scale = height / (device->abs.y + 1.0);
-
-       if (x_res == y_res)
-               return diameter * x_scale;
-
-       /* no orientation available -> estimate resolution using the
-        * average */
-       if (device->abs.absinfo_orientation == NULL) {
-               return diameter * (x_scale + y_scale) / 2.0;
-       } else {
-               /* Why scale x using sine of angle?
-                * axis_angle = 0 indicates that the given diameter
-                * is aligned with the y-axis. */
-               double x_scaling_ratio = fabs(sin(deg2rad(axis_angle)));
-               double y_scaling_ratio = fabs(cos(deg2rad(axis_angle)));
+static inline bool
+fallback_filter_defuzz_touch(struct fallback_dispatch *dispatch,
+                            struct evdev_device *device,
+                            struct mt_slot *slot)
+{
+       struct device_coords point;
 
-               return diameter * (y_scale * y_scaling_ratio +
-                                  x_scale * x_scaling_ratio);
-       }
+       if (!dispatch->mt.want_hysteresis)
+               return false;
+
+       point.x = evdev_hysteresis(slot->point.x,
+                                  slot->hysteresis_center.x,
+                                  dispatch->mt.hysteresis_margin.x);
+       point.y = evdev_hysteresis(slot->point.y,
+                                  slot->hysteresis_center.y,
+                                  dispatch->mt.hysteresis_margin.y);
+
+       slot->hysteresis_center = slot->point;
+       if (point.x == slot->point.x && point.y == slot->point.y)
+               return true;
+
+       slot->point = point;
+
+       return false;
 }
 
-double
-evdev_device_transform_orientation(struct evdev_device *device,
-                                  int32_t orientation)
+static inline void
+fallback_rotate_relative(struct fallback_dispatch *dispatch,
+                        struct evdev_device *device)
 {
-       const struct input_absinfo *orientation_info =
-                                       device->abs.absinfo_orientation;
+       struct device_coords rel = dispatch->rel;
 
-       double angle = DEFAULT_TOUCH_ORIENTATION;
+       if (!device->base.config.rotation)
+               return;
 
-       /* ABS_MT_ORIENTATION is defined as a clockwise rotation - zero
-        * (instead of minimum) is mapped to the y-axis, and maximum is
-        * mapped to the x-axis. So minimum is likely to be negative but
-        * plays no role in scaling the value to degrees.*/
-       if (orientation_info)
-               angle = (90.0 * orientation) / orientation_info->maximum;
+       /* loss of precision for non-90 degrees, but we only support 90 deg
+        * right now anyway */
+       matrix_mult_vec(&dispatch->rotation.matrix, &rel.x, &rel.y);
 
-       return fmod(360.0 + angle, 360.0);
+       dispatch->rel = rel;
 }
 
-double
-evdev_device_transform_pressure(struct evdev_device *device,
-                               int32_t pressure)
+static void
+fallback_flush_relative_motion(struct fallback_dispatch *dispatch,
+                              struct evdev_device *device,
+                              uint64_t time)
 {
-       const struct input_absinfo *pressure_info =
-                                       device->abs.absinfo_pressure;
+       struct libinput *libinput = evdev_libinput_context(device);
+       struct libinput_device *base = &device->base;
+       struct normalized_coords accel, unaccel;
+       struct device_float_coords raw;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
+               return;
+
+       fallback_rotate_relative(dispatch, device);
+
+       normalize_delta(device, &dispatch->rel, &unaccel);
+       raw.x = dispatch->rel.x;
+       raw.y = dispatch->rel.y;
+       dispatch->rel.x = 0;
+       dispatch->rel.y = 0;
+
+       /* Use unaccelerated deltas for pointing stick scroll */
+       if (evdev_post_trackpoint_scroll(device, unaccel, time))
+               return;
 
-       if (pressure_info) {
-               double max_pressure = pressure_info->maximum;
-               double min_pressure = pressure_info->minimum;
-               return (pressure - min_pressure) /
-                                       (max_pressure - min_pressure);
+       if (device->pointer.filter) {
+               /* Apply pointer acceleration. */
+               accel = filter_dispatch(device->pointer.filter,
+                                       &unaccel,
+                                       device,
+                                       time);
        } else {
-               return DEFAULT_TOUCH_PRESSURE;
+               log_bug_libinput(libinput,
+                                "%s: accel filter missing\n",
+                                udev_device_get_devnode(device->udev_device));
+               accel = unaccel;
        }
+
+       if (normalized_is_zero(accel) && normalized_is_zero(unaccel))
+               return;
+
+       pointer_notify_motion(base, time, &accel, &raw);
 }
 
 static void
-evdev_flush_pending_event(struct evdev_device *device, uint64_t time)
+fallback_flush_absolute_motion(struct fallback_dispatch *dispatch,
+                              struct evdev_device *device,
+                              uint64_t time)
 {
-       struct libinput *libinput = device->base.seat->libinput;
-       struct motion_params motion;
-       double dx_unaccel, dy_unaccel;
-       int32_t cx, cy;
-       int32_t x, y;
-       int slot;
+       struct libinput_device *base = &device->base;
+       struct device_coords point;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_POINTER))
+               return;
+
+       point = dispatch->abs.point;
+       evdev_transform_absolute(device, &point);
+
+       pointer_notify_motion_absolute(base, time, &point);
+}
+
+static bool
+fallback_flush_mt_down(struct fallback_dispatch *dispatch,
+                      struct evdev_device *device,
+                      int slot_idx,
+                      uint64_t time)
+{
+       struct libinput_device *base = &device->base;
+       struct libinput_seat *seat = base->seat;
+       struct device_coords point;
+       struct mt_slot *slot;
        int seat_slot;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+               return false;
+
+       slot = &dispatch->mt.slots[slot_idx];
+       if (slot->seat_slot != -1) {
+               struct libinput *libinput = evdev_libinput_context(device);
+
+               log_bug_kernel(libinput,
+                              "%s: Driver sent multiple touch down for the "
+                              "same slot",
+                              udev_device_get_devnode(device->udev_device));
+               return false;
+       }
+
+       seat_slot = ffs(~seat->slot_map) - 1;
+       slot->seat_slot = seat_slot;
+
+       if (seat_slot == -1)
+               return false;
+
+       seat->slot_map |= 1 << seat_slot;
+       point = slot->point;
+       slot->hysteresis_center = point;
+       evdev_transform_absolute(device, &point);
+
+       touch_notify_touch_down(base, time, slot_idx, seat_slot,
+                               &point);
+
+       return true;
+}
+
+static bool
+fallback_flush_mt_motion(struct fallback_dispatch *dispatch,
+                        struct evdev_device *device,
+                        int slot_idx,
+                        uint64_t time)
+{
+       struct libinput_device *base = &device->base;
+       struct device_coords point;
+       struct mt_slot *slot;
+       int seat_slot;
+
+       if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+               return false;
+
+       slot = &dispatch->mt.slots[slot_idx];
+       seat_slot = slot->seat_slot;
+       point = slot->point;
+
+       if (seat_slot == -1)
+               return false;
+
+       if (fallback_filter_defuzz_touch(dispatch, device, slot))
+               return false;
+
+       evdev_transform_absolute(device, &point);
+       touch_notify_touch_motion(base, time, slot_idx, seat_slot,
+                                 &point);
+
+       return true;
+}
+
+static bool
+fallback_flush_mt_up(struct fallback_dispatch *dispatch,
+                    struct evdev_device *device,
+                    int slot_idx,
+                    uint64_t time)
+{
        struct libinput_device *base = &device->base;
        struct libinput_seat *seat = base->seat;
-       struct mt_slot *slot_data;
-       struct ellipse default_touch = {
-               .major = DEFAULT_TOUCH_MAJOR,
-               .minor = DEFAULT_TOUCH_MINOR,
-               .orientation = DEFAULT_TOUCH_ORIENTATION
-       };
+       struct mt_slot *slot;
+       int seat_slot;
 
-       slot = device->mt.slot;
-       slot_data = &device->mt.slots[slot];
+       if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+               return false;
 
-       switch (device->pending_event) {
-       case EVDEV_NONE:
-               return;
-       case EVDEV_RELATIVE_MOTION:
-               dx_unaccel = device->rel.dx / ((double) device->dpi /
-                                              DEFAULT_MOUSE_DPI);
-               dy_unaccel = device->rel.dy / ((double) device->dpi /
-                                              DEFAULT_MOUSE_DPI);
-               device->rel.dx = 0;
-               device->rel.dy = 0;
-
-               /* Use unaccelerated deltas for pointing stick scroll */
-               if (device->scroll.method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN &&
-                   hw_is_key_down(device, device->scroll.button)) {
-                       if (device->scroll.button_scroll_active)
-                               evdev_post_scroll(device, time,
-                                                 LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
-                                                 dx_unaccel, dy_unaccel);
-                       break;
-               }
+       slot = &dispatch->mt.slots[slot_idx];
+       seat_slot = slot->seat_slot;
+       slot->seat_slot = -1;
 
-               /* Apply pointer acceleration. */
-               motion.dx = dx_unaccel;
-               motion.dy = dy_unaccel;
-               filter_dispatch(device->pointer.filter, &motion, device, time);
+       if (seat_slot == -1)
+               return false;
 
-               if (motion.dx == 0.0 && motion.dy == 0.0 &&
-                   dx_unaccel == 0.0 && dy_unaccel == 0.0) {
-                       break;
-               }
+       seat->slot_map &= ~(1 << seat_slot);
 
-               pointer_notify_motion(base, time,
-                                     motion.dx, motion.dy,
-                                     dx_unaccel, dy_unaccel);
-               break;
-       case EVDEV_ABSOLUTE_MT_DOWN:
-               if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-                       break;
+       touch_notify_touch_up(base, time, slot_idx, seat_slot);
 
-               if (slot_data->seat_slot != -1) {
-                       log_bug_kernel(libinput,
-                                      "%s: Driver sent multiple touch down for the "
-                                      "same slot",
-                                      udev_device_get_devnode(device->udev_device));
-                       break;
-               }
+       return true;
+}
 
-               seat_slot = ffs(~seat->slot_map) - 1;
-               slot_data->seat_slot = seat_slot;
+static bool
+fallback_flush_st_down(struct fallback_dispatch *dispatch,
+                      struct evdev_device *device,
+                      uint64_t time)
+{
+       struct libinput_device *base = &device->base;
+       struct libinput_seat *seat = base->seat;
+       struct device_coords point;
+       int seat_slot;
 
-               if (seat_slot == -1)
-                       break;
+       if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+               return false;
 
-               seat->slot_map |= 1 << seat_slot;
-               x = slot_data->x;
-               y = slot_data->y;
-               transform_absolute(device, &x, &y);
+       if (dispatch->abs.seat_slot != -1) {
+               struct libinput *libinput = evdev_libinput_context(device);
 
-               touch_notify_touch_down(base, time, slot, seat_slot, x, y, &slot_data->area, slot_data->pressure);
-               break;
-       case EVDEV_ABSOLUTE_MT_MOTION:
-               if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-                       break;
+               log_bug_kernel(libinput,
+                              "%s: Driver sent multiple touch down for the "
+                              "same slot",
+                              udev_device_get_devnode(device->udev_device));
+               return false;
+       }
 
-               seat_slot = slot_data->seat_slot;
-               x = device->mt.slots[slot].x;
-               y = device->mt.slots[slot].y;
+       seat_slot = ffs(~seat->slot_map) - 1;
+       dispatch->abs.seat_slot = seat_slot;
 
-               if (seat_slot == -1)
-                       break;
+       if (seat_slot == -1)
+               return false;
 
-               transform_absolute(device, &x, &y);
-               touch_notify_touch_motion(base, time, slot, seat_slot, x, y, &slot_data->area, slot_data->pressure);
-               break;
-       case EVDEV_ABSOLUTE_MT_UP:
-               if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-                       break;
+       seat->slot_map |= 1 << seat_slot;
 
-               seat_slot = slot_data->seat_slot;
-               slot_data->seat_slot = -1;
+       point = dispatch->abs.point;
+       evdev_transform_absolute(device, &point);
 
-               if (seat_slot == -1)
-                       break;
+       touch_notify_touch_down(base, time, -1, seat_slot, &point);
 
-               seat->slot_map &= ~(1 << seat_slot);
+       return true;
+}
 
-               touch_notify_touch_up(base, time, slot, seat_slot);
-               break;
-       case EVDEV_ABSOLUTE_TOUCH_DOWN:
-               if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-                       break;
+static bool
+fallback_flush_st_motion(struct fallback_dispatch *dispatch,
+                        struct evdev_device *device,
+                        uint64_t time)
+{
+       struct libinput_device *base = &device->base;
+       struct device_coords point;
+       int seat_slot;
 
-               if (device->abs.seat_slot != -1) {
-                       log_bug_kernel(libinput,
-                                      "%s: Driver sent multiple touch down for the "
-                                      "same slot",
-                                      udev_device_get_devnode(device->udev_device));
-                       break;
-               }
+       point = dispatch->abs.point;
+       evdev_transform_absolute(device, &point);
 
-               seat_slot = ffs(~seat->slot_map) - 1;
-               device->abs.seat_slot = seat_slot;
+       seat_slot = dispatch->abs.seat_slot;
 
-               if (seat_slot == -1)
-                       break;
+       if (seat_slot == -1)
+               return false;
 
-               seat->slot_map |= 1 << seat_slot;
+       touch_notify_touch_motion(base, time, -1, seat_slot, &point);
 
-               cx = device->abs.x;
-               cy = device->abs.y;
-               transform_absolute(device, &cx, &cy);
+       return true;
+}
 
-               touch_notify_touch_down(base, time, -1, seat_slot, cx, cy, &default_touch, DEFAULT_TOUCH_PRESSURE);
-               break;
-       case EVDEV_ABSOLUTE_MOTION:
-               cx = device->abs.x;
-               cy = device->abs.y;
-               transform_absolute(device, &cx, &cy);
-               x = cx;
-               y = cy;
+static bool
+fallback_flush_st_up(struct fallback_dispatch *dispatch,
+                    struct evdev_device *device,
+                    uint64_t time)
+{
+       struct libinput_device *base = &device->base;
+       struct libinput_seat *seat = base->seat;
+       int seat_slot;
 
-               if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
-                       seat_slot = device->abs.seat_slot;
+       if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
+               return false;
 
-                       if (seat_slot == -1)
-                               break;
+       seat_slot = dispatch->abs.seat_slot;
+       dispatch->abs.seat_slot = -1;
 
-                       touch_notify_touch_motion(base, time, -1, seat_slot, x, y, &default_touch, DEFAULT_TOUCH_PRESSURE);
-               } else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
-                       pointer_notify_motion_absolute(base, time, x, y);
-               }
-               break;
-       case EVDEV_ABSOLUTE_TOUCH_UP:
-               if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-                       break;
+       if (seat_slot == -1)
+               return false;
 
-               seat_slot = device->abs.seat_slot;
-               device->abs.seat_slot = -1;
+       seat->slot_map &= ~(1 << seat_slot);
 
-               if (seat_slot == -1)
-                       break;
+       touch_notify_touch_up(base, time, -1, seat_slot);
+
+       return true;
+}
 
-               seat->slot_map &= ~(1 << seat_slot);
+static enum evdev_event_type
+fallback_flush_pending_event(struct fallback_dispatch *dispatch,
+                            struct evdev_device *device,
+                            uint64_t time)
+{
+       enum evdev_event_type sent_event;
+       int slot_idx;
+
+       sent_event = dispatch->pending_event;
 
-               touch_notify_touch_up(base, time, -1, seat_slot);
+       switch (dispatch->pending_event) {
+       case EVDEV_NONE:
+               break;
+       case EVDEV_RELATIVE_MOTION:
+               fallback_flush_relative_motion(dispatch, device, time);
+               break;
+       case EVDEV_ABSOLUTE_MT_DOWN:
+               slot_idx = dispatch->mt.slot;
+               if (!fallback_flush_mt_down(dispatch,
+                                           device,
+                                           slot_idx,
+                                           time))
+                       sent_event = EVDEV_NONE;
+               break;
+       case EVDEV_ABSOLUTE_MT_MOTION:
+               slot_idx = dispatch->mt.slot;
+               if (!fallback_flush_mt_motion(dispatch,
+                                             device,
+                                             slot_idx,
+                                             time))
+                       sent_event = EVDEV_NONE;
+               break;
+       case EVDEV_ABSOLUTE_MT_UP:
+               slot_idx = dispatch->mt.slot;
+               if (!fallback_flush_mt_up(dispatch,
+                                         device,
+                                         slot_idx,
+                                         time))
+                       sent_event = EVDEV_NONE;
+               break;
+       case EVDEV_ABSOLUTE_TOUCH_DOWN:
+               if (!fallback_flush_st_down(dispatch, device, time))
+                       sent_event = EVDEV_NONE;
+               break;
+       case EVDEV_ABSOLUTE_MOTION:
+               if (device->seat_caps & EVDEV_DEVICE_TOUCH) {
+                       if (fallback_flush_st_motion(dispatch,
+                                                    device,
+                                                    time))
+                               sent_event = EVDEV_ABSOLUTE_MT_MOTION;
+                       else
+                               sent_event = EVDEV_NONE;
+               } else if (device->seat_caps & EVDEV_DEVICE_POINTER) {
+                       fallback_flush_absolute_motion(dispatch,
+                                                      device,
+                                                      time);
+               }
+               break;
+       case EVDEV_ABSOLUTE_TOUCH_UP:
+               if (!fallback_flush_st_up(dispatch, device, time))
+                       sent_event = EVDEV_NONE;
                break;
        default:
                assert(0 && "Unknown pending event type");
                break;
        }
 
-       device->pending_event = EVDEV_NONE;
+       dispatch->pending_event = EVDEV_NONE;
+
+       return sent_event;
 }
 
 static enum evdev_key_type
 get_key_type(uint16_t code)
 {
-       if (code == BTN_TOUCH)
+       switch (code) {
+       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:
+       case BTN_TOOL_QUINTTAP:
+       case BTN_TOOL_DOUBLETAP:
+       case BTN_TOOL_TRIPLETAP:
+       case BTN_TOOL_QUADTAP:
+       case BTN_TOOL_FINGER:
+       case BTN_TOUCH:
                return EVDEV_KEY_TYPE_NONE;
+       }
 
        if (code >= KEY_ESC && code <= KEY_MICMUTE)
                return EVDEV_KEY_TYPE_KEY;
@@ -509,61 +738,33 @@ get_key_type(uint16_t code)
                return EVDEV_KEY_TYPE_BUTTON;
        if (code >= KEY_OK && code <= KEY_LIGHTS_TOGGLE)
                return EVDEV_KEY_TYPE_KEY;
-       if (code >= BTN_DPAD_UP && code <= BTN_TRIGGER_HAPPY40)
+       if (code >= BTN_DPAD_UP && code <= BTN_DPAD_RIGHT)
+               return EVDEV_KEY_TYPE_BUTTON;
+       if (code >= KEY_ALS_TOGGLE && code <= KEY_KBDINPUTASSIST_CANCEL)
+               return EVDEV_KEY_TYPE_KEY;
+       if (code >= BTN_TRIGGER_HAPPY && code <= BTN_TRIGGER_HAPPY40)
                return EVDEV_KEY_TYPE_BUTTON;
        return EVDEV_KEY_TYPE_NONE;
 }
 
 static void
-evdev_button_scroll_timeout(uint64_t time, void *data)
+fallback_process_touch_button(struct fallback_dispatch *dispatch,
+                             struct evdev_device *device,
+                             uint64_t time, int value)
 {
-       struct evdev_device *device = data;
-
-       device->scroll.button_scroll_active = true;
-}
+       if (dispatch->pending_event != EVDEV_NONE &&
+           dispatch->pending_event != EVDEV_ABSOLUTE_MOTION)
+               fallback_flush_pending_event(dispatch, device, time);
 
-static void
-evdev_button_scroll_button(struct evdev_device *device,
-                          uint64_t time, int is_press)
-{
-       if (is_press) {
-               libinput_timer_set(&device->scroll.timer,
-                               time + DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT);
-       } else {
-               libinput_timer_cancel(&device->scroll.timer);
-               if (device->scroll.button_scroll_active) {
-                       evdev_stop_scroll(device, time,
-                                         LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
-                       device->scroll.button_scroll_active = false;
-               } else {
-                       /* If the button is released quickly enough emit the
-                        * button press/release events. */
-                       evdev_pointer_notify_button(device, time,
-                                       device->scroll.button,
-                                       LIBINPUT_BUTTON_STATE_PRESSED);
-                       evdev_pointer_notify_button(device, time,
-                                       device->scroll.button,
-                                       LIBINPUT_BUTTON_STATE_RELEASED);
-               }
-       }
-}
-
-static void
-evdev_process_touch_button(struct evdev_device *device,
-                          uint64_t time, int value)
-{
-       if (device->pending_event != EVDEV_NONE &&
-           device->pending_event != EVDEV_ABSOLUTE_MOTION)
-               evdev_flush_pending_event(device, time);
-
-       device->pending_event = (value ?
+       dispatch->pending_event = (value ?
                                 EVDEV_ABSOLUTE_TOUCH_DOWN :
                                 EVDEV_ABSOLUTE_TOUCH_UP);
 }
 
 static inline void
-evdev_process_key(struct evdev_device *device,
-                 struct input_event *e, uint64_t time)
+fallback_process_key(struct fallback_dispatch *dispatch,
+                    struct evdev_device *device,
+                    struct input_event *e, uint64_t time)
 {
        enum evdev_key_type type;
 
@@ -573,11 +774,14 @@ evdev_process_key(struct evdev_device *device,
 
        if (e->code == BTN_TOUCH) {
                if (!device->is_mt)
-                       evdev_process_touch_button(device, time, e->value);
+                       fallback_process_touch_button(dispatch,
+                                                     device,
+                                                     time,
+                                                     e->value);
                return;
        }
 
-       evdev_flush_pending_event(device, time);
+       fallback_flush_pending_event(dispatch, device, time);
 
        type = get_key_type(e->code);
 
@@ -589,19 +793,19 @@ evdev_process_key(struct evdev_device *device,
                        break;
                case EVDEV_KEY_TYPE_KEY:
                case EVDEV_KEY_TYPE_BUTTON:
-                       if (!hw_is_key_down(device, e->code)) {
+                       if (!hw_is_key_down(dispatch, e->code))
                                return;
-                       }
                }
        }
 
-       hw_set_key_down(device, e->code, e->value);
+       hw_set_key_down(dispatch, e->code, e->value);
 
        switch (type) {
        case EVDEV_KEY_TYPE_NONE:
                break;
        case EVDEV_KEY_TYPE_KEY:
-               evdev_keyboard_notify_key(
+               fallback_keyboard_notify_key(
+                       dispatch,
                        device,
                        time,
                        e->code,
@@ -609,12 +813,7 @@ evdev_process_key(struct evdev_device *device,
                                   LIBINPUT_KEY_STATE_RELEASED);
                break;
        case EVDEV_KEY_TYPE_BUTTON:
-               if (device->scroll.method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN &&
-                   e->code == device->scroll.button) {
-                       evdev_button_scroll_button(device, time, e->value);
-                       break;
-               }
-               evdev_pointer_notify_button(
+               evdev_pointer_notify_physical_button(
                        device,
                        time,
                        evdev_to_left_handed(device, e->code),
@@ -625,180 +824,184 @@ evdev_process_key(struct evdev_device *device,
 }
 
 static void
-evdev_process_touch(struct evdev_device *device,
-                   struct input_event *e,
-                   uint64_t time)
-{
-       struct mt_slot *current_slot = &device->mt.slots[device->mt.slot];
-
-       if (e->code == ABS_MT_SLOT) {
-               evdev_flush_pending_event(device, time);
-               device->mt.slot = e->value;
-       } else if(e->code == ABS_MT_TRACKING_ID) {
-               if (device->pending_event != EVDEV_NONE &&
-                   device->pending_event != EVDEV_ABSOLUTE_MT_MOTION)
-                       evdev_flush_pending_event(device, time);
+fallback_process_touch(struct fallback_dispatch *dispatch,
+                      struct evdev_device *device,
+                      struct input_event *e,
+                      uint64_t time)
+{
+       switch (e->code) {
+       case ABS_MT_SLOT:
+               if ((size_t)e->value >= dispatch->mt.slots_len) {
+                       log_bug_libinput(evdev_libinput_context(device),
+                                        "%s exceeds slots (%d of %zd)\n",
+                                        device->devname,
+                                        e->value,
+                                        dispatch->mt.slots_len);
+                       e->value = dispatch->mt.slots_len - 1;
+               }
+               fallback_flush_pending_event(dispatch, device, time);
+               dispatch->mt.slot = e->value;
+               break;
+       case ABS_MT_TRACKING_ID:
+               if (dispatch->pending_event != EVDEV_NONE &&
+                   dispatch->pending_event != EVDEV_ABSOLUTE_MT_MOTION)
+                       fallback_flush_pending_event(dispatch, device, time);
                if (e->value >= 0)
-                       device->pending_event = EVDEV_ABSOLUTE_MT_DOWN;
+                       dispatch->pending_event = EVDEV_ABSOLUTE_MT_DOWN;
                else
-                       device->pending_event = EVDEV_ABSOLUTE_MT_UP;
-       } else {
-               bool needs_wake = true;
-
-               switch (e->code) {
-               case ABS_MT_POSITION_X:
-                       current_slot->x = e->value;
-                       break;
-               case ABS_MT_POSITION_Y:
-                       current_slot->y = e->value;
-                       break;
-               case ABS_MT_TOUCH_MAJOR:
-                       current_slot->area.major = e->value;
-                       break;
-               case ABS_MT_TOUCH_MINOR:
-                       current_slot->area.minor = e->value;
-                       break;
-               case ABS_MT_ORIENTATION:
-                       current_slot->area.orientation = e->value;
-                       break;
-               default:
-                       needs_wake = false;
-                       break;
-               }
-               if (needs_wake && device->pending_event == EVDEV_NONE)
-                       device->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
+                       dispatch->pending_event = EVDEV_ABSOLUTE_MT_UP;
+               break;
+       case ABS_MT_POSITION_X:
+               dispatch->mt.slots[dispatch->mt.slot].point.x = e->value;
+               if (dispatch->pending_event == EVDEV_NONE)
+                       dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
+               break;
+       case ABS_MT_POSITION_Y:
+               dispatch->mt.slots[dispatch->mt.slot].point.y = e->value;
+               if (dispatch->pending_event == EVDEV_NONE)
+                       dispatch->pending_event = EVDEV_ABSOLUTE_MT_MOTION;
+               break;
        }
 }
-
 static inline void
-evdev_process_absolute_motion(struct evdev_device *device,
-                             struct input_event *e)
+fallback_process_absolute_motion(struct fallback_dispatch *dispatch,
+                                struct evdev_device *device,
+                                struct input_event *e)
 {
        switch (e->code) {
        case ABS_X:
-               device->abs.x = e->value;
-               if (device->pending_event == EVDEV_NONE)
-                       device->pending_event = EVDEV_ABSOLUTE_MOTION;
+               dispatch->abs.point.x = e->value;
+               if (dispatch->pending_event == EVDEV_NONE)
+                       dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
                break;
        case ABS_Y:
-               device->abs.y = e->value;
-               if (device->pending_event == EVDEV_NONE)
-                       device->pending_event = EVDEV_ABSOLUTE_MOTION;
+               dispatch->abs.point.y = e->value;
+               if (dispatch->pending_event == EVDEV_NONE)
+                       dispatch->pending_event = EVDEV_ABSOLUTE_MOTION;
                break;
        }
 }
 
-static void
+void
 evdev_notify_axis(struct evdev_device *device,
                  uint64_t time,
                  uint32_t axes,
                  enum libinput_pointer_axis_source source,
-                 double x, double y,
-                 double x_discrete, double y_discrete)
+                 const struct normalized_coords *delta_in,
+                 const struct discrete_coords *discrete_in)
 {
+       struct normalized_coords delta = *delta_in;
+       struct discrete_coords discrete = *discrete_in;
+
        if (device->scroll.natural_scrolling_enabled) {
-               x *= -1;
-               y *= -1;
-               x_discrete *= -1;
-               y_discrete *= -1;
+               delta.x *= -1;
+               delta.y *= -1;
+               discrete.x *= -1;
+               discrete.y *= -1;
        }
 
        pointer_notify_axis(&device->base,
                            time,
                            axes,
                            source,
-                           x, y,
-                           x_discrete, y_discrete);
+                           &delta,
+                           &discrete);
+}
+
+static inline bool
+fallback_reject_relative(struct evdev_device *device,
+                        const struct input_event *e,
+                        uint64_t time)
+{
+       if ((e->code == REL_X || e->code == REL_Y) &&
+           (device->seat_caps & EVDEV_DEVICE_POINTER) == 0) {
+               log_bug_libinput_ratelimit(evdev_libinput_context(device),
+                                          &device->nonpointer_rel_limit,
+                                          "REL_X/Y from device '%s', but this device is not a pointer\n",
+                                          device->devname);
+               return true;
+       }
+
+       return false;
 }
 
 static inline void
-evdev_process_relative(struct evdev_device *device,
-                      struct input_event *e, uint64_t time)
+fallback_process_relative(struct fallback_dispatch *dispatch,
+                         struct evdev_device *device,
+                         struct input_event *e, uint64_t time)
 {
+       struct normalized_coords wheel_degrees = { 0.0, 0.0 };
+       struct discrete_coords discrete = { 0.0, 0.0 };
+
+       if (fallback_reject_relative(device, e, time))
+               return;
+
        switch (e->code) {
        case REL_X:
-               if (device->pending_event != EVDEV_RELATIVE_MOTION)
-                       evdev_flush_pending_event(device, time);
-               device->rel.dx += e->value;
-               device->pending_event = EVDEV_RELATIVE_MOTION;
+               if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
+                       fallback_flush_pending_event(dispatch, device, time);
+               dispatch->rel.x += e->value;
+               dispatch->pending_event = EVDEV_RELATIVE_MOTION;
                break;
        case REL_Y:
-               if (device->pending_event != EVDEV_RELATIVE_MOTION)
-                       evdev_flush_pending_event(device, time);
-               device->rel.dy += e->value;
-               device->pending_event = EVDEV_RELATIVE_MOTION;
+               if (dispatch->pending_event != EVDEV_RELATIVE_MOTION)
+                       fallback_flush_pending_event(dispatch, device, time);
+               dispatch->rel.y += e->value;
+               dispatch->pending_event = EVDEV_RELATIVE_MOTION;
                break;
        case REL_WHEEL:
-               evdev_flush_pending_event(device, time);
+               fallback_flush_pending_event(dispatch, device, time);
+               wheel_degrees.y = -1 * e->value *
+                                       device->scroll.wheel_click_angle.x;
+               discrete.y = -1 * e->value;
                evdev_notify_axis(
                        device,
                        time,
                        AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
                        LIBINPUT_POINTER_AXIS_SOURCE_WHEEL,
-                       0.0,
-                       -1 * e->value * device->scroll.wheel_click_angle,
-                       0.0,
-                       -1 * e->value);
+                       &wheel_degrees,
+                       &discrete);
                break;
        case REL_HWHEEL:
-               evdev_flush_pending_event(device, time);
+               fallback_flush_pending_event(dispatch, device, time);
+               wheel_degrees.x = e->value *
+                                       device->scroll.wheel_click_angle.y;
+               discrete.x = e->value;
                evdev_notify_axis(
                        device,
                        time,
                        AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL),
                        LIBINPUT_POINTER_AXIS_SOURCE_WHEEL,
-                       e->value * device->scroll.wheel_click_angle,
-                       0.0,
-                       e->value,
-                       0.0);
+                       &wheel_degrees,
+                       &discrete);
                break;
        }
 }
 
 static inline void
-evdev_process_absolute(struct evdev_device *device,
-                      struct input_event *e,
-                      uint64_t time)
+fallback_process_absolute(struct fallback_dispatch *dispatch,
+                         struct evdev_device *device,
+                         struct input_event *e,
+                         uint64_t time)
 {
        if (device->is_mt) {
-               evdev_process_touch(device, e, time);
-       } else {
-               evdev_process_absolute_motion(device, e);
-       }
-}
-
-static inline bool
-evdev_any_button_down(struct evdev_device *device)
-{
-       unsigned int button;
-
-       for (button = BTN_LEFT; button < BTN_JOYSTICK; button++) {
-               if (libevdev_has_event_code(device->evdev, EV_KEY, button) &&
-                   hw_is_key_down(device, button))
-                       return true;
-       }
-       return false;
-}
-
-static inline bool
-evdev_need_touch_frame(struct evdev_device *device)
-{
-       if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
-               return false;
-
-       switch (device->pending_event) {
-       case EVDEV_NONE:
-       case EVDEV_RELATIVE_MOTION:
-               break;
-       case EVDEV_ABSOLUTE_MT_DOWN:
-       case EVDEV_ABSOLUTE_MT_MOTION:
-       case EVDEV_ABSOLUTE_MT_UP:
-       case EVDEV_ABSOLUTE_TOUCH_DOWN:
-       case EVDEV_ABSOLUTE_TOUCH_UP:
-       case EVDEV_ABSOLUTE_MOTION:
-               return true;
+               fallback_process_touch(dispatch, device, e, time);
+       } else {
+               fallback_process_absolute_motion(dispatch, device, e);
        }
+}
+
+static inline bool
+fallback_any_button_down(struct fallback_dispatch *dispatch,
+                     struct evdev_device *device)
+{
+       unsigned int button;
 
+       for (button = BTN_LEFT; button < BTN_JOYSTICK; button++) {
+               if (libevdev_has_event_code(device->evdev, EV_KEY, button) &&
+                   hw_is_key_down(dispatch, button))
+                       return true;
+       }
        return false;
 }
 
@@ -809,59 +1012,204 @@ evdev_tag_external_mouse(struct evdev_device *device,
        int bustype;
 
        bustype = libevdev_get_id_bustype(device->evdev);
-       if (bustype == BUS_USB || bustype == BUS_BLUETOOTH) {
-               if (device->seat_caps & EVDEV_DEVICE_POINTER)
-                       device->tags |= EVDEV_TAG_EXTERNAL_MOUSE;
-       }
+       if (bustype == BUS_USB || bustype == BUS_BLUETOOTH)
+               device->tags |= EVDEV_TAG_EXTERNAL_MOUSE;
 }
 
 static void
 evdev_tag_trackpoint(struct evdev_device *device,
                     struct udev_device *udev_device)
 {
-       if (libevdev_has_property(device->evdev, INPUT_PROP_POINTING_STICK))
+       if (libevdev_has_property(device->evdev,
+                                 INPUT_PROP_POINTING_STICK) ||
+           udev_device_get_property_value(udev_device,
+                                          "ID_INPUT_POINTINGSTICK"))
                device->tags |= EVDEV_TAG_TRACKPOINT;
 }
 
 static void
-fallback_process(struct evdev_dispatch *dispatch,
+evdev_tag_keyboard(struct evdev_device *device,
+                  struct udev_device *udev_device)
+{
+       int code;
+
+       if (!libevdev_has_event_type(device->evdev, EV_KEY))
+               return;
+
+       for (code = KEY_Q; code <= KEY_P; code++) {
+               if (!libevdev_has_event_code(device->evdev,
+                                            EV_KEY,
+                                            code))
+                       return;
+       }
+
+       device->tags |= EVDEV_TAG_KEYBOARD;
+}
+
+static void
+fallback_process(struct evdev_dispatch *evdev_dispatch,
                 struct evdev_device *device,
                 struct input_event *event,
                 uint64_t time)
 {
-       bool need_frame = false;
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)evdev_dispatch;
+       enum evdev_event_type sent;
+
+       if (dispatch->ignore_events)
+               return;
 
        switch (event->type) {
        case EV_REL:
-               evdev_process_relative(device, event, time);
+               fallback_process_relative(dispatch, device, event, time);
                break;
        case EV_ABS:
-               evdev_process_absolute(device, event, time);
+               fallback_process_absolute(dispatch, device, event, time);
                break;
        case EV_KEY:
-               evdev_process_key(device, event, time);
+               fallback_process_key(dispatch, device, event, time);
                break;
        case EV_SYN:
-               need_frame = evdev_need_touch_frame(device);
-               evdev_flush_pending_event(device, time);
-               if (need_frame)
+               sent = fallback_flush_pending_event(dispatch, device, time);
+               switch (sent) {
+               case EVDEV_ABSOLUTE_TOUCH_DOWN:
+               case EVDEV_ABSOLUTE_TOUCH_UP:
+               case EVDEV_ABSOLUTE_MT_DOWN:
+               case EVDEV_ABSOLUTE_MT_MOTION:
+               case EVDEV_ABSOLUTE_MT_UP:
                        touch_notify_frame(&device->base, time);
+                       break;
+               case EVDEV_ABSOLUTE_MOTION:
+               case EVDEV_RELATIVE_MOTION:
+               case EVDEV_NONE:
+                       break;
+               }
                break;
        }
 }
 
 static void
-fallback_destroy(struct evdev_dispatch *dispatch)
+release_touches(struct fallback_dispatch *dispatch,
+               struct evdev_device *device,
+               uint64_t time)
 {
-       free(dispatch);
+       unsigned int idx;
+       bool need_frame = false;
+
+       need_frame = fallback_flush_st_up(dispatch, device, time);
+
+       for (idx = 0; idx < dispatch->mt.slots_len; idx++) {
+               struct mt_slot *slot = &dispatch->mt.slots[idx];
+
+               if (slot->seat_slot == -1)
+                       continue;
+
+               if (fallback_flush_mt_up(dispatch, device, idx, time))
+                       need_frame = true;
+       }
+
+       if (need_frame)
+               touch_notify_frame(&device->base, time);
 }
 
 static void
-fallback_tag_device(struct evdev_device *device,
-                   struct udev_device *udev_device)
+release_pressed_keys(struct fallback_dispatch *dispatch,
+                    struct evdev_device *device,
+                    uint64_t time)
+{
+       struct libinput *libinput = evdev_libinput_context(device);
+       int code;
+
+       for (code = 0; code < KEY_CNT; code++) {
+               int count = get_key_down_count(device, code);
+
+               if (count == 0)
+                       continue;
+
+               if (count > 1) {
+                       log_bug_libinput(libinput,
+                                        "Key %d is down %d times.\n",
+                                        code,
+                                        count);
+               }
+
+               switch (get_key_type(code)) {
+               case EVDEV_KEY_TYPE_NONE:
+                       break;
+               case EVDEV_KEY_TYPE_KEY:
+                       fallback_keyboard_notify_key(
+                               dispatch,
+                               device,
+                               time,
+                               code,
+                               LIBINPUT_KEY_STATE_RELEASED);
+                       break;
+               case EVDEV_KEY_TYPE_BUTTON:
+                       evdev_pointer_notify_physical_button(
+                               device,
+                               time,
+                               evdev_to_left_handed(device, code),
+                               LIBINPUT_BUTTON_STATE_RELEASED);
+                       break;
+               }
+
+               count = get_key_down_count(device, code);
+               if (count != 0) {
+                       log_bug_libinput(libinput,
+                                        "Releasing key %d failed.\n",
+                                        code);
+                       break;
+               }
+       }
+}
+
+static void
+fallback_return_to_neutral_state(struct fallback_dispatch *dispatch,
+                                struct evdev_device *device)
+{
+       struct libinput *libinput = evdev_libinput_context(device);
+       uint64_t time;
+
+       if ((time = libinput_now(libinput)) == 0)
+               return;
+
+       release_touches(dispatch, device, time);
+       release_pressed_keys(dispatch, device, time);
+       memset(dispatch->hw_key_mask, 0, sizeof(dispatch->hw_key_mask));
+}
+
+static void
+fallback_suspend(struct evdev_dispatch *evdev_dispatch,
+                struct evdev_device *device)
+{
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)evdev_dispatch;
+
+       fallback_return_to_neutral_state(dispatch, device);
+}
+
+static void
+fallback_toggle_touch(struct evdev_dispatch *evdev_dispatch,
+                     struct evdev_device *device,
+                     bool enable)
+{
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)evdev_dispatch;
+       bool ignore_events = !enable;
+
+       if (ignore_events == dispatch->ignore_events)
+               return;
+
+       if (ignore_events)
+               fallback_return_to_neutral_state(dispatch, device);
+
+       dispatch->ignore_events = ignore_events;
+}
+
+static void
+fallback_destroy(struct evdev_dispatch *evdev_dispatch)
 {
-       evdev_tag_external_mouse(device, udev_device);
-       evdev_tag_trackpoint(device, udev_device);
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)evdev_dispatch;
+
+       free(dispatch->mt.slots);
+       free(dispatch);
 }
 
 static int
@@ -907,13 +1255,15 @@ evdev_calibration_get_default_matrix(struct libinput_device *libinput_device,
 
 struct evdev_dispatch_interface fallback_interface = {
        fallback_process,
+       fallback_suspend,
        NULL, /* remove */
        fallback_destroy,
        NULL, /* device_added */
        NULL, /* device_removed */
        NULL, /* device_suspended */
        NULL, /* device_resumed */
-       fallback_tag_device,
+       NULL, /* post_added */
+       fallback_toggle_touch, /* toggle_touch */
 };
 
 static uint32_t
@@ -974,10 +1324,12 @@ evdev_left_handed_has(struct libinput_device *device)
 static void
 evdev_change_to_left_handed(struct evdev_device *device)
 {
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)device->dispatch;
+
        if (device->left_handed.want_enabled == device->left_handed.enabled)
                return;
 
-       if (evdev_any_button_down(device))
+       if (fallback_any_button_down(dispatch, device))
                return;
 
        device->left_handed.enabled = device->left_handed.want_enabled;
@@ -1011,7 +1363,7 @@ evdev_left_handed_get_default(struct libinput_device *device)
        return 0;
 }
 
-int
+void
 evdev_init_left_handed(struct evdev_device *device,
                       void (*change_to_left_handed)(struct evdev_device *))
 {
@@ -1023,8 +1375,6 @@ evdev_init_left_handed(struct evdev_device *device,
        device->left_handed.enabled = false;
        device->left_handed.want_enabled = false;
        device->left_handed.change_to_enabled = change_to_left_handed;
-
-       return 0;
 }
 
 static uint32_t
@@ -1036,11 +1386,13 @@ evdev_scroll_get_methods(struct libinput_device *device)
 static void
 evdev_change_scroll_method(struct evdev_device *device)
 {
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)device->dispatch;
+
        if (device->scroll.want_method == device->scroll.method &&
            device->scroll.want_button == device->scroll.button)
                return;
 
-       if (evdev_any_button_down(device))
+       if (fallback_any_button_down(dispatch, device))
                return;
 
        device->scroll.method = device->scroll.want_method;
@@ -1074,10 +1426,17 @@ evdev_scroll_get_default_method(struct libinput_device *device)
 {
        struct evdev_device *evdev = (struct evdev_device *)device;
 
-       if (libevdev_has_property(evdev->evdev, INPUT_PROP_POINTING_STICK))
+       if (evdev->tags & EVDEV_TAG_TRACKPOINT)
                return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
-       else
-               return LIBINPUT_CONFIG_SCROLL_NO_SCROLL;
+
+       /* Mice without a scroll wheel but with middle button have on-button
+        * scrolling by default */
+       if (!libevdev_has_event_code(evdev->evdev, EV_REL, REL_WHEEL) &&
+           !libevdev_has_event_code(evdev->evdev, EV_REL, REL_HWHEEL) &&
+           libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE))
+               return LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
+
+       return LIBINPUT_CONFIG_SCROLL_NO_SCROLL;
 }
 
 static enum libinput_config_status
@@ -1107,17 +1466,18 @@ evdev_scroll_get_default_button(struct libinput_device *device)
 {
        struct evdev_device *evdev = (struct evdev_device *)device;
 
-       if (libevdev_has_property(evdev->evdev, INPUT_PROP_POINTING_STICK))
+       if( libevdev_has_event_code(evdev->evdev, EV_KEY, BTN_MIDDLE))
                return BTN_MIDDLE;
-       else
-               return 0;
+
+       return 0;
 }
 
-static int
+static void
 evdev_init_button_scroll(struct evdev_device *device,
                         void (*change_scroll_method)(struct evdev_device *))
 {
-       libinput_timer_init(&device->scroll.timer, device->base.seat->libinput,
+       libinput_timer_init(&device->scroll.timer,
+                           evdev_libinput_context(device),
                            evdev_button_scroll_timeout, device);
        device->scroll.config.get_methods = evdev_scroll_get_methods;
        device->scroll.config.set_method = evdev_scroll_set_method;
@@ -1132,20 +1492,18 @@ evdev_init_button_scroll(struct evdev_device *device,
        device->scroll.button = evdev_scroll_get_default_button((struct libinput_device *)device);
        device->scroll.want_button = device->scroll.button;
        device->scroll.change_scroll_method = change_scroll_method;
-
-       return 0;
 }
 
-static void
+void
 evdev_init_calibration(struct evdev_device *device,
-                      struct evdev_dispatch *dispatch)
+                      struct libinput_device_config_calibration *calibration)
 {
-       device->base.config.calibration = &dispatch->calibration;
+       device->base.config.calibration = calibration;
 
-       dispatch->calibration.has_matrix = evdev_calibration_has_matrix;
-       dispatch->calibration.set_matrix = evdev_calibration_set_matrix;
-       dispatch->calibration.get_matrix = evdev_calibration_get_matrix;
-       dispatch->calibration.get_default_matrix = evdev_calibration_get_default_matrix;
+       calibration->has_matrix = evdev_calibration_has_matrix;
+       calibration->set_matrix = evdev_calibration_set_matrix;
+       calibration->get_matrix = evdev_calibration_get_matrix;
+       calibration->get_default_matrix = evdev_calibration_get_default_matrix;
 }
 
 static void
@@ -1205,51 +1563,234 @@ evdev_init_natural_scroll(struct evdev_device *device)
        device->base.config.natural_scroll = &device->scroll.config_natural;
 }
 
-int
-evdev_scroll_get_wheel_click_angle(struct evdev_device *device)
+static int
+evdev_rotation_config_is_available(struct libinput_device *device)
+{
+       /* This function only gets called when we support rotation */
+       return 1;
+}
+
+static enum libinput_config_status
+evdev_rotation_config_set_angle(struct libinput_device *libinput_device,
+                               unsigned int degrees_cw)
+{
+       struct evdev_device *device = (struct evdev_device*)libinput_device;
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)device->dispatch;
+
+       dispatch->rotation.angle = degrees_cw;
+       matrix_init_rotate(&dispatch->rotation.matrix, degrees_cw);
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static unsigned int
+evdev_rotation_config_get_angle(struct libinput_device *libinput_device)
+{
+       struct evdev_device *device = (struct evdev_device*)libinput_device;
+       struct fallback_dispatch *dispatch = (struct fallback_dispatch*)device->dispatch;
+
+       return dispatch->rotation.angle;
+}
+
+static unsigned int
+evdev_rotation_config_get_default_angle(struct libinput_device *device)
+{
+       return 0;
+}
+
+static void
+evdev_init_rotation(struct evdev_device *device,
+                   struct fallback_dispatch *dispatch)
+{
+       if ((device->model_flags & EVDEV_MODEL_TRACKBALL) == 0)
+               return;
+
+       dispatch->rotation.config.is_available = evdev_rotation_config_is_available;
+       dispatch->rotation.config.set_angle = evdev_rotation_config_set_angle;
+       dispatch->rotation.config.get_angle = evdev_rotation_config_get_angle;
+       dispatch->rotation.config.get_default_angle = evdev_rotation_config_get_default_angle;
+       dispatch->rotation.is_enabled = false;
+       matrix_init_identity(&dispatch->rotation.matrix);
+       device->base.config.rotation = &dispatch->rotation.config;
+}
+
+static inline int
+evdev_need_mtdev(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+
+       return (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) &&
+               libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) &&
+               !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT));
+}
+
+/* Fake MT devices have the ABS_MT_SLOT bit set because of
+   the limited ABS_* range - they aren't MT devices, they
+   just have too many ABS_ axes */
+static inline bool
+evdev_is_fake_mt_device(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+
+       return libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) &&
+               libevdev_get_num_slots(evdev) == -1;
+}
+
+static inline int
+fallback_dispatch_init_slots(struct fallback_dispatch *dispatch,
+                            struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+       struct mt_slot *slots;
+       int num_slots;
+       int active_slot;
+       int slot;
+
+       if (evdev_is_fake_mt_device(device) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
+                return 0;
+
+       /* We only handle the slotted Protocol B in libinput.
+          Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT
+          require mtdev for conversion. */
+       if (evdev_need_mtdev(device)) {
+               device->mtdev = mtdev_new_open(device->fd);
+               if (!device->mtdev)
+                       return -1;
+
+               /* pick 10 slots as default for type A
+                  devices. */
+               num_slots = 10;
+               active_slot = device->mtdev->caps.slot.value;
+       } else {
+               num_slots = libevdev_get_num_slots(device->evdev);
+               active_slot = libevdev_get_current_slot(evdev);
+       }
+
+       slots = calloc(num_slots, sizeof(struct mt_slot));
+       if (!slots)
+               return -1;
+
+       for (slot = 0; slot < num_slots; ++slot) {
+               slots[slot].seat_slot = -1;
+
+               if (evdev_need_mtdev(device))
+                       continue;
+
+               slots[slot].point.x = libevdev_get_slot_value(evdev,
+                                                             slot,
+                                                             ABS_MT_POSITION_X);
+               slots[slot].point.y = libevdev_get_slot_value(evdev,
+                                                             slot,
+                                                             ABS_MT_POSITION_Y);
+       }
+       dispatch->mt.slots = slots;
+       dispatch->mt.slots_len = num_slots;
+       dispatch->mt.slot = active_slot;
+
+       if (device->abs.absinfo_x->fuzz || device->abs.absinfo_y->fuzz) {
+               dispatch->mt.want_hysteresis = true;
+               dispatch->mt.hysteresis_margin.x = device->abs.absinfo_x->fuzz/2;
+               dispatch->mt.hysteresis_margin.y = device->abs.absinfo_y->fuzz/2;
+       }
+
+       return 0;
+}
+
+static inline void
+fallback_dispatch_init_rel(struct fallback_dispatch *dispatch,
+                          struct evdev_device *device)
+{
+       dispatch->rel.x = 0;
+       dispatch->rel.y = 0;
+}
+
+static inline void
+fallback_dispatch_init_abs(struct fallback_dispatch *dispatch,
+                          struct evdev_device *device)
 {
-       return device->scroll.wheel_click_angle;
+       if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_X))
+               return;
+
+       dispatch->abs.point.x = device->abs.absinfo_x->value;
+       dispatch->abs.point.y = device->abs.absinfo_y->value;
+       dispatch->abs.seat_slot = -1;
 }
 
 static struct evdev_dispatch *
 fallback_dispatch_create(struct libinput_device *device)
 {
-       struct evdev_dispatch *dispatch = zalloc(sizeof *dispatch);
+       struct fallback_dispatch *dispatch = zalloc(sizeof *dispatch);
        struct evdev_device *evdev_device = (struct evdev_device *)device;
 
        if (dispatch == NULL)
                return NULL;
 
-       dispatch->interface = &fallback_interface;
+       dispatch->base.interface = &fallback_interface;
+       dispatch->pending_event = EVDEV_NONE;
 
-       if (evdev_device->left_handed.want_enabled &&
-           evdev_init_left_handed(evdev_device,
-                                  evdev_change_to_left_handed) == -1) {
+       fallback_dispatch_init_rel(dispatch, evdev_device);
+       fallback_dispatch_init_abs(dispatch, evdev_device);
+       if (fallback_dispatch_init_slots(dispatch, evdev_device) == -1) {
                free(dispatch);
                return NULL;
        }
 
-       if (evdev_device->scroll.want_button &&
-           evdev_init_button_scroll(evdev_device,
-                                    evdev_change_scroll_method) == -1) {
-               free(dispatch);
-               return NULL;
-       }
+       if (evdev_device->left_handed.want_enabled)
+               evdev_init_left_handed(evdev_device,
+                                      evdev_change_to_left_handed);
+
+       if (evdev_device->scroll.want_button)
+               evdev_init_button_scroll(evdev_device,
+                                        evdev_change_scroll_method);
 
        if (evdev_device->scroll.natural_scrolling_enabled)
                evdev_init_natural_scroll(evdev_device);
 
-       evdev_init_calibration(evdev_device, dispatch);
-       evdev_init_sendevents(evdev_device, dispatch);
+       evdev_init_calibration(evdev_device, &dispatch->calibration);
+       evdev_init_sendevents(evdev_device, &dispatch->base);
+       evdev_init_rotation(evdev_device, dispatch);
+
+       /* BTN_MIDDLE is set on mice even when it's not present. So
+        * we can only use the absence of BTN_MIDDLE to mean something, i.e.
+        * we enable it by default on anything that only has L&R.
+        * If we have L&R and no middle, we don't expose it as config
+        * option */
+       if (libevdev_has_event_code(evdev_device->evdev, EV_KEY, BTN_LEFT) &&
+           libevdev_has_event_code(evdev_device->evdev, EV_KEY, BTN_RIGHT)) {
+               bool has_middle = libevdev_has_event_code(evdev_device->evdev,
+                                                         EV_KEY,
+                                                         BTN_MIDDLE);
+               bool want_config = has_middle;
+               bool enable_by_default = !has_middle;
+
+               evdev_init_middlebutton(evdev_device,
+                                       enable_by_default,
+                                       want_config);
+       }
 
-       return dispatch;
+       return &dispatch->base;
 }
 
 static inline void
 evdev_process_event(struct evdev_device *device, struct input_event *e)
 {
        struct evdev_dispatch *dispatch = device->dispatch;
-       uint64_t time = e->time.tv_sec * 1000ULL + e->time.tv_usec / 1000;
+       uint64_t time = s2us(e->time.tv_sec) + e->time.tv_usec;
+
+#if 0
+       if (libevdev_event_is_code(e, EV_SYN, SYN_REPORT))
+               log_debug(evdev_libinput_context(device),
+                         "-------------- EV_SYN ------------\n");
+       else
+               log_debug(evdev_libinput_context(device),
+                         "%-7s %-16s %-20s %4d\n",
+                         evdev_device_get_sysname(device),
+                         libevdev_event_type_get_name(e->type),
+                         libevdev_event_code_get_name(e->type, e->code),
+                         e->value);
+#endif
 
        dispatch->interface->process(dispatch, device, e, time);
 }
@@ -1258,7 +1799,6 @@ static inline void
 evdev_device_dispatch_one(struct evdev_device *device,
                          struct input_event *ev)
 {
-       TRACE_INPUT_BEGIN(evdev_device_dispatch_one);
        if (!device->mtdev) {
                evdev_process_event(device, ev);
        } else {
@@ -1271,7 +1811,6 @@ evdev_device_dispatch_one(struct evdev_device *device,
                        }
                }
        }
-       TRACE_INPUT_END();
 }
 
 static int
@@ -1295,7 +1834,7 @@ static void
 evdev_device_dispatch(void *data)
 {
        struct evdev_device *device = data;
-       struct libinput *libinput = device->base.seat->libinput;
+       struct libinput *libinput = evdev_libinput_context(device);
        struct input_event ev;
        int rc;
 
@@ -1306,20 +1845,10 @@ evdev_device_dispatch(void *data)
                rc = libevdev_next_event(device->evdev,
                                         LIBEVDEV_READ_FLAG_NORMAL, &ev);
                if (rc == LIBEVDEV_READ_STATUS_SYNC) {
-                       switch (ratelimit_test(&device->syn_drop_limit)) {
-                       case RATELIMIT_PASS:
-                               log_info(libinput, "SYN_DROPPED event from "
-                                        "\"%s\" - some input events have "
-                                        "been lost.\n", device->devname);
-                               break;
-                       case RATELIMIT_THRESHOLD:
-                               log_info(libinput, "SYN_DROPPED flood "
-                                        "from \"%s\"\n",
-                                        device->devname);
-                               break;
-                       case RATELIMIT_EXCEEDED:
-                               break;
-                       }
+                       log_info_ratelimit(libinput,
+                                          &device->syn_drop_limit,
+                                          "SYN_DROPPED event from \"%s\" - some input events have been lost.\n",
+                                          device->devname);
 
                        /* send one more sync event so we handle all
                           currently pending events before we sync up
@@ -1341,6 +1870,29 @@ evdev_device_dispatch(void *data)
        }
 }
 
+static inline bool
+evdev_init_accel(struct evdev_device *device,
+                enum libinput_config_accel_profile which)
+{
+       struct motion_filter *filter;
+
+       if (which == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT)
+               filter = create_pointer_accelerator_filter_flat(device->dpi);
+       else if (device->tags & EVDEV_TAG_TRACKPOINT)
+               filter = create_pointer_accelerator_filter_trackpoint(device->dpi);
+       else if (device->dpi < DEFAULT_MOUSE_DPI)
+               filter = create_pointer_accelerator_filter_linear_low_dpi(device->dpi);
+       else
+               filter = create_pointer_accelerator_filter_linear(device->dpi);
+
+       if (!filter)
+               return false;
+
+       evdev_device_init_pointer_acceleration(device, filter);
+
+       return true;
+}
+
 static int
 evdev_accel_config_available(struct libinput_device *device)
 {
@@ -1374,75 +1926,171 @@ evdev_accel_config_get_default_speed(struct libinput_device *device)
        return 0.0;
 }
 
-int
-evdev_device_init_pointer_acceleration(struct evdev_device *device)
+static uint32_t
+evdev_accel_config_get_profiles(struct libinput_device *libinput_device)
 {
-       device->pointer.filter =
-               create_pointer_accelerator_filter(
-                       pointer_accel_profile_linear);
+       struct evdev_device *device = (struct evdev_device*)libinput_device;
+
        if (!device->pointer.filter)
-               return -1;
+               return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+
+       return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE |
+               LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
+}
+
+static enum libinput_config_status
+evdev_accel_config_set_profile(struct libinput_device *libinput_device,
+                              enum libinput_config_accel_profile profile)
+{
+       struct evdev_device *device = (struct evdev_device*)libinput_device;
+       struct motion_filter *filter;
+       double speed;
 
-       device->pointer.config.available = evdev_accel_config_available;
-       device->pointer.config.set_speed = evdev_accel_config_set_speed;
-       device->pointer.config.get_speed = evdev_accel_config_get_speed;
-       device->pointer.config.get_default_speed = evdev_accel_config_get_default_speed;
-       device->base.config.accel = &device->pointer.config;
+       filter = device->pointer.filter;
+       if (filter_get_type(filter) == profile)
+               return LIBINPUT_CONFIG_STATUS_SUCCESS;
 
-       evdev_accel_config_set_speed(&device->base,
-                    evdev_accel_config_get_default_speed(&device->base));
+       speed = filter_get_speed(filter);
+       device->pointer.filter = NULL;
 
-       return 0;
+       if (evdev_init_accel(device, profile)) {
+               evdev_accel_config_set_speed(libinput_device, speed);
+               filter_destroy(filter);
+       } else {
+               device->pointer.filter = filter;
+       }
+
+       return LIBINPUT_CONFIG_STATUS_SUCCESS;
 }
 
-static inline int
-evdev_need_mtdev(struct evdev_device *device)
+static enum libinput_config_accel_profile
+evdev_accel_config_get_profile(struct libinput_device *libinput_device)
 {
-       struct libevdev *evdev = device->evdev;
+       struct evdev_device *device = (struct evdev_device*)libinput_device;
 
-       return (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) &&
-               libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) &&
-               !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT));
+       return filter_get_type(device->pointer.filter);
+}
+
+static enum libinput_config_accel_profile
+evdev_accel_config_get_default_profile(struct libinput_device *libinput_device)
+{
+       struct evdev_device *device = (struct evdev_device*)libinput_device;
+
+       if (!device->pointer.filter)
+               return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+
+       /* No device has a flat profile as default */
+       return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
+}
+
+void
+evdev_device_init_pointer_acceleration(struct evdev_device *device,
+                                      struct motion_filter *filter)
+{
+       device->pointer.filter = filter;
+
+       if (device->base.config.accel == NULL) {
+               device->pointer.config.available = evdev_accel_config_available;
+               device->pointer.config.set_speed = evdev_accel_config_set_speed;
+               device->pointer.config.get_speed = evdev_accel_config_get_speed;
+               device->pointer.config.get_default_speed = evdev_accel_config_get_default_speed;
+               device->pointer.config.get_profiles = evdev_accel_config_get_profiles;
+               device->pointer.config.set_profile = evdev_accel_config_set_profile;
+               device->pointer.config.get_profile = evdev_accel_config_get_profile;
+               device->pointer.config.get_default_profile = evdev_accel_config_get_default_profile;
+               device->base.config.accel = &device->pointer.config;
+
+               evdev_accel_config_set_speed(&device->base,
+                            evdev_accel_config_get_default_speed(&device->base));
+       }
+}
+
+static inline bool
+evdev_read_wheel_click_prop(struct evdev_device *device,
+                           const char *prop,
+                           int *angle)
+{
+       int val;
+
+       *angle = DEFAULT_WHEEL_CLICK_ANGLE;
+       prop = udev_device_get_property_value(device->udev_device, prop);
+       if (!prop)
+               return false;
+
+       val = parse_mouse_wheel_click_angle_property(prop);
+       if (val) {
+               *angle = val;
+               return true;
+       }
+
+       log_error(evdev_libinput_context(device),
+                 "Mouse wheel click angle '%s' is present but invalid,"
+                 "using %d degrees instead\n",
+                 device->devname,
+                 DEFAULT_WHEEL_CLICK_ANGLE);
+
+       return false;
 }
 
-static void
-evdev_tag_device(struct evdev_device *device)
+static inline struct wheel_angle
+evdev_read_wheel_click_props(struct evdev_device *device)
 {
-       if (device->dispatch->interface->tag_device)
-               device->dispatch->interface->tag_device(device,
-                                                       device->udev_device);
+       struct wheel_angle angles;
+
+       evdev_read_wheel_click_prop(device,
+                                   "MOUSE_WHEEL_CLICK_ANGLE",
+                                   &angles.x);
+       if (!evdev_read_wheel_click_prop(device,
+                                        "MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL",
+                                        &angles.y))
+               angles.y = angles.x;
+
+       return angles;
 }
 
 static inline int
-evdev_read_wheel_click_prop(struct evdev_device *device)
-{
-       struct libinput *libinput = device->base.seat->libinput;
-       const char *prop;
-       int angle = DEFAULT_WHEEL_CLICK_ANGLE;
-
-       prop = udev_device_get_property_value(device->udev_device,
-                                             "MOUSE_WHEEL_CLICK_ANGLE");
-       if (prop) {
-               angle = parse_mouse_wheel_click_angle_property(prop);
-               if (!angle) {
-                       log_error(libinput,
-                                 "Mouse wheel click angle '%s' is present but invalid,"
-                                 "using %d degrees instead\n",
-                                 device->devname,
-                                 DEFAULT_WHEEL_CLICK_ANGLE);
-                       angle = DEFAULT_WHEEL_CLICK_ANGLE;
+evdev_get_trackpoint_dpi(struct evdev_device *device)
+{
+       struct libinput *libinput = evdev_libinput_context(device);
+       const char *trackpoint_accel;
+       double accel = DEFAULT_TRACKPOINT_ACCEL;
+
+       trackpoint_accel = udev_device_get_property_value(
+                               device->udev_device, "POINTINGSTICK_CONST_ACCEL");
+       if (trackpoint_accel) {
+               accel = parse_trackpoint_accel_property(trackpoint_accel);
+               if (accel == 0.0) {
+                       log_error(libinput, "Trackpoint accel property for "
+                                           "'%s' is present but invalid, "
+                                           "using %.2f instead\n",
+                                           device->devname,
+                                           DEFAULT_TRACKPOINT_ACCEL);
+                       accel = DEFAULT_TRACKPOINT_ACCEL;
                }
+               log_info(libinput,
+                         "Device '%s' set to const accel %.2f\n",
+                         device->devname,
+                         accel);
        }
 
-       return angle;
+       return DEFAULT_MOUSE_DPI / accel;
 }
+
 static inline int
 evdev_read_dpi_prop(struct evdev_device *device)
 {
-       struct libinput *libinput = device->base.seat->libinput;
+       struct libinput *libinput = evdev_libinput_context(device);
        const char *mouse_dpi;
        int dpi = DEFAULT_MOUSE_DPI;
 
+       /*
+        * Trackpoints do not have dpi, instead hwdb may contain a
+        * POINTINGSTICK_CONST_ACCEL value to compensate for sensitivity
+        * differences between models, we translate this to a fake dpi.
+        */
+       if (device->tags & EVDEV_TAG_TRACKPOINT)
+               return evdev_get_trackpoint_dpi(device);
+
        mouse_dpi = udev_device_get_property_value(device->udev_device,
                                                   "MOUSE_DPI");
        if (mouse_dpi) {
@@ -1455,28 +2103,147 @@ evdev_read_dpi_prop(struct evdev_device *device)
                                            DEFAULT_MOUSE_DPI);
                        dpi = DEFAULT_MOUSE_DPI;
                }
+               log_info(libinput,
+                        "Device '%s' set to %d DPI\n",
+                        device->devname,
+                        dpi);
        }
 
        return dpi;
 }
 
+static inline uint32_t
+evdev_read_model_flags(struct evdev_device *device)
+{
+       const struct model_map {
+               const char *property;
+               enum evdev_device_model model;
+       } model_map[] = {
+#define MODEL(name) { "LIBINPUT_MODEL_" #name, EVDEV_MODEL_##name }
+               MODEL(LENOVO_X230),
+               MODEL(LENOVO_X230),
+               MODEL(LENOVO_X220_TOUCHPAD_FW81),
+               MODEL(CHROMEBOOK),
+               MODEL(SYSTEM76_BONOBO),
+               MODEL(SYSTEM76_GALAGO),
+               MODEL(SYSTEM76_KUDU),
+               MODEL(CLEVO_W740SU),
+               MODEL(APPLE_TOUCHPAD),
+               MODEL(WACOM_TOUCHPAD),
+               MODEL(ALPS_TOUCHPAD),
+               MODEL(SYNAPTICS_SERIAL_TOUCHPAD),
+               MODEL(JUMPING_SEMI_MT),
+               MODEL(ELANTECH_TOUCHPAD),
+               MODEL(APPLE_INTERNAL_KEYBOARD),
+               MODEL(CYBORG_RAT),
+               MODEL(CYAPA),
+               MODEL(HP_STREAM11_TOUCHPAD),
+               MODEL(LENOVO_T450_TOUCHPAD),
+               MODEL(DELL_TOUCHPAD),
+               MODEL(TRACKBALL),
+               MODEL(APPLE_MAGICMOUSE),
+               MODEL(HP8510_TOUCHPAD),
+#undef MODEL
+               { "ID_INPUT_TRACKBALL", EVDEV_MODEL_TRACKBALL },
+               { NULL, EVDEV_MODEL_DEFAULT },
+       };
+       const struct model_map *m = model_map;
+       uint32_t model_flags = 0;
+       const char *val;
+
+       while (m->property) {
+               val = udev_device_get_property_value(device->udev_device,
+                                                    m->property);
+               if (val && !streq(val, "0")) {
+                       log_debug(evdev_libinput_context(device),
+                                 "%s: tagged as %s\n",
+                                 evdev_device_get_sysname(device),
+                                 m->property);
+                       model_flags |= m->model;
+               }
+               m++;
+       }
+
+       return model_flags;
+}
+
+static inline bool
+evdev_read_attr_res_prop(struct evdev_device *device,
+                        size_t *xres,
+                        size_t *yres)
+{
+       struct udev_device *udev;
+       const char *res_prop;
+
+       udev = device->udev_device;
+       res_prop = udev_device_get_property_value(udev,
+                                                  "LIBINPUT_ATTR_RESOLUTION_HINT");
+       if (!res_prop)
+               return false;
+
+       return parse_dimension_property(res_prop, xres, yres);
+}
+
+static inline bool
+evdev_read_attr_size_prop(struct evdev_device *device,
+                         size_t *size_x,
+                         size_t *size_y)
+{
+       struct udev_device *udev;
+       const char *size_prop;
+
+       udev = device->udev_device;
+       size_prop = udev_device_get_property_value(udev,
+                                                  "LIBINPUT_ATTR_SIZE_HINT");
+       if (!size_prop)
+               return false;
+
+       return parse_dimension_property(size_prop, size_x, size_y);
+}
+
+/* Return 1 if the device is set to the fake resolution or 0 otherwise */
 static inline int
-evdev_fix_abs_resolution(struct libevdev *evdev,
-                        unsigned int code,
-                        const struct input_absinfo *absinfo)
-{
-       struct input_absinfo fixed;
-
-       if (absinfo->resolution == 0) {
-               fixed = *absinfo;
-               fixed.resolution = 1;
-               /* libevdev_set_abs_info() changes the absinfo we already
-                  have a pointer to, no need to fetch it again */
-               libevdev_set_abs_info(evdev, code, &fixed);
-               return 1;
-       } else {
+evdev_fix_abs_resolution(struct evdev_device *device,
+                        unsigned int xcode,
+                        unsigned int ycode)
+{
+       struct libevdev *evdev = device->evdev;
+       const struct input_absinfo *absx, *absy;
+       size_t widthmm = 0, heightmm = 0;
+       size_t xres = EVDEV_FAKE_RESOLUTION,
+              yres = EVDEV_FAKE_RESOLUTION;
+
+       if (!(xcode == ABS_X && ycode == ABS_Y)  &&
+           !(xcode == ABS_MT_POSITION_X && ycode == ABS_MT_POSITION_Y)) {
+               log_bug_libinput(evdev_libinput_context(device),
+                                "Invalid x/y code combination %d/%d\n",
+                                xcode, ycode);
+               return 0;
+       }
+
+       absx = libevdev_get_abs_info(evdev, xcode);
+       absy = libevdev_get_abs_info(evdev, ycode);
+
+       if (absx->resolution != 0 || absy->resolution != 0)
                return 0;
+
+       /* Note: we *do not* override resolutions if provided by the kernel.
+        * If a device needs this, add it to 60-evdev.hwdb. The libinput
+        * property is only for general size hints where we can make
+        * educated guesses but don't know better.
+        */
+       if (!evdev_read_attr_res_prop(device, &xres, &yres) &&
+           evdev_read_attr_size_prop(device, &widthmm, &heightmm)) {
+               xres = (absx->maximum - absx->minimum)/widthmm;
+               yres = (absy->maximum - absy->minimum)/heightmm;
        }
+
+       /* libevdev_set_abs_resolution() changes the absinfo we already
+          have a pointer to, no need to fetch it again */
+       libevdev_set_abs_resolution(evdev, xcode, xres);
+       libevdev_set_abs_resolution(evdev, ycode, yres);
+
+       return xres == EVDEV_FAKE_RESOLUTION;
 }
 
 static enum evdev_device_udev_tags
@@ -1505,18 +2272,168 @@ evdev_device_get_udev_tags(struct evdev_device *device,
        return tags;
 }
 
-static int
-evdev_configure_device(struct evdev_device *device)
+static inline void
+evdev_fix_android_mt(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) ||
+           libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
+               return;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y) ||
+           evdev_is_fake_mt_device(device))
+               return;
+
+       libevdev_enable_event_code(evdev, EV_ABS, ABS_X,
+                     libevdev_get_abs_info(evdev, ABS_MT_POSITION_X));
+       libevdev_enable_event_code(evdev, EV_ABS, ABS_Y,
+                     libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y));
+}
+
+static inline bool
+evdev_check_min_max(struct evdev_device *device, unsigned int code)
 {
-       struct libinput *libinput = device->base.seat->libinput;
        struct libevdev *evdev = device->evdev;
        const struct input_absinfo *absinfo;
-       struct mt_slot *slots;
-       int num_slots;
-       int active_slot;
-       int slot;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, code))
+               return true;
+
+       absinfo = libevdev_get_abs_info(evdev, code);
+       if (absinfo->minimum == absinfo->maximum) {
+               /* Some devices have a sort-of legitimate min/max of 0 for
+                * ABS_MISC and above (e.g. Roccat Kone XTD). Don't ignore
+                * them, simply disable the axes so we won't get events,
+                * we don't know what to do with them anyway.
+                */
+               if (absinfo->minimum == 0 &&
+                   code >= ABS_MISC && code < ABS_MT_SLOT) {
+                       log_info(evdev_libinput_context(device),
+                                "Disabling EV_ABS %#x on device '%s' (min == max == 0)\n",
+                                code,
+                                device->devname);
+                       libevdev_disable_event_code(device->evdev,
+                                                   EV_ABS,
+                                                   code);
+               } else {
+                       log_bug_kernel(evdev_libinput_context(device),
+                                      "Device '%s' has min == max on %s\n",
+                                      device->devname,
+                                      libevdev_event_code_get_name(EV_ABS, code));
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static bool
+evdev_reject_device(struct evdev_device *device)
+{
+       struct libinput *libinput = evdev_libinput_context(device);
+       struct libevdev *evdev = device->evdev;
+       unsigned int code;
+       const struct input_absinfo *absx, *absy;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) ^
+           libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
+               return true;
+
+       if (libevdev_has_event_code(evdev, EV_REL, REL_X) ^
+           libevdev_has_event_code(evdev, EV_REL, REL_Y))
+               return true;
+
+       if (!evdev_is_fake_mt_device(device) &&
+           libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ^
+           libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
+               return true;
+
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) {
+               absx = libevdev_get_abs_info(evdev, ABS_X);
+               absy = libevdev_get_abs_info(evdev, ABS_Y);
+               if ((absx->resolution == 0 && absy->resolution != 0) ||
+                   (absx->resolution != 0 && absy->resolution == 0)) {
+                       log_bug_kernel(libinput,
+                                      "Kernel has only x or y resolution, not both.\n");
+                       return true;
+               }
+       }
+
+       if (!evdev_is_fake_mt_device(device) &&
+           libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X)) {
+               absx = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
+               absy = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
+               if ((absx->resolution == 0 && absy->resolution != 0) ||
+                   (absx->resolution != 0 && absy->resolution == 0)) {
+                       log_bug_kernel(libinput,
+                                      "Kernel has only x or y MT resolution, not both.\n");
+                       return true;
+               }
+       }
+
+       for (code = 0; code < ABS_CNT; code++) {
+               switch (code) {
+               case ABS_MISC:
+               case ABS_MT_SLOT:
+               case ABS_MT_TOOL_TYPE:
+                       break;
+               default:
+                       if (!evdev_check_min_max(device, code))
+                               return true;
+               }
+       }
+
+       return false;
+}
+
+static void
+evdev_extract_abs_axes(struct evdev_device *device)
+{
+       struct libevdev *evdev = device->evdev;
+
+       if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
+                return;
+
+       if (evdev_fix_abs_resolution(device, ABS_X, ABS_Y))
+               device->abs.is_fake_resolution = true;
+       device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_X);
+       device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_Y);
+       device->abs.dimensions.x = abs(device->abs.absinfo_x->maximum -
+                                      device->abs.absinfo_x->minimum);
+       device->abs.dimensions.y = abs(device->abs.absinfo_y->maximum -
+                                      device->abs.absinfo_y->minimum);
+
+       if (evdev_is_fake_mt_device(device) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) ||
+           !libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y))
+                return;
+
+       if (evdev_fix_abs_resolution(device,
+                                    ABS_MT_POSITION_X,
+                                    ABS_MT_POSITION_Y))
+               device->abs.is_fake_resolution = true;
+
+       device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
+       device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
+       device->abs.dimensions.x = abs(device->abs.absinfo_x->maximum -
+                                      device->abs.absinfo_x->minimum);
+       device->abs.dimensions.y = abs(device->abs.absinfo_y->maximum -
+                                      device->abs.absinfo_y->minimum);
+       device->is_mt = 1;
+}
+
+static struct evdev_dispatch *
+evdev_configure_device(struct evdev_device *device)
+{
+       struct libinput *libinput = evdev_libinput_context(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;
+       struct evdev_dispatch *dispatch;
 
        udev_tags = evdev_device_get_udev_tags(device, device->udev_device);
 
@@ -1525,19 +2442,29 @@ evdev_configure_device(struct evdev_device *device)
                log_info(libinput,
                         "input device '%s', %s not tagged as input device\n",
                         device->devname, devnode);
-               return -1;
+               return NULL;
        }
 
        log_info(libinput,
-                "input device '%s', %s is tagged by udev as:%s%s%s%s%s%s%s\n",
+                "input device '%s', %s is tagged by udev as:%s%s%s%s%s%s%s%s%s%s\n",
                 device->devname, devnode,
                 udev_tags & EVDEV_UDEV_TAG_KEYBOARD ? " Keyboard" : "",
                 udev_tags & EVDEV_UDEV_TAG_MOUSE ? " Mouse" : "",
                 udev_tags & EVDEV_UDEV_TAG_TOUCHPAD ? " Touchpad" : "",
                 udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN ? " Touchscreen" : "",
                 udev_tags & EVDEV_UDEV_TAG_TABLET ? " Tablet" : "",
+                udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK ? " Pointingstick" : "",
                 udev_tags & EVDEV_UDEV_TAG_JOYSTICK ? " Joystick" : "",
-                udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER ? " Accelerometer" : "");
+                udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER ? " Accelerometer" : "",
+                udev_tags & EVDEV_UDEV_TAG_TABLET_PAD ? " TabletPad" : "",
+                udev_tags & EVDEV_UDEV_TAG_TRACKBALL ? " Trackball" : "");
+
+       if (udev_tags & EVDEV_UDEV_TAG_ACCELEROMETER) {
+               log_info(libinput,
+                        "input device '%s', %s is an accelerometer, ignoring\n",
+                        device->devname, devnode);
+               return NULL;
+       }
 
        /* libwacom *adds* TABLET, TOUCHPAD but leaves JOYSTICK in place, so
           make sure we only ignore real joystick devices */
@@ -1545,103 +2472,65 @@ evdev_configure_device(struct evdev_device *device)
                log_info(libinput,
                         "input device '%s', %s is a joystick, ignoring\n",
                         device->devname, devnode);
-               return -1;
+               return NULL;
+       }
+
+       if (evdev_reject_device(device)) {
+               log_info(libinput,
+                        "input device '%s', %s was rejected.\n",
+                        device->devname, devnode);
+               return NULL;
        }
 
-       device->abs.absinfo_orientation = libevdev_get_abs_info(evdev, ABS_MT_ORIENTATION);
-       device->abs.absinfo_pressure = libevdev_get_abs_info(evdev, ABS_MT_PRESSURE);
-       device->abs.absinfo_major = libevdev_get_abs_info(evdev, ABS_MT_TOUCH_MAJOR);
-       device->abs.absinfo_minor = libevdev_get_abs_info(evdev, ABS_MT_TOUCH_MINOR);
+       if (!evdev_is_fake_mt_device(device))
+               evdev_fix_android_mt(device);
 
-       if (libevdev_has_event_type(evdev, EV_ABS)) {
-               if ((absinfo = libevdev_get_abs_info(evdev, ABS_X))) {
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_X,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_x = absinfo;
-               }
-               if ((absinfo = libevdev_get_abs_info(evdev, ABS_Y))) {
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_Y,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_y = absinfo;
-               }
+       if (libevdev_has_event_code(evdev, EV_ABS, ABS_X)) {
+               evdev_extract_abs_axes(device);
 
-               /* Fake MT devices have the ABS_MT_SLOT bit set because of
-                  the limited ABS_* range - they aren't MT devices, they
-                  just have too many ABS_ axes */
-               if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT) &&
-                   libevdev_get_num_slots(evdev) == -1) {
+               if (evdev_is_fake_mt_device(device))
                        udev_tags &= ~EVDEV_UDEV_TAG_TOUCHSCREEN;
-               } else if (libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) &&
-                          libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y)) {
-                       absinfo = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_MT_POSITION_X,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_x = absinfo;
-
-                       absinfo = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
-                       if (evdev_fix_abs_resolution(evdev,
-                                                    ABS_MT_POSITION_Y,
-                                                    absinfo))
-                               device->abs.fake_resolution = 1;
-                       device->abs.absinfo_y = absinfo;
-                       device->is_mt = 1;
-
-                       /* We only handle the slotted Protocol B in libinput.
-                          Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT
-                          require mtdev for conversion. */
-                       if (evdev_need_mtdev(device)) {
-                               device->mtdev = mtdev_new_open(device->fd);
-                               if (!device->mtdev)
-                                       return -1;
-
-                               num_slots = device->mtdev->caps.slot.maximum;
-                               if (device->mtdev->caps.slot.minimum < 0 ||
-                                   num_slots <= 0)
-                                       return -1;
-                               active_slot = device->mtdev->caps.slot.value;
-                       } else {
-                               num_slots = libevdev_get_num_slots(device->evdev);
-                               active_slot = libevdev_get_current_slot(evdev);
-                       }
+       }
 
-                       slots = calloc(num_slots, sizeof(struct mt_slot));
-                       if (!slots)
-                               return -1;
-
-                       for (slot = 0; slot < num_slots; ++slot) {
-                               slots[slot].seat_slot = -1;
-                               slots[slot].x = libevdev_get_slot_value(evdev, slot, ABS_MT_POSITION_X);
-                               slots[slot].y = libevdev_get_slot_value(evdev, slot, ABS_MT_POSITION_Y);
-                               slots[slot].area.major = libevdev_get_slot_value(evdev, slot, ABS_MT_TOUCH_MAJOR);
-                               slots[slot].area.minor = libevdev_get_slot_value(evdev, slot, ABS_MT_TOUCH_MINOR);
-                               slots[slot].area.orientation = libevdev_get_slot_value(evdev, slot, ABS_MT_ORIENTATION);
-                               slots[slot].pressure = libevdev_get_slot_value(evdev, slot, ABS_MT_PRESSURE);
-                       }
-                       device->mt.slots = slots;
-                       device->mt.slots_len = num_slots;
-                       device->mt.slot = active_slot;
-               }
+       /* 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;
+
+       /* libwacom assigns tablet _and_ tablet_pad to the pad devices */
+       if (udev_tags & EVDEV_UDEV_TAG_TABLET_PAD) {
+               dispatch = evdev_tablet_pad_create(device);
+               device->seat_caps |= EVDEV_DEVICE_TABLET_PAD;
+               log_info(libinput,
+                        "input device '%s', %s is a tablet pad\n",
+                        device->devname, devnode);
+               return dispatch;
+
+       } else if ((udev_tags & tablet_tags) == EVDEV_UDEV_TAG_TABLET) {
+               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 dispatch;
        }
 
        if (udev_tags & EVDEV_UDEV_TAG_TOUCHPAD) {
-               device->dispatch = evdev_mt_touchpad_create(device);
+               dispatch = evdev_mt_touchpad_create(device);
                log_info(libinput,
                         "input device '%s', %s is a touchpad\n",
                         device->devname, devnode);
-               return device->dispatch == NULL ? -1 : 0;
+
+               return dispatch;
        }
 
-       if (udev_tags & EVDEV_UDEV_TAG_MOUSE) {
-               if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) &&
-                   !libevdev_has_event_code(evdev, EV_ABS, ABS_Y) &&
-                   evdev_device_init_pointer_acceleration(device) == -1)
-                       return -1;
+       if (udev_tags & EVDEV_UDEV_TAG_MOUSE ||
+           udev_tags & EVDEV_UDEV_TAG_POINTINGSTICK) {
+               evdev_tag_external_mouse(device, device->udev_device);
+               evdev_tag_trackpoint(device, device->udev_device);
+               device->dpi = evdev_read_dpi_prop(device);
 
                device->seat_caps |= EVDEV_DEVICE_POINTER;
 
@@ -1662,6 +2551,15 @@ evdev_configure_device(struct evdev_device *device)
                log_info(libinput,
                         "input device '%s', %s is a keyboard\n",
                         device->devname, devnode);
+
+               /* want natural-scroll config option */
+               if (libevdev_has_event_code(evdev, EV_REL, REL_WHEEL) ||
+                   libevdev_has_event_code(evdev, EV_REL, REL_HWHEEL)) {
+                       device->scroll.natural_scrolling_enabled = true;
+                       device->seat_caps |= EVDEV_DEVICE_POINTER;
+               }
+
+               evdev_tag_keyboard(device, device->udev_device);
        }
 
        if (udev_tags & EVDEV_UDEV_TAG_TOUCHSCREEN) {
@@ -1671,7 +2569,17 @@ evdev_configure_device(struct evdev_device *device)
                         device->devname, devnode);
        }
 
-       return 0;
+       if (device->seat_caps & EVDEV_DEVICE_POINTER &&
+           libevdev_has_event_code(evdev, EV_REL, REL_X) &&
+           libevdev_has_event_code(evdev, EV_REL, REL_Y) &&
+           !evdev_init_accel(device, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE)) {
+               log_error(libinput,
+                         "failed to initialize pointer acceleration for %s\n",
+                         device->devname);
+               return NULL;
+       }
+
+       return fallback_dispatch_create(&device->base);
 }
 
 static void
@@ -1684,29 +2592,34 @@ evdev_notify_added_device(struct evdev_device *device)
                if (dev == &device->base)
                        continue;
 
-               /* Notify existing device d about addition of device device */
+               /* Notify existing device d about addition of device */
                if (d->dispatch->interface->device_added)
                        d->dispatch->interface->device_added(d, device);
 
-               /* Notify new device device about existing device d */
+               /* Notify new device about existing device d */
                if (device->dispatch->interface->device_added)
                        device->dispatch->interface->device_added(device, d);
 
-               /* Notify new device device if existing device d is suspended */
-               if (d->suspended && device->dispatch->interface->device_suspended)
+               /* Notify new device if existing device d is suspended */
+               if (d->is_suspended &&
+                   device->dispatch->interface->device_suspended)
                        device->dispatch->interface->device_suspended(device, d);
        }
 
        notify_added_device(&device->base);
+
+       if (device->dispatch->interface->post_added)
+               device->dispatch->interface->post_added(device,
+                                                       device->dispatch);
 }
 
-static int
-evdev_device_compare_syspath(struct udev_device *udev_device, int fd)
+static bool
+evdev_device_have_same_syspath(struct udev_device *udev_device, int fd)
 {
        struct udev *udev = udev_device_get_udev(udev_device);
        struct udev_device *udev_device_new = NULL;
        struct stat st;
-       int rc = 1;
+       bool rc = false;
 
        if (fstat(fd, &st) < 0)
                goto out;
@@ -1715,91 +2628,128 @@ evdev_device_compare_syspath(struct udev_device *udev_device, int fd)
        if (!udev_device_new)
                goto out;
 
-       rc = strcmp(udev_device_get_syspath(udev_device_new),
-                   udev_device_get_syspath(udev_device));
+       rc = streq(udev_device_get_syspath(udev_device_new),
+                  udev_device_get_syspath(udev_device));
 out:
        if (udev_device_new)
                udev_device_unref(udev_device_new);
        return rc;
 }
 
-static int
+static bool
 evdev_set_device_group(struct evdev_device *device,
                       struct udev_device *udev_device)
 {
+       struct libinput *libinput = evdev_libinput_context(device);
        struct libinput_device_group *group = NULL;
        const char *udev_group;
 
        udev_group = udev_device_get_property_value(udev_device,
                                                    "LIBINPUT_DEVICE_GROUP");
-       if (udev_group) {
-               struct libinput_device *d;
-
-               list_for_each(d, &device->base.seat->devices_list, link) {
-                       const char *identifier = d->group->identifier;
-
-                       if (identifier &&
-                           strcmp(identifier, udev_group) == 0) {
-                               group = d->group;
-                               break;
-                       }
-               }
-       }
+       if (udev_group)
+               group = libinput_device_group_find_group(libinput, udev_group);
 
        if (!group) {
-               group = libinput_device_group_create(udev_group);
+               group = libinput_device_group_create(libinput, udev_group);
                if (!group)
-                       return 1;
+                       return false;
                libinput_device_set_device_group(&device->base, group);
                libinput_device_group_unref(group);
        } else {
                libinput_device_set_device_group(&device->base, group);
        }
 
-       return 0;
+       return true;
+}
+
+static inline void
+evdev_drain_fd(int fd)
+{
+       struct input_event ev[24];
+       size_t sz = sizeof ev;
+
+       while (read(fd, &ev, sz) == (int)sz) {
+               /* discard all pending events */
+       }
+}
+
+static inline void
+evdev_pre_configure_model_quirks(struct evdev_device *device)
+{
+       /* The Cyborg RAT has a mode button that cycles through event codes.
+        * On press, we get a release for the current mode and a press for the
+        * next mode:
+        * E: 0.000001 0004 0004 589833 # EV_MSC / MSC_SCAN             589833
+        * E: 0.000001 0001 0118 0000   # EV_KEY / (null)               0
+        * E: 0.000001 0004 0004 589834 # EV_MSC / MSC_SCAN             589834
+        * E: 0.000001 0001 0119 0001   # EV_KEY / (null)               1
+        * E: 0.000001 0000 0000 0000   # ------------ SYN_REPORT (0) ---------- +0ms
+        * E: 0.705000 0004 0004 589834 # EV_MSC / MSC_SCAN             589834
+        * E: 0.705000 0001 0119 0000   # EV_KEY / (null)               0
+        * E: 0.705000 0004 0004 589835 # EV_MSC / MSC_SCAN             589835
+        * E: 0.705000 0001 011a 0001   # EV_KEY / (null)               1
+        * E: 0.705000 0000 0000 0000   # ------------ SYN_REPORT (0) ---------- +705ms
+        * E: 1.496995 0004 0004 589833 # EV_MSC / MSC_SCAN             589833
+        * E: 1.496995 0001 0118 0001   # EV_KEY / (null)               1
+        * E: 1.496995 0004 0004 589835 # EV_MSC / MSC_SCAN             589835
+        * E: 1.496995 0001 011a 0000   # EV_KEY / (null)               0
+        * E: 1.496995 0000 0000 0000   # ------------ SYN_REPORT (0) ---------- +791ms
+        *
+        * https://bugs.freedesktop.org/show_bug.cgi?id=92127
+        *
+        * Disable the event codes to avoid stuck buttons.
+        */
+       if(device->model_flags & EVDEV_MODEL_CYBORG_RAT) {
+               libevdev_disable_event_code(device->evdev, EV_KEY, 0x118);
+               libevdev_disable_event_code(device->evdev, EV_KEY, 0x119);
+               libevdev_disable_event_code(device->evdev, EV_KEY, 0x11a);
+       }
+       /* The Apple MagicMouse has a touchpad built-in but the kernel still
+        * emulates a full 2/3 button mouse for us. Ignore anything from the
+        * ABS interface
+        */
+       if (device->model_flags & EVDEV_MODEL_APPLE_MAGICMOUSE)
+               libevdev_disable_event_type(device->evdev, EV_ABS);
+
+       /* Claims to have double/tripletap but doesn't actually send it
+        * https://bugzilla.redhat.com/show_bug.cgi?id=1351285
+        */
+       if (device->model_flags & EVDEV_MODEL_HP8510_TOUCHPAD) {
+               libevdev_disable_event_code(device->evdev, EV_KEY, BTN_TOOL_DOUBLETAP);
+               libevdev_disable_event_code(device->evdev, EV_KEY, BTN_TOOL_TRIPLETAP);
+       }
+
+       /* Touchpad is a clickpad but INPUT_PROP_BUTTONPAD is not set, see
+        * fdo bug 97147. Remove when RMI4 is commonplace */
+       if (device->model_flags & EVDEV_MODEL_HP_STREAM11_TOUCHPAD)
+               libevdev_enable_property(device->evdev,
+                                        INPUT_PROP_BUTTONPAD);
 }
 
 struct evdev_device *
 evdev_device_create(struct libinput_seat *seat,
                    struct udev_device *udev_device)
 {
-#define STRERR_BUFSIZE 256
        struct libinput *libinput = seat->libinput;
        struct evdev_device *device = NULL;
        int rc;
-       int fd = -1;
+       int fd;
        int unhandled_device = 0;
-       const char *devnode;
-       char buf[STRERR_BUFSIZE] = {0, };
-       struct libinput_device *dev;
-
-#ifdef HAVE_INPUT_SET_DEFAULT_PROPERTY
-       if (input_set_default_property(udev_device) < 0)
-               return NULL;
-#endif
-       devnode = udev_device_get_devnode(udev_device);
-
-       list_for_each(dev, &seat->devices_list, link) {
-               struct evdev_device *d = (struct evdev_device*)dev;
-               if (strcmp(devnode, udev_device_get_devnode(d->udev_device))== 0) {
-                       log_info(libinput,
-                               "%s device is already opened\n", d->devname);
-                       goto err;
-               }
-       }
+       const char *devnode = udev_device_get_devnode(udev_device);
 
        /* Use non-blocking mode so that we can loop on read on
         * evdev_device_data() until all events on the fd are
         * read.  mtdev_get() also expects this. */
-       fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK);
+       fd = open_restricted(libinput, devnode,
+                            O_RDWR | O_NONBLOCK | O_CLOEXEC);
        if (fd < 0) {
                log_info(libinput,
                         "opening input device '%s' failed (%s).\n",
-                        devnode, strerror_r(-fd, buf, STRERR_BUFSIZE));
+                        devnode, strerror(-fd));
                return NULL;
        }
 
-       if (evdev_device_compare_syspath(udev_device, fd) != 0)
+       if (!evdev_device_have_same_syspath(udev_device, fd))
                goto err;
 
        device = zalloc(sizeof *device);
@@ -1809,6 +2759,8 @@ evdev_device_create(struct libinput_seat *seat,
        libinput_device_init(&device->base, seat);
        libinput_seat_ref(seat);
 
+       evdev_drain_fd(fd);
+
        rc = libevdev_new_from_fd(fd, &device->evdev);
        if (rc != 0)
                goto err;
@@ -1819,50 +2771,45 @@ evdev_device_create(struct libinput_seat *seat,
        device->is_mt = 0;
        device->mtdev = NULL;
        device->udev_device = udev_device_ref(udev_device);
-       device->rel.dx = 0;
-       device->rel.dy = 0;
-       device->abs.seat_slot = -1;
        device->dispatch = NULL;
        device->fd = fd;
-       device->pending_event = EVDEV_NONE;
        device->devname = libevdev_get_name(device->evdev);
        device->scroll.threshold = 5.0; /* Default may be overridden */
+       device->scroll.direction_lock_threshold = 5.0; /* Default may be overridden */
        device->scroll.direction = 0;
        device->scroll.wheel_click_angle =
-               evdev_read_wheel_click_prop(device);
-       device->dpi = evdev_read_dpi_prop(device);
+               evdev_read_wheel_click_props(device);
+       device->model_flags = evdev_read_model_flags(device);
+       device->dpi = DEFAULT_MOUSE_DPI;
+
        /* at most 5 SYN_DROPPED log-messages per 30s */
-       ratelimit_init(&device->syn_drop_limit, 30ULL * 1000, 5);
+       ratelimit_init(&device->syn_drop_limit, s2us(30), 5);
+       /* at most 5 log-messages per 5s */
+       ratelimit_init(&device->nonpointer_rel_limit, s2us(5), 5);
 
        matrix_init_identity(&device->abs.calibration);
        matrix_init_identity(&device->abs.usermatrix);
        matrix_init_identity(&device->abs.default_calibration);
 
-       if (evdev_configure_device(device) == -1)
-               goto err;
+       evdev_pre_configure_model_quirks(device);
 
-       if (device->seat_caps == 0) {
-               unhandled_device = 1;
+       device->dispatch = evdev_configure_device(device);
+       if (device->dispatch == NULL) {
+               if (device->seat_caps == 0)
+                       unhandled_device = 1;
                goto err;
        }
 
-       /* If the dispatch was not set up use the fallback. */
-       if (device->dispatch == NULL)
-               device->dispatch = fallback_dispatch_create(&device->base);
-       if (device->dispatch == NULL)
-               goto err;
-
        device->source =
                libinput_add_fd(libinput, fd, evdev_device_dispatch, device);
        if (!device->source)
                goto err;
 
-       if (evdev_set_device_group(device, udev_device))
+       if (!evdev_set_device_group(device, udev_device))
                goto err;
 
        list_insert(seat->devices_list.prev, &device->base.link);
 
-       evdev_tag_device(device);
        evdev_notify_added_device(device);
 
        return device;
@@ -1984,7 +2931,7 @@ evdev_device_calibrate(struct evdev_device *device,
        matrix_mult(&device->abs.calibration, &transform, &scale);
 }
 
-int
+bool
 evdev_device_has_capability(struct evdev_device *device,
                            enum libinput_device_capability capability)
 {
@@ -1995,13 +2942,19 @@ evdev_device_has_capability(struct evdev_device *device,
                return !!(device->seat_caps & EVDEV_DEVICE_KEYBOARD);
        case LIBINPUT_DEVICE_CAP_TOUCH:
                return !!(device->seat_caps & EVDEV_DEVICE_TOUCH);
+       case LIBINPUT_DEVICE_CAP_GESTURE:
+               return !!(device->seat_caps & EVDEV_DEVICE_GESTURE);
+       case LIBINPUT_DEVICE_CAP_TABLET_TOOL:
+               return !!(device->seat_caps & EVDEV_DEVICE_TABLET);
+       case LIBINPUT_DEVICE_CAP_TABLET_PAD:
+               return !!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD);
        default:
-               return 0;
+               return false;
        }
 }
 
 int
-evdev_device_get_size(struct evdev_device *device,
+evdev_device_get_size(const struct evdev_device *device,
                      double *width,
                      double *height)
 {
@@ -2010,7 +2963,7 @@ evdev_device_get_size(struct evdev_device *device,
        x = libevdev_get_abs_info(device->evdev, ABS_X);
        y = libevdev_get_abs_info(device->evdev, ABS_Y);
 
-       if (!x || !y || device->abs.fake_resolution ||
+       if (!x || !y || device->abs.is_fake_resolution ||
            !x->resolution || !y->resolution)
                return -1;
 
@@ -2029,6 +2982,15 @@ evdev_device_has_button(struct evdev_device *device, uint32_t code)
        return libevdev_has_event_code(device->evdev, EV_KEY, code);
 }
 
+int
+evdev_device_has_key(struct evdev_device *device, uint32_t code)
+{
+       if (!(device->seat_caps & EVDEV_DEVICE_KEYBOARD))
+               return -1;
+
+       return libevdev_has_event_code(device->evdev, EV_KEY, code);
+}
+
 static inline bool
 evdev_is_scrolling(const struct evdev_device *device,
                   enum libinput_pointer_axis axis)
@@ -2053,20 +3015,19 @@ void
 evdev_post_scroll(struct evdev_device *device,
                  uint64_t time,
                  enum libinput_pointer_axis_source source,
-                 double dx,
-                 double dy)
+                 const struct normalized_coords *delta)
 {
-       double trigger_horiz, trigger_vert;
+       const struct normalized_coords *trigger;
+       struct normalized_coords event;
 
        if (!evdev_is_scrolling(device,
                                LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL))
-               device->scroll.buildup_vertical += dy;
+               device->scroll.buildup.y += delta->y;
        if (!evdev_is_scrolling(device,
                                LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
-               device->scroll.buildup_horizontal += dx;
+               device->scroll.buildup.x += delta->x;
 
-       trigger_vert = device->scroll.buildup_vertical;
-       trigger_horiz = device->scroll.buildup_horizontal;
+       trigger = &device->scroll.buildup;
 
        /* If we're not scrolling yet, use a distance trigger: moving
           past a certain distance starts scrolling */
@@ -2074,43 +3035,55 @@ evdev_post_scroll(struct evdev_device *device,
                                LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL) &&
            !evdev_is_scrolling(device,
                                LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
-               if (fabs(trigger_vert) >= device->scroll.threshold)
+               if (fabs(trigger->y) >= device->scroll.threshold)
                        evdev_start_scrolling(device,
                                              LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
-               if (fabs(trigger_horiz) >= device->scroll.threshold)
+               if (fabs(trigger->x) >= device->scroll.threshold)
                        evdev_start_scrolling(device,
                                              LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
        /* We're already scrolling in one direction. Require some
           trigger speed to start scrolling in the other direction */
        } else if (!evdev_is_scrolling(device,
                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
-               if (fabs(dy) >= device->scroll.threshold)
+               if (fabs(delta->y) >= device->scroll.direction_lock_threshold)
                        evdev_start_scrolling(device,
                                      LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
        } else if (!evdev_is_scrolling(device,
                                LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) {
-               if (fabs(dx) >= device->scroll.threshold)
+               if (fabs(delta->x) >= device->scroll.direction_lock_threshold)
                        evdev_start_scrolling(device,
                                      LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
        }
 
+       event = *delta;
+
        /* We use the trigger to enable, but the delta from this event for
         * the actual scroll movement. Otherwise we get a jump once
         * scrolling engages */
        if (!evdev_is_scrolling(device,
                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL))
-               dy = 0.0;
+               event.y = 0.0;
+
        if (!evdev_is_scrolling(device,
                               LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
-               dx = 0.0;
+               event.x = 0.0;
+
+       if (!normalized_is_zero(event)) {
+               const struct discrete_coords zero_discrete = { 0.0, 0.0 };
+               uint32_t axes = device->scroll.direction;
+
+               if (event.y == 0.0)
+                       axes &= ~AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               if (event.x == 0.0)
+                       axes &= ~AS_MASK(LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
 
-       if (dx != 0.0 || dy != 0.0)
                evdev_notify_axis(device,
                                  time,
-                                 device->scroll.direction,
+                                 axes,
                                  source,
-                                 dx, dy,
-                                 0.0, 0.0);
+                                 &event,
+                                 &zero_discrete);
+       }
 }
 
 void
@@ -2118,69 +3091,29 @@ evdev_stop_scroll(struct evdev_device *device,
                  uint64_t time,
                  enum libinput_pointer_axis_source source)
 {
+       const struct normalized_coords zero = { 0.0, 0.0 };
+       const struct discrete_coords zero_discrete = { 0.0, 0.0 };
+
        /* terminate scrolling with a zero scroll event */
        if (device->scroll.direction != 0)
                pointer_notify_axis(&device->base,
                                    time,
                                    device->scroll.direction,
                                    source,
-                                   0.0, 0.0,
-                                   0.0, 0.0);
+                                   &zero,
+                                   &zero_discrete);
 
-       device->scroll.buildup_horizontal = 0;
-       device->scroll.buildup_vertical = 0;
+       device->scroll.buildup.x = 0;
+       device->scroll.buildup.y = 0;
        device->scroll.direction = 0;
 }
 
-static void
-release_pressed_keys(struct evdev_device *device)
-{
-       struct libinput *libinput = device->base.seat->libinput;
-       uint64_t time;
-       int code;
-
-       if ((time = libinput_now(libinput)) == 0)
-               return;
-
-       for (code = 0; code < KEY_CNT; code++) {
-               int count = get_key_down_count(device, code);
-
-               if (count > 1) {
-                       log_bug_libinput(libinput,
-                                        "Key %d is down %d times.\n",
-                                        code,
-                                        count);
-               }
-
-               while (get_key_down_count(device, code) > 0) {
-                       switch (get_key_type(code)) {
-                       case EVDEV_KEY_TYPE_NONE:
-                               break;
-                       case EVDEV_KEY_TYPE_KEY:
-                               evdev_keyboard_notify_key(
-                                       device,
-                                       time,
-                                       code,
-                                       LIBINPUT_KEY_STATE_RELEASED);
-                               break;
-                       case EVDEV_KEY_TYPE_BUTTON:
-                               evdev_pointer_notify_button(
-                                       device,
-                                       time,
-                                       evdev_to_left_handed(device, code),
-                                       LIBINPUT_BUTTON_STATE_RELEASED);
-                               break;
-                       }
-               }
-       }
-}
-
 void
 evdev_notify_suspended_device(struct evdev_device *device)
 {
        struct libinput_device *it;
 
-       if (device->suspended)
+       if (device->is_suspended)
                return;
 
        list_for_each(it, &device->base.seat->devices_list, link) {
@@ -2192,7 +3125,7 @@ evdev_notify_suspended_device(struct evdev_device *device)
                        d->dispatch->interface->device_suspended(d, device);
        }
 
-       device->suspended = 1;
+       device->is_suspended = true;
 }
 
 void
@@ -2200,7 +3133,7 @@ evdev_notify_resumed_device(struct evdev_device *device)
 {
        struct libinput_device *it;
 
-       if (!device->suspended)
+       if (!device->is_suspended)
                return;
 
        list_for_each(it, &device->base.seat->devices_list, link) {
@@ -2212,39 +3145,40 @@ evdev_notify_resumed_device(struct evdev_device *device)
                        d->dispatch->interface->device_resumed(d, device);
        }
 
-       device->suspended = 0;
+       device->is_suspended = false;
 }
 
-int
+void
 evdev_device_suspend(struct evdev_device *device)
 {
+       struct libinput *libinput = evdev_libinput_context(device);
+
        evdev_notify_suspended_device(device);
 
+       if (device->dispatch->interface->suspend)
+               device->dispatch->interface->suspend(device->dispatch,
+                                                    device);
+
        if (device->source) {
-               libinput_remove_source(device->base.seat->libinput,
-                                      device->source);
+               libinput_remove_source(libinput, device->source);
                device->source = NULL;
        }
 
-       release_pressed_keys(device);
-
        if (device->mtdev) {
                mtdev_close_delete(device->mtdev);
                device->mtdev = NULL;
        }
 
        if (device->fd != -1) {
-               close_restricted(device->base.seat->libinput, device->fd);
+               close_restricted(libinput, device->fd);
                device->fd = -1;
        }
-
-       return 0;
 }
 
 int
 evdev_device_resume(struct evdev_device *device)
 {
-       struct libinput *libinput = device->base.seat->libinput;
+       struct libinput *libinput = evdev_libinput_context(device);
        int fd;
        const char *devnode;
        struct input_event ev;
@@ -2257,16 +3191,19 @@ evdev_device_resume(struct evdev_device *device)
                return -ENODEV;
 
        devnode = udev_device_get_devnode(device->udev_device);
-       fd = open_restricted(libinput, devnode, O_RDWR | O_NONBLOCK);
+       fd = open_restricted(libinput, devnode,
+                            O_RDWR | O_NONBLOCK | O_CLOEXEC);
 
        if (fd < 0)
                return -errno;
 
-       if (evdev_device_compare_syspath(device->udev_device, fd)) {
+       if (!evdev_device_have_same_syspath(device->udev_device, fd)) {
                close_restricted(libinput, fd);
                return -ENODEV;
        }
 
+       evdev_drain_fd(fd);
+
        device->fd = fd;
 
        if (evdev_need_mtdev(device)) {
@@ -2296,8 +3233,6 @@ evdev_device_resume(struct evdev_device *device)
                return -ENOMEM;
        }
 
-       memset(device->hw_key_mask, 0, sizeof(device->hw_key_mask));
-
        evdev_notify_resumed_device(device);
 
        return 0;
@@ -2348,6 +3283,55 @@ evdev_device_destroy(struct evdev_device *device)
        libinput_seat_unref(device->base.seat);
        libevdev_free(device->evdev);
        udev_device_unref(device->udev_device);
-       free(device->mt.slots);
        free(device);
 }
+
+bool
+evdev_tablet_has_left_handed(struct evdev_device *device)
+{
+       bool has_left_handed = false;
+#if HAVE_LIBWACOM
+       struct libinput *libinput = evdev_libinput_context(device);
+       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");
+               goto out;
+       }
+
+       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))
+                       has_left_handed = true;
+       } else if (libwacom_error_get_code(error) == WERROR_UNKNOWN_MODEL) {
+               log_info(libinput,
+                        "%s: tablet unknown to libwacom\n",
+                        device->devname);
+       } 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);
+
+out:
+#endif
+       return has_left_handed;
+}
index 85cbaa942d6a808e26986173b3bd33ac3142bffd..4e28e05c9dc9642e366d744d1ff7c5360422c82a 100644 (file)
@@ -1,24 +1,26 @@
 /*
  * Copyright © 2011, 2012 Intel Corporation
  * Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef EVDEV_H
 
 #include "libinput-private.h"
 #include "timer.h"
+#include "filter.h"
+
+/*
+ * The constant (linear) acceleration factor we use to normalize trackpoint
+ * deltas before calculating pointer acceleration.
+ */
+#define DEFAULT_TRACKPOINT_ACCEL 1.0
+
+/* The fake resolution value for abs devices without resolution */
+#define EVDEV_FAKE_RESOLUTION 1
 
 enum evdev_event_type {
        EVDEV_NONE,
@@ -47,21 +59,73 @@ enum evdev_event_type {
 enum evdev_device_seat_capability {
        EVDEV_DEVICE_POINTER = (1 << 0),
        EVDEV_DEVICE_KEYBOARD = (1 << 1),
-       EVDEV_DEVICE_TOUCH = (1 << 2)
+       EVDEV_DEVICE_TOUCH = (1 << 2),
+       EVDEV_DEVICE_TABLET = (1 << 3),
+       EVDEV_DEVICE_TABLET_PAD = (1 << 4),
+       EVDEV_DEVICE_GESTURE = (1 << 5),
 };
 
 enum evdev_device_tags {
        EVDEV_TAG_EXTERNAL_MOUSE = (1 << 0),
        EVDEV_TAG_INTERNAL_TOUCHPAD = (1 << 1),
-       EVDEV_TAG_TRACKPOINT = (1 << 2),
-       EVDEV_TAG_TOUCHPAD_TRACKPOINT = (1 << 3),
+       EVDEV_TAG_EXTERNAL_TOUCHPAD = (1 << 2),
+       EVDEV_TAG_TRACKPOINT = (1 << 3),
+       EVDEV_TAG_KEYBOARD = (1 << 4),
+};
+
+enum evdev_middlebutton_state {
+       MIDDLEBUTTON_IDLE,
+       MIDDLEBUTTON_LEFT_DOWN,
+       MIDDLEBUTTON_RIGHT_DOWN,
+       MIDDLEBUTTON_MIDDLE,
+       MIDDLEBUTTON_LEFT_UP_PENDING,
+       MIDDLEBUTTON_RIGHT_UP_PENDING,
+       MIDDLEBUTTON_IGNORE_LR,
+       MIDDLEBUTTON_IGNORE_L,
+       MIDDLEBUTTON_IGNORE_R,
+       MIDDLEBUTTON_PASSTHROUGH,
+};
+
+enum evdev_middlebutton_event {
+       MIDDLEBUTTON_EVENT_L_DOWN,
+       MIDDLEBUTTON_EVENT_R_DOWN,
+       MIDDLEBUTTON_EVENT_OTHER,
+       MIDDLEBUTTON_EVENT_L_UP,
+       MIDDLEBUTTON_EVENT_R_UP,
+       MIDDLEBUTTON_EVENT_TIMEOUT,
+       MIDDLEBUTTON_EVENT_ALL_UP,
+};
+
+enum evdev_device_model {
+       EVDEV_MODEL_DEFAULT = 0,
+       EVDEV_MODEL_LENOVO_X230 = (1 << 0),
+       EVDEV_MODEL_CHROMEBOOK = (1 << 1),
+       EVDEV_MODEL_SYSTEM76_BONOBO = (1 << 2),
+       EVDEV_MODEL_SYSTEM76_GALAGO = (1 << 3),
+       EVDEV_MODEL_SYSTEM76_KUDU = (1 << 4),
+       EVDEV_MODEL_CLEVO_W740SU = (1 << 5),
+       EVDEV_MODEL_APPLE_TOUCHPAD = (1 << 6),
+       EVDEV_MODEL_WACOM_TOUCHPAD = (1 << 7),
+       EVDEV_MODEL_ALPS_TOUCHPAD = (1 << 8),
+       EVDEV_MODEL_SYNAPTICS_SERIAL_TOUCHPAD = (1 << 9),
+       EVDEV_MODEL_JUMPING_SEMI_MT = (1 << 10),
+       EVDEV_MODEL_ELANTECH_TOUCHPAD = (1 << 11),
+       EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81 = (1 << 12),
+       EVDEV_MODEL_APPLE_INTERNAL_KEYBOARD = (1 << 13),
+       EVDEV_MODEL_CYBORG_RAT = (1 << 14),
+       EVDEV_MODEL_CYAPA = (1 << 15),
+       EVDEV_MODEL_HP_STREAM11_TOUCHPAD = (1 << 16),
+       EVDEV_MODEL_LENOVO_T450_TOUCHPAD= (1 << 17),
+       EVDEV_MODEL_DELL_TOUCHPAD = (1 << 18),
+       EVDEV_MODEL_TRACKBALL = (1 << 19),
+       EVDEV_MODEL_APPLE_MAGICMOUSE = (1 << 20),
+       EVDEV_MODEL_HP8510_TOUCHPAD = (1 << 21),
 };
 
 struct mt_slot {
        int32_t seat_slot;
-       int32_t x, y;
-       struct ellipse area;
-       int32_t pressure;
+       struct device_coords point;
+       struct device_coords hysteresis_center;
 };
 
 struct evdev_device {
@@ -76,30 +140,27 @@ struct evdev_device {
        const char *devname;
        bool was_removed;
        int fd;
+       enum evdev_device_seat_capability seat_caps;
+       enum evdev_device_tags tags;
+       bool is_mt;
+       bool is_suspended;
+       int dpi; /* HW resolution */
+       struct ratelimit syn_drop_limit; /* ratelimit for SYN_DROPPED logging */
+       struct ratelimit nonpointer_rel_limit; /* ratelimit for REL_* events from non-pointer devices */
+       uint32_t model_flags;
+       struct mtdev *mtdev;
+
        struct {
                const struct input_absinfo *absinfo_x, *absinfo_y;
-               const struct input_absinfo *absinfo_major, *absinfo_minor, *absinfo_pressure, *absinfo_orientation;
-               int fake_resolution;
-
-               int32_t x, y;
-               int32_t seat_slot;
+               bool is_fake_resolution;
 
                int apply_calibration;
                struct matrix calibration;
                struct matrix default_calibration; /* from LIBINPUT_CALIBRATION_MATRIX */
                struct matrix usermatrix; /* as supplied by the caller */
-       } abs;
 
-       struct {
-               int slot;
-               struct mt_slot *slots;
-               size_t slots_len;
-       } mt;
-       struct mtdev *mtdev;
-
-       struct {
-               int dx, dy;
-       } rel;
+               struct device_coords dimensions;
+       } abs;
 
        struct {
                struct libinput_timer timer;
@@ -107,6 +168,8 @@ struct evdev_device {
                /* Currently enabled method, button */
                enum libinput_config_scroll_method method;
                uint32_t button;
+               uint64_t button_down_time;
+
                /* set during device init, used at runtime to delay changes
                 * until all buttons are up */
                enum libinput_config_scroll_method want_method;
@@ -114,10 +177,11 @@ struct evdev_device {
                /* Checks if buttons are down and commits the setting */
                void (*change_scroll_method)(struct evdev_device *device);
                bool button_scroll_active;
+               bool button_scroll_btn_pressed;
                double threshold;
+               double direction_lock_threshold;
                uint32_t direction;
-               double buildup_vertical;
-               double buildup_horizontal;
+               struct normalized_coords buildup;
 
                struct libinput_device_config_natural_scroll config_natural;
                /* set during device init if we want natural scrolling,
@@ -125,24 +189,14 @@ struct evdev_device {
                bool natural_scrolling_enabled;
 
                /* angle per REL_WHEEL click in degrees */
-               int wheel_click_angle;
+               struct wheel_angle wheel_click_angle;
        } scroll;
 
-       enum evdev_event_type pending_event;
-       enum evdev_device_seat_capability seat_caps;
-       enum evdev_device_tags tags;
-
-       int is_mt;
-       int suspended;
-
        struct {
                struct libinput_device_config_accel config;
                struct motion_filter *filter;
        } pointer;
 
-       /* Bitmask of pressed keys used to ignore initial release events from
-        * the kernel. */
-       unsigned long hw_key_mask[NLONGS(KEY_CNT)];
        /* Key counter used for multiplexing button events internally in
         * libinput. */
        uint8_t key_count[KEY_CNT];
@@ -158,8 +212,17 @@ struct evdev_device {
                void (*change_to_enabled)(struct evdev_device *device);
        } left_handed;
 
-       int dpi; /* HW resolution */
-       struct ratelimit syn_drop_limit; /* ratelimit for SYN_DROPPED logging */
+       struct {
+               struct libinput_device_config_middle_emulation config;
+               /* middle-button emulation enabled */
+               bool enabled;
+               bool enabled_default;
+               bool want_enabled;
+               enum evdev_middlebutton_state state;
+               struct libinput_timer timer;
+               uint32_t button_mask;
+               uint64_t first_event_time;
+       } middlebutton;
 };
 
 #define EVDEV_UNHANDLED_DEVICE ((struct evdev_device *) 1)
@@ -173,6 +236,10 @@ struct evdev_dispatch_interface {
                        struct input_event *event,
                        uint64_t time);
 
+       /* Device is being suspended */
+       void (*suspend)(struct evdev_dispatch *dispatch,
+                       struct evdev_device *device);
+
        /* Device is being removed (may be NULL) */
        void (*remove)(struct evdev_dispatch *dispatch);
 
@@ -195,14 +262,18 @@ struct evdev_dispatch_interface {
        void (*device_resumed)(struct evdev_device *device,
                               struct evdev_device *resumed_device);
 
-       /* Tag device with one of EVDEV_TAG */
-       void (*tag_device)(struct evdev_device *device,
-                          struct udev_device *udev_device);
+       /* Called immediately after the LIBINPUT_EVENT_DEVICE_ADDED event
+        * was sent */
+       void (*post_added)(struct evdev_device *device,
+                          struct evdev_dispatch *dispatch);
+
+       void (*toggle_touch)(struct evdev_dispatch *dispatch,
+                            struct evdev_device *device,
+                            bool enable);
 };
 
 struct evdev_dispatch {
        struct evdev_dispatch_interface *interface;
-       struct libinput_device_config_calibration calibration;
 
        struct {
                struct libinput_device_config_send_events config;
@@ -210,12 +281,63 @@ struct evdev_dispatch {
        } sendevents;
 };
 
+struct fallback_dispatch {
+       struct evdev_dispatch base;
+
+       struct libinput_device_config_calibration calibration;
+
+       struct {
+               bool is_enabled;
+               int angle;
+               struct matrix matrix;
+               struct libinput_device_config_rotation config;
+       } rotation;
+
+       struct {
+               struct device_coords point;
+               int32_t seat_slot;
+       } abs;
+
+       struct {
+               int slot;
+               struct mt_slot *slots;
+               size_t slots_len;
+               bool want_hysteresis;
+               struct device_coords hysteresis_margin;
+       } mt;
+
+       struct device_coords rel;
+
+       /* Bitmask of pressed keys used to ignore initial release events from
+        * the kernel. */
+       unsigned long hw_key_mask[NLONGS(KEY_CNT)];
+
+       enum evdev_event_type pending_event;
+
+       /* true if we're reading events (i.e. not suspended) but we're
+          ignoring them */
+       bool ignore_events;
+};
+
 struct evdev_device *
 evdev_device_create(struct libinput_seat *seat,
                    struct udev_device *device);
 
-int
-evdev_device_init_pointer_acceleration(struct evdev_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 libinput_device_config_calibration *calibration);
+
+void
+evdev_device_init_pointer_acceleration(struct evdev_device *device,
+                                      struct motion_filter *filter);
 
 struct evdev_dispatch *
 evdev_touchpad_create(struct evdev_device *device);
@@ -223,6 +345,12 @@ 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);
+
+struct evdev_dispatch *
+evdev_tablet_pad_create(struct evdev_device *device);
+
 void
 evdev_device_led_update(struct evdev_device *device, enum libinput_led leds);
 
@@ -254,18 +382,46 @@ void
 evdev_device_calibrate(struct evdev_device *device,
                       const float calibration[6]);
 
-int
+bool
 evdev_device_has_capability(struct evdev_device *device,
                            enum libinput_device_capability capability);
 
 int
-evdev_device_get_size(struct evdev_device *device,
+evdev_device_get_size(const struct evdev_device *device,
                      double *w,
                      double *h);
 
 int
 evdev_device_has_button(struct evdev_device *device, uint32_t code);
 
+int
+evdev_device_has_key(struct evdev_device *device, uint32_t code);
+
+int
+evdev_device_tablet_pad_get_num_buttons(struct evdev_device *device);
+
+int
+evdev_device_tablet_pad_get_num_rings(struct evdev_device *device);
+
+int
+evdev_device_tablet_pad_get_num_strips(struct evdev_device *device);
+
+int
+evdev_device_tablet_pad_get_num_mode_groups(struct evdev_device *device);
+
+struct libinput_tablet_pad_mode_group *
+evdev_device_tablet_pad_get_mode_group(struct evdev_device *device,
+                                      unsigned int index);
+
+unsigned int
+evdev_device_tablet_pad_mode_group_get_button_target(
+                                    struct libinput_tablet_pad_mode_group *g,
+                                    unsigned int button_index);
+
+struct libinput_tablet_pad_led *
+evdev_device_tablet_pad_get_led(struct evdev_device *device,
+                               unsigned int led);
+
 double
 evdev_device_transform_x(struct evdev_device *device,
                         double x,
@@ -275,28 +431,7 @@ double
 evdev_device_transform_y(struct evdev_device *device,
                         double y,
                         uint32_t height);
-
-double
-evdev_device_transform_ellipse_diameter_to_mm(struct evdev_device *device,
-                                             int32_t diameter,
-                                             double axis_angle);
-
-double
-evdev_device_transform_ellipse_diameter(struct evdev_device *device,
-                                       int32_t diameter,
-                                       double axis_angle,
-                                       uint32_t width,
-                                       uint32_t height);
-
-double
-evdev_device_transform_orientation(struct evdev_device *device,
-                                  int32_t orientation);
-
-double
-evdev_device_transform_pressure(struct evdev_device *device,
-                               int32_t pressure);
-
-int
+void
 evdev_device_suspend(struct evdev_device *device);
 
 int
@@ -308,30 +443,32 @@ evdev_notify_suspended_device(struct evdev_device *device);
 void
 evdev_notify_resumed_device(struct evdev_device *device);
 
-void
-evdev_keyboard_notify_key(struct evdev_device *device,
-                         uint32_t time,
-                         int key,
-                         enum libinput_key_state state);
-
 void
 evdev_pointer_notify_button(struct evdev_device *device,
-                           uint32_t time,
-                           int button,
+                           uint64_t time,
+                           unsigned int button,
                            enum libinput_button_state state);
+void
+evdev_pointer_notify_physical_button(struct evdev_device *device,
+                                    uint64_t time,
+                                    int button,
+                                    enum libinput_button_state state);
 
 void
 evdev_init_natural_scroll(struct evdev_device *device);
 
-int
-evdev_scroll_get_wheel_click_angle(struct evdev_device *device);
-
+void
+evdev_notify_axis(struct evdev_device *device,
+                 uint64_t time,
+                 uint32_t axes,
+                 enum libinput_pointer_axis_source source,
+                 const struct normalized_coords *delta_in,
+                 const struct discrete_coords *discrete_in);
 void
 evdev_post_scroll(struct evdev_device *device,
                  uint64_t time,
                  enum libinput_pointer_axis_source source,
-                 double dx,
-                 double dy);
+                 const struct normalized_coords *delta);
 
 void
 evdev_stop_scroll(struct evdev_device *device,
@@ -344,6 +481,26 @@ evdev_device_remove(struct evdev_device *device);
 void
 evdev_device_destroy(struct evdev_device *device);
 
+bool
+evdev_middlebutton_filter_button(struct evdev_device *device,
+                                uint64_t time,
+                                int button,
+                                enum libinput_button_state state);
+
+void
+evdev_init_middlebutton(struct evdev_device *device,
+                       bool enabled,
+                       bool want_config);
+
+enum libinput_config_middle_emulation_state
+evdev_middlebutton_get(struct libinput_device *device);
+
+int
+evdev_middlebutton_is_available(struct libinput_device *device);
+
+enum libinput_config_middle_emulation_state
+evdev_middlebutton_get_default(struct libinput_device *device);
+
 static inline double
 evdev_convert_to_mm(const struct input_absinfo *absinfo, double v)
 {
@@ -351,10 +508,13 @@ evdev_convert_to_mm(const struct input_absinfo *absinfo, double v)
        return value/absinfo->resolution;
 }
 
-int
+void
 evdev_init_left_handed(struct evdev_device *device,
                       void (*change_to_left_handed)(struct evdev_device *));
 
+bool
+evdev_tablet_has_left_handed(struct evdev_device *device);
+
 static inline uint32_t
 evdev_to_left_handed(struct evdev_device *device,
                     uint32_t button)
@@ -368,4 +528,78 @@ evdev_to_left_handed(struct evdev_device *device,
        return button;
 }
 
+static inline int
+evdev_hysteresis(int in, int center, int margin)
+{
+       int diff = in - center;
+       if (abs(diff) <= margin)
+               return center;
+
+       if (diff > margin)
+               return center + diff - margin;
+       else
+               return center + diff + margin;
+}
+
+static inline struct libinput *
+evdev_libinput_context(const struct evdev_device *device)
+{
+       return device->base.seat->libinput;
+}
+
+/**
+ * Convert the pair of coordinates in device space to mm. This takes the
+ * axis min into account, i.e. a unit of min is equivalent to 0 mm.
+ */
+static inline struct phys_coords
+evdev_device_units_to_mm(const struct evdev_device* device,
+                        const struct device_coords *units)
+{
+       struct phys_coords mm = { 0,  0 };
+       const struct input_absinfo *absx, *absy;
+
+       if (device->abs.absinfo_x == NULL ||
+           device->abs.absinfo_y == NULL) {
+               log_bug_libinput(evdev_libinput_context(device),
+                                "%s: is not an abs device\n",
+                                device->devname);
+               return mm;
+       }
+
+       absx = device->abs.absinfo_x;
+       absy = device->abs.absinfo_y;
+
+       mm.x = (units->x - absx->minimum)/absx->resolution;
+       mm.y = (units->y - absy->minimum)/absy->resolution;
+
+       return mm;
+}
+
+/**
+ * Convert the pair of coordinates in mm to device units. This takes the
+ * axis min into account, i.e. 0 mm  is equivalent to the min.
+ */
+static inline struct device_coords
+evdev_device_mm_to_units(const struct evdev_device *device,
+                        const struct phys_coords *mm)
+{
+       struct device_coords units = { 0,  0 };
+       const struct input_absinfo *absx, *absy;
+
+       if (device->abs.absinfo_x == NULL ||
+           device->abs.absinfo_y == NULL) {
+               log_bug_libinput(evdev_libinput_context(device),
+                                "%s: is not an abs device\n",
+                                device->devname);
+               return units;
+       }
+
+       absx = device->abs.absinfo_x;
+       absy = device->abs.absinfo_y;
+
+       units.x = mm->x * absx->resolution + absx->minimum;
+       units.y = mm->y * absy->resolution + absy->minimum;
+
+       return units;
+}
 #endif /* EVDEV_H */
index dcce12595ec55596328df32888dab057c9fbfe78..637125a0b010b2c501c2c345f8670108c65c5e40 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2012 Jonas Ådahl
  *
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef FILTER_PRIVATE_H
 #include "filter.h"
 
 struct motion_filter_interface {
-       void (*filter)(struct motion_filter *filter,
-                      struct motion_params *motion,
-                      void *data, uint64_t time);
+       enum libinput_config_accel_profile type;
+       struct normalized_coords (*filter)(
+                          struct motion_filter *filter,
+                          const struct normalized_coords *unaccelerated,
+                          void *data, uint64_t time);
+       struct normalized_coords (*filter_constant)(
+                          struct motion_filter *filter,
+                          const struct normalized_coords *unaccelerated,
+                          void *data, uint64_t time);
+       void (*restart)(struct motion_filter *filter,
+                       void *data,
+                       uint64_t time);
        void (*destroy)(struct motion_filter *filter);
        bool (*set_speed)(struct motion_filter *filter,
-                         double speed);
+                         double speed_adjustment);
 };
 
 struct motion_filter {
-       double speed; /* normalized [-1, 1] */
+       double speed_adjustment; /* normalized [-1, 1] */
        struct motion_filter_interface *interface;
 };
 
-
 #endif
index 72ef7609f987e3bc7cbca06bfbc6479f78b51821..0bb066c958a0d8edf2402f2f4dfaa59afc9e5703 100644 (file)
@@ -1,23 +1,26 @@
 /*
+ * Copyright © 2006-2009 Simon Thum
  * Copyright © 2012 Jonas Ådahl
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
 #include "libinput-util.h"
 #include "filter-private.h"
 
-void
+/* Once normalized, touchpads see the same acceleration as mice. that is
+ * technically correct but subjectively wrong, we expect a touchpad to be a
+ * lot slower than a mouse. Apply a magic factor to slow down all movements
+ */
+#define TP_MAGIC_SLOWDOWN 0.4 /* unitless factor */
+
+/* Convert speed/velocity from units/us to units/ms */
+static inline double
+v_us2ms(double units_per_us)
+{
+       return units_per_us * 1000.0;
+}
+
+/* Convert speed/velocity from units/ms to units/us */
+static inline double
+v_ms2us(double units_per_ms)
+{
+       return units_per_ms/1000.0;
+}
+
+struct normalized_coords
 filter_dispatch(struct motion_filter *filter,
-               struct motion_params *motion,
+               const struct normalized_coords *unaccelerated,
                void *data, uint64_t time)
 {
-       filter->interface->filter(filter, motion, data, time);
+       return filter->interface->filter(filter, unaccelerated, data, time);
+}
+
+struct normalized_coords
+filter_dispatch_constant(struct motion_filter *filter,
+                        const struct normalized_coords *unaccelerated,
+                        void *data, uint64_t time)
+{
+       return filter->interface->filter_constant(filter, unaccelerated, data, time);
+}
+
+void
+filter_restart(struct motion_filter *filter,
+              void *data, uint64_t time)
+{
+       if (filter->interface->restart)
+               filter->interface->restart(filter, data, time);
 }
 
 void
 filter_destroy(struct motion_filter *filter)
 {
-       if (!filter)
+       if (!filter || !filter->interface->destroy)
                return;
 
        filter->interface->destroy(filter);
@@ -52,79 +91,107 @@ filter_destroy(struct motion_filter *filter)
 
 bool
 filter_set_speed(struct motion_filter *filter,
-                double speed)
+                double speed_adjustment)
 {
-       return filter->interface->set_speed(filter, speed);
+       return filter->interface->set_speed(filter, speed_adjustment);
 }
 
 double
 filter_get_speed(struct motion_filter *filter)
 {
-       return filter->speed;
+       return filter->speed_adjustment;
+}
+
+enum libinput_config_accel_profile
+filter_get_type(struct motion_filter *filter)
+{
+       return filter->interface->type;
 }
 
 /*
  * Default parameters for pointer acceleration profiles.
  */
 
-#define DEFAULT_THRESHOLD 0.4                  /* in units/ms */
+#define DEFAULT_THRESHOLD v_ms2us(0.4)         /* in units/us */
+#define MINIMUM_THRESHOLD v_ms2us(0.2)         /* in units/us */
 #define DEFAULT_ACCELERATION 2.0               /* unitless factor */
 #define DEFAULT_INCLINE 1.1                    /* unitless factor */
 
+/* for the Lenovo x230 custom accel. do not touch */
+#define X230_THRESHOLD v_ms2us(0.4)            /* in units/us */
+#define X230_ACCELERATION 2.0                  /* unitless factor */
+#define X230_INCLINE 1.1                       /* unitless factor */
+#define X230_MAGIC_SLOWDOWN 0.4                        /* unitless */
+#define X230_TP_MAGIC_LOW_RES_FACTOR 4.0       /* unitless */
+
 /*
  * Pointer acceleration filter constants
  */
 
-#define MAX_VELOCITY_DIFF      1.0 /* units/ms */
-#define MOTION_TIMEOUT         300 /* (ms) */
+#define MAX_VELOCITY_DIFF      v_ms2us(1) /* units/us */
+#define MOTION_TIMEOUT         ms2us(1000)
 #define NUM_POINTER_TRACKERS   16
 
 struct pointer_tracker {
-       double dx;      /* delta to most recent event, in device units */
-       double dy;      /* delta to most recent event, in device units */
-       uint64_t time;  /* ms */
+       struct normalized_coords delta; /* delta to most recent event */
+       uint64_t time;  /* us */
        int dir;
 };
 
-struct pointer_accelerator;
 struct pointer_accelerator {
        struct motion_filter base;
 
        accel_profile_func_t profile;
 
-       double velocity;        /* units/ms */
-       double last_velocity;   /* units/ms */
-       int last_dx;            /* device units */
-       int last_dy;            /* device units */
+       double velocity;        /* units/us */
+       double last_velocity;   /* units/us */
 
        struct pointer_tracker *trackers;
        int cur_tracker;
 
-       double threshold;       /* units/ms */
+       double threshold;       /* units/us */
        double accel;           /* unitless factor */
        double incline;         /* incline of the function */
+
+       double dpi_factor;
+};
+
+struct pointer_accelerator_flat {
+       struct motion_filter base;
+
+       double factor;
+       double dpi_factor;
+};
+
+struct tablet_accelerator_flat {
+       struct motion_filter base;
+
+       double factor;
+       int xres, yres;
+       double xres_scale, /* 1000dpi : tablet res */
+              yres_scale; /* 1000dpi : tablet res */
 };
 
 static void
 feed_trackers(struct pointer_accelerator *accel,
-             double dx, double dy,
+             const struct normalized_coords *delta,
              uint64_t time)
 {
        int i, current;
        struct pointer_tracker *trackers = accel->trackers;
 
        for (i = 0; i < NUM_POINTER_TRACKERS; i++) {
-               trackers[i].dx += dx;
-               trackers[i].dy += dy;
+               trackers[i].delta.x += delta->x;
+               trackers[i].delta.y += delta->y;
        }
 
        current = (accel->cur_tracker + 1) % NUM_POINTER_TRACKERS;
        accel->cur_tracker = current;
 
-       trackers[current].dx = 0.0;
-       trackers[current].dy = 0.0;
+       trackers[current].delta.x = 0.0;
+       trackers[current].delta.y = 0.0;
        trackers[current].time = time;
-       trackers[current].dir = vector_get_direction(dx, dy);
+       trackers[current].dir = normalized_get_direction(*delta);
 }
 
 static struct pointer_tracker *
@@ -139,14 +206,26 @@ tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset)
 static double
 calculate_tracker_velocity(struct pointer_tracker *tracker, uint64_t time)
 {
-       double dx;
-       double dy;
-       double distance;
+       double tdelta = time - tracker->time + 1;
+       return normalized_length(tracker->delta) / tdelta; /* units/us */
+}
 
-       dx = tracker->dx;
-       dy = tracker->dy;
-       distance = sqrt(dx*dx + dy*dy);
-       return distance / (double)(time - tracker->time); /* units/ms */
+static inline double
+calculate_velocity_after_timeout(struct pointer_tracker *tracker)
+{
+       /* First movement after timeout needs special handling.
+        *
+        * When we trigger the timeout, the last event is too far in the
+        * past to use it for velocity calculation across multiple tracker
+        * values.
+        *
+        * Use the motion timeout itself to calculate the speed rather than
+        * the last tracker time. This errs on the side of being too fast
+        * for really slow movements but provides much more useful initial
+        * movement in normal use-cases (pause, move, pause, move)
+        */
+       return calculate_tracker_velocity(tracker,
+                                         tracker->time + MOTION_TIMEOUT);
 }
 
 static double
@@ -168,15 +247,23 @@ calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
 
                /* Stop if too far away in time */
                if (time - tracker->time > MOTION_TIMEOUT ||
-                   tracker->time > time)
+                   tracker->time > time) {
+                       if (offset == 1)
+                               result = calculate_velocity_after_timeout(tracker);
                        break;
+               }
+
+               velocity = calculate_tracker_velocity(tracker, time);
 
                /* Stop if direction changed */
                dir &= tracker->dir;
-               if (dir == 0)
+               if (dir == 0) {
+                       /* First movement after dirchange - velocity is that
+                        * of the last movement */
+                       if (offset == 1)
+                               result = velocity;
                        break;
-
-               velocity = calculate_tracker_velocity(tracker, time);
+               }
 
                if (initial_velocity == 0.0) {
                        result = initial_velocity = velocity;
@@ -190,7 +277,7 @@ calculate_velocity(struct pointer_accelerator *accel, uint64_t time)
                }
        }
 
-       return result; /* units/ms */
+       return result; /* units/us */
 }
 
 static double
@@ -202,17 +289,20 @@ acceleration_profile(struct pointer_accelerator *accel,
 
 static double
 calculate_acceleration(struct pointer_accelerator *accel,
-                      void *data, double velocity, uint64_t time)
+                      void *data,
+                      double velocity,
+                      double last_velocity,
+                      uint64_t time)
 {
        double factor;
 
        /* Use Simpson's rule to calculate the avarage acceleration between
         * the previous motion and the most recent. */
        factor = acceleration_profile(accel, data, velocity, time);
-       factor += acceleration_profile(accel, data, accel->last_velocity, time);
+       factor += acceleration_profile(accel, data, last_velocity, time);
        factor += 4.0 *
                acceleration_profile(accel, data,
-                                    (accel->last_velocity + velocity) / 2,
+                                    (last_velocity + velocity) / 2,
                                     time);
 
        factor = factor / 6.0;
@@ -220,27 +310,190 @@ calculate_acceleration(struct pointer_accelerator *accel,
        return factor; /* unitless factor */
 }
 
-static void
+static inline double
+calculate_acceleration_factor(struct pointer_accelerator *accel,
+                             const struct normalized_coords *unaccelerated,
+                             void *data,
+                             uint64_t time)
+{
+       double velocity; /* units/us */
+       double accel_factor;
+
+       feed_trackers(accel, unaccelerated, time);
+       velocity = calculate_velocity(accel, time);
+       accel_factor = calculate_acceleration(accel,
+                                             data,
+                                             velocity,
+                                             accel->last_velocity,
+                                             time);
+       accel->last_velocity = velocity;
+
+       return accel_factor;
+}
+
+static struct normalized_coords
 accelerator_filter(struct motion_filter *filter,
-                  struct motion_params *motion,
+                  const struct normalized_coords *unaccelerated,
                   void *data, uint64_t time)
 {
        struct pointer_accelerator *accel =
                (struct pointer_accelerator *) filter;
-       double velocity; /* units/ms */
        double accel_value; /* unitless factor */
+       struct normalized_coords accelerated;
 
-       feed_trackers(accel, motion->dx, motion->dy, time);
-       velocity = calculate_velocity(accel, time);
-       accel_value = calculate_acceleration(accel, data, velocity, time);
+       accel_value = calculate_acceleration_factor(accel,
+                                                   unaccelerated,
+                                                   data,
+                                                   time);
+
+       accelerated.x = accel_value * unaccelerated->x;
+       accelerated.y = accel_value * unaccelerated->y;
+
+       return accelerated;
+}
+
+static struct normalized_coords
+accelerator_filter_noop(struct motion_filter *filter,
+                       const struct normalized_coords *unaccelerated,
+                       void *data, uint64_t time)
+{
+       return *unaccelerated;
+}
+
+static struct normalized_coords
+accelerator_filter_low_dpi(struct motion_filter *filter,
+                          const struct normalized_coords *unaccelerated,
+                          void *data, uint64_t time)
+{
+       struct pointer_accelerator *accel =
+               (struct pointer_accelerator *) filter;
+       double accel_value; /* unitless factor */
+       struct normalized_coords accelerated;
+       struct normalized_coords unnormalized;
+       double dpi_factor = accel->dpi_factor;
+
+       /* For low-dpi mice, use device units, everything else uses
+          1000dpi normalized */
+       dpi_factor = min(1.0, dpi_factor);
+       unnormalized.x = unaccelerated->x * dpi_factor;
+       unnormalized.y = unaccelerated->y * dpi_factor;
+
+       accel_value = calculate_acceleration_factor(accel,
+                                                   &unnormalized,
+                                                   data,
+                                                   time);
+
+       accelerated.x = accel_value * unnormalized.x;
+       accelerated.y = accel_value * unnormalized.y;
+
+       return accelerated;
+}
 
-       motion->dx = accel_value * motion->dx;
-       motion->dy = accel_value * motion->dy;
+static struct normalized_coords
+accelerator_filter_trackpoint(struct motion_filter *filter,
+                             const struct normalized_coords *unaccelerated,
+                             void *data, uint64_t time)
+{
+       struct pointer_accelerator *accel =
+               (struct pointer_accelerator *) filter;
+       double accel_value; /* unitless factor */
+       struct normalized_coords accelerated;
+       struct normalized_coords unnormalized;
+       double dpi_factor = accel->dpi_factor;
+
+       /* trackpoints with a dpi factor have a const accel set, remove that
+        * and restore device units. The accel profile takes const accel
+        * into account */
+       dpi_factor = min(1.0, dpi_factor);
+       unnormalized.x = unaccelerated->x * dpi_factor;
+       unnormalized.y = unaccelerated->y * dpi_factor;
+
+       accel_value = calculate_acceleration_factor(accel,
+                                                   &unnormalized,
+                                                   data,
+                                                   time);
+
+       accelerated.x = accel_value * unnormalized.x;
+       accelerated.y = accel_value * unnormalized.y;
+
+       return accelerated;
+}
 
-       accel->last_dx = motion->dx;
-       accel->last_dy = motion->dy;
+static struct normalized_coords
+accelerator_filter_x230(struct motion_filter *filter,
+                       const struct normalized_coords *unaccelerated,
+                       void *data, uint64_t time)
+{
+       struct pointer_accelerator *accel =
+               (struct pointer_accelerator *) filter;
+       double accel_factor; /* unitless factor */
+       struct normalized_coords accelerated;
+       double velocity; /* units/us */
 
+       feed_trackers(accel, unaccelerated, time);
+       velocity = calculate_velocity(accel, time);
+       accel_factor = calculate_acceleration(accel,
+                                             data,
+                                             velocity,
+                                             accel->last_velocity,
+                                             time);
        accel->last_velocity = velocity;
+
+       accelerated.x = accel_factor * unaccelerated->x;
+       accelerated.y = accel_factor * unaccelerated->y;
+
+       return accelerated;
+}
+
+static struct normalized_coords
+accelerator_filter_constant_x230(struct motion_filter *filter,
+                                const struct normalized_coords *unaccelerated,
+                                void *data, uint64_t time)
+{
+       struct normalized_coords normalized;
+       const double factor =
+               X230_MAGIC_SLOWDOWN/X230_TP_MAGIC_LOW_RES_FACTOR;
+
+       normalized.x = factor * unaccelerated->x;
+       normalized.y = factor * unaccelerated->y;
+
+       return normalized;
+}
+
+static struct normalized_coords
+touchpad_constant_filter(struct motion_filter *filter,
+                        const struct normalized_coords *unaccelerated,
+                        void *data, uint64_t time)
+{
+       struct normalized_coords normalized;
+
+       normalized.x = TP_MAGIC_SLOWDOWN * unaccelerated->x;
+       normalized.y = TP_MAGIC_SLOWDOWN * unaccelerated->y;
+
+       return normalized;
+}
+
+static void
+accelerator_restart(struct motion_filter *filter,
+                   void *data,
+                   uint64_t time)
+{
+       struct pointer_accelerator *accel =
+               (struct pointer_accelerator *) filter;
+       unsigned int offset;
+       struct pointer_tracker *tracker;
+
+       for (offset = 1; offset < NUM_POINTER_TRACKERS; offset++) {
+               tracker = tracker_by_offset(accel, offset);
+               tracker->time = 0;
+               tracker->dir = 0;
+               tracker->delta.x = 0;
+               tracker->delta.y = 0;
+       }
+
+       tracker = tracker_by_offset(accel, 0);
+       tracker->time = time;
+       tracker->dir = UNDEFINED_DIRECTION;
 }
 
 static void
@@ -255,34 +508,245 @@ accelerator_destroy(struct motion_filter *filter)
 
 static bool
 accelerator_set_speed(struct motion_filter *filter,
-                     double speed)
+                     double speed_adjustment)
 {
        struct pointer_accelerator *accel_filter =
                (struct pointer_accelerator *)filter;
 
-       assert(speed >= -1.0 && speed <= 1.0);
+       assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
+
+       /* Note: the numbers below are nothing but trial-and-error magic,
+          don't read more into them other than "they mostly worked ok" */
 
        /* delay when accel kicks in */
-       accel_filter->threshold = DEFAULT_THRESHOLD - speed/6.0;
+       accel_filter->threshold = DEFAULT_THRESHOLD -
+                                       v_ms2us(0.25) * speed_adjustment;
+       if (accel_filter->threshold < MINIMUM_THRESHOLD)
+               accel_filter->threshold = MINIMUM_THRESHOLD;
 
        /* adjust max accel factor */
-       accel_filter->accel = DEFAULT_ACCELERATION + speed;
+       accel_filter->accel = DEFAULT_ACCELERATION + speed_adjustment * 1.5;
 
        /* higher speed -> faster to reach max */
-       accel_filter->incline = DEFAULT_INCLINE + speed/2.0;
+       accel_filter->incline = DEFAULT_INCLINE + speed_adjustment * 0.75;
 
-       filter->speed = speed;
+       filter->speed_adjustment = speed_adjustment;
        return true;
 }
 
+/**
+ * Custom acceleration function for mice < 1000dpi.
+ * At slow motion, a single device unit causes a one-pixel movement.
+ * The threshold/max accel depends on the DPI, the smaller the DPI the
+ * earlier we accelerate and the higher the maximum acceleration is. Result:
+ * at low speeds we get pixel-precision, at high speeds we get approx. the
+ * same movement as a high-dpi mouse.
+ *
+ * Note: data fed to this function is in device units, not normalized.
+ */
+double
+pointer_accel_profile_linear_low_dpi(struct motion_filter *filter,
+                                    void *data,
+                                    double speed_in, /* in device units (units/us) */
+                                    uint64_t time)
+{
+       struct pointer_accelerator *accel_filter =
+               (struct pointer_accelerator *)filter;
+
+       double max_accel = accel_filter->accel; /* unitless factor */
+       double threshold = accel_filter->threshold; /* units/us */
+       const double incline = accel_filter->incline;
+       double factor; /* unitless */
+       double dpi_factor = accel_filter->dpi_factor;
+
+       /* dpi_factor is always < 1.0, increase max_accel, reduce
+          the threshold so it kicks in earlier */
+       max_accel /= dpi_factor;
+       threshold *= dpi_factor;
+
+       /* see pointer_accel_profile_linear for a long description */
+       if (v_us2ms(speed_in) < 0.07)
+               factor = 10 * v_us2ms(speed_in) + 0.3;
+       else if (speed_in < threshold)
+               factor = 1;
+       else
+               factor = incline * v_us2ms(speed_in - threshold) + 1;
+
+       factor = min(max_accel, factor);
+
+       return factor;
+}
+
+double
+pointer_accel_profile_linear(struct motion_filter *filter,
+                            void *data,
+                            double speed_in, /* 1000-dpi normalized */
+                            uint64_t time)
+{
+       struct pointer_accelerator *accel_filter =
+               (struct pointer_accelerator *)filter;
+       const double max_accel = accel_filter->accel; /* unitless factor */
+       const double threshold = accel_filter->threshold; /* units/us */
+       const double incline = accel_filter->incline;
+       double factor; /* unitless */
+
+       /*
+          Our acceleration function calculates a factor to accelerate input
+          deltas with. The function is a double incline with a plateau,
+          with a rough shape like this:
+
+         accel
+        factor
+          ^
+          |        /
+          |  _____/
+          | /
+          |/
+          +-------------> speed in
+
+          The two inclines are linear functions in the form
+                  y = ax + b
+                  where y is speed_out
+                        x is speed_in
+                        a is the incline of acceleration
+                        b is minimum acceleration factor
+
+          for speeds up to 0.07 u/ms, we decelerate, down to 30% of input
+          speed.
+                  hence 1 = a * 0.07 + 0.3
+                      0.3 = a * 0.00 + 0.3 => a := 10
+                  deceleration function is thus:
+                       y = 10x + 0.3
+
+         Note:
+         * 0.07u/ms as threshold is a result of trial-and-error and
+           has no other intrinsic meaning.
+         * 0.3 is chosen simply because it is above the Nyquist frequency
+           for subpixel motion within a pixel.
+       */
+       if (v_us2ms(speed_in) < 0.07) {
+               factor = 10 * v_us2ms(speed_in) + 0.3;
+       /* up to the threshold, we keep factor 1, i.e. 1:1 movement */
+       } else if (speed_in < threshold) {
+               factor = 1;
+
+       } else {
+       /* Acceleration function above the threshold:
+               y = ax' + b
+               where T is threshold
+                     x is speed_in
+                     x' is speed
+               and
+                       y(T) == 1
+               hence 1 = ax' + 1
+                       => x' := (x - T)
+        */
+               factor = incline * v_us2ms(speed_in - threshold) + 1;
+       }
+
+       /* Cap at the maximum acceleration factor */
+       factor = min(max_accel, factor);
+
+       return factor;
+}
+
+double
+touchpad_accel_profile_linear(struct motion_filter *filter,
+                              void *data,
+                              double speed_in, /* units/us */
+                              uint64_t time)
+{
+       double factor; /* unitless */
+
+       speed_in *= TP_MAGIC_SLOWDOWN;
+
+       factor = pointer_accel_profile_linear(filter, data, speed_in, time);
+
+       return factor * TP_MAGIC_SLOWDOWN;
+}
+
+double
+touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
+                                     void *data,
+                                     double speed_in,
+                                     uint64_t time)
+{
+       /* Those touchpads presents an actual lower resolution that what is
+        * advertised. We see some jumps from the cursor due to the big steps
+        * in X and Y when we are receiving data.
+        * Apply a factor to minimize those jumps at low speed, and try
+        * keeping the same feeling as regular touchpads at high speed.
+        * It still feels slower but it is usable at least */
+       double factor; /* unitless */
+       struct pointer_accelerator *accel_filter =
+               (struct pointer_accelerator *)filter;
+
+       double f1, f2; /* unitless */
+       const double max_accel = accel_filter->accel *
+                                 X230_TP_MAGIC_LOW_RES_FACTOR; /* unitless factor */
+       const double threshold = accel_filter->threshold /
+                                 X230_TP_MAGIC_LOW_RES_FACTOR; /* units/us */
+       const double incline = accel_filter->incline * X230_TP_MAGIC_LOW_RES_FACTOR;
+
+       /* Note: the magic values in this function are obtained by
+        * trial-and-error. No other meaning should be interpreted.
+        * The calculation is a compressed form of
+        * pointer_accel_profile_linear(), look at the git history of that
+        * function for an explanation of what the min/max/etc. does.
+        */
+       speed_in *= X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR;
+
+       f1 = min(1, v_us2ms(speed_in) * 5);
+       f2 = 1 + (v_us2ms(speed_in) - v_us2ms(threshold)) * incline;
+
+       factor = min(max_accel, f2 > 1 ? f2 : f1);
+
+       return factor * X230_MAGIC_SLOWDOWN / X230_TP_MAGIC_LOW_RES_FACTOR;
+}
+
+double
+trackpoint_accel_profile(struct motion_filter *filter,
+                               void *data,
+                               double speed_in, /* 1000-dpi normalized */
+                               uint64_t time)
+{
+       struct pointer_accelerator *accel_filter =
+               (struct pointer_accelerator *)filter;
+       double max_accel = accel_filter->accel; /* unitless factor */
+       double threshold = accel_filter->threshold; /* units/ms */
+       const double incline = accel_filter->incline;
+       double factor;
+       double dpi_factor = accel_filter->dpi_factor;
+
+       /* dpi_factor is always < 1.0, increase max_accel, reduce
+          the threshold so it kicks in earlier */
+       max_accel /= dpi_factor;
+       threshold *= dpi_factor;
+
+       /* see pointer_accel_profile_linear for a long description */
+       if (v_us2ms(speed_in) < 0.07)
+               factor = 10 * v_us2ms(speed_in) + 0.3;
+       else if (speed_in < threshold)
+               factor = 1;
+       else
+               factor = incline * v_us2ms(speed_in - threshold) + 1;
+
+       factor = min(max_accel, factor);
+
+       return factor;
+}
+
 struct motion_filter_interface accelerator_interface = {
-       accelerator_filter,
-       accelerator_destroy,
-       accelerator_set_speed,
+       .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+       .filter = accelerator_filter,
+       .filter_constant = accelerator_filter_noop,
+       .restart = accelerator_restart,
+       .destroy = accelerator_destroy,
+       .set_speed = accelerator_set_speed,
 };
 
-struct motion_filter *
-create_pointer_accelerator_filter(accel_profile_func_t profile)
+static struct pointer_accelerator *
+create_default_filter(int dpi)
 {
        struct pointer_accelerator *filter;
 
@@ -290,17 +754,143 @@ create_pointer_accelerator_filter(accel_profile_func_t profile)
        if (filter == NULL)
                return NULL;
 
+       filter->last_velocity = 0.0;
+
+       filter->trackers =
+               calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers);
+       filter->cur_tracker = 0;
+
+       filter->threshold = DEFAULT_THRESHOLD;
+       filter->accel = DEFAULT_ACCELERATION;
+       filter->incline = DEFAULT_INCLINE;
+
+       filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_DPI;
+
+       return filter;
+}
+
+struct motion_filter *
+create_pointer_accelerator_filter_linear(int dpi)
+{
+       struct pointer_accelerator *filter;
+
+       filter = create_default_filter(dpi);
+       if (!filter)
+               return NULL;
+
        filter->base.interface = &accelerator_interface;
+       filter->profile = pointer_accel_profile_linear;
+
+       return &filter->base;
+}
+
+struct motion_filter_interface accelerator_interface_low_dpi = {
+       .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+       .filter = accelerator_filter_low_dpi,
+       .filter_constant = accelerator_filter_noop,
+       .restart = accelerator_restart,
+       .destroy = accelerator_destroy,
+       .set_speed = accelerator_set_speed,
+};
+
+struct motion_filter *
+create_pointer_accelerator_filter_linear_low_dpi(int dpi)
+{
+       struct pointer_accelerator *filter;
+
+       filter = create_default_filter(dpi);
+       if (!filter)
+               return NULL;
+
+       filter->base.interface = &accelerator_interface_low_dpi;
+       filter->profile = pointer_accel_profile_linear_low_dpi;
+
+       return &filter->base;
+}
+
+struct motion_filter_interface accelerator_interface_touchpad = {
+       .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+       .filter = accelerator_filter,
+       .filter_constant = touchpad_constant_filter,
+       .restart = accelerator_restart,
+       .destroy = accelerator_destroy,
+       .set_speed = accelerator_set_speed,
+};
+
+struct motion_filter *
+create_pointer_accelerator_filter_touchpad(int dpi)
+{
+       struct pointer_accelerator *filter;
+
+       filter = create_default_filter(dpi);
+       if (!filter)
+               return NULL;
+
+       filter->base.interface = &accelerator_interface_touchpad;
+       filter->profile = touchpad_accel_profile_linear;
+
+       return &filter->base;
+}
+
+struct motion_filter_interface accelerator_interface_x230 = {
+       .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+       .filter = accelerator_filter_x230,
+       .filter_constant = accelerator_filter_constant_x230,
+       .restart = accelerator_restart,
+       .destroy = accelerator_destroy,
+       .set_speed = accelerator_set_speed,
+};
+
+/* The Lenovo x230 has a bad touchpad. This accel method has been
+ * trial-and-error'd, any changes to it will require re-testing everything.
+ * Don't touch this.
+ */
+struct motion_filter *
+create_pointer_accelerator_filter_lenovo_x230(int dpi)
+{
+       struct pointer_accelerator *filter;
+
+       filter = zalloc(sizeof *filter);
+       if (filter == NULL)
+               return NULL;
 
-       filter->profile = profile;
+       filter->base.interface = &accelerator_interface_x230;
+       filter->profile = touchpad_lenovo_x230_accel_profile;
        filter->last_velocity = 0.0;
-       filter->last_dx = 0;
-       filter->last_dy = 0;
 
        filter->trackers =
                calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers);
        filter->cur_tracker = 0;
 
+       filter->threshold = X230_THRESHOLD;
+       filter->accel = X230_ACCELERATION; /* unitless factor */
+       filter->incline = X230_INCLINE; /* incline of the acceleration function */
+
+       filter->dpi_factor = 1; /* unused for this accel method */
+
+       return &filter->base;
+}
+
+struct motion_filter_interface accelerator_interface_trackpoint = {
+       .type = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+       .filter = accelerator_filter_trackpoint,
+       .filter_constant = accelerator_filter_noop,
+       .restart = accelerator_restart,
+       .destroy = accelerator_destroy,
+       .set_speed = accelerator_set_speed,
+};
+
+struct motion_filter *
+create_pointer_accelerator_filter_trackpoint(int dpi)
+{
+       struct pointer_accelerator *filter;
+
+       filter = create_default_filter(dpi);
+       if (!filter)
+               return NULL;
+
+       filter->base.interface = &accelerator_interface_trackpoint;
+       filter->profile = trackpoint_accel_profile;
        filter->threshold = DEFAULT_THRESHOLD;
        filter->accel = DEFAULT_ACCELERATION;
        filter->incline = DEFAULT_INCLINE;
@@ -308,22 +898,221 @@ create_pointer_accelerator_filter(accel_profile_func_t profile)
        return &filter->base;
 }
 
-double
-pointer_accel_profile_linear(struct motion_filter *filter,
-                            void *data,
-                            double speed_in,
-                            uint64_t time)
+static struct normalized_coords
+accelerator_filter_flat(struct motion_filter *filter,
+                       const struct normalized_coords *unaccelerated,
+                       void *data, uint64_t time)
 {
-       struct pointer_accelerator *accel_filter =
-               (struct pointer_accelerator *)filter;
+       struct pointer_accelerator_flat *accel_filter =
+               (struct pointer_accelerator_flat *)filter;
+       double factor; /* unitless factor */
+       struct normalized_coords accelerated;
+       struct normalized_coords unnormalized;
+
+       /* You want flat acceleration, you get flat acceleration for the
+        * device */
+       unnormalized.x = unaccelerated->x * accel_filter->dpi_factor;
+       unnormalized.y = unaccelerated->y * accel_filter->dpi_factor;
+       factor = accel_filter->factor;
+
+       accelerated.x = factor * unnormalized.x;
+       accelerated.y = factor * unnormalized.y;
+
+       return accelerated;
+}
 
-       double s1, s2;
-       const double max_accel = accel_filter->accel; /* unitless factor */
-       const double threshold = accel_filter->threshold; /* units/ms */
-       const double incline = accel_filter->incline;
+static bool
+accelerator_set_speed_flat(struct motion_filter *filter,
+                          double speed_adjustment)
+{
+       struct pointer_accelerator_flat *accel_filter =
+               (struct pointer_accelerator_flat *)filter;
+
+       assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
+
+       /* Speed rage is 0-200% of the nominal speed, with 0 mapping to the
+        * nominal speed. Anything above 200 is pointless, we're already
+        * skipping over ever second pixel at 200% speed.
+        */
+
+       accel_filter->factor = 1 + speed_adjustment;
+       filter->speed_adjustment = speed_adjustment;
+
+       return true;
+}
+
+static void
+accelerator_destroy_flat(struct motion_filter *filter)
+{
+       struct pointer_accelerator_flat *accel =
+               (struct pointer_accelerator_flat *) filter;
+
+       free(accel);
+}
+
+struct motion_filter_interface accelerator_interface_flat = {
+       .type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
+       .filter = accelerator_filter_flat,
+       .filter_constant = accelerator_filter_noop,
+       .restart = NULL,
+       .destroy = accelerator_destroy_flat,
+       .set_speed = accelerator_set_speed_flat,
+};
 
-       s1 = min(1, speed_in * 5);
-       s2 = 1 + (speed_in - threshold) * incline;
+struct motion_filter *
+create_pointer_accelerator_filter_flat(int dpi)
+{
+       struct pointer_accelerator_flat *filter;
+
+       filter = zalloc(sizeof *filter);
+       if (filter == NULL)
+               return NULL;
+
+       filter->base.interface = &accelerator_interface_flat;
+       filter->dpi_factor = dpi/(double)DEFAULT_MOUSE_DPI;
+
+       return &filter->base;
+}
 
-       return min(max_accel, s2 > 1 ? s2 : s1);
+static inline struct normalized_coords
+tablet_accelerator_filter_flat_mouse(struct tablet_accelerator_flat *filter,
+                                    const struct normalized_coords *units)
+{
+       struct normalized_coords accelerated;
+
+       /*
+          The input for and output of accel methods is usually a delta in
+          1000dpi equivalents. Tablets are high res (Intuos 4 is 5080 dpi)
+          and unmodified deltas are way too high. Slow it down to the
+          equivalent of a 1000dpi mouse. The ratio of that is:
+               ratio = 1000/(resolution_per_mm * 25.4)
+
+          i.e. on the Intuos4 it's a ratio of ~1/5.
+
+        */
+
+       accelerated.x = units->x * filter->xres_scale;
+       accelerated.y = units->y * filter->yres_scale;
+
+       accelerated.x *= filter->factor;
+       accelerated.y *= filter->factor;
+
+       return accelerated;
+}
+
+static struct normalized_coords
+tablet_accelerator_filter_flat_pen(struct tablet_accelerator_flat *filter,
+                                  const struct normalized_coords *units)
+{
+       struct normalized_coords accelerated;
+
+       /* Tablet input is in device units, 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 */
+       struct normalized_coords mm;
+
+       mm.x = 1.0 * units->x/filter->xres;
+       mm.y = 1.0 * units->y/filter->yres;
+       accelerated.x = mm.x * filter->factor * DPI_CONVERSION;
+       accelerated.y = mm.y * filter->factor * DPI_CONVERSION;
+
+       return accelerated;
+}
+
+static struct normalized_coords
+tablet_accelerator_filter_flat(struct motion_filter *filter,
+                              const struct normalized_coords *units,
+                              void *data, uint64_t time)
+{
+       struct tablet_accelerator_flat *accel_filter =
+               (struct tablet_accelerator_flat *)filter;
+       struct libinput_tablet_tool *tool = (struct libinput_tablet_tool*)data;
+       enum libinput_tablet_tool_type type;
+       struct normalized_coords accel;
+
+       type = libinput_tablet_tool_get_type(tool);
+
+       switch (type) {
+       case LIBINPUT_TABLET_TOOL_TYPE_MOUSE:
+       case LIBINPUT_TABLET_TOOL_TYPE_LENS:
+               accel = tablet_accelerator_filter_flat_mouse(accel_filter,
+                                                            units);
+               break;
+       default:
+               accel = tablet_accelerator_filter_flat_pen(accel_filter,
+                                                          units);
+               break;
+       }
+
+       return accel;
+}
+
+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->factor = 1.0;
+       filter->xres = xres;
+       filter->yres = yres;
+       filter->xres_scale = DEFAULT_MOUSE_DPI/(25.4 * xres);
+       filter->yres_scale = DEFAULT_MOUSE_DPI/(25.4 * 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;
 }
index bffeb5f98516e9473794c00c54bfc7035bd1a281..c1b43a5dfe4afee8f72ba5031f389ecb253c0028 100644 (file)
@@ -1,23 +1,25 @@
 /*
  * Copyright © 2012 Jonas Ådahl
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef FILTER_H
 #include <stdbool.h>
 #include <stdint.h>
 
-/* The HW DPI rate we normalize to before calculating pointer acceleration */
-#define DEFAULT_MOUSE_DPI 1000
-
-struct motion_params {
-       double dx, dy; /* in units/ms @ DEFAULT_MOUSE_DPI resolution */
-};
+#include "libinput-private.h"
 
 struct motion_filter;
 
-void
+/**
+ * Accelerate the given coordinates.
+ * Takes a set of unaccelerated deltas and accelerates them based on the
+ * current and previous motion.
+ *
+ * This is a superset of filter_dispatch_constant()
+ *
+ * @see filter_dispatch_constant
+ */
+struct normalized_coords
 filter_dispatch(struct motion_filter *filter,
-               struct motion_params *motion,
+               const struct normalized_coords *unaccelerated,
                void *data, uint64_t time);
+
+/**
+ * Apply constant motion filters, but no acceleration.
+ *
+ * Takes a set of unaccelerated deltas and applies any constant filters to
+ * it but does not accelerate the delta in the conventional sense.
+ *
+ * @see filter_dispatch
+ */
+struct normalized_coords
+filter_dispatch_constant(struct motion_filter *filter,
+                        const struct normalized_coords *unaccelerated,
+                        void *data, uint64_t time);
+
+void
+filter_restart(struct motion_filter *filter,
+              void *data, uint64_t time);
+
 void
 filter_destroy(struct motion_filter *filter);
 
@@ -50,22 +74,63 @@ filter_set_speed(struct motion_filter *filter,
 double
 filter_get_speed(struct motion_filter *filter);
 
+enum libinput_config_accel_profile
+filter_get_type(struct motion_filter *filter);
+
 typedef double (*accel_profile_func_t)(struct motion_filter *filter,
                                       void *data,
                                       double velocity,
                                       uint64_t time);
 
+/* Pointer acceleration types */
 struct motion_filter *
-create_pointer_accelerator_filter(accel_profile_func_t filter);
+create_pointer_accelerator_filter_flat(int dpi);
 
+struct motion_filter *
+create_pointer_accelerator_filter_linear(int dpi);
+
+struct motion_filter *
+create_pointer_accelerator_filter_linear_low_dpi(int dpi);
+
+struct motion_filter *
+create_pointer_accelerator_filter_touchpad(int dpi);
+
+struct motion_filter *
+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.
  */
 
 double
+pointer_accel_profile_linear_low_dpi(struct motion_filter *filter,
+                                    void *data,
+                                    double speed_in,
+                                    uint64_t time);
+double
 pointer_accel_profile_linear(struct motion_filter *filter,
                             void *data,
                             double speed_in,
                             uint64_t time);
+double
+touchpad_accel_profile_linear(struct motion_filter *filter,
+                             void *data,
+                             double speed_in,
+                             uint64_t time);
+double
+touchpad_lenovo_x230_accel_profile(struct motion_filter *filter,
+                                     void *data,
+                                     double speed_in,
+                                     uint64_t time);
+double
+trackpoint_accel_profile(struct motion_filter *filter,
+                        void *data,
+                        double speed_in,
+                        uint64_t time);
 #endif /* FILTER_H */
index e86df634962338506574ba625e214e94f7d2fb4b..28656e07e1dd4c99ca83177c0c626385894c667a 100644 (file)
 /*
  * Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef LIBINPUT_PRIVATE_H
 #define LIBINPUT_PRIVATE_H
 
+#include "config.h"
+
 #include <errno.h>
+#include <math.h>
 
 #include "linux/input.h"
 
 #include "libinput.h"
 #include "libinput-util.h"
 
-#ifdef ENABLE_TTRACE
-#include <ttrace.h>
-
-#define TRACE_INPUT_BEGIN(NAME) traceBegin(TTRACE_TAG_INPUT, "INPUT:LIBINPUT:"#NAME)
-#define TRACE_INPUT_END() traceEnd(TTRACE_TAG_INPUT)
+#if LIBINPUT_VERSION_MICRO >= 90
+#define HTTP_DOC_LINK "https://wayland.freedesktop.org/libinput/doc/latest/"
 #else
-#define TRACE_INPUT_BEGIN(NAME)
-#define TRACE_INPUT_END()
+#define HTTP_DOC_LINK "https://wayland.freedesktop.org/libinput/doc/" VERSION "/"
 #endif
 
 struct libinput_source;
 
-/* Ellipse parameters in device coordinates */
-struct ellipse {
-       int major, minor, orientation;
+/* A coordinate pair in device coordinates */
+struct device_coords {
+       int x, y;
+};
+
+/*
+ * A coordinate pair in device coordinates, capable of holding non discrete
+ * values, this is necessary e.g. when device coordinates get averaged.
+ */
+struct device_float_coords {
+       double x, y;
+};
+
+/* A dpi-normalized coordinate pair */
+struct normalized_coords {
+       double x, y;
+};
+
+/* A discrete step pair (mouse wheels) */
+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 pair of angles in degrees */
+struct wheel_angle {
+       int x, y;
+};
+
+/* A pair of angles in degrees */
+struct tilt_degrees {
+       double x, y;
+};
+
+/* A threshold with an upper and lower limit */
+struct threshold {
+       int upper;
+       int lower;
+};
+
+/* A pair of coordinates in mm */
+struct phys_coords {
+       double x;
+       double y;
+};
+
+struct tablet_axes {
+       struct device_coords point;
+       struct normalized_coords delta;
+       double distance;
+       double pressure;
+       struct tilt_degrees tilt;
+       double rotation;
+       double slider;
+       double wheel;
+       int wheel_discrete;
 };
 
 struct libinput_interface_backend {
@@ -73,6 +131,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;
 
@@ -80,6 +140,8 @@ struct libinput {
        enum libinput_log_priority log_priority;
        void *user_data;
        int refcount;
+
+       struct list device_group_list;
 };
 
 typedef void (*libinput_seat_destroy_func) (struct libinput_seat *seat);
@@ -106,6 +168,21 @@ struct libinput_device_config_tap {
                                                   enum libinput_config_tap_state enable);
        enum libinput_config_tap_state (*get_enabled)(struct libinput_device *device);
        enum libinput_config_tap_state (*get_default)(struct libinput_device *device);
+
+       enum libinput_config_status (*set_map)(struct libinput_device *device,
+                                                  enum libinput_config_tap_button_map map);
+       enum libinput_config_tap_button_map (*get_map)(struct libinput_device *device);
+       enum libinput_config_tap_button_map (*get_default_map)(struct libinput_device *device);
+
+       enum libinput_config_status (*set_drag_enabled)(struct libinput_device *device,
+                                                       enum libinput_config_drag_state);
+       enum libinput_config_drag_state (*get_drag_enabled)(struct libinput_device *device);
+       enum libinput_config_drag_state (*get_default_drag_enabled)(struct libinput_device *device);
+
+       enum libinput_config_status (*set_draglock_enabled)(struct libinput_device *device,
+                                                           enum libinput_config_drag_lock_state);
+       enum libinput_config_drag_lock_state (*get_draglock_enabled)(struct libinput_device *device);
+       enum libinput_config_drag_lock_state (*get_default_draglock_enabled)(struct libinput_device *device);
 };
 
 struct libinput_device_config_calibration {
@@ -132,6 +209,12 @@ struct libinput_device_config_accel {
                                                 double speed);
        double (*get_speed)(struct libinput_device *device);
        double (*get_default_speed)(struct libinput_device *device);
+
+       uint32_t (*get_profiles)(struct libinput_device *device);
+       enum libinput_config_status (*set_profile)(struct libinput_device *device,
+                                                  enum libinput_config_accel_profile);
+       enum libinput_config_accel_profile (*get_profile)(struct libinput_device *device);
+       enum libinput_config_accel_profile (*get_default_profile)(struct libinput_device *device);
 };
 
 struct libinput_device_config_natural_scroll {
@@ -169,6 +252,37 @@ struct libinput_device_config_click_method {
        enum libinput_config_click_method (*get_default_method)(struct libinput_device *device);
 };
 
+struct libinput_device_config_middle_emulation {
+       int (*available)(struct libinput_device *device);
+       enum libinput_config_status (*set)(
+                        struct libinput_device *device,
+                        enum libinput_config_middle_emulation_state);
+       enum libinput_config_middle_emulation_state (*get)(
+                        struct libinput_device *device);
+       enum libinput_config_middle_emulation_state (*get_default)(
+                        struct libinput_device *device);
+};
+
+struct libinput_device_config_dwt {
+       int (*is_available)(struct libinput_device *device);
+       enum libinput_config_status (*set_enabled)(
+                        struct libinput_device *device,
+                        enum libinput_config_dwt_state enable);
+       enum libinput_config_dwt_state (*get_enabled)(
+                        struct libinput_device *device);
+       enum libinput_config_dwt_state (*get_default_enabled)(
+                        struct libinput_device *device);
+};
+
+struct libinput_device_config_rotation {
+       int (*is_available)(struct libinput_device *device);
+       enum libinput_config_status (*set_angle)(
+                        struct libinput_device *device,
+                        unsigned int degrees_cw);
+       unsigned int (*get_angle)(struct libinput_device *device);
+       unsigned int (*get_default_angle)(struct libinput_device *device);
+};
+
 struct libinput_device_config {
        struct libinput_device_config_tap *tap;
        struct libinput_device_config_calibration *calibration;
@@ -178,12 +292,17 @@ struct libinput_device_config {
        struct libinput_device_config_left_handed *left_handed;
        struct libinput_device_config_scroll_method *scroll_method;
        struct libinput_device_config_click_method *click_method;
+       struct libinput_device_config_middle_emulation *middle_emulation;
+       struct libinput_device_config_dwt *dwt;
+       struct libinput_device_config_rotation *rotation;
 };
 
 struct libinput_device_group {
        int refcount;
        void *user_data;
        char *identifier; /* unique identifier or NULL for singletons */
+
+       struct list link;
 };
 
 struct libinput_device {
@@ -196,6 +315,55 @@ 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_tablet_pad_mode_group {
+       struct libinput_device *device;
+       struct list link;
+       int refcount;
+       void *user_data;
+
+       unsigned int index;
+       unsigned int num_modes;
+       unsigned int current_mode;
+
+       uint32_t button_mask;
+       uint32_t ring_mask;
+       uint32_t strip_mask;
+
+       uint32_t toggle_button_mask;
+
+       void (*destroy)(struct libinput_tablet_pad_mode_group *group);
+};
+
 struct libinput_event {
        enum libinput_event_type type;
        struct libinput_device *device;
@@ -207,11 +375,6 @@ struct libinput_event_listener {
        void *notify_func_data;
 };
 
-struct device_node {
-       struct list link;
-       char *devname;
-};
-
 typedef void (*libinput_source_dispatch_t)(void *data);
 
 #define log_debug(li_, ...) log_msg((li_), LIBINPUT_LOG_PRIORITY_DEBUG, __VA_ARGS__)
@@ -221,16 +384,32 @@ typedef void (*libinput_source_dispatch_t)(void *data);
 #define log_bug_libinput(li_, ...) log_msg((li_), LIBINPUT_LOG_PRIORITY_ERROR, "libinput bug: " __VA_ARGS__)
 #define log_bug_client(li_, ...) log_msg((li_), LIBINPUT_LOG_PRIORITY_ERROR, "client bug: " __VA_ARGS__)
 
+#define log_debug_ratelimit(li_, r_, ...) log_msg_ratelimit((li_), (r_), LIBINPUT_LOG_PRIORITY_DEBUG, __VA_ARGS__)
+#define log_info_ratelimit(li_, r_, ...) log_msg_ratelimit((li_), (r_), LIBINPUT_LOG_PRIORITY_INFO, __VA_ARGS__)
+#define log_error_ratelimit(li_, r_, ...) log_msg_ratelimit((li_), (r_), LIBINPUT_LOG_PRIORITY_ERROR, __VA_ARGS__)
+#define log_bug_kernel_ratelimit(li_, r_, ...) log_msg_ratelimit((li_), (r_), LIBINPUT_LOG_PRIORITY_ERROR, "kernel bug: " __VA_ARGS__)
+#define log_bug_libinput_ratelimit(li_, r_, ...) log_msg_ratelimit((li_), (r_), LIBINPUT_LOG_PRIORITY_ERROR, "libinput bug: " __VA_ARGS__)
+#define log_bug_client_ratelimit(li_, r_, ...) log_msg_ratelimit((li_), (r_), LIBINPUT_LOG_PRIORITY_ERROR, "client bug: " __VA_ARGS__)
+
+void
+log_msg_ratelimit(struct libinput *libinput,
+                 struct ratelimit *ratelimit,
+                 enum libinput_log_priority priority,
+                 const char *format, ...)
+       LIBINPUT_ATTRIBUTE_PRINTF(4, 5);
+
 void
 log_msg(struct libinput *libinput,
        enum libinput_log_priority priority,
-       const char *format, ...);
+       const char *format, ...)
+       LIBINPUT_ATTRIBUTE_PRINTF(3, 4);
 
 void
 log_msg_va(struct libinput *libinput,
           enum libinput_log_priority priority,
           const char *format,
-          va_list args);
+          va_list args)
+       LIBINPUT_ATTRIBUTE_PRINTF(3, 0);
 
 int
 libinput_init(struct libinput *libinput,
@@ -255,6 +434,9 @@ open_restricted(struct libinput *libinput,
 void
 close_restricted(struct libinput *libinput, int fd);
 
+bool
+ignore_litest_test_suite_device(struct udev_device *device);
+
 void
 libinput_seat_init(struct libinput_seat *seat,
                   struct libinput *libinput,
@@ -267,7 +449,12 @@ libinput_device_init(struct libinput_device *device,
                     struct libinput_seat *seat);
 
 struct libinput_device_group *
-libinput_device_group_create(const char *identifier);
+libinput_device_group_create(struct libinput *libinput,
+                            const char *identifier);
+
+struct libinput_device_group *
+libinput_device_group_find_group(struct libinput *libinput,
+                                const char *identifier);
 
 void
 libinput_device_set_device_group(struct libinput_device *device,
@@ -300,16 +487,13 @@ keyboard_notify_key(struct libinput_device *device,
 void
 pointer_notify_motion(struct libinput_device *device,
                      uint64_t time,
-                     double dx,
-                     double dy,
-                     double dx_unaccel,
-                     double dy_unaccel);
+                     const struct normalized_coords *delta,
+                     const struct device_float_coords *raw);
 
 void
 pointer_notify_motion_absolute(struct libinput_device *device,
                               uint64_t time,
-                              double x,
-                              double y);
+                              const struct device_coords *point);
 
 void
 pointer_notify_button(struct libinput_device *device,
@@ -322,30 +506,22 @@ pointer_notify_axis(struct libinput_device *device,
                    uint64_t time,
                    uint32_t axes,
                    enum libinput_pointer_axis_source source,
-                   double x,
-                   double y,
-                   double x_discrete,
-                   double y_discrete);
+                   const struct normalized_coords *delta,
+                   const struct discrete_coords *discrete);
 
 void
 touch_notify_touch_down(struct libinput_device *device,
                        uint64_t time,
                        int32_t slot,
                        int32_t seat_slot,
-                       double x,
-                       double y,
-                       const struct ellipse *area,
-                       int32_t pressure);
+                       const struct device_coords *point);
 
 void
 touch_notify_touch_motion(struct libinput_device *device,
                          uint64_t time,
                          int32_t slot,
                          int32_t seat_slot,
-                         double x,
-                         double y,
-                         const struct ellipse *area,
-                         int32_t pressure);
+                         const struct device_coords *point);
 
 void
 touch_notify_touch_up(struct libinput_device *device,
@@ -353,10 +529,95 @@ touch_notify_touch_up(struct libinput_device *device,
                      int32_t slot,
                      int32_t seat_slot);
 
+void
+gesture_notify_swipe(struct libinput_device *device,
+                    uint64_t time,
+                    enum libinput_event_type type,
+                    int finger_count,
+                    const struct normalized_coords *delta,
+                    const struct normalized_coords *unaccel);
+
+void
+gesture_notify_swipe_end(struct libinput_device *device,
+                        uint64_t time,
+                        int finger_count,
+                        int cancelled);
+
+void
+gesture_notify_pinch(struct libinput_device *device,
+                    uint64_t time,
+                    enum libinput_event_type type,
+                    int finger_count,
+                    const struct normalized_coords *delta,
+                    const struct normalized_coords *unaccel,
+                    double scale,
+                    double angle);
+
+void
+gesture_notify_pinch_end(struct libinput_device *device,
+                        uint64_t time,
+                        int finger_count,
+                        double scale,
+                        int cancelled);
+
 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);
+
+void
+tablet_pad_notify_button(struct libinput_device *device,
+                        uint64_t time,
+                        int32_t button,
+                        enum libinput_button_state state,
+                        struct libinput_tablet_pad_mode_group *group);
+void
+tablet_pad_notify_ring(struct libinput_device *device,
+                      uint64_t time,
+                      unsigned int number,
+                      double value,
+                      enum libinput_tablet_pad_ring_axis_source source,
+                      struct libinput_tablet_pad_mode_group *group);
+void
+tablet_pad_notify_strip(struct libinput_device *device,
+                       uint64_t time,
+                       unsigned int number,
+                       double value,
+                       enum libinput_tablet_pad_strip_axis_source source,
+                       struct libinput_tablet_pad_mode_group *group);
+
 static inline uint64_t
 libinput_now(struct libinput *libinput)
 {
@@ -367,9 +628,120 @@ libinput_now(struct libinput *libinput)
                return 0;
        }
 
-       return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000;
+       return s2us(ts.tv_sec) + ns2us(ts.tv_nsec);
+}
+
+static inline struct device_float_coords
+device_delta(struct device_coords a, struct device_coords b)
+{
+       struct device_float_coords delta;
+
+       delta.x = a.x - b.x;
+       delta.y = a.y - b.y;
+
+       return delta;
+}
+
+static inline struct device_float_coords
+device_average(struct device_coords a, struct device_coords b)
+{
+       struct device_float_coords average;
+
+       average.x = (a.x + b.x) / 2.0;
+       average.y = (a.y + b.y) / 2.0;
+
+       return average;
+}
+
+static inline struct device_float_coords
+device_float_delta(struct device_float_coords a, struct device_float_coords b)
+{
+       struct device_float_coords delta;
+
+       delta.x = a.x - b.x;
+       delta.y = a.y - b.y;
+
+       return delta;
+}
+
+static inline struct device_float_coords
+device_float_average(struct device_float_coords a, struct device_float_coords b)
+{
+       struct device_float_coords average;
+
+       average.x = (a.x + b.x) / 2.0;
+       average.y = (a.y + b.y) / 2.0;
+
+       return average;
+}
+
+static inline double
+normalized_length(struct normalized_coords norm)
+{
+       return hypot(norm.x, norm.y);
+}
+
+static inline bool
+normalized_is_zero(struct normalized_coords norm)
+{
+       return norm.x == 0.0 && norm.y == 0.0;
+}
+
+enum directions {
+       N  = 1 << 0,
+       NE = 1 << 1,
+       E  = 1 << 2,
+       SE = 1 << 3,
+       S  = 1 << 4,
+       SW = 1 << 5,
+       W  = 1 << 6,
+       NW = 1 << 7,
+       UNDEFINED_DIRECTION = 0xff
+};
+
+static inline int
+normalized_get_direction(struct normalized_coords norm)
+{
+       int dir = UNDEFINED_DIRECTION;
+       int d1, d2;
+       double r;
+
+       if (fabs(norm.x) < 2.0 && fabs(norm.y) < 2.0) {
+               if (norm.x > 0.0 && norm.y > 0.0)
+                       dir = S | SE | E;
+               else if (norm.x > 0.0 && norm.y < 0.0)
+                       dir = N | NE | E;
+               else if (norm.x < 0.0 && norm.y > 0.0)
+                       dir = S | SW | W;
+               else if (norm.x < 0.0 && norm.y < 0.0)
+                       dir = N | NW | W;
+               else if (norm.x > 0.0)
+                       dir = NE | E | SE;
+               else if (norm.x < 0.0)
+                       dir = NW | W | SW;
+               else if (norm.y > 0.0)
+                       dir = SE | S | SW;
+               else if (norm.y < 0.0)
+                       dir = NE | N | NW;
+       } else {
+               /* Calculate r within the interval  [0 to 8)
+                *
+                * r = [0 .. 2π] where 0 is North
+                * d_f = r / 2π  ([0 .. 1))
+                * d_8 = 8 * d_f
+                */
+               r = atan2(norm.y, norm.x);
+               r = fmod(r + 2.5*M_PI, 2*M_PI);
+               r *= 4*M_1_PI;
+
+               /* Mark one or two close enough octants */
+               d1 = (int)(r + 0.9) % 8;
+               d2 = (int)(r + 0.1) % 8;
+
+               dir = (1 << d1) | (1 << d2);
+       }
+
+       return dir;
 }
 
-struct list *
-libinput_path_get_devices(void);
 #endif /* LIBINPUT_PRIVATE_H */
diff --git a/src/libinput-uninstalled.pc.in b/src/libinput-uninstalled.pc.in
new file mode 100644 (file)
index 0000000..fa76ca5
--- /dev/null
@@ -0,0 +1,10 @@
+libdir=@abs_builddir@/.libs
+includedir=@abs_srcdir@
+
+Name: Libinput
+Description: Input device library (not installed)
+Version: @LIBINPUT_VERSION@
+Cflags: -I${includedir}
+Libs: -L${libdir} -linput
+Libs.private: -lm -lrt
+Requires.private: libudev
index 49e297af48ae94d6f1ef7af114206905a32f514b..4b90fbb06115224f8b770d5cf2748888bd254712 100644 (file)
@@ -1,24 +1,26 @@
 /*
  * Copyright © 2008-2011 Kristian Høgsberg
  * Copyright © 2011 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and its
- * documentation for any purpose is hereby granted without fee, provided that
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 /*
@@ -29,6 +31,7 @@
 #include "config.h"
 
 #include <ctype.h>
+#include <locale.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -62,16 +65,16 @@ list_remove(struct list *elm)
        elm->prev = NULL;
 }
 
-int
+bool
 list_empty(const struct list *list)
 {
        return list->next == list;
 }
 
 void
-ratelimit_init(struct ratelimit *r, uint64_t ival_ms, unsigned int burst)
+ratelimit_init(struct ratelimit *r, uint64_t ival_us, unsigned int burst)
 {
-       r->interval = ival_ms;
+       r->interval = ival_us;
        r->begin = 0;
        r->burst = burst;
        r->num = 0;
@@ -94,17 +97,17 @@ enum ratelimit_state
 ratelimit_test(struct ratelimit *r)
 {
        struct timespec ts;
-       uint64_t mtime;
+       uint64_t utime;
 
        if (r->interval <= 0 || r->burst <= 0)
                return RATELIMIT_PASS;
 
        clock_gettime(CLOCK_MONOTONIC, &ts);
-       mtime = ts.tv_sec * 1000 + ts.tv_nsec / 1000 / 1000;
+       utime = s2us(ts.tv_sec) + ns2us(ts.tv_nsec);
 
-       if (r->begin <= 0 || r->begin + r->interval < mtime) {
+       if (r->begin <= 0 || r->begin + r->interval < utime) {
                /* reset counter */
-               r->begin = mtime;
+               r->begin = utime;
                r->num = 1;
                return RATELIMIT_PASS;
        } else if (r->num < r->burst) {
@@ -201,3 +204,63 @@ parse_mouse_wheel_click_angle_property(const char *prop)
 
         return angle;
 }
+
+/**
+ * Helper function to parse the TRACKPOINT_CONST_ACCEL property from udev.
+ * Property is of the form:
+ * TRACKPOINT_CONST_ACCEL=<float>
+ *
+ * @param prop The value of the udev property (without the TRACKPOINT_CONST_ACCEL=)
+ * @return The acceleration, or 0.0 on error.
+ */
+double
+parse_trackpoint_accel_property(const char *prop)
+{
+       locale_t c_locale;
+       double accel;
+       char *endp;
+
+       /* Create a "C" locale to force strtod to use '.' as separator */
+       c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
+       if (c_locale == (locale_t)0)
+               return 0.0;
+
+       accel = strtod_l(prop, &endp, c_locale);
+
+       freelocale(c_locale);
+
+       if (*endp != '\0')
+               return 0.0;
+
+       return accel;
+}
+
+/**
+ * Parses a simple dimension string in the form of "10x40". The two
+ * numbers must be positive integers in decimal notation.
+ * On success, the two numbers are stored in w and h. On failure, w and h
+ * are unmodified.
+ *
+ * @param prop The value of the property
+ * @param w Returns the first component of the dimension
+ * @param h Returns the second component of the dimension
+ * @return true on success, false otherwise
+ */
+bool
+parse_dimension_property(const char *prop, size_t *w, size_t *h)
+{
+       int x, y;
+
+       if (!prop)
+               return false;
+
+       if (sscanf(prop, "%dx%d", &x, &y) != 2)
+               return false;
+
+       if (x < 0 || y < 0)
+               return false;
+
+       *w = (size_t)x;
+       *h = (size_t)y;
+       return true;
+}
index 88aff93c3c463f03ae47dd17454507a21abc6566..e31860dd9ee4121f833706733637b03633477695 100644 (file)
@@ -1,40 +1,55 @@
 /*
  * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and its
- * documentation for any purpose is hereby granted without fee, provided that
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef LIBINPUT_UTIL_H
 #define LIBINPUT_UTIL_H
 
+#include "config.h"
+
+#include <assert.h>
 #include <unistd.h>
+#include <limits.h>
 #include <math.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
 #include <string.h>
 #include <time.h>
 
 #include "libinput.h"
 
-void
-set_logging_enabled(int enabled);
+#define VENDOR_ID_APPLE 0x5ac
+#define VENDOR_ID_LOGITECH 0x46d
+#define VENDOR_ID_WACOM 0x56a
+#define VENDOR_ID_SYNAPTICS_SERIAL 0x002
+#define PRODUCT_ID_APPLE_KBD_TOUCHPAD 0x273
+#define PRODUCT_ID_SYNAPTICS_SERIAL 0x007
+
+/* The HW DPI rate we normalize to before calculating pointer acceleration */
+#define DEFAULT_MOUSE_DPI 1000
 
-void
-log_info(const char *format, ...);
+#define CASE_RETURN_STRING(a) case a: return #a
 
 /*
  * This list data structure is a verbatim copy from wayland-util.h from the
@@ -49,7 +64,7 @@ struct list {
 void list_init(struct list *list);
 void list_insert(struct list *list, struct list *elm);
 void list_remove(struct list *elm);
-int list_empty(const struct list *list);
+bool list_empty(const struct list *list);
 
 #ifdef __GNUC__
 #define container_of(ptr, sample, member)                              \
@@ -74,6 +89,7 @@ int list_empty(const struct list *list);
             pos = tmp,                                                 \
             tmp = container_of(pos->member.next, tmp, member))
 
+#define NBITS(b) (b * 8)
 #define LONG_BITS (sizeof(long) * 8)
 #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
 #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
@@ -83,6 +99,20 @@ int list_empty(const struct list *list);
 
 #define min(a, b) (((a) < (b)) ? (a) : (b))
 #define max(a, b) (((a) > (b)) ? (a) : (b))
+#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 { \
+       printf("%s:%d %s() - ", __FILE__, __LINE__, __func__); \
+       printf(__VA_ARGS__); \
+       } while (0)
+#else
+#define debug_trace(...) { }
+#endif
 
 #define LIBINPUT_EXPORT __attribute__ ((visibility("default")))
 
@@ -92,91 +122,50 @@ zalloc(size_t size)
        return calloc(1, size);
 }
 
-static inline void
-msleep(unsigned int ms)
+/* 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 bool
+bit_is_set(const unsigned char *array, int bit)
 {
-       usleep(ms * 1000);
+       return !!(array[bit / 8] & (1 << (bit % 8)));
 }
 
-static inline double
-deg2rad(double angle)
+       static inline void
+set_bit(unsigned char *array, int bit)
 {
-       return angle * M_PI/180.0;
+       array[bit / 8] |= (1 << (bit % 8));
 }
 
-enum directions {
-       N  = 1 << 0,
-       NE = 1 << 1,
-       E  = 1 << 2,
-       SE = 1 << 3,
-       S  = 1 << 4,
-       SW = 1 << 5,
-       W  = 1 << 6,
-       NW = 1 << 7,
-       UNDEFINED_DIRECTION = 0xff
-};
-
-static inline int
-vector_get_direction(int dx, int dy)
+       static inline void
+clear_bit(unsigned char *array, int bit)
 {
-       int dir = UNDEFINED_DIRECTION;
-       int d1, d2;
-       double r;
-
-       if (abs(dx) < 2 && abs(dy) < 2) {
-               if (dx > 0 && dy > 0)
-                       dir = S | SE | E;
-               else if (dx > 0 && dy < 0)
-                       dir = N | NE | E;
-               else if (dx < 0 && dy > 0)
-                       dir = S | SW | W;
-               else if (dx < 0 && dy < 0)
-                       dir = N | NW | W;
-               else if (dx > 0)
-                       dir = NE | E | SE;
-               else if (dx < 0)
-                       dir = NW | W | SW;
-               else if (dy > 0)
-                       dir = SE | S | SW;
-               else if (dy < 0)
-                       dir = NE | N | NW;
-       } else {
-               /* Calculate r within the interval  [0 to 8)
-                *
-                * r = [0 .. 2π] where 0 is North
-                * d_f = r / 2π  ([0 .. 1))
-                * d_8 = 8 * d_f
-                */
-               r = atan2(dy, dx);
-               r = fmod(r + 2.5*M_PI, 2*M_PI);
-               r *= 4*M_1_PI;
-
-               /* Mark one or two close enough octants */
-               d1 = (int)(r + 0.9) % 8;
-               d2 = (int)(r + 0.1) % 8;
-
-               dir = (1 << d1) | (1 << d2);
-       }
+       array[bit / 8] &= ~(1 << (bit % 8));
+}
 
-       return dir;
+static inline void
+msleep(unsigned int ms)
+{
+       usleep(ms * 1000);
 }
 
-static inline int
+static inline bool
 long_bit_is_set(const unsigned long *array, int bit)
 {
-    return !!(array[bit / LONG_BITS] & (1LL << (bit % LONG_BITS)));
+       return !!(array[bit / LONG_BITS] & (1LL << (bit % LONG_BITS)));
 }
 
 static inline void
 long_set_bit(unsigned long *array, int bit)
 {
-    array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS));
+       array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS));
 }
 
 static inline void
 long_clear_bit(unsigned long *array, int bit)
 {
-    array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS));
+       array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS));
 }
 
 static inline void
@@ -188,6 +177,25 @@ long_set_bit_state(unsigned long *array, int bit, int state)
                long_clear_bit(array, bit);
 }
 
+static inline bool
+long_any_bit_set(unsigned long *array, size_t size)
+{
+       unsigned long i;
+
+       assert(size > 0);
+
+       for (i = 0; i < size; i++)
+               if (array[i] != 0)
+                       return true;
+       return false;
+}
+
+static inline double
+deg2rad(int degree)
+{
+       return M_PI * degree / 180.0;
+}
+
 struct matrix {
        float val[3][3]; /* [row][col] */
 };
@@ -229,8 +237,23 @@ matrix_init_translate(struct matrix *m, float x, float y)
        m->val[1][2] = y;
 }
 
-static inline int
-matrix_is_identity(struct matrix *m)
+static inline void
+matrix_init_rotate(struct matrix *m, int degrees)
+{
+       double s, c;
+
+       s = sin(deg2rad(degrees));
+       c = cos(deg2rad(degrees));
+
+       matrix_init_identity(m);
+       m->val[0][0] = c;
+       m->val[0][1] = -s;
+       m->val[1][0] = s;
+       m->val[1][1] = c;
+}
+
+static inline bool
+matrix_is_identity(const struct matrix *m)
 {
        return (m->val[0][0] == 1 &&
                m->val[0][1] == 0 &&
@@ -265,7 +288,7 @@ matrix_mult(struct matrix *dest,
 }
 
 static inline void
-matrix_mult_vec(struct matrix *m, int *x, int *y)
+matrix_mult_vec(const struct matrix *m, int *x, int *y)
 {
        int tx, ty;
 
@@ -287,6 +310,48 @@ 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.
+ * The standard asprintf() call does not guarantee the passed in pointer
+ * will be NULL'ed upon failure, whereas this wrapper does.
+ *
+ * @param strp pointer to set to newly allocated string.
+ * This pointer should be passed to free() to release when done.
+ * @param fmt the format string to use for printing.
+ * @return The number of bytes printed (excluding the null byte terminator)
+ * upon success or -1 upon failure. In the case of failure the pointer is set
+ * to NULL.
+ */
+static inline int
+xasprintf(char **strp, const char *fmt, ...)
+       LIBINPUT_ATTRIBUTE_PRINTF(2, 3);
+
+static inline int
+xasprintf(char **strp, const char *fmt, ...)
+{
+       int rc = 0;
+       va_list args;
+
+       va_start(args, fmt);
+       rc = vasprintf(strp, fmt, args);
+       va_end(args);
+       if ((rc == -1) && strp)
+               *strp = NULL;
+
+       return rc;
+}
+
 enum ratelimit_state {
        RATELIMIT_EXCEEDED,
        RATELIMIT_THRESHOLD,
@@ -305,5 +370,56 @@ enum ratelimit_state ratelimit_test(struct ratelimit *r);
 
 int parse_mouse_dpi_property(const char *prop);
 int parse_mouse_wheel_click_angle_property(const char *prop);
+double parse_trackpoint_accel_property(const char *prop);
+bool parse_dimension_property(const char *prop, size_t *width, size_t *height);
+
+static inline uint64_t
+us(uint64_t us)
+{
+       return us;
+}
+
+static inline uint64_t
+ns2us(uint64_t ns)
+{
+       return us(ns / 1000);
+}
+
+static inline uint64_t
+ms2us(uint64_t ms)
+{
+       return us(ms * 1000);
+}
+
+static inline uint64_t
+s2us(uint64_t s)
+{
+       return ms2us(s * 1000);
+}
+
+static inline uint32_t
+us2ms(uint64_t us)
+{
+       return (uint32_t)(us / 1000);
+}
+
+static inline bool
+safe_atoi(const char *str, int *val)
+{
+        char *endptr;
+        long v;
+
+        v = strtol(str, &endptr, 10);
+        if (str == endptr)
+                return false;
+        if (*str != '\0' && *endptr != '\0')
+                return false;
+
+        if (v > INT_MAX || v < INT_MIN)
+                return false;
+
+        *val = v;
+        return true;
+}
 
 #endif /* LIBINPUT_UTIL_H */
index 2b1c4ea20254af369bcfc12f45362835188c8f45..0bcd7e5583f346a3f8ad92c36eb7634cef5cc3ee 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 Jonas Ådahl
  *
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef LIBINPUT_VERSION_H
index 9ebc6899e88953a10c6f4348b0d228c794d9cbe2..69580429b7f1e0a2dcd99e6391801016584c470b 100644 (file)
@@ -1,23 +1,25 @@
 /*
  * Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
@@ -65,8 +67,8 @@ check_event_type(struct libinput *libinput,
 
        if (!rc)
                log_bug_client(libinput,
-                                  "Invalid event type %d passed to %s()\n",
-                                  type_in, function_name);
+                              "Invalid event type %d passed to %s()\n",
+                              type_in, function_name);
 
        return rc;
 }
@@ -84,7 +86,7 @@ struct libinput_event_device_notify {
 
 struct libinput_event_keyboard {
        struct libinput_event base;
-       uint32_t time;
+       uint64_t time;
        uint32_t key;
        uint32_t seat_key_count;
        enum libinput_key_state state;
@@ -92,13 +94,11 @@ struct libinput_event_keyboard {
 
 struct libinput_event_pointer {
        struct libinput_event base;
-       uint32_t time;
-       double x;
-       double y;
-       double x_discrete;
-       double y_discrete;
-       double dx_unaccel;
-       double dy_unaccel;
+       uint64_t time;
+       struct normalized_coords delta;
+       struct device_float_coords delta_raw;
+       struct device_coords absolute;
+       struct discrete_coords discrete;
        uint32_t button;
        uint32_t seat_button_count;
        enum libinput_button_state state;
@@ -108,13 +108,55 @@ struct libinput_event_pointer {
 
 struct libinput_event_touch {
        struct libinput_event base;
-       uint32_t time;
+       uint64_t time;
        int32_t slot;
        int32_t seat_slot;
-       double x;
-       double y;
-       struct ellipse area;
-       int32_t pressure;
+       struct device_coords point;
+};
+
+struct libinput_event_gesture {
+       struct libinput_event base;
+       uint64_t time;
+       int finger_count;
+       int cancelled;
+       struct normalized_coords delta;
+       struct normalized_coords delta_unaccel;
+       double scale;
+       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;
+};
+
+struct libinput_event_tablet_pad {
+       struct libinput_event base;
+       unsigned int mode;
+       struct libinput_tablet_pad_mode_group *mode_group;
+       uint64_t time;
+       struct {
+               uint32_t number;
+               enum libinput_button_state state;
+       } button;
+       struct {
+               enum libinput_tablet_pad_ring_axis_source source;
+               double position;
+               int number;
+       } ring;
+       struct {
+               enum libinput_tablet_pad_strip_axis_source source;
+               double position;
+               int number;
+       } strip;
 };
 
 static void
@@ -158,6 +200,31 @@ log_msg(struct libinput *libinput,
        va_end(args);
 }
 
+void
+log_msg_ratelimit(struct libinput *libinput,
+                 struct ratelimit *ratelimit,
+                 enum libinput_log_priority priority,
+                 const char *format, ...)
+{
+       va_list args;
+       enum ratelimit_state state;
+
+       state = ratelimit_test(ratelimit);
+       if (state == RATELIMIT_EXCEEDED)
+               return;
+
+       va_start(args, format);
+       log_msg_va(libinput, priority, format, args);
+       va_end(args);
+
+       if (state == RATELIMIT_THRESHOLD)
+               log_msg(libinput,
+                       priority,
+                       "WARNING: log rate limit exceeded (%d msgs per %dms). Discarding future messages.\n",
+                       ratelimit->burst,
+                       us2ms(ratelimit->interval));
+}
+
 LIBINPUT_EXPORT void
 libinput_log_set_priority(struct libinput *libinput,
                          enum libinput_log_priority priority)
@@ -178,6 +245,9 @@ libinput_log_set_handler(struct libinput *libinput,
        libinput->log_handler = log_handler;
 }
 
+static void
+libinput_device_group_destroy(struct libinput_device_group *group);
+
 static void
 libinput_post_event(struct libinput *libinput,
                    struct libinput_event *event);
@@ -203,120 +273,138 @@ libinput_event_get_device(struct libinput_event *event)
 LIBINPUT_EXPORT struct libinput_event_pointer *
 libinput_event_get_pointer_event(struct libinput_event *event)
 {
-       switch (event->type) {
-       case LIBINPUT_EVENT_NONE:
-               abort(); /* not used as actual event type */
-       case LIBINPUT_EVENT_DEVICE_ADDED:
-       case LIBINPUT_EVENT_DEVICE_REMOVED:
-       case LIBINPUT_EVENT_KEYBOARD_KEY:
-               break;
-       case LIBINPUT_EVENT_POINTER_MOTION:
-       case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
-       case LIBINPUT_EVENT_POINTER_BUTTON:
-       case LIBINPUT_EVENT_POINTER_AXIS:
-               return (struct libinput_event_pointer *) event;
-       case LIBINPUT_EVENT_TOUCH_DOWN:
-       case LIBINPUT_EVENT_TOUCH_UP:
-       case LIBINPUT_EVENT_TOUCH_MOTION:
-       case LIBINPUT_EVENT_TOUCH_CANCEL:
-       case LIBINPUT_EVENT_TOUCH_FRAME:
-               break;
-       }
+       require_event_type(libinput_event_get_context(event),
+                          event->type,
+                          NULL,
+                          LIBINPUT_EVENT_POINTER_MOTION,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
+                          LIBINPUT_EVENT_POINTER_BUTTON,
+                          LIBINPUT_EVENT_POINTER_AXIS);
 
-       return NULL;
+       return (struct libinput_event_pointer *) event;
 }
 
 LIBINPUT_EXPORT struct libinput_event_keyboard *
 libinput_event_get_keyboard_event(struct libinput_event *event)
 {
-       switch (event->type) {
-       case LIBINPUT_EVENT_NONE:
-               abort(); /* not used as actual event type */
-       case LIBINPUT_EVENT_DEVICE_ADDED:
-       case LIBINPUT_EVENT_DEVICE_REMOVED:
-               break;
-       case LIBINPUT_EVENT_KEYBOARD_KEY:
-               return (struct libinput_event_keyboard *) event;
-       case LIBINPUT_EVENT_POINTER_MOTION:
-       case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
-       case LIBINPUT_EVENT_POINTER_BUTTON:
-       case LIBINPUT_EVENT_POINTER_AXIS:
-       case LIBINPUT_EVENT_TOUCH_DOWN:
-       case LIBINPUT_EVENT_TOUCH_UP:
-       case LIBINPUT_EVENT_TOUCH_MOTION:
-       case LIBINPUT_EVENT_TOUCH_CANCEL:
-       case LIBINPUT_EVENT_TOUCH_FRAME:
-               break;
-       }
+       require_event_type(libinput_event_get_context(event),
+                          event->type,
+                          NULL,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       return NULL;
+       return (struct libinput_event_keyboard *) event;
 }
 
 LIBINPUT_EXPORT struct libinput_event_touch *
 libinput_event_get_touch_event(struct libinput_event *event)
 {
-       switch (event->type) {
-       case LIBINPUT_EVENT_NONE:
-               abort(); /* not used as actual event type */
-       case LIBINPUT_EVENT_DEVICE_ADDED:
-       case LIBINPUT_EVENT_DEVICE_REMOVED:
-       case LIBINPUT_EVENT_KEYBOARD_KEY:
-       case LIBINPUT_EVENT_POINTER_MOTION:
-       case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
-       case LIBINPUT_EVENT_POINTER_BUTTON:
-       case LIBINPUT_EVENT_POINTER_AXIS:
-               break;
-       case LIBINPUT_EVENT_TOUCH_DOWN:
-       case LIBINPUT_EVENT_TOUCH_UP:
-       case LIBINPUT_EVENT_TOUCH_MOTION:
-       case LIBINPUT_EVENT_TOUCH_CANCEL:
-       case LIBINPUT_EVENT_TOUCH_FRAME:
-               return (struct libinput_event_touch *) event;
-       }
+       require_event_type(libinput_event_get_context(event),
+                          event->type,
+                          NULL,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_UP,
+                          LIBINPUT_EVENT_TOUCH_MOTION,
+                          LIBINPUT_EVENT_TOUCH_CANCEL,
+                          LIBINPUT_EVENT_TOUCH_FRAME);
+       return (struct libinput_event_touch *) event;
+}
 
-       return NULL;
+LIBINPUT_EXPORT struct libinput_event_gesture *
+libinput_event_get_gesture_event(struct libinput_event *event)
+{
+       require_event_type(libinput_event_get_context(event),
+                          event->type,
+                          NULL,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END);
+
+       return (struct libinput_event_gesture *) event;
+}
+
+LIBINPUT_EXPORT struct libinput_event_tablet_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_tablet_pad *
+libinput_event_get_tablet_pad_event(struct libinput_event *event)
+{
+       require_event_type(libinput_event_get_context(event),
+                          event->type,
+                          NULL,
+                          LIBINPUT_EVENT_TABLET_PAD_RING,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return (struct libinput_event_tablet_pad *) event;
 }
 
 LIBINPUT_EXPORT struct libinput_event_device_notify *
 libinput_event_get_device_notify_event(struct libinput_event *event)
 {
-       switch (event->type) {
-       case LIBINPUT_EVENT_NONE:
-               abort(); /* not used as actual event type */
-       case LIBINPUT_EVENT_DEVICE_ADDED:
-       case LIBINPUT_EVENT_DEVICE_REMOVED:
-               return (struct libinput_event_device_notify *) event;
-       case LIBINPUT_EVENT_KEYBOARD_KEY:
-       case LIBINPUT_EVENT_POINTER_MOTION:
-       case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
-       case LIBINPUT_EVENT_POINTER_BUTTON:
-       case LIBINPUT_EVENT_POINTER_AXIS:
-       case LIBINPUT_EVENT_TOUCH_DOWN:
-       case LIBINPUT_EVENT_TOUCH_UP:
-       case LIBINPUT_EVENT_TOUCH_MOTION:
-       case LIBINPUT_EVENT_TOUCH_CANCEL:
-       case LIBINPUT_EVENT_TOUCH_FRAME:
-               break;
-       }
+       require_event_type(libinput_event_get_context(event),
+                          event->type,
+                          NULL,
+                          LIBINPUT_EVENT_DEVICE_ADDED,
+                          LIBINPUT_EVENT_DEVICE_REMOVED);
 
-       return NULL;
+       return (struct libinput_event_device_notify *) event;
 }
 
 LIBINPUT_EXPORT uint32_t
 libinput_event_keyboard_get_time(struct libinput_event_keyboard *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       return us2ms(event->time);
+}
+
+LIBINPUT_EXPORT uint64_t
+libinput_event_keyboard_get_time_usec(struct libinput_event_keyboard *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
+
        return event->time;
 }
 
 LIBINPUT_EXPORT uint32_t
 libinput_event_keyboard_get_key(struct libinput_event_keyboard *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
+
        return event->key;
 }
 
 LIBINPUT_EXPORT enum libinput_key_state
 libinput_event_keyboard_get_key_state(struct libinput_event_keyboard *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
+
        return event->state;
 }
 
@@ -324,39 +412,86 @@ LIBINPUT_EXPORT uint32_t
 libinput_event_keyboard_get_seat_key_count(
        struct libinput_event_keyboard *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
+
        return event->seat_key_count;
 }
 
 LIBINPUT_EXPORT uint32_t
 libinput_event_pointer_get_time(struct libinput_event_pointer *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
+                          LIBINPUT_EVENT_POINTER_BUTTON,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
+       return us2ms(event->time);
+}
+
+LIBINPUT_EXPORT uint64_t
+libinput_event_pointer_get_time_usec(struct libinput_event_pointer *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
+                          LIBINPUT_EVENT_POINTER_BUTTON,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
        return event->time;
 }
 
 LIBINPUT_EXPORT double
 libinput_event_pointer_get_dx(struct libinput_event_pointer *event)
 {
-       return event->x;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION);
+
+       return event->delta.x;
 }
 
 LIBINPUT_EXPORT double
 libinput_event_pointer_get_dy(struct libinput_event_pointer *event)
 {
-       return event->y;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION);
+
+       return event->delta.y;
 }
 
 LIBINPUT_EXPORT double
 libinput_event_pointer_get_dx_unaccelerated(
        struct libinput_event_pointer *event)
 {
-       return event->dx_unaccel;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION);
+
+       return event->delta_raw.x;
 }
 
 LIBINPUT_EXPORT double
 libinput_event_pointer_get_dy_unaccelerated(
        struct libinput_event_pointer *event)
 {
-       return event->dy_unaccel;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION);
+
+       return event->delta_raw.y;
 }
 
 LIBINPUT_EXPORT double
@@ -365,7 +500,12 @@ libinput_event_pointer_get_absolute_x(struct libinput_event_pointer *event)
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_convert_to_mm(device->abs.absinfo_x, event->x);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+
+       return evdev_convert_to_mm(device->abs.absinfo_x, event->absolute.x);
 }
 
 LIBINPUT_EXPORT double
@@ -374,7 +514,12 @@ libinput_event_pointer_get_absolute_y(struct libinput_event_pointer *event)
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_convert_to_mm(device->abs.absinfo_y, event->y);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+
+       return evdev_convert_to_mm(device->abs.absinfo_y, event->absolute.y);
 }
 
 LIBINPUT_EXPORT double
@@ -385,7 +530,12 @@ libinput_event_pointer_get_absolute_x_transformed(
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_device_transform_x(device, event->x, width);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+
+       return evdev_device_transform_x(device, event->absolute.x, width);
 }
 
 LIBINPUT_EXPORT double
@@ -396,18 +546,33 @@ libinput_event_pointer_get_absolute_y_transformed(
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_device_transform_y(device, event->y, height);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+
+       return evdev_device_transform_y(device, event->absolute.y, height);
 }
 
 LIBINPUT_EXPORT uint32_t
 libinput_event_pointer_get_button(struct libinput_event_pointer *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_BUTTON);
+
        return event->button;
 }
 
 LIBINPUT_EXPORT enum libinput_button_state
 libinput_event_pointer_get_button_state(struct libinput_event_pointer *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_BUTTON);
+
        return event->state;
 }
 
@@ -415,6 +580,11 @@ LIBINPUT_EXPORT uint32_t
 libinput_event_pointer_get_seat_button_count(
        struct libinput_event_pointer *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_BUTTON);
+
        return event->seat_button_count;
 }
 
@@ -422,13 +592,17 @@ LIBINPUT_EXPORT int
 libinput_event_pointer_has_axis(struct libinput_event_pointer *event,
                                enum libinput_pointer_axis axis)
 {
-       if (event->base.type == LIBINPUT_EVENT_POINTER_AXIS) {
-               switch (axis) {
-               case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
-               case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
-                       return !!(event->axes & AS_MASK(axis));
-               }
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
+       switch (axis) {
+       case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
+       case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
+               return !!(event->axes & AS_MASK(axis));
        }
+
        return 0;
 }
 
@@ -439,15 +613,20 @@ libinput_event_pointer_get_axis_value(struct libinput_event_pointer *event,
        struct libinput *libinput = event->base.device->seat->libinput;
        double value = 0;
 
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
        if (!libinput_event_pointer_has_axis(event, axis)) {
                log_bug_client(libinput, "value requested for unset axis\n");
        } else {
                switch (axis) {
                case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
-                       value = event->x;
+                       value = event->delta.x;
                        break;
                case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
-                       value = event->y;
+                       value = event->delta.y;
                        break;
                }
        }
@@ -462,15 +641,20 @@ libinput_event_pointer_get_axis_value_discrete(struct libinput_event_pointer *ev
        struct libinput *libinput = event->base.device->seat->libinput;
        double value = 0;
 
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
        if (!libinput_event_pointer_has_axis(event, axis)) {
                log_bug_client(libinput, "value requested for unset axis\n");
        } else {
                switch (axis) {
                case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
-                       value = event->x_discrete;
+                       value = event->discrete.x;
                        break;
                case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
-                       value = event->y_discrete;
+                       value = event->discrete.y;
                        break;
                }
        }
@@ -480,24 +664,69 @@ libinput_event_pointer_get_axis_value_discrete(struct libinput_event_pointer *ev
 LIBINPUT_EXPORT enum libinput_pointer_axis_source
 libinput_event_pointer_get_axis_source(struct libinput_event_pointer *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
        return event->source;
 }
 
 LIBINPUT_EXPORT uint32_t
 libinput_event_touch_get_time(struct libinput_event_touch *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_UP,
+                          LIBINPUT_EVENT_TOUCH_MOTION,
+                          LIBINPUT_EVENT_TOUCH_CANCEL,
+                          LIBINPUT_EVENT_TOUCH_FRAME);
+
+       return us2ms(event->time);
+}
+
+LIBINPUT_EXPORT uint64_t
+libinput_event_touch_get_time_usec(struct libinput_event_touch *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_UP,
+                          LIBINPUT_EVENT_TOUCH_MOTION,
+                          LIBINPUT_EVENT_TOUCH_CANCEL,
+                          LIBINPUT_EVENT_TOUCH_FRAME);
+
        return event->time;
 }
 
 LIBINPUT_EXPORT int32_t
 libinput_event_touch_get_slot(struct libinput_event_touch *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_UP,
+                          LIBINPUT_EVENT_TOUCH_MOTION,
+                          LIBINPUT_EVENT_TOUCH_CANCEL);
+
        return event->slot;
 }
 
 LIBINPUT_EXPORT int32_t
 libinput_event_touch_get_seat_slot(struct libinput_event_touch *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_UP,
+                          LIBINPUT_EVENT_TOUCH_MOTION,
+                          LIBINPUT_EVENT_TOUCH_CANCEL);
+
        return event->seat_slot;
 }
 
@@ -507,7 +736,13 @@ libinput_event_touch_get_x(struct libinput_event_touch *event)
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_convert_to_mm(device->abs.absinfo_x, event->x);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_MOTION);
+
+       return evdev_convert_to_mm(device->abs.absinfo_x, event->point.x);
 }
 
 LIBINPUT_EXPORT double
@@ -517,7 +752,13 @@ libinput_event_touch_get_x_transformed(struct libinput_event_touch *event,
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_device_transform_x(device, event->x, width);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_MOTION);
+
+       return evdev_device_transform_x(device, event->point.x, width);
 }
 
 LIBINPUT_EXPORT double
@@ -527,237 +768,743 @@ libinput_event_touch_get_y_transformed(struct libinput_event_touch *event,
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
 
-       return evdev_device_transform_y(device, event->y, height);
-}
-
-LIBINPUT_EXPORT double
-libinput_event_touch_get_y(struct libinput_event_touch *event)
-{
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
-
-       return evdev_convert_to_mm(device->abs.absinfo_y, event->y);
-}
-
-LIBINPUT_EXPORT double
-libinput_event_touch_get_major(struct libinput_event_touch *event)
-{
-#if 0
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
-       double angle;
-#endif
        require_event_type(libinput_event_get_context(&event->base),
                           event->base.type,
                           0,
                           LIBINPUT_EVENT_TOUCH_DOWN,
                           LIBINPUT_EVENT_TOUCH_MOTION);
 
-       /* return touch major value directly.
-           Currently transfrom touch major value is not needed */
-       return event->area.major;
-#if 0
-       angle = evdev_device_transform_orientation(device,
-                                                  event->area.orientation);
-
-       return evdev_device_transform_ellipse_diameter_to_mm(device,
-                                                            event->area.major,
-                                                            angle);
-#endif
+       return evdev_device_transform_y(device, event->point.y, height);
 }
 
 LIBINPUT_EXPORT double
-libinput_event_touch_get_major_transformed(struct libinput_event_touch *event,
-                                          uint32_t width,
-                                          uint32_t height)
+libinput_event_touch_get_y(struct libinput_event_touch *event)
 {
-#if 0
        struct evdev_device *device =
                (struct evdev_device *) event->base.device;
-       double angle;
-#endif
+
        require_event_type(libinput_event_get_context(&event->base),
                           event->base.type,
                           0,
                           LIBINPUT_EVENT_TOUCH_DOWN,
                           LIBINPUT_EVENT_TOUCH_MOTION);
 
-       /* return touch major value directly.
-           Currently transfrom touch major value is not needed */
-       return event->area.major;
-#if 0
-       angle = evdev_device_transform_orientation(device,
-                                                  event->area.orientation);
-
-       return evdev_device_transform_ellipse_diameter(device,
-                                                      event->area.major,
-                                                      angle,
-                                                      width,
-                                                      height);
-#endif
+       return evdev_convert_to_mm(device->abs.absinfo_y, event->point.y);
 }
 
-LIBINPUT_EXPORT int
-libinput_event_touch_has_major(struct libinput_event_touch *event)
+LIBINPUT_EXPORT uint32_t
+libinput_event_gesture_get_time(struct libinput_event_gesture *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_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
 
-       return device->abs.absinfo_major != 0;
+       return us2ms(event->time);
 }
 
-LIBINPUT_EXPORT double
-libinput_event_touch_get_minor(struct libinput_event_touch *event)
+LIBINPUT_EXPORT uint64_t
+libinput_event_gesture_get_time_usec(struct libinput_event_gesture *event)
 {
-#if 0
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
-       double angle;
-#endif
        require_event_type(libinput_event_get_context(&event->base),
                           event->base.type,
                           0,
-                          LIBINPUT_EVENT_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
 
-       /* return touch minor value directly.
-           Currently transfrom touch minor value is not needed */
-       return event->area.minor;
-#if 0
-       angle = evdev_device_transform_orientation(device,
-                                                  event->area.orientation);
-
-       /* angle + 90 since the minor diameter is perpendicular to the
-        * major axis */
-       return evdev_device_transform_ellipse_diameter_to_mm(device,
-                                                            event->area.minor,
-                                                            angle + 90.0);
-#endif
+       return event->time;
 }
 
-LIBINPUT_EXPORT double
-libinput_event_touch_get_minor_transformed(struct libinput_event_touch *event,
-                                          uint32_t width,
-                                          uint32_t height)
+LIBINPUT_EXPORT int
+libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event)
 {
-#if 0
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
-       double angle;
-       int diameter;
-#endif
        require_event_type(libinput_event_get_context(&event->base),
                           event->base.type,
                           0,
-                          LIBINPUT_EVENT_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
-
-       /* return touch minor value directly.
-           Currently transfrom touch minor value is not needed */
-       return event->area.minor;
-#if 0
-       angle = evdev_device_transform_orientation(device,
-                                                  event->area.orientation);
-
-       /* use major diameter if minor is not available, but if it is
-        * add 90 since the minor diameter is perpendicular to the
-        * major axis */
-       if (device->abs.absinfo_minor) {
-               diameter = event->area.minor,
-               angle += 90.0;
-       } else {
-               diameter = event->area.major;
-       }
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
 
-       return evdev_device_transform_ellipse_diameter(device,
-                                                      diameter,
-                                                      angle,
-                                                      width,
-                                                      height);
-#endif
+       return event->finger_count;
 }
 
 LIBINPUT_EXPORT int
-libinput_event_touch_has_minor(struct libinput_event_touch *event)
+libinput_event_gesture_get_cancelled(struct libinput_event_gesture *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_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
 
-       return device->abs.absinfo_minor != 0;
+       return event->cancelled;
 }
 
 LIBINPUT_EXPORT double
-libinput_event_touch_get_orientation(struct libinput_event_touch *event)
+libinput_event_gesture_get_dx(struct libinput_event_gesture *event)
 {
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+       return event->delta.x;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dy(struct libinput_event_gesture *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+       return event->delta.y;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dx_unaccelerated(
+       struct libinput_event_gesture *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+       return event->delta_unaccel.x;
+}
 
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_dy_unaccelerated(
+       struct libinput_event_gesture *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_SWIPE_END);
+
+       return event->delta_unaccel.y;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_scale(struct libinput_event_gesture *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END);
+
+       return event->scale;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                          LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                          LIBINPUT_EVENT_GESTURE_PINCH_END);
+
+       return event->angle;
+}
+
+LIBINPUT_EXPORT int
+libinput_event_tablet_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_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+                          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;
+}
 
-       return evdev_device_transform_orientation(device,
-                                                 event->area.orientation);
+LIBINPUT_EXPORT uint64_t
+libinput_tablet_tool_get_serial(struct libinput_tablet_tool *tool)
+{
+       return tool->serial;
 }
 
 LIBINPUT_EXPORT int
-libinput_event_touch_has_orientation(struct libinput_event_touch *event)
+libinput_tablet_tool_has_pressure(struct libinput_tablet_tool *tool)
 {
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
+       return bit_is_set(tool->axis_caps,
+                         LIBINPUT_TABLET_TOOL_AXIS_PRESSURE);
+}
 
-       require_event_type(libinput_event_get_context(&event->base),
-                          event->base.type,
-                          0,
-                          LIBINPUT_EVENT_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+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);
+}
 
-       return device->abs.absinfo_orientation != 0;
+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 double
-libinput_event_touch_get_pressure(struct libinput_event_touch *event)
+LIBINPUT_EXPORT int
+libinput_tablet_tool_has_rotation(struct libinput_tablet_tool *tool)
 {
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
+       return bit_is_set(tool->axis_caps,
+                         LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z);
+}
 
-       require_event_type(libinput_event_get_context(&event->base),
-                          event->base.type,
-                          0,
-                          LIBINPUT_EVENT_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+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);
+}
 
-       return evdev_device_transform_pressure(device,
-                                              event->pressure);
+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_event_touch_has_pressure(struct libinput_event_touch *event)
+libinput_tablet_tool_has_button(struct libinput_tablet_tool *tool,
+                               uint32_t code)
 {
-       struct evdev_device *device =
-               (struct evdev_device *) event->base.device;
+       if (NCHARS(code) > sizeof(tool->buttons))
+               return 0;
 
-       require_event_type(libinput_event_get_context(&event->base),
-                          event->base.type,
-                          0,
-                          LIBINPUT_EVENT_TOUCH_DOWN,
-                          LIBINPUT_EVENT_TOUCH_MOTION);
+       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;
+}
 
-       return device->abs.absinfo_pressure != 0;
+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 *
@@ -804,6 +1551,9 @@ libinput_init(struct libinput *libinput,
              const struct libinput_interface_backend *interface_backend,
              void *user_data)
 {
+       assert(interface->open_restricted != NULL);
+       assert(interface->close_restricted != NULL);
+
        libinput->epoll_fd = epoll_create1(EPOLL_CLOEXEC);;
        if (libinput->epoll_fd < 0)
                return -1;
@@ -823,6 +1573,8 @@ libinput_init(struct libinput *libinput,
        libinput->refcount = 1;
        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);
@@ -862,6 +1614,8 @@ 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)
                return NULL;
@@ -889,6 +1643,17 @@ libinput_unref(struct libinput *libinput)
                libinput_seat_destroy(seat);
        }
 
+       list_for_each_safe(group,
+                          next_group,
+                          &libinput->device_group_list,
+                          link) {
+               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);
@@ -897,12 +1662,42 @@ libinput_unref(struct libinput *libinput)
        return NULL;
 }
 
+static void
+libinput_event_tablet_tool_destroy(struct libinput_event_tablet_tool *event)
+{
+       libinput_tablet_tool_unref(event->tool);
+}
+
+static void
+libinput_event_tablet_pad_destroy(struct libinput_event_tablet_pad *event)
+{
+       libinput_tablet_pad_mode_group_unref(event->mode_group);
+}
+
 LIBINPUT_EXPORT void
 libinput_event_destroy(struct libinput_event *event)
 {
        if (event == NULL)
                return;
 
+       switch(event->type) {
+       case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
+       case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
+       case LIBINPUT_EVENT_TABLET_TOOL_TIP:
+       case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
+               libinput_event_tablet_tool_destroy(
+                  libinput_event_get_tablet_tool_event(event));
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_RING:
+       case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+       case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+               libinput_event_tablet_pad_destroy(
+                  libinput_event_get_tablet_pad_event(event));
+               break;
+       default:
+               break;
+       }
+
        if (event->device)
                libinput_device_unref(event->device);
 
@@ -924,6 +1719,16 @@ close_restricted(struct libinput *libinput, int fd)
        return libinput->interface->close_restricted(fd, libinput->user_data);
 }
 
+bool
+ignore_litest_test_suite_device(struct udev_device *device)
+{
+       if (!getenv("LIBINPUT_RUNNING_TEST_SUITE") &&
+           udev_device_get_property_value(device, "LIBINPUT_TEST_DEVICE"))
+               return true;
+
+       return false;
+}
+
 void
 libinput_seat_init(struct libinput_seat *seat,
                   struct libinput *libinput,
@@ -1167,19 +1972,13 @@ notify_added_device(struct libinput_device *device)
 {
        struct libinput_event_device_notify *added_device_event;
 
-       TRACE_INPUT_BEGIN(notify_added_device);
-
        added_device_event = zalloc(sizeof *added_device_event);
-       if (!added_device_event) {
-               TRACE_INPUT_END();
+       if (!added_device_event)
                return;
-       }
 
        post_base_event(device,
                        LIBINPUT_EVENT_DEVICE_ADDED,
                        &added_device_event->base);
-
-       TRACE_INPUT_END();
 }
 
 void
@@ -1187,19 +1986,51 @@ notify_removed_device(struct libinput_device *device)
 {
        struct libinput_event_device_notify *removed_device_event;
 
-       TRACE_INPUT_BEGIN(notify_removed_device);
-
        removed_device_event = zalloc(sizeof *removed_device_event);
-       if (!removed_device_event) {
-               TRACE_INPUT_END();
+       if (!removed_device_event)
                return;
-       }
 
        post_base_event(device,
                        LIBINPUT_EVENT_DEVICE_REMOVED,
                        &removed_device_event->base);
+}
+
+static inline bool
+device_has_cap(struct libinput_device *device,
+              enum libinput_device_capability cap)
+{
+       const char *capability;
+
+       if (libinput_device_has_capability(device, cap))
+               return true;
+
+       switch (cap) {
+       case LIBINPUT_DEVICE_CAP_POINTER:
+               capability = "CAP_POINTER";
+               break;
+       case LIBINPUT_DEVICE_CAP_KEYBOARD:
+               capability = "CAP_KEYBOARD";
+               break;
+       case LIBINPUT_DEVICE_CAP_TOUCH:
+               capability = "CAP_TOUCH";
+               break;
+       case LIBINPUT_DEVICE_CAP_GESTURE:
+               capability = "CAP_GESTURE";
+               break;
+       case LIBINPUT_DEVICE_CAP_TABLET_TOOL:
+               capability = "CAP_TABLET_TOOL";
+               break;
+       case LIBINPUT_DEVICE_CAP_TABLET_PAD:
+               capability = "CAP_TABLET_PAD";
+               break;
+       }
+
+       log_bug_libinput(device->seat->libinput,
+                        "Event for missing capability %s on device \"%s\"\n",
+                        capability,
+                        libinput_device_get_name(device));
 
-       TRACE_INPUT_END();
+       return false;
 }
 
 void
@@ -1211,13 +2042,12 @@ keyboard_notify_key(struct libinput_device *device,
        struct libinput_event_keyboard *key_event;
        uint32_t seat_key_count;
 
-       TRACE_INPUT_BEGIN(keyboard_notify_key);
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_KEYBOARD))
+               return;
 
        key_event = zalloc(sizeof *key_event);
-       if (!key_event) {
-               TRACE_INPUT_END();
+       if (!key_event)
                return;
-       }
 
        seat_key_count = update_seat_key_count(device->seat, key, state);
 
@@ -1231,70 +2061,56 @@ keyboard_notify_key(struct libinput_device *device,
        post_device_event(device, time,
                          LIBINPUT_EVENT_KEYBOARD_KEY,
                          &key_event->base);
-
-       TRACE_INPUT_END();
 }
 
 void
 pointer_notify_motion(struct libinput_device *device,
                      uint64_t time,
-                     double dx,
-                     double dy,
-                     double dx_unaccel,
-                     double dy_unaccel)
+                     const struct normalized_coords *delta,
+                     const struct device_float_coords *raw)
 {
        struct libinput_event_pointer *motion_event;
 
-       TRACE_INPUT_BEGIN(pointer_notify_motion);
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER))
+               return;
 
        motion_event = zalloc(sizeof *motion_event);
-       if (!motion_event) {
-               TRACE_INPUT_END();
+       if (!motion_event)
                return;
-       }
 
        *motion_event = (struct libinput_event_pointer) {
                .time = time,
-               .x = dx,
-               .y = dy,
-               .dx_unaccel = dx_unaccel,
-               .dy_unaccel = dy_unaccel,
+               .delta = *delta,
+               .delta_raw = *raw,
        };
 
        post_device_event(device, time,
                          LIBINPUT_EVENT_POINTER_MOTION,
                          &motion_event->base);
-
-       TRACE_INPUT_END();
 }
 
 void
 pointer_notify_motion_absolute(struct libinput_device *device,
                               uint64_t time,
-                              double x,
-                              double y)
+                              const struct device_coords *point)
 {
        struct libinput_event_pointer *motion_absolute_event;
 
-       TRACE_INPUT_BEGIN(pointer_notify_motion_absolute);
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER))
+               return;
 
        motion_absolute_event = zalloc(sizeof *motion_absolute_event);
-       if (!motion_absolute_event) {
-               TRACE_INPUT_END();
+       if (!motion_absolute_event)
                return;
-       }
 
        *motion_absolute_event = (struct libinput_event_pointer) {
                .time = time,
-               .x = x,
-               .y = y,
+               .absolute = *point,
        };
 
        post_device_event(device, time,
                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
                          &motion_absolute_event->base);
-
-       TRACE_INPUT_END();
 }
 
 void
@@ -1306,13 +2122,12 @@ pointer_notify_button(struct libinput_device *device,
        struct libinput_event_pointer *button_event;
        int32_t seat_button_count;
 
-       TRACE_INPUT_BEGIN(pointer_notify_button);
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER))
+               return;
 
        button_event = zalloc(sizeof *button_event);
-       if (!button_event) {
-               TRACE_INPUT_END();
+       if (!button_event)
                return;
-       }
 
        seat_button_count = update_seat_button_count(device->seat,
                                                     button,
@@ -1328,8 +2143,6 @@ pointer_notify_button(struct libinput_device *device,
        post_device_event(device, time,
                          LIBINPUT_EVENT_POINTER_BUTTON,
                          &button_event->base);
-
-       TRACE_INPUT_END();
 }
 
 void
@@ -1337,34 +2150,29 @@ pointer_notify_axis(struct libinput_device *device,
                    uint64_t time,
                    uint32_t axes,
                    enum libinput_pointer_axis_source source,
-                   double x, double y,
-                   double x_discrete, double y_discrete)
+                   const struct normalized_coords *delta,
+                   const struct discrete_coords *discrete)
 {
        struct libinput_event_pointer *axis_event;
 
-       TRACE_INPUT_BEGIN(pointer_notify_axis);
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_POINTER))
+               return;
 
        axis_event = zalloc(sizeof *axis_event);
-       if (!axis_event) {
-               TRACE_INPUT_END();
+       if (!axis_event)
                return;
-       }
 
        *axis_event = (struct libinput_event_pointer) {
                .time = time,
-               .x = x,
-               .y = y,
+               .delta = *delta,
                .source = source,
                .axes = axes,
-               .x_discrete = x_discrete,
-               .y_discrete = y_discrete,
+               .discrete = *discrete,
        };
 
        post_device_event(device, time,
                          LIBINPUT_EVENT_POINTER_AXIS,
                          &axis_event->base);
-
-       TRACE_INPUT_END();
 }
 
 void
@@ -1372,127 +2180,449 @@ touch_notify_touch_down(struct libinput_device *device,
                        uint64_t time,
                        int32_t slot,
                        int32_t seat_slot,
-                       double x,
-                       double y,
-                       const struct ellipse *area,
-                       int32_t pressure)
+                       const struct device_coords *point)
 {
        struct libinput_event_touch *touch_event;
 
-       TRACE_INPUT_BEGIN(touch_notify_touch_down);
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH))
+               return;
 
        touch_event = zalloc(sizeof *touch_event);
-       if (!touch_event) {
-               TRACE_INPUT_END();
+       if (!touch_event)
                return;
-       }
 
        *touch_event = (struct libinput_event_touch) {
                .time = time,
                .slot = slot,
                .seat_slot = seat_slot,
-               .x = x,
-               .y = y,
-               .area = *area,
-               .pressure = pressure,
+               .point = *point,
        };
 
        post_device_event(device, time,
                          LIBINPUT_EVENT_TOUCH_DOWN,
                          &touch_event->base);
+}
+
+void
+touch_notify_touch_motion(struct libinput_device *device,
+                         uint64_t time,
+                         int32_t slot,
+                         int32_t seat_slot,
+                         const struct device_coords *point)
+{
+       struct libinput_event_touch *touch_event;
+
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH))
+               return;
+
+       touch_event = zalloc(sizeof *touch_event);
+       if (!touch_event)
+               return;
+
+       *touch_event = (struct libinput_event_touch) {
+               .time = time,
+               .slot = slot,
+               .seat_slot = seat_slot,
+               .point = *point,
+       };
+
+       post_device_event(device, time,
+                         LIBINPUT_EVENT_TOUCH_MOTION,
+                         &touch_event->base);
+}
+
+void
+touch_notify_touch_up(struct libinput_device *device,
+                     uint64_t time,
+                     int32_t slot,
+                     int32_t seat_slot)
+{
+       struct libinput_event_touch *touch_event;
+
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH))
+               return;
+
+       touch_event = zalloc(sizeof *touch_event);
+       if (!touch_event)
+               return;
+
+       *touch_event = (struct libinput_event_touch) {
+               .time = time,
+               .slot = slot,
+               .seat_slot = seat_slot,
+       };
+
+       post_device_event(device, time,
+                         LIBINPUT_EVENT_TOUCH_UP,
+                         &touch_event->base);
+}
+
+void
+touch_notify_frame(struct libinput_device *device,
+                  uint64_t time)
+{
+       struct libinput_event_touch *touch_event;
+
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_TOUCH))
+               return;
+
+       touch_event = zalloc(sizeof *touch_event);
+       if (!touch_event)
+               return;
+
+       *touch_event = (struct libinput_event_touch) {
+               .time = time,
+       };
+
+       post_device_event(device, time,
+                         LIBINPUT_EVENT_TOUCH_FRAME,
+                         &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 = libinput_tablet_tool_ref(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 = libinput_tablet_tool_ref(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 = libinput_tablet_tool_ref(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 = libinput_tablet_tool_ref(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);
+}
+
+void
+tablet_pad_notify_button(struct libinput_device *device,
+                        uint64_t time,
+                        int32_t button,
+                        enum libinput_button_state state,
+                        struct libinput_tablet_pad_mode_group *group)
+{
+       struct libinput_event_tablet_pad *button_event;
+       unsigned int mode;
+
+       button_event = zalloc(sizeof *button_event);
+       if (!button_event)
+               return;
+
+       mode = libinput_tablet_pad_mode_group_get_mode(group);
+
+       *button_event = (struct libinput_event_tablet_pad) {
+               .time = time,
+               .button.number = button,
+               .button.state = state,
+               .mode_group = libinput_tablet_pad_mode_group_ref(group),
+               .mode = mode,
+       };
+
+       post_device_event(device,
+                         time,
+                         LIBINPUT_EVENT_TABLET_PAD_BUTTON,
+                         &button_event->base);
+}
+
+void
+tablet_pad_notify_ring(struct libinput_device *device,
+                      uint64_t time,
+                      unsigned int number,
+                      double value,
+                      enum libinput_tablet_pad_ring_axis_source source,
+                      struct libinput_tablet_pad_mode_group *group)
+{
+       struct libinput_event_tablet_pad *ring_event;
+       unsigned int mode;
+
+       ring_event = zalloc(sizeof *ring_event);
+       if (!ring_event)
+               return;
+
+       mode = libinput_tablet_pad_mode_group_get_mode(group);
 
-       TRACE_INPUT_END();
+       *ring_event = (struct libinput_event_tablet_pad) {
+               .time = time,
+               .ring.number = number,
+               .ring.position = value,
+               .ring.source = source,
+               .mode_group = libinput_tablet_pad_mode_group_ref(group),
+               .mode = mode,
+       };
+
+       post_device_event(device,
+                         time,
+                         LIBINPUT_EVENT_TABLET_PAD_RING,
+                         &ring_event->base);
 }
 
 void
-touch_notify_touch_motion(struct libinput_device *device,
-                         uint64_t time,
-                         int32_t slot,
-                         int32_t seat_slot,
-                         double x,
-                         double y,
-                         const struct ellipse *area,
-                         int32_t pressure)
+tablet_pad_notify_strip(struct libinput_device *device,
+                       uint64_t time,
+                       unsigned int number,
+                       double value,
+                       enum libinput_tablet_pad_strip_axis_source source,
+                       struct libinput_tablet_pad_mode_group *group)
 {
-       struct libinput_event_touch *touch_event;
-
-       TRACE_INPUT_BEGIN(touch_notify_touch_motion);
+       struct libinput_event_tablet_pad *strip_event;
+       unsigned int mode;
 
-       touch_event = zalloc(sizeof *touch_event);
-       if (!touch_event) {
-               TRACE_INPUT_END();
+       strip_event = zalloc(sizeof *strip_event);
+       if (!strip_event)
                return;
-       }
 
-       *touch_event = (struct libinput_event_touch) {
+       mode = libinput_tablet_pad_mode_group_get_mode(group);
+
+       *strip_event = (struct libinput_event_tablet_pad) {
                .time = time,
-               .slot = slot,
-               .seat_slot = seat_slot,
-               .x = x,
-               .y = y,
-               .area = *area,
-               .pressure = pressure,
+               .strip.number = number,
+               .strip.position = value,
+               .strip.source = source,
+               .mode_group = libinput_tablet_pad_mode_group_ref(group),
+               .mode = mode,
        };
 
-       post_device_event(device, time,
-                         LIBINPUT_EVENT_TOUCH_MOTION,
-                         &touch_event->base);
-
-       TRACE_INPUT_END();
+       post_device_event(device,
+                         time,
+                         LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                         &strip_event->base);
 }
 
-void
-touch_notify_touch_up(struct libinput_device *device,
-                     uint64_t time,
-                     int32_t slot,
-                     int32_t seat_slot)
-{
-       struct libinput_event_touch *touch_event;
-
-       TRACE_INPUT_BEGIN(touch_notify_touch_up);
+static void
+gesture_notify(struct libinput_device *device,
+              uint64_t time,
+              enum libinput_event_type type,
+              int finger_count,
+              int cancelled,
+              const struct normalized_coords *delta,
+              const struct normalized_coords *unaccel,
+              double scale,
+              double angle)
+{
+       struct libinput_event_gesture *gesture_event;
+
+       if (!device_has_cap(device, LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
 
-       touch_event = zalloc(sizeof *touch_event);
-       if (!touch_event) {
-               TRACE_INPUT_END();
+       gesture_event = zalloc(sizeof *gesture_event);
+       if (!gesture_event)
                return;
-       }
 
-       *touch_event = (struct libinput_event_touch) {
+       *gesture_event = (struct libinput_event_gesture) {
                .time = time,
-               .slot = slot,
-               .seat_slot = seat_slot,
+               .finger_count = finger_count,
+               .cancelled = cancelled,
+               .delta = *delta,
+               .delta_unaccel = *unaccel,
+               .scale = scale,
+               .angle = angle,
        };
 
-       post_device_event(device, time,
-                         LIBINPUT_EVENT_TOUCH_UP,
-                         &touch_event->base);
-
-       TRACE_INPUT_END();
+       post_device_event(device, time, type,
+                         &gesture_event->base);
 }
 
 void
-touch_notify_frame(struct libinput_device *device,
-                  uint64_t time)
+gesture_notify_swipe(struct libinput_device *device,
+                    uint64_t time,
+                    enum libinput_event_type type,
+                    int finger_count,
+                    const struct normalized_coords *delta,
+                    const struct normalized_coords *unaccel)
 {
-       struct libinput_event_touch *touch_event;
+       gesture_notify(device, time, type, finger_count, 0, delta, unaccel,
+                      0.0, 0.0);
+}
 
-       TRACE_INPUT_BEGIN(touch_notify_frame);
+void
+gesture_notify_swipe_end(struct libinput_device *device,
+                        uint64_t time,
+                        int finger_count,
+                        int cancelled)
+{
+       const struct normalized_coords zero = { 0.0, 0.0 };
 
-       touch_event = zalloc(sizeof *touch_event);
-       if (!touch_event) {
-               TRACE_INPUT_END();
-               return;
-       }
+       gesture_notify(device, time, LIBINPUT_EVENT_GESTURE_SWIPE_END,
+                      finger_count, cancelled, &zero, &zero, 0.0, 0.0);
+}
 
-       *touch_event = (struct libinput_event_touch) {
-               .time = time,
-       };
+void
+gesture_notify_pinch(struct libinput_device *device,
+                    uint64_t time,
+                    enum libinput_event_type type,
+                    int finger_count,
+                    const struct normalized_coords *delta,
+                    const struct normalized_coords *unaccel,
+                    double scale,
+                    double angle)
+{
+       gesture_notify(device, time, type, finger_count, 0,
+                      delta, unaccel, scale, angle);
+}
 
-       post_device_event(device, time,
-                         LIBINPUT_EVENT_TOUCH_FRAME,
-                         &touch_event->base);
+void
+gesture_notify_pinch_end(struct libinput_device *device,
+                        uint64_t time,
+                        int finger_count,
+                        double scale,
+                        int cancelled)
+{
+       const struct normalized_coords zero = { 0.0, 0.0 };
+
+       gesture_notify(device, time, LIBINPUT_EVENT_GESTURE_PINCH_END,
+                      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_TABLET_PAD_BUTTON);
+       CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_RING);
+       CASE_RETURN_STRING(LIBINPUT_EVENT_TABLET_PAD_STRIP);
+       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();
+       }
 
-       TRACE_INPUT_END();
+       return NULL;
 }
 
 static void
@@ -1505,13 +2635,18 @@ 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;
                events = realloc(events, events_len * sizeof *events);
                if (!events) {
-                       fprintf(stderr, "Failed to reallocate event ring "
-                               "buffer");
+                       log_error(libinput,
+                                 "Failed to reallocate event ring buffer. "
+                                 "Events may be discarded\n");
                        return;
                }
 
@@ -1665,70 +2800,404 @@ libinput_device_set_seat_logical_name(struct libinput_device *device,
                                                               name);
 }
 
-LIBINPUT_EXPORT struct udev_device *
-libinput_device_get_udev_device(struct libinput_device *device)
+LIBINPUT_EXPORT struct udev_device *
+libinput_device_get_udev_device(struct libinput_device *device)
+{
+       return evdev_device_get_udev_device((struct evdev_device *)device);
+}
+
+LIBINPUT_EXPORT void
+libinput_device_led_update(struct libinput_device *device,
+                          enum libinput_led leds)
+{
+       evdev_device_led_update((struct evdev_device *) device, leds);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_has_capability(struct libinput_device *device,
+                              enum libinput_device_capability capability)
+{
+       return evdev_device_has_capability((struct evdev_device *) device,
+                                          capability);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_get_size(struct libinput_device *device,
+                        double *width,
+                        double *height)
+{
+       return evdev_device_get_size((struct evdev_device *)device,
+                                    width,
+                                    height);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code)
+{
+       return evdev_device_has_button((struct evdev_device *)device, code);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_keyboard_has_key(struct libinput_device *device, uint32_t code)
+{
+       return evdev_device_has_key((struct evdev_device *)device, code);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_tablet_pad_get_num_buttons(struct libinput_device *device)
+{
+       return evdev_device_tablet_pad_get_num_buttons((struct evdev_device *)device);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_tablet_pad_get_num_rings(struct libinput_device *device)
+{
+       return evdev_device_tablet_pad_get_num_rings((struct evdev_device *)device);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_tablet_pad_get_num_strips(struct libinput_device *device)
+{
+       return evdev_device_tablet_pad_get_num_strips((struct evdev_device *)device);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_tablet_pad_get_num_mode_groups(struct libinput_device *device)
+{
+       return evdev_device_tablet_pad_get_num_mode_groups((struct evdev_device *)device);
+}
+
+LIBINPUT_EXPORT struct libinput_tablet_pad_mode_group*
+libinput_device_tablet_pad_get_mode_group(struct libinput_device *device,
+                                         unsigned int index)
+{
+       return evdev_device_tablet_pad_get_mode_group((struct evdev_device *)device,
+                                                     index);
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_tablet_pad_mode_group_get_num_modes(
+                                    struct libinput_tablet_pad_mode_group *group)
+{
+       return group->num_modes;
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_tablet_pad_mode_group_get_mode(struct libinput_tablet_pad_mode_group *group)
+{
+       return group->current_mode;
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_tablet_pad_mode_group_get_index(struct libinput_tablet_pad_mode_group *group)
+{
+       return group->index;
+}
+
+LIBINPUT_EXPORT int
+libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group *group,
+                                         unsigned int button)
+{
+       if ((int)button >=
+           libinput_device_tablet_pad_get_num_buttons(group->device))
+               return 0;
+
+       return !!(group->button_mask & (1 << button));
+}
+
+LIBINPUT_EXPORT int
+libinput_tablet_pad_mode_group_has_ring(struct libinput_tablet_pad_mode_group *group,
+                                       unsigned int ring)
+{
+       if ((int)ring >=
+           libinput_device_tablet_pad_get_num_rings(group->device))
+               return 0;
+
+       return !!(group->ring_mask & (1 << ring));
+}
+
+LIBINPUT_EXPORT int
+libinput_tablet_pad_mode_group_has_strip(struct libinput_tablet_pad_mode_group *group,
+                                        unsigned int strip)
+{
+       if ((int)strip >=
+           libinput_device_tablet_pad_get_num_strips(group->device))
+               return 0;
+
+       return !!(group->strip_mask & (1 << strip));
+}
+
+LIBINPUT_EXPORT int
+libinput_tablet_pad_mode_group_button_is_toggle(struct libinput_tablet_pad_mode_group *group,
+                                               unsigned int button)
+{
+       if ((int)button >=
+           libinput_device_tablet_pad_get_num_buttons(group->device))
+               return 0;
+
+       return !!(group->toggle_button_mask & (1 << button));
+}
+
+LIBINPUT_EXPORT struct libinput_tablet_pad_mode_group *
+libinput_tablet_pad_mode_group_ref(
+                       struct libinput_tablet_pad_mode_group *group)
+{
+       group->refcount++;
+       return group;
+}
+
+LIBINPUT_EXPORT struct libinput_tablet_pad_mode_group *
+libinput_tablet_pad_mode_group_unref(
+                       struct libinput_tablet_pad_mode_group *group)
+{
+       assert(group->refcount > 0);
+
+       group->refcount--;
+       if (group->refcount > 0)
+               return group;
+
+       list_remove(&group->link);
+       group->destroy(group);
+       return NULL;
+}
+
+LIBINPUT_EXPORT void
+libinput_tablet_pad_mode_group_set_user_data(
+                       struct libinput_tablet_pad_mode_group *group,
+                       void *user_data)
+{
+       group->user_data = user_data;
+}
+
+LIBINPUT_EXPORT void *
+libinput_tablet_pad_mode_group_get_user_data(
+                       struct libinput_tablet_pad_mode_group *group)
+{
+       return group->user_data;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
+libinput_event_device_notify_get_base_event(struct libinput_event_device_notify *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          NULL,
+                          LIBINPUT_EVENT_DEVICE_ADDED,
+                          LIBINPUT_EVENT_DEVICE_REMOVED);
+
+       return &event->base;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
+libinput_event_keyboard_get_base_event(struct libinput_event_keyboard *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          NULL,
+                          LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       return &event->base;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
+libinput_event_pointer_get_base_event(struct libinput_event_pointer *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          NULL,
+                          LIBINPUT_EVENT_POINTER_MOTION,
+                          LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
+                          LIBINPUT_EVENT_POINTER_BUTTON,
+                          LIBINPUT_EVENT_POINTER_AXIS);
+
+       return &event->base;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
+libinput_event_touch_get_base_event(struct libinput_event_touch *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          NULL,
+                          LIBINPUT_EVENT_TOUCH_DOWN,
+                          LIBINPUT_EVENT_TOUCH_UP,
+                          LIBINPUT_EVENT_TOUCH_MOTION,
+                          LIBINPUT_EVENT_TOUCH_CANCEL,
+                          LIBINPUT_EVENT_TOUCH_FRAME);
+
+       return &event->base;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
+libinput_event_gesture_get_base_event(struct libinput_event_gesture *event)
+{
+       return &event->base;
+}
+
+LIBINPUT_EXPORT struct libinput_event *
+libinput_event_tablet_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 double
+libinput_event_tablet_pad_get_ring_position(struct libinput_event_tablet_pad *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_TABLET_PAD_RING);
+
+       return event->ring.position;
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_event_tablet_pad_get_ring_number(struct libinput_event_tablet_pad *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TABLET_PAD_RING);
+
+       return event->ring.number;
+}
+
+LIBINPUT_EXPORT enum libinput_tablet_pad_ring_axis_source
+libinput_event_tablet_pad_get_ring_source(struct libinput_event_tablet_pad *event)
+{
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN,
+                          LIBINPUT_EVENT_TABLET_PAD_RING);
+
+       return event->ring.source;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_tablet_pad_get_strip_position(struct libinput_event_tablet_pad *event)
 {
-       return evdev_device_get_udev_device((struct evdev_device *)device);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0.0,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP);
+
+       return event->strip.position;
 }
 
-LIBINPUT_EXPORT void
-libinput_device_led_update(struct libinput_device *device,
-                          enum libinput_led leds)
+LIBINPUT_EXPORT unsigned int
+libinput_event_tablet_pad_get_strip_number(struct libinput_event_tablet_pad *event)
 {
-       evdev_device_led_update((struct evdev_device *) device, leds);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP);
+
+       return event->strip.number;
 }
 
-LIBINPUT_EXPORT int
-libinput_device_has_capability(struct libinput_device *device,
-                              enum libinput_device_capability capability)
+LIBINPUT_EXPORT enum libinput_tablet_pad_strip_axis_source
+libinput_event_tablet_pad_get_strip_source(struct libinput_event_tablet_pad *event)
 {
-       return evdev_device_has_capability((struct evdev_device *) device,
-                                          capability);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP);
+
+       return event->strip.source;
 }
 
-LIBINPUT_EXPORT int
-libinput_device_get_size(struct libinput_device *device,
-                        double *width,
-                        double *height)
+LIBINPUT_EXPORT uint32_t
+libinput_event_tablet_pad_get_button_number(struct libinput_event_tablet_pad *event)
 {
-       return evdev_device_get_size((struct evdev_device *)device,
-                                    width,
-                                    height);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return event->button.number;
 }
 
-LIBINPUT_EXPORT int
-libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code)
+LIBINPUT_EXPORT enum libinput_button_state
+libinput_event_tablet_pad_get_button_state(struct libinput_event_tablet_pad *event)
 {
-       return evdev_device_has_button((struct evdev_device *)device, code);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          LIBINPUT_BUTTON_STATE_RELEASED,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return event->button.state;
 }
 
-LIBINPUT_EXPORT int
-libinput_device_has_button(struct libinput_device *device, uint32_t code)
+LIBINPUT_EXPORT unsigned int
+libinput_event_tablet_pad_get_mode(struct libinput_event_tablet_pad *event)
 {
-       return libinput_device_pointer_has_button(device, code);
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TABLET_PAD_RING,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return event->mode;
 }
 
-LIBINPUT_EXPORT struct libinput_event *
-libinput_event_device_notify_get_base_event(struct libinput_event_device_notify *event)
+LIBINPUT_EXPORT struct libinput_tablet_pad_mode_group *
+libinput_event_tablet_pad_get_mode_group(struct libinput_event_tablet_pad *event)
 {
-       return &event->base;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          NULL,
+                          LIBINPUT_EVENT_TABLET_PAD_RING,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return event->mode_group;
 }
 
-LIBINPUT_EXPORT struct libinput_event *
-libinput_event_keyboard_get_base_event(struct libinput_event_keyboard *event)
+LIBINPUT_EXPORT uint32_t
+libinput_event_tablet_pad_get_time(struct libinput_event_tablet_pad *event)
 {
-       return &event->base;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TABLET_PAD_RING,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return us2ms(event->time);
 }
 
-LIBINPUT_EXPORT struct libinput_event *
-libinput_event_pointer_get_base_event(struct libinput_event_pointer *event)
+LIBINPUT_EXPORT uint64_t
+libinput_event_tablet_pad_get_time_usec(struct libinput_event_tablet_pad *event)
 {
-       return &event->base;
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          0,
+                          LIBINPUT_EVENT_TABLET_PAD_RING,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
+       return event->time;
 }
 
 LIBINPUT_EXPORT struct libinput_event *
-libinput_event_touch_get_base_event(struct libinput_event_touch *event)
+libinput_event_tablet_pad_get_base_event(struct libinput_event_tablet_pad *event)
 {
+       require_event_type(libinput_event_get_context(&event->base),
+                          event->base.type,
+                          NULL,
+                          LIBINPUT_EVENT_TABLET_PAD_RING,
+                          LIBINPUT_EVENT_TABLET_PAD_STRIP,
+                          LIBINPUT_EVENT_TABLET_PAD_BUTTON);
+
        return &event->base;
 }
 
@@ -1740,7 +3209,8 @@ libinput_device_group_ref(struct libinput_device_group *group)
 }
 
 struct libinput_device_group *
-libinput_device_group_create(const char *identifier)
+libinput_device_group_create(struct libinput *libinput,
+                            const char *identifier)
 {
        struct libinput_device_group *group;
 
@@ -1753,13 +3223,32 @@ libinput_device_group_create(const char *identifier)
                group->identifier = strdup(identifier);
                if (!group->identifier) {
                        free(group);
-                       group = NULL;
+                       return NULL;
                }
        }
 
+       list_init(&group->link);
+       list_insert(&libinput->device_group_list, &group->link);
+
        return group;
 }
 
+struct libinput_device_group *
+libinput_device_group_find_group(struct libinput *libinput,
+                                const char *identifier)
+{
+       struct libinput_device_group *g = NULL;
+
+       list_for_each(g, &libinput->device_group_list, link) {
+               if (identifier && g->identifier &&
+                   streq(g->identifier, identifier)) {
+                       return g;
+               }
+       }
+
+       return NULL;
+}
+
 void
 libinput_device_set_device_group(struct libinput_device *device,
                                 struct libinput_device_group *group)
@@ -1771,6 +3260,7 @@ libinput_device_set_device_group(struct libinput_device *device,
 static void
 libinput_device_group_destroy(struct libinput_device_group *group)
 {
+       list_remove(&group->link);
        free(group->identifier);
        free(group);
 }
@@ -1835,11 +3325,12 @@ libinput_device_config_tap_set_enabled(struct libinput_device *device,
            enable != LIBINPUT_CONFIG_TAP_DISABLED)
                return LIBINPUT_CONFIG_STATUS_INVALID;
 
-       if (enable &&
-           libinput_device_config_tap_get_finger_count(device) == 0)
-               return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return enable ? LIBINPUT_CONFIG_STATUS_UNSUPPORTED :
+                               LIBINPUT_CONFIG_STATUS_SUCCESS;
 
        return device->config.tap->set_enabled(device, enable);
+
 }
 
 LIBINPUT_EXPORT enum libinput_config_tap_state
@@ -1860,6 +3351,108 @@ libinput_device_config_tap_get_default_enabled(struct libinput_device *device)
        return device->config.tap->get_default(device);
 }
 
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_tap_set_button_map(struct libinput_device *device,
+                                           enum libinput_config_tap_button_map map)
+{
+       switch (map) {
+       case LIBINPUT_CONFIG_TAP_MAP_LRM:
+       case LIBINPUT_CONFIG_TAP_MAP_LMR:
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+       }
+
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+
+       return device->config.tap->set_map(device, map);
+}
+
+LIBINPUT_EXPORT enum libinput_config_tap_button_map
+libinput_device_config_tap_get_button_map(struct libinput_device *device)
+{
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_TAP_MAP_LRM;
+
+       return device->config.tap->get_map(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_tap_button_map
+libinput_device_config_tap_get_default_button_map(struct libinput_device *device)
+{
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_TAP_MAP_LRM;
+
+       return device->config.tap->get_default_map(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_tap_set_drag_enabled(struct libinput_device *device,
+                                           enum libinput_config_drag_state enable)
+{
+       if (enable != LIBINPUT_CONFIG_DRAG_ENABLED &&
+           enable != LIBINPUT_CONFIG_DRAG_DISABLED)
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return enable ? LIBINPUT_CONFIG_STATUS_UNSUPPORTED :
+                               LIBINPUT_CONFIG_STATUS_SUCCESS;
+
+       return device->config.tap->set_drag_enabled(device, enable);
+}
+
+LIBINPUT_EXPORT enum libinput_config_drag_state
+libinput_device_config_tap_get_drag_enabled(struct libinput_device *device)
+{
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_DRAG_DISABLED;
+
+       return device->config.tap->get_drag_enabled(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_drag_state
+libinput_device_config_tap_get_default_drag_enabled(struct libinput_device *device)
+{
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_DRAG_DISABLED;
+
+       return device->config.tap->get_default_drag_enabled(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device,
+                                                enum libinput_config_drag_lock_state enable)
+{
+       if (enable != LIBINPUT_CONFIG_DRAG_LOCK_ENABLED &&
+           enable != LIBINPUT_CONFIG_DRAG_LOCK_DISABLED)
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return enable ? LIBINPUT_CONFIG_STATUS_UNSUPPORTED :
+                               LIBINPUT_CONFIG_STATUS_SUCCESS;
+
+       return device->config.tap->set_draglock_enabled(device, enable);
+}
+
+LIBINPUT_EXPORT enum libinput_config_drag_lock_state
+libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device)
+{
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
+
+       return device->config.tap->get_draglock_enabled(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_drag_lock_state
+libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device *device)
+{
+       if (libinput_device_config_tap_get_finger_count(device) == 0)
+               return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
+
+       return device->config.tap->get_default_draglock_enabled(device);
+}
+
 LIBINPUT_EXPORT int
 libinput_device_config_calibration_has_matrix(struct libinput_device *device)
 {
@@ -1956,7 +3549,6 @@ libinput_device_config_accel_set_speed(struct libinput_device *device,
 
        return device->config.accel->set_speed(device, speed);
 }
-
 LIBINPUT_EXPORT double
 libinput_device_config_accel_get_speed(struct libinput_device *device)
 {
@@ -1975,6 +3567,52 @@ libinput_device_config_accel_get_default_speed(struct libinput_device *device)
        return device->config.accel->get_default_speed(device);
 }
 
+LIBINPUT_EXPORT uint32_t
+libinput_device_config_accel_get_profiles(struct libinput_device *device)
+{
+       if (!libinput_device_config_accel_is_available(device))
+               return 0;
+
+       return device->config.accel->get_profiles(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_accel_profile
+libinput_device_config_accel_get_profile(struct libinput_device *device)
+{
+       if (!libinput_device_config_accel_is_available(device))
+               return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+
+       return device->config.accel->get_profile(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_accel_profile
+libinput_device_config_accel_get_default_profile(struct libinput_device *device)
+{
+       if (!libinput_device_config_accel_is_available(device))
+               return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+
+       return device->config.accel->get_default_profile(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_accel_set_profile(struct libinput_device *device,
+                                        enum libinput_config_accel_profile profile)
+{
+       switch (profile) {
+       case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT:
+       case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE:
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+       }
+
+       if (!libinput_device_config_accel_is_available(device) ||
+           (libinput_device_config_accel_get_profiles(device) & profile) == 0)
+               return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+
+       return device->config.accel->set_profile(device, profile);
+}
+
 LIBINPUT_EXPORT int
 libinput_device_config_scroll_has_natural_scroll(struct libinput_device *device)
 {
@@ -2012,12 +3650,6 @@ libinput_device_config_scroll_get_default_natural_scroll_enabled(struct libinput
        return device->config.natural_scroll->get_default_enabled(device);
 }
 
-LIBINPUT_EXPORT int
-libinput_device_config_scroll_get_wheel_click_angle(struct libinput_device *device)
-{
-       return evdev_scroll_get_wheel_click_angle((struct evdev_device *) device);
-}
-
 LIBINPUT_EXPORT int
 libinput_device_config_left_handed_is_available(struct libinput_device *device)
 {
@@ -2068,9 +3700,6 @@ LIBINPUT_EXPORT enum libinput_config_status
 libinput_device_config_click_set_method(struct libinput_device *device,
                                        enum libinput_config_click_method method)
 {
-       if ((libinput_device_config_click_get_methods(device) & method) != method)
-               return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
-
        /* Check method is a single valid method */
        switch (method) {
        case LIBINPUT_CONFIG_CLICK_METHOD_NONE:
@@ -2081,6 +3710,9 @@ libinput_device_config_click_set_method(struct libinput_device *device,
                return LIBINPUT_CONFIG_STATUS_INVALID;
        }
 
+       if ((libinput_device_config_click_get_methods(device) & method) != method)
+               return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+
        if (device->config.click_method)
                return device->config.click_method->set_method(device, method);
        else /* method must be _NONE to get here */
@@ -2105,6 +3737,60 @@ libinput_device_config_click_get_default_method(struct libinput_device *device)
                return LIBINPUT_CONFIG_CLICK_METHOD_NONE;
 }
 
+LIBINPUT_EXPORT int
+libinput_device_config_middle_emulation_is_available(
+               struct libinput_device *device)
+{
+       if (device->config.middle_emulation)
+               return device->config.middle_emulation->available(device);
+       else
+               return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_middle_emulation_set_enabled(
+               struct libinput_device *device,
+               enum libinput_config_middle_emulation_state enable)
+{
+       int available =
+               libinput_device_config_middle_emulation_is_available(device);
+
+       switch (enable) {
+       case LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED:
+               if (!available)
+                       return LIBINPUT_CONFIG_STATUS_SUCCESS;
+               break;
+       case LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED:
+               if (!available)
+                       return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+               break;
+       default:
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+       }
+
+       return device->config.middle_emulation->set(device, enable);
+}
+
+LIBINPUT_EXPORT enum libinput_config_middle_emulation_state
+libinput_device_config_middle_emulation_get_enabled(
+               struct libinput_device *device)
+{
+       if (!libinput_device_config_middle_emulation_is_available(device))
+               return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
+
+       return device->config.middle_emulation->get(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_middle_emulation_state
+libinput_device_config_middle_emulation_get_default_enabled(
+               struct libinput_device *device)
+{
+       if (!libinput_device_config_middle_emulation_is_available(device))
+               return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
+
+       return device->config.middle_emulation->get_default(device);
+}
+
 LIBINPUT_EXPORT uint32_t
 libinput_device_config_scroll_get_methods(struct libinput_device *device)
 {
@@ -2189,3 +3875,86 @@ libinput_device_config_scroll_get_default_button(struct libinput_device *device)
 
        return device->config.scroll_method->get_default_button(device);
 }
+
+LIBINPUT_EXPORT int
+libinput_device_config_dwt_is_available(struct libinput_device *device)
+{
+       if (!device->config.dwt)
+               return 0;
+
+       return device->config.dwt->is_available(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_dwt_set_enabled(struct libinput_device *device,
+                                      enum libinput_config_dwt_state enable)
+{
+       if (enable != LIBINPUT_CONFIG_DWT_ENABLED &&
+           enable != LIBINPUT_CONFIG_DWT_DISABLED)
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+
+       if (!libinput_device_config_dwt_is_available(device))
+               return enable ? LIBINPUT_CONFIG_STATUS_UNSUPPORTED :
+                               LIBINPUT_CONFIG_STATUS_SUCCESS;
+
+       return device->config.dwt->set_enabled(device, enable);
+}
+
+LIBINPUT_EXPORT enum libinput_config_dwt_state
+libinput_device_config_dwt_get_enabled(struct libinput_device *device)
+{
+       if (!libinput_device_config_dwt_is_available(device))
+               return LIBINPUT_CONFIG_DWT_DISABLED;
+
+       return device->config.dwt->get_enabled(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_dwt_state
+libinput_device_config_dwt_get_default_enabled(struct libinput_device *device)
+{
+       if (!libinput_device_config_dwt_is_available(device))
+               return LIBINPUT_CONFIG_DWT_DISABLED;
+
+       return device->config.dwt->get_default_enabled(device);
+}
+
+LIBINPUT_EXPORT int
+libinput_device_config_rotation_is_available(struct libinput_device *device)
+{
+       if (!device->config.rotation)
+               return 0;
+
+       return device->config.rotation->is_available(device);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_device_config_rotation_set_angle(struct libinput_device *device,
+                                         unsigned int degrees_cw)
+{
+       if (!libinput_device_config_rotation_is_available(device))
+               return degrees_cw ? LIBINPUT_CONFIG_STATUS_UNSUPPORTED :
+                                   LIBINPUT_CONFIG_STATUS_SUCCESS;
+
+       if (degrees_cw >= 360 || degrees_cw % 90)
+               return LIBINPUT_CONFIG_STATUS_INVALID;
+
+       return device->config.rotation->set_angle(device, degrees_cw);
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_device_config_rotation_get_angle(struct libinput_device *device)
+{
+       if (!libinput_device_config_rotation_is_available(device))
+               return 0;
+
+       return device->config.rotation->get_angle(device);
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_device_config_rotation_get_default_angle(struct libinput_device *device)
+{
+       if (!libinput_device_config_rotation_is_available(device))
+               return 0;
+
+       return device->config.rotation->get_default_angle(device);
+}
index 779218649bd9edcf023343a335922b72b04929fb..18a96bd4e1f1072d61834b2e8b0d6b163c985413 100644 (file)
@@ -1,23 +1,25 @@
 /*
  * Copyright © 2013 Jonas Ådahl
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef LIBINPUT_H
@@ -36,6 +38,134 @@ extern "C" {
 #define LIBINPUT_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated))
 
 /**
+ * @ingroup base
+ * @struct libinput
+ *
+ * A handle for accessing libinput. This struct is refcounted, use
+ * libinput_ref() and libinput_unref().
+ */
+struct libinput;
+
+/**
+ * @ingroup device
+ * @struct libinput_device
+ *
+ * A base handle for accessing libinput devices. This struct is
+ * refcounted, use libinput_device_ref() and libinput_device_unref().
+ */
+struct libinput_device;
+
+/**
+ * @ingroup device
+ * @struct libinput_device_group
+ *
+ * A base handle for accessing libinput device groups. This struct is
+ * refcounted, use libinput_device_group_ref() and
+ * libinput_device_group_unref().
+ */
+struct libinput_device_group;
+
+/**
+ * @ingroup seat
+ * @struct libinput_seat
+ *
+ * The base handle for accessing libinput seats. This struct is
+ * refcounted, use libinput_seat_ref() and libinput_seat_unref().
+ */
+struct libinput_seat;
+
+/**
+ * @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 event
+ * @struct libinput_event
+ *
+ * The base event type. Use libinput_event_get_pointer_event() or similar to
+ * get the actual event type.
+ *
+ * @warning Unlike other structs events are considered transient and
+ * <b>not</b> refcounted.
+ */
+struct libinput_event;
+
+/**
+ * @ingroup event
+ * @struct libinput_event_device_notify
+ *
+ * An event notifying the caller of a device being added or removed.
+ */
+struct libinput_event_device_notify;
+
+/**
+ * @ingroup event_keyboard
+ * @struct libinput_event_keyboard
+ *
+ * A keyboard event representing a key press/release.
+ */
+struct libinput_event_keyboard;
+
+/**
+ * @ingroup event_pointer
+ * @struct libinput_event_pointer
+ *
+ * A pointer event representing relative or absolute pointer movement,
+ * a button press/release or scroll axis events.
+ */
+struct libinput_event_pointer;
+
+/**
+ * @ingroup event_touch
+ * @struct libinput_event_touch
+ *
+ * Touch event representing a touch down, move or up, as well as a touch
+ * cancel and touch frame events. Valid event types for this event are @ref
+ * LIBINPUT_EVENT_TOUCH_DOWN, @ref LIBINPUT_EVENT_TOUCH_MOTION, @ref
+ * LIBINPUT_EVENT_TOUCH_UP, @ref LIBINPUT_EVENT_TOUCH_CANCEL and @ref
+ * LIBINPUT_EVENT_TOUCH_FRAME.
+ */
+struct libinput_event_touch;
+
+/**
+ * @ingroup event_tablet
+ * @struct libinput_event_tablet_tool
+ *
+ * Tablet tool 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;
+
+/**
+ * @ingroup event_tablet_pad
+ * @struct libinput_event_tablet_pad
+ *
+ * Tablet pad event representing a button press, or ring/strip update on
+ * the tablet pad itself. Valid event types for this event are @ref
+ * LIBINPUT_EVENT_TABLET_PAD_BUTTON, @ref LIBINPUT_EVENT_TABLET_PAD_RING and
+ * @ref LIBINPUT_EVENT_TABLET_PAD_STRIP.
+ */
+struct libinput_event_tablet_pad;
+
+/**
+ * @ingroup base
+ *
  * Log priority for internal logging messages.
  */
 enum libinput_log_priority {
@@ -48,13 +178,15 @@ enum libinput_log_priority {
  * @ingroup device
  *
  * Capabilities on a device. A device may have one or more capabilities
- * at a time, and capabilities may appear or disappear during the
- * lifetime of the device.
+ * at a time, capabilities remain static for the lifetime of the device.
  */
 enum libinput_device_capability {
        LIBINPUT_DEVICE_CAP_KEYBOARD = 0,
        LIBINPUT_DEVICE_CAP_POINTER = 1,
-       LIBINPUT_DEVICE_CAP_TOUCH = 2
+       LIBINPUT_DEVICE_CAP_TOUCH = 2,
+       LIBINPUT_DEVICE_CAP_TABLET_TOOL = 3,
+       LIBINPUT_DEVICE_CAP_TABLET_PAD = 4,
+       LIBINPUT_DEVICE_CAP_GESTURE = 5,
 };
 
 /**
@@ -93,7 +225,8 @@ enum libinput_button_state {
 /**
  * @ingroup device
  *
- * Axes on a device that are not x or y coordinates.
+ * Axes on a device with the capability @ref LIBINPUT_DEVICE_CAP_POINTER
+ * that are not x or y coordinates.
  *
  * The two scroll axes @ref LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL and
  * @ref LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL are engaged separately,
@@ -129,206 +262,560 @@ enum libinput_pointer_axis_source {
 };
 
 /**
- * @ingroup base
+ * @ingroup event_tablet_pad
  *
- * Event type for events returned by libinput_get_event().
+ * The source for a @ref LIBINPUT_EVENT_TABLET_PAD_RING event. See
+ * libinput_event_tablet_pad_get_ring_source() for details.
  */
-enum libinput_event_type {
-       /**
-        * This is not a real event type, and is only used to tell the user that
-        * no new event is available in the queue. See
-        * libinput_next_event_type().
-        */
-       LIBINPUT_EVENT_NONE = 0,
-
-       /**
-        * Signals that a device has been added to the context. The device will
-        * not be read until the next time the user calls libinput_dispatch()
-        * and data is available.
-        *
-        * This allows setting up initial device configuration before any events
-        * are created.
-        */
-       LIBINPUT_EVENT_DEVICE_ADDED,
-
+enum libinput_tablet_pad_ring_axis_source {
+       LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN = 1,
        /**
-        * Signals that a device has been removed. No more events from the
-        * associated device will be in the queue or be queued after this event.
+        * The event is caused by the movement of one or more fingers on
+        * the ring.
         */
-       LIBINPUT_EVENT_DEVICE_REMOVED,
-
-       LIBINPUT_EVENT_KEYBOARD_KEY = 300,
-
-       LIBINPUT_EVENT_POINTER_MOTION = 400,
-       LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
-       LIBINPUT_EVENT_POINTER_BUTTON,
-       LIBINPUT_EVENT_POINTER_AXIS,
+       LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
+};
 
-       LIBINPUT_EVENT_TOUCH_DOWN = 500,
-       LIBINPUT_EVENT_TOUCH_UP,
-       LIBINPUT_EVENT_TOUCH_MOTION,
-       LIBINPUT_EVENT_TOUCH_CANCEL,
+/**
+ * @ingroup event_tablet_pad
+ *
+ * The source for a @ref LIBINPUT_EVENT_TABLET_PAD_STRIP event. See
+ * libinput_event_tablet_pad_get_strip_source() for details.
+ */
+enum libinput_tablet_pad_strip_axis_source {
+       LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN = 1,
        /**
-        * Signals the end of a set of touchpoints at one device sample
-        * time. This event has no coordinate information attached.
+        * The event is caused by the movement of one or more fingers on
+        * the strip.
         */
-       LIBINPUT_EVENT_TOUCH_FRAME
+       LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
 };
 
 /**
- * @ingroup base
- * @struct libinput
+ * @ingroup device
  *
- * A handle for accessing libinput. This struct is refcounted, use
- * libinput_ref() and libinput_unref().
- */
-struct libinput;
+ * 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
- * @struct libinput_device
  *
- * A base handle for accessing libinput devices. This struct is
- * refcounted, use libinput_device_ref() and libinput_device_unref().
+ * 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.
  */
-struct libinput_device;
+enum libinput_tablet_tool_proximity_state {
+       LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT = 0,
+       LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN = 1,
+};
 
 /**
  * @ingroup device
- * @struct libinput_device_group
  *
- * A base handle for accessing libinput device groups. This struct is
- * refcounted, use libinput_device_group_ref() and
- * libinput_device_group_unref().
+ * 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.
  */
-struct libinput_device_group;
+enum libinput_tablet_tool_tip_state {
+       LIBINPUT_TABLET_TOOL_TIP_UP = 0,
+       LIBINPUT_TABLET_TOOL_TIP_DOWN = 1,
+};
 
 /**
- * @ingroup seat
- * @struct libinput_seat
+ * @defgroup tablet_pad_modes Tablet pad modes
  *
- * The base handle for accessing libinput seats. This struct is
- * refcounted, use libinput_seat_ref() and libinput_seat_unref().
+ * Handling the virtual mode groups of buttons, strips and rings on tablet
+ * pad devices. See @ref tablet-pad-modes for details.
  */
-struct libinput_seat;
 
 /**
- * @ingroup event
- * @struct libinput_event
+ * @ingroup tablet_pad_modes
+ * @struct libinput_tablet_pad_mode_group
  *
- * The base event type. Use libinput_event_get_pointer_event() or similar to
- * get the actual event type.
+ * A mode on a tablet pad is a virtual grouping of functionality, usually
+ * based on some visual feedback like LEDs on the pad. The set of buttons,
+ * rings and strips that share the same mode are a "mode group". Whenever
+ * the mode changes, all buttons, rings and strips within this mode group
+ * are affected. See @ref tablet-pad-modes for detail.
  *
- * @warning Unlike other structs events are considered transient and
- * <b>not</b> refcounted.
+ * Most tablets only have a single mode group, some tablets provide multiple
+ * mode groups through independent banks of LEDs (e.g. the Wacom Cintiq
+ * 24HD). libinput guarantees that at least one mode group is always
+ * available.
+ *
+ * This struct is refcounted, use libinput_tablet_pad_mode_group_ref() and
+ * libinput_tablet_pad_mode_group_unref().
  */
-struct libinput_event;
+struct libinput_tablet_pad_mode_group;
 
 /**
- * @ingroup event
- * @struct libinput_event_device_notify
+ * @ingroup tablet_pad_modes
  *
- * An event notifying the caller of a device being added or removed.
+ * Most devices only provide a single mode group, however devices such as
+ * the Wacom Cintiq 22HD provide two mode groups. If multiple mode groups
+ * are available, a caller should use
+ * libinput_tablet_pad_mode_group_has_button(),
+ * libinput_tablet_pad_mode_group_has_ring() and
+ * libinput_tablet_pad_mode_group_has_strip() to associate each button,
+ * ring and strip with the correct mode group.
+ *
+ * @return the number of mode groups available on this device
  */
-struct libinput_event_device_notify;
+int
+libinput_device_tablet_pad_get_num_mode_groups(struct libinput_device *device);
 
 /**
- * @ingroup event_keyboard
- * @struct libinput_event_keyboard
+ * @ingroup tablet_pad_modes
  *
- * A keyboard event representing a key press/release.
+ * The returned mode group is not refcounted and may become invalid after
+ * the next call to libinput. Use libinput_tablet_pad_mode_group_ref() and
+ * libinput_tablet_pad_mode_group_unref() to continue using the handle
+ * outside of the immediate scope.
+ *
+ * While at least one reference is kept by the caller, the returned mode
+ * group will be identical for each subsequent call of this function with
+ * the same index and that same struct is returned from
+ * libinput_event_tablet_pad_get_mode_group(), provided the event was
+ * generated by this mode group.
+ *
+ * @param device A device with the @ref LIBINPUT_DEVICE_CAP_TABLET_PAD
+ * capability
+ * @param index A mode group index
+ * @return the mode group with the given index or NULL if an invalid index
+ * is given.
  */
-struct libinput_event_keyboard;
+struct libinput_tablet_pad_mode_group*
+libinput_device_tablet_pad_get_mode_group(struct libinput_device *device,
+                                         unsigned int index);
 
 /**
- * @ingroup event_pointer
- * @struct libinput_event_pointer
+ * @ingroup tablet_pad_modes
  *
- * A pointer event representing relative or absolute pointer movement,
- * a button press/release or scroll axis events.
+ * The returned number is the same index as passed to
+ * libinput_device_tablet_pad_get_mode_group(). For tablets with only one
+ * mode this number is always 0.
+ *
+ * @param group A previously obtained mode group
+ * @return the numeric index this mode group represents, starting at 0
  */
-struct libinput_event_pointer;
+unsigned int
+libinput_tablet_pad_mode_group_get_index(struct libinput_tablet_pad_mode_group *group);
 
 /**
- * @ingroup event_touch
- * @struct libinput_event_touch
+ * @ingroup tablet_pad_modes
  *
- * Touch event representing a touch down, move or up, as well as a touch
- * cancel and touch frame events. Valid event types for this event are @ref
- * LIBINPUT_EVENT_TOUCH_DOWN, @ref LIBINPUT_EVENT_TOUCH_MOTION, @ref
- * LIBINPUT_EVENT_TOUCH_UP, @ref LIBINPUT_EVENT_TOUCH_CANCEL and @ref
- * LIBINPUT_EVENT_TOUCH_FRAME.
+ * Query the mode group for the number of available modes. The number of
+ * modes is usually decided by the number of physical LEDs available on the
+ * device. Different mode groups may have a different number of modes. Use
+ * libinput_tablet_pad_mode_group_get_mode() to get the currently active
+ * mode.
+ *
+ * libinput guarantees that at least one mode is available. A device without
+ * mode switching capability has a single mode group and a single mode.
+ *
+ * @param group A previously obtained mode group
+ * @return the number of modes available in this mode group
  */
-struct libinput_event_touch;
+unsigned int
+libinput_tablet_pad_mode_group_get_num_modes(struct libinput_tablet_pad_mode_group *group);
 
 /**
- * @defgroup event Accessing and destruction of events
+ * @ingroup tablet_pad_modes
+ *
+ * Return the current mode this mode group is in. Note that the returned
+ * mode is the mode valid as of completing the last libinput_dispatch().
+ * The returned mode may thus be different than the mode returned by
+ * libinput_event_tablet_pad_get_mode().
+ *
+ * For example, if the mode was toggled three times between the call to
+ * libinput_dispatch(), this function returns the third mode but the events
+ * in the event queue will return the modes 1, 2 and 3, respectively.
+ *
+ * @param group A previously obtained mode group
+ * @return the numeric index of the current mode in this group, starting at 0
+ *
+ * @see libinput_event_tablet_pad_get_mode
  */
+unsigned int
+libinput_tablet_pad_mode_group_get_mode(struct libinput_tablet_pad_mode_group *group);
 
 /**
- * @ingroup event
- *
- * Destroy the event, freeing all associated resources. Resources obtained
- * from this event must be considered invalid after this call.
+ * @ingroup tablet_pad_modes
  *
- * @warning Unlike other structs events are considered transient and
- * <b>not</b> refcounted. Calling libinput_event_destroy() <b>will</b>
- * destroy the event.
+ * Devices without mode switching capabilities return true for every button.
  *
- * @param event An event retrieved by libinput_get_event().
+ * @param group A previously obtained mode group
+ * @param button A button index, starting at 0
+ * @return true if the given button index is part of this mode group or
+ * false otherwise
  */
-void
-libinput_event_destroy(struct libinput_event *event);
+int
+libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group *group,
+                                         unsigned int button);
 
 /**
- * @ingroup event
+ * @ingroup tablet_pad_modes
  *
- * Get the type of the event.
+ * Devices without mode switching capabilities return true for every ring.
  *
- * @param event An event retrieved by libinput_get_event().
+ * @param group A previously obtained mode group
+ * @param ring A ring index, starting at 0
+ * @return true if the given ring index is part of this mode group or
+ * false otherwise
  */
-enum libinput_event_type
-libinput_event_get_type(struct libinput_event *event);
+int
+libinput_tablet_pad_mode_group_has_ring(struct libinput_tablet_pad_mode_group *group,
+                                         unsigned int ring);
 
 /**
- * @ingroup event
+ * @ingroup tablet_pad_modes
  *
- * Get the libinput context from the event.
+ * Devices without mode switching capabilities return true for every strip.
  *
- * @param event The libinput event
- * @return The libinput context for this event.
+ * @param group A previously obtained mode group
+ * @param strip A strip index, starting at 0
+ * @return true if the given strip index is part of this mode group or
+ * false otherwise
  */
-struct libinput *
-libinput_event_get_context(struct libinput_event *event);
+int
+libinput_tablet_pad_mode_group_has_strip(struct libinput_tablet_pad_mode_group *group,
+                                         unsigned int strip);
 
 /**
- * @ingroup event
+ * @ingroup tablet_pad_modes
  *
- * Return the device associated with this event, if applicable. For device
- * added/removed events this is the device added or removed. For all other
- * device events, this is the device that generated the event.
+ * The toggle button in a mode group is the button assigned to cycle to or
+ * directly assign a new mode when pressed. Not all devices have a toggle
+ * button and some devices may have more than one toggle button. For
+ * example, the Wacom Cintiq 24HD has six toggle buttons in two groups, each
+ * directly selecting one of the three modes per group.
  *
- * This device is not refcounted and its lifetime is that of the event. Use
- * libinput_device_ref() before using the device outside of this scope.
+ * Devices without mode switching capabilities return false for every button.
  *
- * @return The device associated with this event
+ * @param group A previously obtained mode group
+ * @param button A button index, starting at 0
+ * @retval non-zero if the button is a mode toggle button for this group, or
+ * zero otherwise
  */
-
-struct libinput_device *
-libinput_event_get_device(struct libinput_event *event);
+int
+libinput_tablet_pad_mode_group_button_is_toggle(struct libinput_tablet_pad_mode_group *group,
+                                               unsigned int button);
 
 /**
- * @ingroup event
- *
- * Return the pointer event that is this input event. If the event type does
- * not match the pointer event types, this function returns NULL.
+ * @ingroup tablet_pad_modes
  *
- * The inverse of this function is libinput_event_pointer_get_base_event().
+ * Increase the refcount of the mode group. A mode group will be
+ * freed whenever the refcount reaches 0.
  *
- * @return A pointer event, or NULL for other events
+ * @param group A previously obtained mode group
+ * @return The passed mode group
  */
-struct libinput_event_pointer *
-libinput_event_get_pointer_event(struct libinput_event *event);
+struct libinput_tablet_pad_mode_group *
+libinput_tablet_pad_mode_group_ref(
+                       struct libinput_tablet_pad_mode_group *group);
+
+/**
+ * @ingroup tablet_pad_modes
+ *
+ * Decrease the refcount of the mode group. A mode group will be
+ * freed whenever the refcount reaches 0.
+ *
+ * @param group A previously obtained mode group
+ * @return NULL if the group was destroyed, otherwise the passed mode group
+ */
+struct libinput_tablet_pad_mode_group *
+libinput_tablet_pad_mode_group_unref(
+                       struct libinput_tablet_pad_mode_group *group);
+
+/**
+ * @ingroup tablet_pad_modes
+ *
+ * Set caller-specific data associated with this mode group. libinput does
+ * not manage, look at, or modify this data. The caller must ensure the
+ * data is valid.
+ *
+ * @param group A previously obtained mode group
+ * @param user_data Caller-specific data pointer
+ * @see libinput_tablet_pad_mode_group_get_user_data
+ *
+ */
+void
+libinput_tablet_pad_mode_group_set_user_data(
+                       struct libinput_tablet_pad_mode_group *group,
+                       void *user_data);
+
+/**
+ * @ingroup tablet_pad_modes
+ *
+ * Get the caller-specific data associated with this mode group, if any.
+ *
+ * @param group A previously obtained mode group
+ * @return Caller-specific data pointer or NULL if none was set
+ * @see libinput_tablet_pad_mode_group_set_user_data
+ */
+void *
+libinput_tablet_pad_mode_group_get_user_data(
+                       struct libinput_tablet_pad_mode_group *group);
+
+/**
+ * @ingroup base
+ *
+ * Event type for events returned by libinput_get_event().
+ */
+enum libinput_event_type {
+       /**
+        * This is not a real event type, and is only used to tell the user that
+        * no new event is available in the queue. See
+        * libinput_next_event_type().
+        */
+       LIBINPUT_EVENT_NONE = 0,
+
+       /**
+        * Signals that a device has been added to the context. The device will
+        * not be read until the next time the user calls libinput_dispatch()
+        * and data is available.
+        *
+        * This allows setting up initial device configuration before any events
+        * are created.
+        */
+       LIBINPUT_EVENT_DEVICE_ADDED,
+
+       /**
+        * Signals that a device has been removed. No more events from the
+        * associated device will be in the queue or be queued after this event.
+        */
+       LIBINPUT_EVENT_DEVICE_REMOVED,
+
+       LIBINPUT_EVENT_KEYBOARD_KEY = 300,
+
+       LIBINPUT_EVENT_POINTER_MOTION = 400,
+       LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
+       LIBINPUT_EVENT_POINTER_BUTTON,
+       LIBINPUT_EVENT_POINTER_AXIS,
+
+       LIBINPUT_EVENT_TOUCH_DOWN = 500,
+       LIBINPUT_EVENT_TOUCH_UP,
+       LIBINPUT_EVENT_TOUCH_MOTION,
+       LIBINPUT_EVENT_TOUCH_CANCEL,
+       /**
+        * Signals the end of a set of touchpoints at one device sample
+        * time. This event has no coordinate information attached.
+        */
+       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.
+        *
+        * This event is not to be confused with the button events emitted
+        * by the tablet pad. See @ref LIBINPUT_EVENT_TABLET_PAD_BUTTON.
+        *
+        * @see LIBINPUT_EVENT_TABLET_BUTTON
+        */
+       LIBINPUT_EVENT_TABLET_TOOL_BUTTON,
+
+       /**
+        * A button pressed on a device with the @ref
+        * LIBINPUT_DEVICE_CAP_TABLET_PAD capability.
+        *
+        * This event is not to be confused with the button events emitted
+        * by tools on a tablet. See @ref LIBINPUT_EVENT_TABLET_TOOL_BUTTON.
+        */
+       LIBINPUT_EVENT_TABLET_PAD_BUTTON = 700,
+       /**
+        * A status change on a tablet ring with the
+        * LIBINPUT_DEVICE_CAP_TABLET_PAD capability.
+        */
+       LIBINPUT_EVENT_TABLET_PAD_RING,
+
+       /**
+        * A status change on a strip on a device with the @ref
+        * LIBINPUT_DEVICE_CAP_TABLET_PAD capability.
+        */
+       LIBINPUT_EVENT_TABLET_PAD_STRIP,
+
+       LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800,
+       LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+       LIBINPUT_EVENT_GESTURE_SWIPE_END,
+       LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+       LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+       LIBINPUT_EVENT_GESTURE_PINCH_END,
+};
+
+/**
+ * @defgroup event Accessing and destruction of events
+ */
+
+/**
+ * @ingroup event
+ *
+ * Destroy the event, freeing all associated resources. Resources obtained
+ * from this event must be considered invalid after this call.
+ *
+ * @warning Unlike other structs events are considered transient and
+ * <b>not</b> refcounted. Calling libinput_event_destroy() <b>will</b>
+ * destroy the event.
+ *
+ * @param event An event retrieved by libinput_get_event().
+ */
+void
+libinput_event_destroy(struct libinput_event *event);
+
+/**
+ * @ingroup event
+ *
+ * Get the type of the event.
+ *
+ * @param event An event retrieved by libinput_get_event().
+ */
+enum libinput_event_type
+libinput_event_get_type(struct libinput_event *event);
+
+/**
+ * @ingroup event
+ *
+ * Get the libinput context from the event.
+ *
+ * @param event The libinput event
+ * @return The libinput context for this event.
+ */
+struct libinput *
+libinput_event_get_context(struct libinput_event *event);
+
+/**
+ * @ingroup event
+ *
+ * Return the device associated with this event. For device added/removed
+ * events this is the device added or removed. For all other device events,
+ * this is the device that generated the event.
+ *
+ * This device is not refcounted and its lifetime is that of the event. Use
+ * libinput_device_ref() before using the device outside of this scope.
+ *
+ * @return The device associated with this event
+ */
+
+struct libinput_device *
+libinput_event_get_device(struct libinput_event *event);
+
+/**
+ * @ingroup event
+ *
+ * Return the pointer event that is this input event. If the event type does
+ * not match the pointer event types, this function returns NULL.
+ *
+ * The inverse of this function is libinput_event_pointer_get_base_event().
+ *
+ * @return A pointer event, or NULL for other events
+ */
+struct libinput_event_pointer *
+libinput_event_get_pointer_event(struct libinput_event *event);
 
 /**
  * @ingroup event
@@ -356,6 +843,45 @@ libinput_event_get_keyboard_event(struct libinput_event *event);
 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.
+ *
+ * The inverse of this function is libinput_event_gesture_get_base_event().
+ *
+ * @return A gesture event, or NULL for other events
+ */
+struct libinput_event_gesture *
+libinput_event_get_gesture_event(struct libinput_event *event);
+
+/**
+ * @ingroup event
+ *
+ * Return the tablet tool event that is this input event. If the event type
+ * does not match the tablet tool event types, this function returns NULL.
+ *
+ * The inverse of this function is libinput_event_tablet_tool_get_base_event().
+ *
+ * @return A tablet tool 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 tablet pad event that is this input event. If the event type does not
+ * match the tablet pad event types, this function returns NULL.
+ *
+ * The inverse of this function is libinput_event_tablet_pad_get_base_event().
+ *
+ * @return A tablet pad event, or NULL for other events
+ */
+struct libinput_event_tablet_pad *
+libinput_event_get_tablet_pad_event(struct libinput_event *event);
+
 /**
  * @ingroup event
  *
@@ -393,6 +919,14 @@ libinput_event_device_notify_get_base_event(struct libinput_event_device_notify
 uint32_t
 libinput_event_keyboard_get_time(struct libinput_event_keyboard *event);
 
+/**
+ * @ingroup event_keyboard
+ *
+ * @return The event time for this event in microseconds
+ */
+uint64_t
+libinput_event_keyboard_get_time_usec(struct libinput_event_keyboard *event);
+
 /**
  * @ingroup event_keyboard
  *
@@ -424,10 +958,10 @@ libinput_event_keyboard_get_base_event(struct libinput_event_keyboard *event);
  * of keys pressed on all devices on the associated seat after the event was
  * triggered.
  *
" @note It is an application bug to call this function for events other than
* @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_KEYBOARD_KEY. For other events, this function returns 0.
  *
- * @return the seat wide pressed key count for the key of this event
+ * @return The seat wide pressed key count for the key of this event
  */
 uint32_t
 libinput_event_keyboard_get_seat_key_count(
@@ -448,6 +982,14 @@ libinput_event_keyboard_get_seat_key_count(
 uint32_t
 libinput_event_pointer_get_time(struct libinput_event_pointer *event);
 
+/**
+ * @ingroup event_pointer
+ *
+ * @return The event time for this event in microseconds
+ */
+uint64_t
+libinput_event_pointer_get_time_usec(struct libinput_event_pointer *event);
+
 /**
  * @ingroup event_pointer
  *
@@ -458,13 +1000,13 @@ libinput_event_pointer_get_time(struct libinput_event_pointer *event);
  * If a device employs pointer acceleration, the delta returned by this
  * function is the accelerated delta.
  *
- * Relative motion deltas are normalized to represent those of a device with
- * 1000dpi resolution. See @ref motion_normalization for more details.
+ * Relative motion deltas are to be interpreted as pixel movement of a
+ * standardized mouse. See @ref motion_normalization for more details.
  *
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_MOTION.
  *
- * @return the relative x movement since the last event
+ * @return The relative x movement since the last event
  */
 double
 libinput_event_pointer_get_dx(struct libinput_event_pointer *event);
@@ -479,13 +1021,13 @@ libinput_event_pointer_get_dx(struct libinput_event_pointer *event);
  * If a device employs pointer acceleration, the delta returned by this
  * function is the accelerated delta.
  *
- * Relative motion deltas are normalized to represent those of a device with
- * 1000dpi resolution. See @ref motion_normalization for more details.
+ * Relative motion deltas are to be interpreted as pixel movement of a
+ * standardized mouse. See @ref motion_normalization for more details.
  *
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_MOTION.
  *
- * @return the relative y movement since the last event
+ * @return The relative y movement since the last event
  */
 double
 libinput_event_pointer_get_dy(struct libinput_event_pointer *event);
@@ -497,15 +1039,19 @@ libinput_event_pointer_get_dy(struct libinput_event_pointer *event);
  * current event. For pointer events that are not of type @ref
  * LIBINPUT_EVENT_POINTER_MOTION, this function returns 0.
  *
- * Relative unaccelerated motion deltas are normalized to represent those of a
- * device with 1000dpi resolution. See @ref motion_normalization for more
- * details. Note that unaccelerated events are not equivalent to 'raw' events
- * as read from the device.
+ * Relative unaccelerated motion deltas are raw device coordinates.
+ * Note that these coordinates are subject to the device's native
+ * resolution. Touchpad coordinates represent raw device coordinates in the
+ * X resolution of the touchpad. See @ref motion_normalization for more
+ * details.
+ *
+ * Any rotation applied to the device also applies to unaccelerated motion
+ * (see libinput_device_config_rotation_set_angle()).
  *
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_MOTION.
  *
- * @return the unaccelerated relative x movement since the last event
+ * @return The unaccelerated relative x movement since the last event
  */
 double
 libinput_event_pointer_get_dx_unaccelerated(
@@ -518,15 +1064,19 @@ libinput_event_pointer_get_dx_unaccelerated(
  * current event. For pointer events that are not of type @ref
  * LIBINPUT_EVENT_POINTER_MOTION, this function returns 0.
  *
- * Relative unaccelerated motion deltas are normalized to represent those of a
- * device with 1000dpi resolution. See @ref motion_normalization for more
- * details. Note that unaccelerated events are not equivalent to 'raw' events
- * as read from the device.
+ * Relative unaccelerated motion deltas are raw device coordinates.
+ * Note that these coordinates are subject to the device's native
+ * resolution. Touchpad coordinates represent raw device coordinates in the
+ * X resolution of the touchpad. See @ref motion_normalization for more
+ * details.
+ *
+ * Any rotation applied to the device also applies to unaccelerated motion
+ * (see libinput_device_config_rotation_set_angle()).
  *
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_MOTION.
  *
- * @return the unaccelerated relative y movement since the last event
+ * @return The unaccelerated relative y movement since the last event
  */
 double
 libinput_event_pointer_get_dy_unaccelerated(
@@ -545,7 +1095,7 @@ libinput_event_pointer_get_dy_unaccelerated(
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE.
  *
- * @return the current absolute x coordinate
+ * @return The current absolute x coordinate
  */
 double
 libinput_event_pointer_get_absolute_x(struct libinput_event_pointer *event);
@@ -563,7 +1113,7 @@ libinput_event_pointer_get_absolute_x(struct libinput_event_pointer *event);
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE.
  *
- * @return the current absolute y coordinate
+ * @return The current absolute y coordinate
  */
 double
 libinput_event_pointer_get_absolute_y(struct libinput_event_pointer *event);
@@ -583,7 +1133,7 @@ libinput_event_pointer_get_absolute_y(struct libinput_event_pointer *event);
  *
  * @param event The libinput pointer event
  * @param width The current output screen width
- * @return the current absolute x coordinate transformed to a screen coordinate
+ * @return The current absolute x coordinate transformed to a screen coordinate
  */
 double
 libinput_event_pointer_get_absolute_x_transformed(
@@ -605,7 +1155,7 @@ libinput_event_pointer_get_absolute_x_transformed(
  *
  * @param event The libinput pointer event
  * @param height The current output screen height
- * @return the current absolute y coordinate transformed to a screen coordinate
+ * @return The current absolute y coordinate transformed to a screen coordinate
  */
 double
 libinput_event_pointer_get_absolute_y_transformed(
@@ -622,7 +1172,7 @@ libinput_event_pointer_get_absolute_y_transformed(
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_BUTTON.
  *
- * @return the button triggering this event
+ * @return The button triggering this event
  */
 uint32_t
 libinput_event_pointer_get_button(struct libinput_event_pointer *event);
@@ -637,7 +1187,7 @@ libinput_event_pointer_get_button(struct libinput_event_pointer *event);
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_BUTTON.
  *
- * @return the button state triggering this event
+ * @return The button state triggering this event
  */
 enum libinput_button_state
 libinput_event_pointer_get_button_state(struct libinput_event_pointer *event);
@@ -649,11 +1199,11 @@ libinput_event_pointer_get_button_state(struct libinput_event_pointer *event);
  * total number of buttons pressed on all devices on the associated seat
  * after the event was triggered.
  *
" @note It is an application bug to call this function for events other than
* @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_BUTTON. For other events, this function
  * returns 0.
  *
- * @return the seat wide pressed button count for the key of this event
+ * @return The seat wide pressed button count for the key of this event
  */
 uint32_t
 libinput_event_pointer_get_seat_button_count(
@@ -668,7 +1218,13 @@ libinput_event_pointer_get_seat_button_count(
  * libinput_event_pointer_get_axis_value() returns a value of 0, the event
  * is a scroll stop event.
  *
- * @return non-zero if this event contains a value for this axis
+ * For pointer events that are not of type @ref LIBINPUT_EVENT_POINTER_AXIS,
+ * this function returns 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_POINTER_AXIS.
+ *
+ * @return Non-zero if this event contains a value for this axis
  */
 int
 libinput_event_pointer_has_axis(struct libinput_event_pointer *event,
@@ -694,7 +1250,7 @@ libinput_event_pointer_has_axis(struct libinput_event_pointer *event,
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_AXIS.
  *
- * @return the axis value of this event
+ * @return The axis value of this event
  *
  * @see libinput_event_pointer_get_axis_value_discrete
  */
@@ -735,19 +1291,19 @@ libinput_event_pointer_get_axis_value(struct libinput_event_pointer *event,
  * @note It is an application bug to call this function for events other than
  * @ref LIBINPUT_EVENT_POINTER_AXIS.
  *
- * @return the source for this axis event
+ * @return The source for this axis event
  */
 enum libinput_pointer_axis_source
 libinput_event_pointer_get_axis_source(struct libinput_event_pointer *event);
 
 /**
- * @ingroup pointer
+ * @ingroup event_pointer
  *
  * Return the axis value in discrete steps for a given axis event. How a
  * value translates into a discrete step depends on the source.
  *
  * If the source is @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL, the discrete
- * value correspond to the number of physical mouse clicks.
+ * value correspond to the number of physical mouse wheel clicks.
  *
  * If the source is @ref LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS or @ref
  * LIBINPUT_POINTER_AXIS_SOURCE_FINGER, the discrete value is always 0.
@@ -782,6 +1338,14 @@ libinput_event_pointer_get_base_event(struct libinput_event_pointer *event);
 uint32_t
 libinput_event_touch_get_time(struct libinput_event_touch *event);
 
+/**
+ * @ingroup event_touch
+ *
+ * @return The event time for this event in microseconds
+ */
+uint64_t
+libinput_event_touch_get_time_usec(struct libinput_event_touch *event);
+
 /**
  * @ingroup event_touch
  *
@@ -791,8 +1355,13 @@ libinput_event_touch_get_time(struct libinput_event_touch *event);
  * If the touch event has no assigned slot, for example if it is from a
  * single touch device, this function returns -1.
  *
- * @note this function should not be called for @ref
- * LIBINPUT_EVENT_TOUCH_CANCEL or @ref LIBINPUT_EVENT_TOUCH_FRAME.
+ * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
+ * LIBINPUT_EVENT_TOUCH_UP, @ref LIBINPUT_EVENT_TOUCH_MOTION or @ref
+ * LIBINPUT_EVENT_TOUCH_CANCEL, this function returns 0.
+ *
+ * @note It is an application bug to call this function for events of type
+ * other than @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref LIBINPUT_EVENT_TOUCH_UP,
+ * @ref LIBINPUT_EVENT_TOUCH_MOTION or @ref LIBINPUT_EVENT_TOUCH_CANCEL.
  *
  * @return The slot of this touch event
  */
@@ -808,8 +1377,13 @@ libinput_event_touch_get_slot(struct libinput_event_touch *event);
  * Events from single touch devices will be represented as one individual
  * touch point per device.
  *
- * @note this function should not be called for @ref
- * LIBINPUT_EVENT_TOUCH_CANCEL or @ref LIBINPUT_EVENT_TOUCH_FRAME.
+ * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
+ * LIBINPUT_EVENT_TOUCH_UP, @ref LIBINPUT_EVENT_TOUCH_MOTION or @ref
+ * LIBINPUT_EVENT_TOUCH_CANCEL, this function returns 0.
+ *
+ * @note It is an application bug to call this function for events of type
+ * other than @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref LIBINPUT_EVENT_TOUCH_UP,
+ * @ref LIBINPUT_EVENT_TOUCH_MOTION or @ref LIBINPUT_EVENT_TOUCH_CANCEL.
  *
  * @return The seat slot of the touch event
  */
@@ -823,11 +1397,15 @@ libinput_event_touch_get_seat_slot(struct libinput_event_touch *event);
  * the top left corner of the device. To get the corresponding output screen
  * coordinate, use libinput_event_touch_get_x_transformed().
  *
- * @note this function should only be called for @ref
- * LIBINPUT_EVENT_TOUCH_DOWN and @ref LIBINPUT_EVENT_TOUCH_MOTION.
+ * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ *
+ * @note It is an application bug to call this function for events of type
+ * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION.
  *
  * @param event The libinput touch event
- * @return the current absolute x coordinate
+ * @return The current absolute x coordinate
  */
 double
 libinput_event_touch_get_x(struct libinput_event_touch *event);
@@ -839,13 +1417,15 @@ libinput_event_touch_get_x(struct libinput_event_touch *event);
  * the top left corner of the device. To get the corresponding output screen
  * coordinate, use libinput_event_touch_get_y_transformed().
  *
- * For @ref LIBINPUT_EVENT_TOUCH_UP 0 is returned.
+ * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
  *
- * @note this function should only be called for @ref LIBINPUT_EVENT_TOUCH_DOWN and
- * @ref LIBINPUT_EVENT_TOUCH_MOTION.
+ * @note It is an application bug to call this function for events of type
+ * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION.
  *
  * @param event The libinput touch event
- * @return the current absolute y coordinate
+ * @return The current absolute y coordinate
  */
 double
 libinput_event_touch_get_y(struct libinput_event_touch *event);
@@ -856,12 +1436,16 @@ libinput_event_touch_get_y(struct libinput_event_touch *event);
  * Return the current absolute x coordinate of the touch event, transformed to
  * screen coordinates.
  *
- * @note this function should only be called for @ref
- * LIBINPUT_EVENT_TOUCH_DOWN and @ref LIBINPUT_EVENT_TOUCH_MOTION.
+ * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
  *
- * @param event The libinput touch event
- * @param width The current output screen width
- * @return the current absolute x coordinate transformed to a screen coordinate
+ * @note It is an application bug to call this function for events of type
+ * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION.
+ *
+ * @param event The libinput touch event
+ * @param width The current output screen width
+ * @return The current absolute x coordinate transformed to a screen coordinate
  */
 double
 libinput_event_touch_get_x_transformed(struct libinput_event_touch *event,
@@ -873,12 +1457,16 @@ libinput_event_touch_get_x_transformed(struct libinput_event_touch *event,
  * Return the current absolute y coordinate of the touch event, transformed to
  * screen coordinates.
  *
- * @note this function should only be called for @ref
- * LIBINPUT_EVENT_TOUCH_DOWN and @ref LIBINPUT_EVENT_TOUCH_MOTION.
+ * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ *
+ * @note It is an application bug to call this function for events of type
+ * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
+ * LIBINPUT_EVENT_TOUCH_MOTION.
  *
  * @param event The libinput touch event
  * @param height The current output screen height
- * @return the current absolute y coordinate transformed to a screen coordinate
+ * @return The current absolute y coordinate transformed to a screen coordinate
  */
 double
 libinput_event_touch_get_y_transformed(struct libinput_event_touch *event,
@@ -887,232 +1475,1213 @@ libinput_event_touch_get_y_transformed(struct libinput_event_touch *event,
 /**
  * @ingroup event_touch
  *
- * Return the diameter of the major axis of the touch ellipse in mm.
- * This value might not be provided by the device, in that case the value
- * 0.0 is returned.
+ * @return The generic libinput_event of this event
+ */
+struct libinput_event *
+libinput_event_touch_get_base_event(struct libinput_event_touch *event);
+
+/**
+ * @defgroup event_gesture Gesture events
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * Gesture events are generated when a gesture is recognized on a touchpad.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
- * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ * Gesture sequences always start with a LIBINPUT_EVENT_GESTURE_FOO_START
+ * event. All following gesture events will be of the
+ * LIBINPUT_EVENT_GESTURE_FOO_UPDATE type until a
+ * LIBINPUT_EVENT_GESTURE_FOO_END is generated which signals the end of the
+ * gesture.
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * See @ref gestures for more information on gesture handling.
+ */
+
+/**
+ * @ingroup event_gesture
  *
- * @param event The libinput touch event
- * @return The current major axis diameter
+ * @return The event time for this event
+ */
+uint32_t
+libinput_event_gesture_get_time(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * @return The event time for this event in microseconds
+ */
+uint64_t
+libinput_event_gesture_get_time_usec(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * @return The generic libinput_event of this event
+ */
+struct libinput_event *
+libinput_event_gesture_get_base_event(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the number of fingers used for a gesture. This can be used e.g.
+ * to differentiate between 3 or 4 finger swipes.
+ *
+ * This function can be called on all gesture events and the returned finger
+ * count value will not change during a sequence.
+ *
+ * @return the number of fingers used for a gesture
+ */
+int
+libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return if the gesture ended normally, or if it was cancelled.
+ * For gesture events that are not of type
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_END or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_END, this function returns 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_END or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_END.
+ *
+ * @return 0 or 1, with 1 indicating that the gesture was cancelled.
+ */
+int
+libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the delta between the last event and the current event. For gesture
+ * events that are not of type @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
+ *
+ * If a device employs pointer acceleration, the delta returned by this
+ * function is the accelerated delta.
+ *
+ * Relative motion deltas are normalized to represent those of a device with
+ * 1000dpi resolution. See @ref motion_normalization for more details.
+ *
+ * @return the relative x movement since the last event
  */
 double
-libinput_event_touch_get_major(struct libinput_event_touch *event);
+libinput_event_gesture_get_dx(struct libinput_event_gesture *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_gesture
  *
- * Return the diameter of the major axis of the touch ellipse in screen
- * space. This value might not be provided by the device, in that case the
- * value 0.0 is returned.
+ * Return the delta between the last event and the current event. For gesture
+ * events that are not of type @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * If a device employs pointer acceleration, the delta returned by this
+ * function is the accelerated delta.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION this function returns 0.
+ * Relative motion deltas are normalized to represent those of a device with
+ * 1000dpi resolution. See @ref motion_normalization for more details.
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * @return the relative y movement since the last event
+ */
+double
+libinput_event_gesture_get_dy(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
  *
- * @param event The libinput touch event
- * @param width The current output screen width
- * @param height The current output screen height
- * @return The current major axis diameter
+ * Return the relative delta of the unaccelerated motion vector of the
+ * current event. For gesture events that are not of type
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
+ *
+ * Relative unaccelerated motion deltas are normalized to represent those of a
+ * device with 1000dpi resolution. See @ref motion_normalization for more
+ * details. Note that unaccelerated events are not equivalent to 'raw' events
+ * as read from the device.
+ *
+ * Any rotation applied to the device also applies to gesture motion
+ * (see libinput_device_config_rotation_set_angle()).
+ *
+ * @return the unaccelerated relative x movement since the last event
  */
 double
-libinput_event_touch_get_major_transformed(struct libinput_event_touch *event,
-                                          uint32_t width,
-                                          uint32_t height);
+libinput_event_gesture_get_dx_unaccelerated(
+       struct libinput_event_gesture *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_gesture
+ *
+ * Return the relative delta of the unaccelerated motion vector of the
+ * current event. For gesture events that are not of type
+ * @ref LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this function returns 0.
  *
- * Return whether the event contains a major axis value of the touch ellipse.
+ * Relative unaccelerated motion deltas are normalized to represent those of a
+ * device with 1000dpi resolution. See @ref motion_normalization for more
+ * details. Note that unaccelerated events are not equivalent to 'raw' events
+ * as read from the device.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * Any rotation applied to the device also applies to gesture motion
+ * (see libinput_device_config_rotation_set_angle()).
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
- * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ * @return the unaccelerated relative y movement since the last event
+ */
+double
+libinput_event_gesture_get_dy_unaccelerated(
+       struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * Return the absolute scale of a pinch gesture, the scale is the division
+ * of the current distance between the fingers and the distance at the start
+ * of the gesture. The scale begins at 1.0, and if e.g. the fingers moved
+ * together by 50% then the scale will become 0.5, if they move twice as far
+ * apart as initially the scale becomes 2.0, etc.
  *
- * @param event The libinput touch event
- * @return Non-zero when a major diameter is available
+ * For gesture events that are of type @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_BEGIN, this function returns 1.0.
+ *
+ * For gesture events that are of type @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_END, this function returns the scale value
+ * of the most recent @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE event (if
+ * any) or 1.0 otherwise.
+ *
+ * For all other events this function returns 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_BEGIN, @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_END or
+ * @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE.
+ *
+ * @return the absolute scale of a pinch gesture
+ */
+double
+libinput_event_gesture_get_scale(struct libinput_event_gesture *event);
+
+/**
+ * @ingroup event_gesture
+ *
+ * Return the angle delta in degrees between the last and the current @ref
+ * LIBINPUT_EVENT_GESTURE_PINCH_UPDATE event. For gesture events that
+ * are not of type @ref LIBINPUT_EVENT_GESTURE_PINCH_UPDATE, this
+ * function returns 0.
+ *
+ * The angle delta is defined as the change in angle of the line formed by
+ * the 2 fingers of a pinch gesture. Clockwise rotation is represented
+ * by a positive delta, counter-clockwise by a negative delta. If e.g. the
+ * fingers are on the 12 and 6 location of a clock face plate and they move
+ * to the 1 resp. 7 location in a single event then the angle delta is
+ * 30 degrees.
+ *
+ * If more than two fingers are present, the angle represents the rotation
+ * around the center of gravity. The calculation of the center of gravity is
+ * implementation-dependent.
+ *
+ * @return the angle delta since the last event
+ */
+double
+libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event);
+
+/**
+ * @defgroup event_tablet Tablet events
+ *
+ * Events that come from tools on tablet devices. For events from the pad,
+ * see @ref event_tablet_pad.
+ *
+ * Events from tablet devices are exposed by two interfaces, tools and pads.
+ * Tool events originate (usually) from a stylus-like device, pad events
+ * reflect any events originating from the physical tablet itself.
+ *
+ * Note that many tablets support touch events. These are exposed through
+ * the @ref LIBINPUT_DEVICE_CAP_POINTER interface (for external touchpad-like
+ * devices such as the Wacom Intuos series) or @ref
+ * LIBINPUT_DEVICE_CAP_TOUCH interface (for built-in touchscreen-like
+ * devices such as the Wacom Cintiq series).
+ */
+
+/**
+ * @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 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 tool event
+ * @return 1 if the axis was updated or 0 otherwise
  */
 int
-libinput_event_touch_has_major(struct libinput_event_touch *event);
+libinput_event_tablet_tool_x_has_changed(
+                               struct libinput_event_tablet_tool *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_tablet
  *
- * Return the diameter of the minor axis of the touch ellipse in mm.
- * This value might not be provided by the device, in this case the value
- * 0.0 is returned.
+ * Check if the y axis was updated in this event.
+ * For 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.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * @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.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION this function returns 0.
+ * @param event The libinput tablet tool 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
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * Check if the pressure axis was updated in this event.
+ * For 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.
  *
- * @param event The libinput touch event
- * @return The current minor diameter
+ * @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 tool 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 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 tool 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 tool 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 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 tool 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 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 tool 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 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 tool 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 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 tool 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 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 tool 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.
+ *
+ * @note On some devices, returned value may be negative or larger than the
+ * width of the device. See @ref tablet-bounds for more details.
+ *
+ * @param event The libinput tablet tool event
+ * @return The current value of the the axis
  */
 double
-libinput_event_touch_get_minor(struct libinput_event_touch *event);
+libinput_event_tablet_tool_get_x(struct libinput_event_tablet_tool *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_tablet
  *
- * Return the diameter of the minor axis of the touch ellipse in screen
- * space. This value might not be provided by the device, in this case
- * the value 0.0 is returned.
+ * 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.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * @note On some devices, returned value may be negative or larger than the
+ * width of the device. See @ref tablet-bounds for more details.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION this function returns 0.
+ * @param event The libinput tablet tool 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
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * 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.
  *
- * @param event The libinput touch event
+ * 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 tool 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 tool 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, in degrees off the tablet's z axis. That is, if the tool is
+ * perfectly orthogonal to the tablet, the tilt angle is 0. When the top
+ * tilts towards the logical top/left of the tablet, the x/y tilt angles are
+ * negative, if the top tilts towards the logical bottom/right of the
+ * tablet, the x/y tilt angles are positive.
+ *
+ * If this axis does not exist on the current tool, this function returns 0.
+ *
+ * @param event The libinput tablet tool event
+ * @return The current value of the the axis in degrees
+ */
+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, in degrees off the tablet's z axis. That is, if the tool is
+ * perfectly orthogonal to the tablet, the tilt angle is 0. When the top
+ * tilts towards the logical top/left of the tablet, the x/y tilt angles are
+ * negative, if the top tilts towards the logical bottom/right of the
+ * tablet, the x/y tilt angles are positive.
+ *
+ * If this axis does not exist on the current tool, this function returns 0.
+ *
+ * @param event The libinput tablet tool event
+ * @return The current value of the the axis in degrees
+ */
+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 tool 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 tool 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 tool 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 tool 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 tool 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.
+ *
+ * @note On some devices, returned value may be negative or larger than the
+ * width of the device. See @ref tablet-bounds for more details.
+ *
+ * @param event The libinput tablet tool 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 tool 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.
+ *
+ * @note On some devices, returned value may be negative or larger than the
+ * width of the device. See @ref tablet-bounds for more details.
+ *
+ * @param event The libinput tablet tool event
  * @param height The current output screen height
- * @return The current minor diameter
+ * @return the current absolute y coordinate transformed to a screen coordinate
  */
 double
-libinput_event_touch_get_minor_transformed(struct libinput_event_touch *event,
-                                          uint32_t width,
-                                          uint32_t height);
+libinput_event_tablet_tool_get_y_transformed(struct libinput_event_tablet_tool *event,
+                                            uint32_t height);
+
 /**
- * @ingroup event_touch
+ * @ingroup event_tablet
+ *
+ * Returns the tool that was in use during this event.
+ *
+ * The returned tablet tool is not refcounted and may become invalid after
+ * the next call to libinput. Use libinput_tablet_tool_ref() and
+ * libinput_tablet_tool_unref() to continue using the handle outside of the
+ * immediate scope.
+ *
+ * If the caller holds at least one reference, this struct is used
+ * whenever the tools enters proximity again.
+  *
+ * @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 tool 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 tool 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 tool 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 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 tool 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 tool 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.
+ *
+ * @param event The libinput tablet tool event
+ * @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 tool 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 tool 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 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 the tool reaches 0, the memory allocated for the 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 event contains a minor axis value of the touch ellipse.
+ * Return whether the tablet tool has a relative wheel.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * @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 event_tablet_pad Tablet pad events
+ *
+ * Events that come from the pad of tablet devices.  For events from the
+ * tablet tools, see @ref event_tablet.
+ */
+
+/**
+ * @ingroup event_tablet_pad
+ *
+ * @return The generic libinput_event of this event
+ */
+struct libinput_event *
+libinput_event_tablet_pad_get_base_event(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet_pad
+ *
+ * Returns the current position of the ring, in degrees counterclockwise
+ * from the northern-most point of the ring in the tablet's current logical
+ * orientation.
+ *
+ * If the source is @ref LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
+ * libinput sends a terminating event with a ring value of -1 when the
+ * finger is lifted from the ring. A caller may use this information to e.g.
+ * determine if kinetic scrolling should be triggered.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_RING.  For other events, this function
+ * returns 0.
+ *
+ * @param event The libinput tablet pad event
+ * @return The current value of the the axis
+ * @retval -1 The finger was lifted
+ */
+double
+libinput_event_tablet_pad_get_ring_position(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet_pad
+ *
+ * Returns the number of the ring that has changed state, with 0 being the
+ * first ring. On tablets with only one ring, this function always returns
+ * 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_RING.  For other events, this function
+ * returns 0.
+ *
+ * @param event The libinput tablet pad event
+ * @return The index of the ring that changed state
+ */
+unsigned int
+libinput_event_tablet_pad_get_ring_number(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet_pad
+ *
+ * Returns the source of the interaction with the ring. If the source is
+ * @ref LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER, libinput sends a ring
+ * position value of -1 to terminate the current interaction.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_RING.  For other events, this function
+ * returns 0.
+ *
+ * @param event The libinput tablet pad event
+ * @return The source of the ring interaction
+ */
+enum libinput_tablet_pad_ring_axis_source
+libinput_event_tablet_pad_get_ring_source(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet_pad
+ *
+ * Returns the current position of the strip, normalized to the range
+ * [0, 1], with 0 being the top/left-most point in the tablet's current
+ * logical orientation.
+ *
+ * If the source is @ref LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
+ * libinput sends a terminating event with a ring value of -1 when the
+ * finger is lifted from the ring. A caller may use this information to e.g.
+ * determine if kinetic scrolling should be triggered.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_STRIP.  For other events, this function
+ * returns 0.
+ *
+ * @param event The libinput tablet pad event
+ * @return The current value of the the axis
+ * @retval -1 The finger was lifted
+ */
+double
+libinput_event_tablet_pad_get_strip_position(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet_pad
+ *
+ * Returns the number of the strip that has changed state, with 0 being the
+ * first strip. On tablets with only one strip, this function always returns
+ * 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_STRIP.  For other events, this function
+ * returns 0.
+ *
+ * @param event The libinput tablet pad event
+ * @return The index of the strip that changed state
+ */
+unsigned int
+libinput_event_tablet_pad_get_strip_number(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet_pad
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
- * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ * Returns the source of the interaction with the strip. If the source is
+ * @ref LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER, libinput sends a strip
+ * position value of -1 to terminate the current interaction.
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_STRIP.  For other events, this function
+ * returns 0.
  *
- * @param event The libinput touch event
- * @return Non-zero when a minor diameter is available
+ * @param event The libinput tablet pad event
+ * @return The source of the strip interaction
  */
-int
-libinput_event_touch_has_minor(struct libinput_event_touch *event);
+enum libinput_tablet_pad_strip_axis_source
+libinput_event_tablet_pad_get_strip_source(struct libinput_event_tablet_pad *event);
 
 /**
- * @ingroup event_touch
- *
- * Return the pressure value applied to the touch contact normalized to the
- * range [0, 1]. If this value is not available the function returns the maximum
- * value 1.0.
+ * @ingroup event_tablet_pad
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * Return the button number that triggered this event, starting at 0.
+ * For events that are not of type @ref LIBINPUT_EVENT_TABLET_PAD_BUTTON,
+ * this function returns 0.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION this function returns 0.
+ * Note that the number returned is a generic sequential button number and
+ * not a semantic button code as defined in linux/input.h.
+ * See @ref tablet-pad-buttons for more details.
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_BUTTON. For other events, this function
+ * returns 0.
  *
- * @param event The libinput touch event
- * @return The current pressure value
+ * @param event The libinput tablet pad event
+ * @return the button triggering this event
  */
-double
-libinput_event_touch_get_pressure(struct libinput_event_touch *event);
+uint32_t
+libinput_event_tablet_pad_get_button_number(struct libinput_event_tablet_pad *event);
 
 /**
- * @ingroup event_touch
- *
- * Return whether the event contains a pressure value for the touch contact.
+ * @ingroup event_tablet_pad
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * Return the button state of the event.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
- * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
- *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * @note It is an application bug to call this function for events other than
+ * @ref LIBINPUT_EVENT_TABLET_PAD_BUTTON. For other events, this function
+ * returns 0.
  *
- * @param event The libinput touch event
- * @return Non-zero when a pressure value is available
+ * @param event The libinput tablet pad event
+ * @return the button state triggering this event
  */
-int
-libinput_event_touch_has_pressure(struct libinput_event_touch *event);
+enum libinput_button_state
+libinput_event_tablet_pad_get_button_state(struct libinput_event_tablet_pad *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_tablet_pad
  *
- * Return the major axis rotation in degrees, clockwise from the logical north
- * of the touch screen.
+ * Returns the mode the button, ring, or strip that triggered this event is
+ * in, at the time of the event.
  *
- * @note Even when the orientation is measured by the device, it might be only
- * available in coarse steps (e.g only indicating alignment with either of the
- * axes).
+ * The mode is a virtual grouping of functionality, usually based on some
+ * visual feedback like LEDs on the pad. See @ref tablet-pad-modes for
+ * details. Mode indices start at 0, a device that does not support modes
+ * always returns 0.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * Mode switching is controlled by libinput and more than one mode may exist
+ * on the tablet. This function returns the mode that this event's button,
+ * ring or strip is logically in. If the button is a mode toggle button
+ * and the button event caused a new mode to be toggled, the mode returned
+ * is the new mode the button is in.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
- * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ * Note that the returned mode is the mode valid as of the time of the
+ * event. The returned mode may thus be different to the mode returned by
+ * libinput_tablet_pad_mode_group_get_mode(). See
+ * libinput_tablet_pad_mode_group_get_mode() for details.
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * @param event The libinput tablet pad event
+ * @return the 0-indexed mode of this button, ring or strip at the time of
+ * the event
  *
- * @param event The libinput touch event
- * @return The current orientation value
+ * @see libinput_tablet_pad_mode_group_get_mode
  */
-double
-libinput_event_touch_get_orientation(struct libinput_event_touch *event);
+unsigned int
+libinput_event_tablet_pad_get_mode(struct libinput_event_tablet_pad *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_tablet_pad
  *
- * Return whether the event contains a orientation value for the touch contact.
+ * Returns the mode group that the button, ring, or strip that triggered
+ * this event is considered in. The mode is a virtual grouping of
+ * functionality, usually based on some visual feedback like LEDs on the
+ * pad. See @ref tablet-pad-modes for details.
  *
- * A more detailed explanation can be found in @ref touch_event_properties.
+ * The returned mode group is not refcounted and may become invalid after
+ * the next call to libinput. Use libinput_tablet_pad_mode_group_ref() and
+ * libinput_tablet_pad_mode_group_unref() to continue using the handle
+ * outside of the immediate scope.
  *
- * For events not of type @ref LIBINPUT_EVENT_TOUCH_DOWN, @ref
- * LIBINPUT_EVENT_TOUCH_MOTION, this function returns 0.
+ * @param event The libinput tablet pad event
+ * @return the mode group of the button, ring or strip that caused this event
  *
- * @note It is an application bug to call this function for events of type
- * other than @ref LIBINPUT_EVENT_TOUCH_DOWN or @ref
- * LIBINPUT_EVENT_TOUCH_MOTION.
+ * @see libinput_device_tablet_pad_get_mode_group
+ */
+struct libinput_tablet_pad_mode_group *
+libinput_event_tablet_pad_get_mode_group(struct libinput_event_tablet_pad *event);
+
+/**
+ * @ingroup event_tablet
  *
- * @param event The libinput touch event
- * @return Non-zero when an orientation value is available
+ * @param event The libinput tablet pad event
+ * @return The event time for this event
  */
-int
-libinput_event_touch_has_orientation(struct libinput_event_touch *event);
+uint32_t
+libinput_event_tablet_pad_get_time(struct libinput_event_tablet_pad *event);
 
 /**
- * @ingroup event_touch
+ * @ingroup event_tablet_pad
  *
- * @return The generic libinput_event of this event
+ * @param event The libinput tablet pad event
+ * @return The event time for this event in microseconds
  */
-struct libinput_event *
-libinput_event_touch_get_base_event(struct libinput_event_touch *event);
+uint64_t
+libinput_event_tablet_pad_get_time_usec(struct libinput_event_tablet_pad *event);
 
 /**
  * @defgroup base Initialization and manipulation of libinput contexts
@@ -1139,7 +2708,7 @@ struct libinput_interface {
         * @param user_data The user_data provided in
         * libinput_udev_create_context()
         *
-        * @return the file descriptor, or a negative errno on failure.
+        * @return The file descriptor, or a negative errno on failure.
         */
        int (*open_restricted)(const char *path, int flags, void *user_data);
        /**
@@ -1269,7 +2838,7 @@ libinput_path_remove_device(struct libinput_device *device);
  * libinput keeps a single file descriptor for all events. Call into
  * libinput_dispatch() if any events become available on this fd.
  *
- * @return the file descriptor used to notify of pending events.
+ * @return The file descriptor used to notify of pending events.
  */
 int
 libinput_get_fd(struct libinput *libinput);
@@ -1281,7 +2850,11 @@ libinput_get_fd(struct libinput *libinput);
  * and processes them internally. Use libinput_get_event() to retrieve the
  * events.
  *
- * Dispatching does not necessarily queue libinput events.
+ * Dispatching does not necessarily queue libinput events. This function
+ * should be called immediately once data is available on the file
+ * descriptor returned by libinput_get_fd(). libinput has a number of
+ * timing-sensitive features (e.g. tap-to-click), any delay in calling
+ * libinput_dispatch() may prevent these features from working correctly.
  *
  * @param libinput A previously initialized libinput context
  *
@@ -1312,8 +2885,8 @@ libinput_get_event(struct libinput *libinput);
  * libinput_get_event() returns that event.
  *
  * @param libinput A previously initialized libinput context
- * @return The event type of the next available event or LIBINPUT_EVENT_NONE
- * if no event is availble.
+ * @return The event type of the next available event or @ref
+ * LIBINPUT_EVENT_NONE if no event is available.
  */
 enum libinput_event_type
 libinput_next_event_type(struct libinput *libinput);
@@ -1321,6 +2894,10 @@ libinput_next_event_type(struct libinput *libinput);
 /**
  * @ingroup base
  *
+ * Set caller-specific data associated with this context. libinput does
+ * not manage, look at, or modify this data. The caller must ensure the
+ * data is valid.
+ *
  * @param libinput A previously initialized libinput context
  * @param user_data Caller-specific data passed to the various callback
  * interfaces.
@@ -1332,8 +2909,10 @@ libinput_set_user_data(struct libinput *libinput,
 /**
  * @ingroup base
  *
+ * Get the caller-specific data associated with this context, if any.
+ *
  * @param libinput A previously initialized libinput context
- * @return the caller-specific data previously assigned in
+ * @return The caller-specific data previously assigned in
  * libinput_create_udev().
  */
 void *
@@ -1384,6 +2963,26 @@ libinput_ref(struct libinput *libinput);
  * destroyed, if the last reference was dereferenced. If so, the context is
  * invalid and may not be interacted with.
  *
+ * @bug When the refcount reaches zero, libinput_unref() releases resources
+ * even if a caller still holds refcounted references to related resources
+ * (e.g. a libinput_device). When libinput_unref() returns
+ * NULL, the caller must consider any resources related to that context
+ * invalid. See https://bugs.freedesktop.org/show_bug.cgi?id=91872.
+ * Example code:
+ * @code
+ * li = libinput_path_create_context(&interface, NULL);
+ * device = libinput_path_add_device(li, "/dev/input/event0");
+ * // get extra reference to device
+ * libinput_device_ref(device);
+ *
+ * // refcount reaches 0, so *all* resources are cleaned up,
+ * // including device
+ * libinput_unref(li);
+ *
+ * // INCORRECT: device has been cleaned up and must not be used
+ * // li = libinput_device_get_context(device);
+ * @endcode
+ *
  * @param libinput A previously initialized libinput context
  * @return NULL if context was destroyed otherwise the passed context
  */
@@ -1393,10 +2992,11 @@ libinput_unref(struct libinput *libinput);
 /**
  * @ingroup base
  *
- * Set the global log priority. Messages with priorities equal to or
- * higher than the argument will be printed to the current log handler.
+ * Set the log priority for the libinput context. Messages with priorities
+ * equal to or higher than the argument will be printed to the context's
+ * log handler.
  *
- * The default log priority is LIBINPUT_LOG_PRIORITY_ERROR.
+ * The default log priority is @ref LIBINPUT_LOG_PRIORITY_ERROR.
  *
  * @param libinput A previously initialized libinput context
  * @param priority The minimum priority of log messages to print.
@@ -1411,10 +3011,10 @@ libinput_log_set_priority(struct libinput *libinput,
 /**
  * @ingroup base
  *
- * Get the global log priority. Messages with priorities equal to or
+ * Get the context's log priority. Messages with priorities equal to or
  * higher than the argument will be printed to the current log handler.
  *
- * The default log priority is LIBINPUT_LOG_PRIORITY_ERROR.
+ * The default log priority is @ref LIBINPUT_LOG_PRIORITY_ERROR.
  *
  * @param libinput A previously initialized libinput context
  * @return The minimum priority of log messages to print.
@@ -1447,8 +3047,8 @@ typedef void (*libinput_log_handler)(struct libinput *libinput,
 /**
  * @ingroup base
  *
- * Set the global log handler. Messages with priorities equal to or higher
- * than the current log priority will be passed to the given
+ * Set the context's log handler. Messages with priorities equal to or
+ * higher than the context's log priority will be passed to the given
  * log handler.
  *
  * The default log handler prints to stderr.
@@ -1476,7 +3076,7 @@ libinput_log_set_handler(struct libinput *libinput,
  * @ingroup seat
  *
  * Increase the refcount of the seat. A seat will be freed whenever the
- * refcount reaches 0. This may happen during dispatch if the
+ * refcount reaches 0. This may happen during libinput_dispatch() if the
  * seat was removed from the system. A caller must ensure to reference
  * the seat correctly to avoid dangling pointers.
  *
@@ -1490,7 +3090,7 @@ libinput_seat_ref(struct libinput_seat *seat);
  * @ingroup seat
  *
  * Decrease the refcount of the seat. A seat will be freed whenever the
- * refcount reaches 0. This may happen during dispatch if the
+ * refcount reaches 0. This may happen during libinput_dispatch() if the
  * seat was removed from the system. A caller must ensure to reference
  * the seat correctly to avoid dangling pointers.
  *
@@ -1551,7 +3151,7 @@ libinput_seat_get_context(struct libinput_seat *seat);
  * be available to the caller.
  *
  * @param seat A previously obtained seat
- * @return the physical name of this seat
+ * @return The physical name of this seat
  */
 const char *
 libinput_seat_get_physical_name(struct libinput_seat *seat);
@@ -1563,7 +3163,7 @@ libinput_seat_get_physical_name(struct libinput_seat *seat);
  * of devices within the compositor.
  *
  * @param seat A previously obtained seat
- * @return the logical name of this seat
+ * @return The logical name of this seat
  */
 const char *
 libinput_seat_get_logical_name(struct libinput_seat *seat);
@@ -1576,9 +3176,9 @@ libinput_seat_get_logical_name(struct libinput_seat *seat);
  * @ingroup device
  *
  * Increase the refcount of the input device. An input device will be freed
- * whenever the refcount reaches 0. This may happen during dispatch if the
- * device was removed from the system. A caller must ensure to reference
- * the device correctly to avoid dangling pointers.
+ * whenever the refcount reaches 0. This may happen during
+ * libinput_dispatch() if the device was removed from the system. A caller
+ * must ensure to reference the device correctly to avoid dangling pointers.
  *
  * @param device A previously obtained device
  * @return The passed device
@@ -1590,9 +3190,9 @@ libinput_device_ref(struct libinput_device *device);
  * @ingroup device
  *
  * Decrease the refcount of the input device. An input device will be freed
- * whenever the refcount reaches 0. This may happen during dispatch if the
- * device was removed from the system. A caller must ensure to reference
- * the device correctly to avoid dangling pointers.
+ * whenever the refcount reaches 0. This may happen during libinput_dispatch
+ * if the device was removed from the system. A caller must ensure to
+ * reference the device correctly to avoid dangling pointers.
  *
  * @param device A previously obtained device
  * @return NULL if the device was destroyed, otherwise the passed device
@@ -1760,7 +3360,7 @@ libinput_device_get_id_vendor(struct libinput_device *device);
  * beyond the boundaries of this output. An absolute device has its input
  * coordinates mapped to the extents of this output.
  *
- * @return the name of the output this device is mapped to, or NULL if no
+ * @return The name of the output this device is mapped to, or NULL if no
  * output is set
  */
 const char *
@@ -1769,13 +3369,19 @@ libinput_device_get_output_name(struct libinput_device *device);
 /**
  * @ingroup device
  *
- * Get the seat associated with this input device.
+ * Get the seat associated with this input device, see @ref seats for
+ * details.
  *
  * A seat can be uniquely identified by the physical and logical seat name.
  * There will ever be only one seat instance with a given physical and logical
  * seat name pair at any given time, but if no external reference is kept, it
  * may be destroyed if no device belonging to it is left.
  *
+ * The returned seat is not refcounted and may become invalid after
+ * the next call to libinput. Use libinput_seat_ref() and
+ * libinput_seat_unref() to continue using the handle outside of the
+ * immediate scope.
+ *
  * @param device A previously obtained device
  * @return The seat this input device belongs to
  */
@@ -1789,10 +3395,13 @@ libinput_device_get_seat(struct libinput_device *device);
  * device and adding it to the new seat.
  *
  * This command is identical to physically unplugging the device, then
- * re-plugging it as member of the new seat,
- * @ref LIBINPUT_EVENT_DEVICE_REMOVED and @ref LIBINPUT_EVENT_DEVICE_ADDED
- * events are sent accordingly. Those events mark the end of the lifetime
- * of this device and the start of a new device.
+ * re-plugging it as member of the new seat. libinput will generate a
+ * @ref LIBINPUT_EVENT_DEVICE_REMOVED event and this @ref libinput_device is
+ * considered removed from the context; it will not generate further events
+ * and will be freed when the refcount reaches zero.
+ * A @ref LIBINPUT_EVENT_DEVICE_ADDED event is generated with a new @ref
+ * libinput_device handle. It is the caller's responsibility to update
+ * references to the new device accordingly.
  *
  * If the logical seat name already exists in the device's physical seat,
  * the device is added to this seat. Otherwise, a new seat is created.
@@ -1813,7 +3422,9 @@ libinput_device_set_seat_logical_name(struct libinput_device *device,
  *
  * Return a udev handle to the device that is this libinput device, if any.
  * The returned handle has a refcount of at least 1, the caller must call
- * udev_device_unref() once to release the associated resources.
+ * <i>udev_device_unref()</i> once to release the associated resources.
+ * See the [libudev documentation]
+ * (http://www.freedesktop.org/software/systemd/libudev/) for details.
  *
  * Some devices may not have a udev device, or the udev device may be
  * unobtainable. This function returns NULL if no udev device was available.
@@ -1847,7 +3458,7 @@ libinput_device_led_update(struct libinput_device *device,
  *
  * Check if the given device has the specified capability
  *
- * @return 1 if the given device has the capability or 0 if not
+ * @return Non-zero if the given device has the capability or zero otherwise
  */
 int
 libinput_device_has_capability(struct libinput_device *device,
@@ -1876,10 +3487,10 @@ libinput_device_get_size(struct libinput_device *device,
  * @ingroup device
  *
  * Check if a @ref LIBINPUT_DEVICE_CAP_POINTER device has a button with the
- * passed in code (see linux/input.h).
+ * given code (see linux/input.h).
  *
  * @param device A current input device
- * @param code button code to check for
+ * @param code Button code to check for, e.g. <i>BTN_LEFT</i>
  *
  * @return 1 if the device supports this button code, 0 if it does not, -1
  * on error.
@@ -1890,18 +3501,72 @@ libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code
 /**
  * @ingroup device
  *
- * @deprecated Use libinput_device_pointer_has_button() instead.
+ * Check if a @ref LIBINPUT_DEVICE_CAP_KEYBOARD device has a key with the
+ * given code (see linux/input.h).
+ *
+ * @param device A current input device
+ * @param code Key code to check for, e.g. <i>KEY_ESC</i>
+ *
+ * @return 1 if the device supports this key code, 0 if it does not, -1
+ * on error.
+ */
+int
+libinput_device_keyboard_has_key(struct libinput_device *device,
+                                uint32_t code);
+
+/**
+ * @ingroup device
+ *
+ * Return the number of buttons on a device with the
+ * @ref LIBINPUT_DEVICE_CAP_TABLET_PAD capability.
+ * Buttons on a pad device are numbered sequentially, see @ref
+ * tablet-pad-buttons for details.
+ *
+ * @param device A current input device
+ *
+ * @return The number of buttons supported by the device.
+ */
+int
+libinput_device_tablet_pad_get_num_buttons(struct libinput_device *device);
+
+/**
+ * @ingroup device
+ *
+ * Return the number of rings a device with the @ref
+ * LIBINPUT_DEVICE_CAP_TABLET_PAD capability provides.
+ *
+ * @param device A current input device
+ *
+ * @return The number of rings or 0 if the device has no rings.
+ *
+ * @see libinput_event_tablet_pad_get_ring_number
+ */
+int
+libinput_device_tablet_pad_get_num_rings(struct libinput_device *device);
+
+/**
+ * @ingroup device
+ *
+ * Return the number of strips a device with the @ref
+ * LIBINPUT_DEVICE_CAP_TABLET_PAD capability provides.
+ *
+ * @param device A current input device
+ *
+ * @return The number of strips or 0 if the device has no strips.
+ *
+ * @see libinput_event_tablet_pad_get_strip_number
  */
 int
-libinput_device_has_button(struct libinput_device *device, uint32_t code) LIBINPUT_ATTRIBUTE_DEPRECATED;
+libinput_device_tablet_pad_get_num_strips(struct libinput_device *device);
 
 /**
  * @ingroup device
  *
  * Increase the refcount of the device group. A device group will be freed
- * whenever the refcount reaches 0. This may happen during dispatch if all
- * devices of this group were removed from the system. A caller must ensure
- * to reference the device group correctly to avoid dangling pointers.
+ * whenever the refcount reaches 0. This may happen during
+ * libinput_dispatch() if all devices of this group were removed from the
+ * system. A caller must ensure to reference the device group correctly to
+ * avoid dangling pointers.
  *
  * @param group A previously obtained device group
  * @return The passed device group
@@ -1913,9 +3578,10 @@ libinput_device_group_ref(struct libinput_device_group *group);
  * @ingroup device
  *
  * Decrease the refcount of the device group. A device group will be freed
- * whenever the refcount reaches 0. This may happen during dispatch if all
- * devices of this group were removed from the system. A caller must ensure
- * to reference the device group correctly to avoid dangling pointers.
+ * whenever the refcount reaches 0. This may happen during
+ * libinput_dispatch() if all devices of this group were removed from the
+ * system. A caller must ensure to reference the device group correctly to
+ * avoid dangling pointers.
  *
  * @param group A previously obtained device group
  * @return NULL if the device group was destroyed, otherwise the passed
@@ -1960,10 +3626,37 @@ libinput_device_group_get_user_data(struct libinput_device_group *group);
  * configuration. This default can be obtained with the respective
  * get_default call.
  *
+ * Configuration options are device dependent and not all options are
+ * supported on all devices. For all configuration options, libinput
+ * provides a call to check if a configuration option is available on a
+ * device (e.g. libinput_device_config_calibration_has_matrix())
+ *
  * Some configuration option may be dependent on or mutually exclusive with
  * with other options. The behavior in those cases is
- * implementation-defined, the caller must ensure that the options are set
+ * implementation-dependent, the caller must ensure that the options are set
  * in the right order.
+ *
+ * Below is a general grouping of configuration options according to device
+ * type. Note that this is a guide only and not indicative of any specific
+ * device.
+ * - Touchpad:
+ *    - libinput_device_config_tap_set_enabled()
+ *    - libinput_device_config_tap_set_drag_enabled()
+ *    - libinput_device_config_tap_set_drag_lock_enabled()
+ *    - libinput_device_config_click_set_method()
+ *    - libinput_device_config_scroll_set_method()
+ *    - libinput_device_config_dwt_set_enabled()
+ * - Touchscreens:
+ *    - libinput_device_config_calibration_set_matrix()
+ * - Pointer devices (mice, trackballs, touchpads):
+ *    - libinput_device_config_accel_set_speed()
+ *    - libinput_device_config_accel_set_profile()
+ *    - libinput_device_config_scroll_set_natural_scroll_enabled()
+ *    - libinput_device_config_left_handed_set()
+ *    - libinput_device_config_middle_emulation_set_enabled()
+ *    - libinput_device_config_rotation_set_angle()
+ * - All devices:
+ *    - libinput_device_config_send_events_set_mode()
  */
 
 /**
@@ -1981,99 +3674,336 @@ enum libinput_config_status {
 /**
  * @ingroup config
  *
- * Return a string describing the error.
+ * Return a string describing the error.
+ *
+ * @param status The status to translate to a string
+ * @return A human-readable string representing the error or NULL for an
+ * invalid status.
+ */
+const char *
+libinput_config_status_to_str(enum libinput_config_status status);
+
+/**
+ * @ingroup config
+ */
+enum libinput_config_tap_state {
+       LIBINPUT_CONFIG_TAP_DISABLED, /**< Tapping is to be disabled, or is
+                                       currently disabled */
+       LIBINPUT_CONFIG_TAP_ENABLED, /**< Tapping is to be enabled, or is
+                                      currently enabled */
+};
+
+/**
+ * @ingroup config
+ *
+ * Check if the device supports tap-to-click and how many fingers can be
+ * used for tapping. See
+ * libinput_device_config_tap_set_enabled() for more information.
+ *
+ * @param device The device to configure
+ * @return The number of fingers that can generate a tap event, or 0 if the
+ * device does not support tapping.
+ *
+ * @see libinput_device_config_tap_set_enabled
+ * @see libinput_device_config_tap_get_enabled
+ * @see libinput_device_config_tap_get_default_enabled
+ */
+int
+libinput_device_config_tap_get_finger_count(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Enable or disable tap-to-click on this device, with a default mapping of
+ * 1, 2, 3 finger tap mapping to left, right, middle click, respectively.
+ * Tapping is limited by the number of simultaneous touches
+ * supported by the device, see
+ * libinput_device_config_tap_get_finger_count().
+ *
+ * @param device The device to configure
+ * @param enable @ref LIBINPUT_CONFIG_TAP_ENABLED to enable tapping or @ref
+ * LIBINPUT_CONFIG_TAP_DISABLED to disable tapping
+ *
+ * @return A config status code. Disabling tapping on a device that does not
+ * support tapping always succeeds.
+ *
+ * @see libinput_device_config_tap_get_finger_count
+ * @see libinput_device_config_tap_get_enabled
+ * @see libinput_device_config_tap_get_default_enabled
+ */
+enum libinput_config_status
+libinput_device_config_tap_set_enabled(struct libinput_device *device,
+                                      enum libinput_config_tap_state enable);
+
+/**
+ * @ingroup config
+ *
+ * Check if tap-to-click is enabled on this device. If the device does not
+ * support tapping, this function always returns @ref
+ * LIBINPUT_CONFIG_TAP_DISABLED.
+ *
+ * @param device The device to configure
+ *
+ * @retval LIBINPUT_CONFIG_TAP_ENABLED If tapping is currently enabled
+ * @retval LIBINPUT_CONFIG_TAP_DISABLED If tapping is currently disabled
+ *
+ * @see libinput_device_config_tap_get_finger_count
+ * @see libinput_device_config_tap_set_enabled
+ * @see libinput_device_config_tap_get_default_enabled
+ */
+enum libinput_config_tap_state
+libinput_device_config_tap_get_enabled(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Return the default setting for whether tap-to-click is enabled on this
+ * device.
+ *
+ * @param device The device to configure
+ * @retval LIBINPUT_CONFIG_TAP_ENABLED If tapping is enabled by default
+ * @retval LIBINPUT_CONFIG_TAP_DISABLED If tapping Is disabled by default
+ *
+ * @see libinput_device_config_tap_get_finger_count
+ * @see libinput_device_config_tap_set_enabled
+ * @see libinput_device_config_tap_get_enabled
+ */
+enum libinput_config_tap_state
+libinput_device_config_tap_get_default_enabled(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ */
+enum libinput_config_tap_button_map {
+       /** 1/2/3 finger tap maps to left/right/middle */
+       LIBINPUT_CONFIG_TAP_MAP_LRM,
+       /** 1/2/3 finger tap maps to left/middle/right*/
+       LIBINPUT_CONFIG_TAP_MAP_LMR,
+};
+
+/**
+ * @ingroup config
+ *
+ * Set the finger number to button number mapping for tap-to-click. The
+ * default mapping on most devices is to have a 1, 2 and 3 finger tap to map
+ * to the left, right and middle button, respectively.
+ * A device may permit changing the button mapping but disallow specific
+ * maps. In this case @ref LIBINPUT_CONFIG_STATUS_UNSUPPORTED is returned,
+ * the caller is expected to handle this case correctly.
+ *
+ * Changing the button mapping may not take effect immediately,
+ * the device may wait until it is in a neutral state before applying any
+ * changes.
+ *
+ * The mapping may be changed when tap-to-click is disabled. The new mapping
+ * takes effect when tap-to-click is enabled in the future.
+ *
+ * @note It is an application bug to call this function for devices where
+ * libinput_device_config_tap_get_finger_count() returns 0.
+ *
+ * @param device The device to configure
+ * @param map The new finger-to-button number mapping
+ * @return A config status code. Changing the order on a device that does not
+ * support tapping always fails with @ref LIBINPUT_CONFIG_STATUS_UNSUPPORTED.
+ *
+ * @see libinput_device_config_tap_get_button_map
+ * @see libinput_device_config_tap_get_default_button_map
+ */
+enum libinput_config_status
+libinput_device_config_tap_set_button_map(struct libinput_device *device,
+                                           enum libinput_config_tap_button_map map);
+
+/**
+ * @ingroup config
+ *
+ * Get the finger number to button number mapping for tap-to-click.
+ *
+ * The return value for a device that does not support tapping is always
+ * @ref LIBINPUT_CONFIG_TAP_MAP_LRM.
+ *
+ * @note It is an application bug to call this function for devices where
+ * libinput_device_config_tap_get_finger_count() returns 0.
+ *
+ * @param device The device to configure
+ * @return The current finger-to-button number mapping
+ *
+ * @see libinput_device_config_tap_set_button_map
+ * @see libinput_device_config_tap_get_default_button_map
+ */
+enum libinput_config_tap_button_map
+libinput_device_config_tap_get_button_map(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Get the default finger number to button number mapping for tap-to-click.
+ *
+ * The return value for a device that does not support tapping is always
+ * @ref LIBINPUT_CONFIG_TAP_MAP_LRM.
+ *
+ * @note It is an application bug to call this function for devices where
+ * libinput_device_config_tap_get_finger_count() returns 0.
+ *
+ * @param device The device to configure
+ * @return The current finger-to-button number mapping
+ *
+ * @see libinput_device_config_tap_set_button_map
+ * @see libinput_device_config_tap_get_default_button_map
+ */
+enum libinput_config_tap_button_map
+libinput_device_config_tap_get_default_button_map(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * A config status to distinguish or set dragging on a device. Currently
+ * implemented for tap-and-drag only, see
+ * libinput_device_config_tap_set_drag_enabled()
+ */
+enum libinput_config_drag_state {
+       /**
+        * Drag is to be disabled, or is
+        * currently disabled.
+        */
+       LIBINPUT_CONFIG_DRAG_DISABLED,
+       /**
+        * Drag is to be enabled, or is
+        * currently enabled
+        */
+       LIBINPUT_CONFIG_DRAG_ENABLED,
+};
+
+/**
+ * @ingroup config
+ *
+ * Enable or disable tap-and-drag on this device. When enabled, a
+ * single-finger tap immediately followed by a finger down results in a
+ * button down event, subsequent finger motion thus triggers a drag. The
+ * button is released on finger up. See @ref tapndrag for more details.
+ *
+ * @param device The device to configure
+ * @param enable @ref LIBINPUT_CONFIG_DRAG_ENABLED to enable, @ref
+ * LIBINPUT_CONFIG_DRAG_DISABLED to disable tap-and-drag
+ *
+ * @see libinput_device_config_tap_drag_get_enabled
+ * @see libinput_device_config_tap_drag_get_default_enabled
+ */
+enum libinput_config_status
+libinput_device_config_tap_set_drag_enabled(struct libinput_device *device,
+                                           enum libinput_config_drag_state enable);
+
+/**
+ * @ingroup config
+ *
+ * Return whether tap-and-drag is enabled or disabled on this device.
  *
- * @param status The status to translate to a string
- * @return A human-readable string representing the error or NULL for an
- * invalid status.
+ * @param device The device to check
+ * @retval LIBINPUT_CONFIG_DRAG_ENABLED if tap-and-drag is enabled
+ * @retval LIBINPUT_CONFIG_DRAG_DISABLED if tap-and-drag is
+ * disabled
+ *
+ * @see libinput_device_config_tap_drag_set_enabled
+ * @see libinput_device_config_tap_drag_get_default_enabled
  */
-const char *
-libinput_config_status_to_str(enum libinput_config_status status);
+enum libinput_config_drag_state
+libinput_device_config_tap_get_drag_enabled(struct libinput_device *device);
 
 /**
  * @ingroup config
+ *
+ * Return whether tap-and-drag is enabled or disabled by default on this
+ * device.
+ *
+ * @param device The device to check
+ * @retval LIBINPUT_CONFIG_DRAG_ENABLED if tap-and-drag is enabled by
+ * default
+ * @retval LIBINPUT_CONFIG_DRAG_DISABLED if tap-and-drag is
+ * disabled by default
+ *
+ * @see libinput_device_config_tap_drag_set_enabled
+ * @see libinput_device_config_tap_drag_get_enabled
  */
-enum libinput_config_tap_state {
-       LIBINPUT_CONFIG_TAP_DISABLED, /**< Tapping is to be disabled, or is
-                                       currently disabled */
-       LIBINPUT_CONFIG_TAP_ENABLED, /**< Tapping is to be enabled, or is
-                                      currently enabled */
-};
+enum libinput_config_drag_state
+libinput_device_config_tap_get_default_drag_enabled(struct libinput_device *device);
 
 /**
  * @ingroup config
- *
- * Check if the device supports tap-to-click. See
- * libinput_device_config_tap_set_enabled() for more information.
- *
- * @param device The device to configure
- * @return The number of fingers that can generate a tap event, or 0 if the
- * device does not support tapping.
- *
- * @see libinput_device_config_tap_set_enabled
- * @see libinput_device_config_tap_get_enabled
- * @see libinput_device_config_tap_get_default_enabled
  */
-int
-libinput_device_config_tap_get_finger_count(struct libinput_device *device);
+enum libinput_config_drag_lock_state {
+       /** Drag lock is to be disabled, or is currently disabled */
+       LIBINPUT_CONFIG_DRAG_LOCK_DISABLED,
+       /** Drag lock is to be enabled, or is currently disabled */
+       LIBINPUT_CONFIG_DRAG_LOCK_ENABLED,
+};
 
 /**
  * @ingroup config
  *
- * Enable or disable tap-to-click on this device, with a default mapping of
- * 1, 2, 3 finger tap mapping to left, right, middle click, respectively.
- * Tapping is limited by the number of simultaneous touches
- * supported by the device, see
- * libinput_device_config_tap_get_finger_count().
+ * Enable or disable drag-lock during tapping on this device. When enabled,
+ * a finger may be lifted and put back on the touchpad within a timeout and
+ * the drag process continues. When disabled, lifting the finger during a
+ * tap-and-drag will immediately stop the drag. See @ref tapndrag for
+ * details.
+ *
+ * Enabling drag lock on a device that has tapping disabled is permitted,
+ * but has no effect until tapping is enabled.
  *
  * @param device The device to configure
- * @param enable @ref LIBINPUT_CONFIG_TAP_ENABLED to enable tapping or @ref
- * LIBINPUT_CONFIG_TAP_DISABLED to disable tapping
+ * @param enable @ref LIBINPUT_CONFIG_DRAG_LOCK_ENABLED to enable drag lock
+ * or @ref LIBINPUT_CONFIG_DRAG_LOCK_DISABLED to disable drag lock
  *
- * @return A config status code. Disabling tapping on a device that does not
+ * @return A config status code. Disabling drag lock on a device that does not
  * support tapping always succeeds.
  *
- * @see libinput_device_config_tap_get_finger_count
- * @see libinput_device_config_tap_get_enabled
- * @see libinput_device_config_tap_get_default_enabled
+ * @see libinput_device_config_tap_get_drag_lock_enabled
+ * @see libinput_device_config_tap_get_default_drag_lock_enabled
  */
 enum libinput_config_status
-libinput_device_config_tap_set_enabled(struct libinput_device *device,
-                                      enum libinput_config_tap_state enable);
+libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device,
+                                                enum libinput_config_drag_lock_state enable);
 
 /**
  * @ingroup config
  *
- * Check if tap-to-click is enabled on this device. If the device does not
- * support tapping, this function always returns 0.
+ * Check if drag-lock during tapping is enabled on this device. If the
+ * device does not support tapping, this function always returns
+ * @ref LIBINPUT_CONFIG_DRAG_LOCK_DISABLED.
+ *
+ * Drag lock may be enabled even when tapping is disabled.
  *
  * @param device The device to configure
  *
- * @return @ref LIBINPUT_CONFIG_TAP_ENABLED if tapping is currently enabled,
- * or @ref LIBINPUT_CONFIG_TAP_DISABLED is currently disabled
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED If drag lock is currently enabled
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_DISABLED If drag lock is currently disabled
  *
- * @see libinput_device_config_tap_get_finger_count
- * @see libinput_device_config_tap_set_enabled
- * @see libinput_device_config_tap_get_default_enabled
+ * @see libinput_device_config_tap_set_drag_lock_enabled
+ * @see libinput_device_config_tap_get_default_drag_lock_enabled
  */
-enum libinput_config_tap_state
-libinput_device_config_tap_get_enabled(struct libinput_device *device);
+enum libinput_config_drag_lock_state
+libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device);
 
 /**
  * @ingroup config
  *
- * Return the default setting for whether tapping is enabled on this device.
+ * Check if drag-lock during tapping is enabled by default on this device.
+ * If the device does not support tapping, this function always returns
+ * @ref LIBINPUT_CONFIG_DRAG_LOCK_DISABLED.
+ *
+ * Drag lock may be enabled by default even when tapping is disabled by
+ * default.
  *
  * @param device The device to configure
- * @return @ref LIBINPUT_CONFIG_TAP_ENABLED if tapping is enabled by default,
- * or @ref LIBINPUT_CONFIG_TAP_DISABLED is disabled by default
  *
- * @see libinput_device_config_tap_get_finger_count
- * @see libinput_device_config_tap_set_enabled
- * @see libinput_device_config_tap_get_enabled
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_ENABLED If drag lock is enabled by
+ * default
+ * @retval LIBINPUT_CONFIG_DRAG_LOCK_DISABLED If drag lock is disabled by
+ * default
+ *
+ * @see libinput_device_config_tap_set_drag_lock_enabled
+ * @see libinput_device_config_tap_get_drag_lock_enabled
  */
-enum libinput_config_tap_state
-libinput_device_config_tap_get_default_enabled(struct libinput_device *device);
+enum libinput_config_drag_lock_state
+libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device *device);
 
 /**
  * @ingroup config
@@ -2081,7 +4011,7 @@ libinput_device_config_tap_get_default_enabled(struct libinput_device *device);
  * Check if the device can be calibrated via a calibration matrix.
  *
  * @param device The device to check
- * @return non-zero if the device can be calibrated, zero otherwise.
+ * @return Non-zero if the device can be calibrated, zero otherwise.
  *
  * @see libinput_device_config_calibration_set_matrix
  * @see libinput_device_config_calibration_get_matrix
@@ -2169,21 +4099,7 @@ libinput_device_config_calibration_get_matrix(struct libinput_device *device,
  * Return the default calibration matrix for this device. On most devices,
  * this is the identity matrix. If the udev property
  * <b>LIBINPUT_CALIBRATION_MATRIX</b> is set on the respective udev device,
- * that property's value becomes the default matrix.
- *
- * The udev property is parsed as 6 floating point numbers separated by a
- * single space each (scanf(3) format "%f %f %f %f %f %f").
- * The 6 values represent the first two rows of the calibration matrix as
- * described in libinput_device_config_calibration_set_matrix().
- *
- * Example values are:
- * @code
- * ENV{LIBINPUT_CALIBRATION_MATRIX}="1 0 0 0 1 0" # default
- * ENV{LIBINPUT_CALIBRATION_MATRIX}="0 -1 1 1 0 0" # 90 degree clockwise
- * ENV{LIBINPUT_CALIBRATION_MATRIX}="-1 0 1 0 -1 1" # 180 degree clockwise
- * ENV{LIBINPUT_CALIBRATION_MATRIX}="0 1 0 -1 0 1" # 270 degree clockwise
- * ENV{LIBINPUT_CALIBRATION_MATRIX}="-1 0 1 1 0 0" # reflect along y axis
- * @endcode
+ * that property's value becomes the default matrix, see @ref udev_config.
  *
  * @param device The device to configure
  * @param matrix Set to the array representing the first two rows of a 3x3 matrix as
@@ -2201,6 +4117,8 @@ libinput_device_config_calibration_get_default_matrix(struct libinput_device *de
                                                      float matrix[6]);
 
 /**
+ * @ingroup config
+ *
  * The send-event mode of a device defines when a device may generate events
  * and pass those events to the caller.
  */
@@ -2379,6 +4297,83 @@ libinput_device_config_accel_get_speed(struct libinput_device *device);
 double
 libinput_device_config_accel_get_default_speed(struct libinput_device *device);
 
+/**
+ * @ingroup config
+ */
+enum libinput_config_accel_profile {
+       /**
+        * Placeholder for devices that don't have a configurable pointer
+        * acceleration profile.
+        */
+       LIBINPUT_CONFIG_ACCEL_PROFILE_NONE = 0,
+       /**
+        * A flat acceleration profile. Pointer motion is accelerated by a
+        * constant (device-specific) factor, depending on the current
+        * speed.
+        *
+        * @see libinput_device_config_accel_set_speed
+        */
+       LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT = (1 << 0),
+
+       /**
+        * An adaptive acceleration profile. Pointer acceleration depends
+        * on the input speed. This is the default profile for most devices.
+        */
+       LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE = (1 << 1),
+};
+
+/**
+ * @ingroup config
+ *
+ * Returns a bitmask of the configurable acceleration modes available on
+ * this device.
+ *
+ * @param device The device to configure
+ *
+ * @return A bitmask of all configurable modes available on this device.
+ */
+uint32_t
+libinput_device_config_accel_get_profiles(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Set the pointer acceleration profile of this pointer device to the given
+ * mode.
+ *
+ * @param device The device to configure
+ * @param mode The mode to set the device to.
+ *
+ * @return A config status code
+ */
+enum libinput_config_status
+libinput_device_config_accel_set_profile(struct libinput_device *device,
+                                        enum libinput_config_accel_profile mode);
+
+/**
+ * @ingroup config
+ *
+ * Get the current pointer acceleration profile for this pointer device.
+ *
+ * @param device The device to configure
+ *
+ * @return The currently configured pointer acceleration profile.
+ */
+enum libinput_config_accel_profile
+libinput_device_config_accel_get_profile(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Return the default pointer acceleration profile for this pointer device.
+ *
+ * @param device The device to configure
+ *
+ * @return The default acceleration profile for this device.
+ */
+enum libinput_config_accel_profile
+libinput_device_config_accel_get_default_profile(struct libinput_device *device);
+
 /**
  * @ingroup config
  *
@@ -2401,7 +4396,7 @@ libinput_device_config_accel_get_default_speed(struct libinput_device *device);
  *
  * @param device The device to configure
  *
- * @return 0 if natural scrolling is not supported, non-zero if natural
+ * @return Zero if natural scrolling is not supported, non-zero if natural
  * scrolling is supported by this device
  *
  * @see libinput_device_config_set_natural_scroll_enabled
@@ -2419,7 +4414,7 @@ libinput_device_config_scroll_has_natural_scroll(struct libinput_device *device)
  * @param device The device to configure
  * @param enable non-zero to enable, zero to disable natural scrolling
  *
- * @return a config status code
+ * @return A config status code
  *
  * @see libinput_device_config_has_natural_scroll
  * @see libinput_device_config_get_natural_scroll_enabled
@@ -2435,7 +4430,7 @@ libinput_device_config_scroll_set_natural_scroll_enabled(struct libinput_device
  *
  * @param device The device to configure
  *
- * @return zero if natural scrolling is disabled, non-zero if enabled
+ * @return Zero if natural scrolling is disabled, non-zero if enabled
  *
  * @see libinput_device_config_has_natural_scroll
  * @see libinput_device_config_set_natural_scroll_enabled
@@ -2451,7 +4446,7 @@ libinput_device_config_scroll_get_natural_scroll_enabled(struct libinput_device
  *
  * @param device The device to configure
  *
- * @return zero if natural scrolling is disabled by default, non-zero if enabled
+ * @return Zero if natural scrolling is disabled by default, non-zero if enabled
  *
  * @see libinput_device_config_has_natural_scroll
  * @see libinput_device_config_set_natural_scroll_enabled
@@ -2460,9 +4455,6 @@ libinput_device_config_scroll_get_natural_scroll_enabled(struct libinput_device
 int
 libinput_device_config_scroll_get_default_natural_scroll_enabled(struct libinput_device *device);
 
-int
-libinput_device_config_scroll_get_wheel_click_angle(struct libinput_device *device);
-
 /**
  * @ingroup config
  *
@@ -2482,9 +4474,7 @@ libinput_device_config_left_handed_is_available(struct libinput_device *device);
 /**
  * @ingroup config
  *
- * Set the left-handed configuration of the device. For example, a pointing
- * device may reverse it's buttons and send a right button click when the
- * left button is pressed, and vice versa.
+ * Set the left-handed configuration of the device.
  *
  * The exact behavior is device-dependent. On a mouse and most pointing
  * devices, left and right buttons are swapped but the middle button is
@@ -2550,7 +4540,7 @@ libinput_device_config_left_handed_get_default(struct libinput_device *device);
 enum libinput_config_click_method {
        /**
         * Do not send software-emulated button events. This has no effect
-        * on physical button generations.
+        * on events generated by physical buttons.
         */
        LIBINPUT_CONFIG_CLICK_METHOD_NONE = 0,
        /**
@@ -2641,6 +4631,125 @@ libinput_device_config_click_get_method(struct libinput_device *device);
 enum libinput_config_click_method
 libinput_device_config_click_get_default_method(struct libinput_device *device);
 
+/**
+ * @ingroup config
+ */
+enum libinput_config_middle_emulation_state {
+       /**
+        * Middle mouse button emulation is to be disabled, or
+        * is currently disabled.
+        */
+       LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED,
+       /**
+        * Middle mouse button emulation is to be enabled, or
+        * is currently enabled.
+        */
+       LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED,
+};
+
+/**
+ * @ingroup config
+ *
+ * Check if middle mouse button emulation configuration is available on this
+ * device. See @ref middle_button_emulation for details.
+ *
+ * @note Some devices provide middle mouse button emulation but do not allow
+ * enabling/disabling that emulation. These devices return zero in
+ * libinput_device_config_middle_emulation_is_available().
+ *
+ * @param device The device to query
+ *
+ * @return Non-zero if middle mouse button emulation is available and can be
+ * configured, zero otherwise.
+ *
+ * @see libinput_device_config_middle_emulation_set_enabled
+ * @see libinput_device_config_middle_emulation_get_enabled
+ * @see libinput_device_config_middle_emulation_get_default_enabled
+ */
+int
+libinput_device_config_middle_emulation_is_available(
+               struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Enable or disable middle button emulation on this device. When enabled, a
+ * simultaneous press of the left and right button generates a middle mouse
+ * button event. Releasing the buttons generates a middle mouse button
+ * release, the left and right button events are discarded otherwise.
+ *
+ * See @ref middle_button_emulation for details.
+ *
+ * @param device The device to configure
+ * @param enable @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED to
+ * disable, @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED To enable
+ * middle button emulation.
+ *
+ * @return A config status code. Disabling middle button emulation on a
+ * device that does not support middle button emulation always succeeds.
+ *
+ * @see libinput_device_config_middle_emulation_is_available
+ * @see libinput_device_config_middle_emulation_get_enabled
+ * @see libinput_device_config_middle_emulation_get_default_enabled
+ */
+enum libinput_config_status
+libinput_device_config_middle_emulation_set_enabled(
+               struct libinput_device *device,
+               enum libinput_config_middle_emulation_state enable);
+
+/**
+ * @ingroup config
+ *
+ * Check if configurable middle button emulation is enabled on this device.
+ * See @ref middle_button_emulation for details.
+ *
+ * If the device does not have configurable middle button emulation, this
+ * function returns @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED.
+ *
+ * @note Some devices provide middle mouse button emulation but do not allow
+ * enabling/disabling that emulation. These devices always return @ref
+ * LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED.
+ *
+ * @param device The device to configure
+ * @return @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED if disabled
+ * or not available/configurable, @ref
+ * LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED If enabled.
+ *
+ * @see libinput_device_config_middle_emulation_is_available
+ * @see libinput_device_config_middle_emulation_set_enabled
+ * @see libinput_device_config_middle_emulation_get_default_enabled
+ */
+enum libinput_config_middle_emulation_state
+libinput_device_config_middle_emulation_get_enabled(
+               struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Check if configurable middle button emulation is enabled by default on
+ * this device. See @ref middle_button_emulation for details.
+ *
+ * If the device does not have configurable middle button
+ * emulation, this function returns @ref
+ * LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED.
+ *
+ * @note Some devices provide middle mouse button emulation but do not allow
+ * enabling/disabling that emulation. These devices always return @ref
+ * LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED.
+ *
+ * @param device The device to configure
+ * @return @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED If disabled
+ * or not available, @ref LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED if
+ * enabled.
+ *
+ * @see libinput_device_config_middle_emulation_is_available
+ * @see libinput_device_config_middle_emulation_set_enabled
+ * @see libinput_device_config_middle_emulation_get_enabled
+ */
+enum libinput_config_middle_emulation_state
+libinput_device_config_middle_emulation_get_default_enabled(
+               struct libinput_device *device);
+
 /**
  * @ingroup config
  *
@@ -2650,15 +4759,16 @@ libinput_device_config_click_get_default_method(struct libinput_device *device);
 enum libinput_config_scroll_method {
        /**
         * Never send scroll events instead of pointer motion events.
-        * Note scroll wheels, etc. will still send scroll events.
+        * This has no effect on events generated by scroll wheels.
         */
        LIBINPUT_CONFIG_SCROLL_NO_SCROLL = 0,
        /**
-        * Send scroll events when 2 fingers are down on the device.
+        * Send scroll events when two fingers are logically down on the
+        * device.
         */
        LIBINPUT_CONFIG_SCROLL_2FG = (1 << 0),
        /**
-        * Send scroll events when a finger is moved along the bottom or
+        * Send scroll events when a finger moves along the bottom or
         * right edge of a device.
         */
        LIBINPUT_CONFIG_SCROLL_EDGE = (1 << 1),
@@ -2776,10 +4886,11 @@ libinput_device_config_scroll_get_default_method(struct libinput_device *device)
  * @param device The device to configure
  * @param button The button which when pressed switches to sending scroll events
  *
- * @return a config status code
- * @retval LIBINPUT_CONFIG_STATUS_SUCCESS on success
- * @retval LIBINPUT_CONFIG_STATUS_UNSUPPORTED if @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN is not supported
- * @retval LIBINPUT_CONFIG_STATUS_INVALID the given button does not
+ * @return A config status code
+ * @retval LIBINPUT_CONFIG_STATUS_SUCCESS On success
+ * @retval LIBINPUT_CONFIG_STATUS_UNSUPPORTED If @ref
+ * LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN is not supported
+ * @retval LIBINPUT_CONFIG_STATUS_INVALID The given button does not
  * exist on this device
  *
  * @see libinput_device_config_scroll_get_methods
@@ -2796,11 +4907,11 @@ libinput_device_config_scroll_set_button(struct libinput_device *device,
 /**
  * @ingroup config
  *
- * Get the button for the @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method for
- * this device.
+ * Get the button for the @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method
+ * for this device.
  *
- * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not supported,
- * or no button is set, this function returns 0.
+ * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not
+ * supported, or no button is set, this function returns 0.
  *
  * @note The return value is independent of the currently selected
  * scroll-method. For button scrolling to activate, a device must have the
@@ -2823,14 +4934,15 @@ libinput_device_config_scroll_get_button(struct libinput_device *device);
 /**
  * @ingroup config
  *
- * Get the default button for LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method
- * for this device.
+ * Get the default button for the @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN
+ * method for this device.
  *
  * If @ref LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN scroll method is not supported,
  * or no default button is set, this function returns 0.
  *
  * @param device The device to configure
- * @return The default button for LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method
+ * @return The default button for the @ref
+ * LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN method
  *
  * @see libinput_device_config_scroll_get_methods
  * @see libinput_device_config_scroll_set_method
@@ -2842,6 +4954,179 @@ libinput_device_config_scroll_get_button(struct libinput_device *device);
 uint32_t
 libinput_device_config_scroll_get_default_button(struct libinput_device *device);
 
+/**
+ * @ingroup config
+ *
+ * Possible states for the disable-while-typing feature. See @ref
+ * disable-while-typing for details.
+ */
+enum libinput_config_dwt_state {
+       LIBINPUT_CONFIG_DWT_DISABLED,
+       LIBINPUT_CONFIG_DWT_ENABLED,
+};
+
+/**
+ * @ingroup config
+ *
+ * Check if this device supports configurable disable-while-typing feature.
+ * This feature is usually available on built-in touchpads and disables the
+ * touchpad while typing. See @ref disable-while-typing for details.
+ *
+ * @param device The device to configure
+ * @return 0 if this device does not support disable-while-typing, or 1
+ * otherwise.
+ *
+ * @see libinput_device_config_dwt_set_enabled
+ * @see libinput_device_config_dwt_get_enabled
+ * @see libinput_device_config_dwt_get_default_enabled
+ */
+int
+libinput_device_config_dwt_is_available(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Enable or disable the disable-while-typing feature. When enabled, the
+ * device will be disabled while typing and for a short period after. See
+ * @ref disable-while-typing for details.
+ *
+ * @note Enabling or disabling disable-while-typing may not take effect
+ * immediately.
+ *
+ * @param device The device to configure
+ * @param enable @ref LIBINPUT_CONFIG_DWT_DISABLED to disable
+ * disable-while-typing, @ref LIBINPUT_CONFIG_DWT_ENABLED to enable
+ *
+ * @return A config status code. Disabling disable-while-typing on a
+ * device that does not support the feature always succeeds.
+ *
+ * @see libinput_device_config_dwt_is_available
+ * @see libinput_device_config_dwt_get_enabled
+ * @see libinput_device_config_dwt_get_default_enabled
+ */
+enum libinput_config_status
+libinput_device_config_dwt_set_enabled(struct libinput_device *device,
+                                      enum libinput_config_dwt_state enable);
+
+/**
+ * @ingroup config
+ *
+ * Check if the disable-while typing feature is currently enabled on this
+ * device. If the device does not support disable-while-typing, this
+ * function returns @ref LIBINPUT_CONFIG_DWT_DISABLED.
+ *
+ * @param device The device to configure
+ * @return @ref LIBINPUT_CONFIG_DWT_DISABLED if disabled, @ref
+ * LIBINPUT_CONFIG_DWT_ENABLED if enabled.
+ *
+ * @see libinput_device_config_dwt_is_available
+ * @see libinput_device_config_dwt_set_enabled
+ * @see libinput_device_config_dwt_get_default_enabled
+ */
+enum libinput_config_dwt_state
+libinput_device_config_dwt_get_enabled(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Check if the disable-while typing feature is enabled on this device by
+ * default. If the device does not support disable-while-typing, this
+ * function returns @ref LIBINPUT_CONFIG_DWT_DISABLED.
+ *
+ * @param device The device to configure
+ * @return @ref LIBINPUT_CONFIG_DWT_DISABLED if disabled, @ref
+ * LIBINPUT_CONFIG_DWT_ENABLED if enabled.
+ *
+ * @see libinput_device_config_dwt_is_available
+ * @see libinput_device_config_dwt_set_enabled
+ * @see libinput_device_config_dwt_get_enabled
+ */
+enum libinput_config_dwt_state
+libinput_device_config_dwt_get_default_enabled(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Check whether a device can have a custom rotation applied.
+ *
+ * @param device The device to configure
+ * @return Non-zero if a device can be rotated, zero otherwise.
+ *
+ * @see libinput_device_config_rotation_set_angle
+ * @see libinput_device_config_rotation_get_angle
+ * @see libinput_device_config_rotation_get_default_angle
+ */
+int
+libinput_device_config_rotation_is_available(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Set the rotation of a device in degrees clockwise off the logical neutral
+ * position. Any subsequent motion events are adjusted according to the
+ * given angle.
+ *
+ * The angle has to be in the range of [0, 360[ degrees, otherwise this
+ * function returns LIBINPUT_CONFIG_STATUS_INVALID. If the angle is a
+ * multiple of 360 or negative, the caller must ensure the correct ranging
+ * before calling this function.
+ *
+ * libinput guarantees that this function accepts multiples of 90 degrees.
+ * If a value is within the [0, 360[ range but not a multiple of 90 degrees,
+ * this function may return LIBINPUT_CONFIG_STATUS_INVALID if the underlying
+ * device or implementation does not support finer-grained rotation angles.
+ *
+ * The rotation angle is applied to all motion events emitted by the device.
+ * Thus, rotating the device also changes the angle required or presented by
+ * scrolling, gestures, etc.
+ *
+ * @param device The device to configure
+ * @param degrees_cw The angle in degrees clockwise
+ * @return A config status code. Setting a rotation of 0 degrees on a
+ * device that does not support rotation always succeeds.
+ *
+ * @see libinput_device_config_rotation_is_available
+ * @see libinput_device_config_rotation_get_angle
+ * @see libinput_device_config_rotation_get_default_angle
+ */
+enum libinput_config_status
+libinput_device_config_rotation_set_angle(struct libinput_device *device,
+                                         unsigned int degrees_cw);
+
+/**
+ * @ingroup config
+ *
+ * Get the current rotation of a device in degrees clockwise off the logical
+ * neutral position. If this device does not support rotation, the return
+ * value is always 0.
+ *
+ * @param device The device to configure
+ * @return The angle in degrees clockwise
+ *
+ * @see libinput_device_config_rotation_is_available
+ * @see libinput_device_config_rotation_set_angle
+ * @see libinput_device_config_rotation_get_default_angle
+ */
+unsigned int
+libinput_device_config_rotation_get_angle(struct libinput_device *device);
+
+/**
+ * @ingroup config
+ *
+ * Get the default rotation of a device in degrees clockwise off the logical
+ * neutral position. If this device does not support rotation, the return
+ * value is always 0.
+ *
+ * @param device The device to configure
+ * @return The default angle in degrees clockwise
+ *
+ * @see libinput_device_config_rotation_is_available
+ * @see libinput_device_config_rotation_set_angle
+ * @see libinput_device_config_rotation_get_angle
+ */
+unsigned int
+libinput_device_config_rotation_get_default_angle(struct libinput_device *device);
+
 #ifdef __cplusplus
 }
 #endif
index d17fb14aabe677ca8c9d171760cb777fb8358bbe..97bb57f729870a573754ac6cb0af6a2b4cde128b 100644 (file)
@@ -1,41 +1,45 @@
 /* in alphabetical order! */
 
-LIBINPUT_0.8.0 {
+LIBINPUT_0.12.0 {
 global:
        libinput_config_status_to_str;
        libinput_device_config_accel_get_default_speed;
        libinput_device_config_accel_get_speed;
        libinput_device_config_accel_is_available;
        libinput_device_config_accel_set_speed;
-       libinput_device_config_left_handed_get_default;
-       libinput_device_config_left_handed_get;
-       libinput_device_config_left_handed_is_available;
-       libinput_device_config_left_handed_set;
        libinput_device_config_calibration_get_default_matrix;
        libinput_device_config_calibration_get_matrix;
        libinput_device_config_calibration_has_matrix;
        libinput_device_config_calibration_set_matrix;
+       libinput_device_config_click_get_default_method;
+       libinput_device_config_click_get_method;
+       libinput_device_config_click_get_methods;
+       libinput_device_config_click_set_method;
+       libinput_device_config_left_handed_get;
+       libinput_device_config_left_handed_get_default;
+       libinput_device_config_left_handed_is_available;
+       libinput_device_config_left_handed_set;
        libinput_device_config_scroll_get_button;
        libinput_device_config_scroll_get_default_button;
        libinput_device_config_scroll_get_default_method;
        libinput_device_config_scroll_get_default_natural_scroll_enabled;
-       libinput_device_config_scroll_get_methods;
        libinput_device_config_scroll_get_method;
+       libinput_device_config_scroll_get_methods;
        libinput_device_config_scroll_get_natural_scroll_enabled;
        libinput_device_config_scroll_has_natural_scroll;
        libinput_device_config_scroll_set_button;
        libinput_device_config_scroll_set_method;
        libinput_device_config_scroll_set_natural_scroll_enabled;
-       libinput_device_config_scroll_get_wheel_click_angle;
        libinput_device_config_send_events_get_default_mode;
-       libinput_device_config_send_events_get_modes;
        libinput_device_config_send_events_get_mode;
+       libinput_device_config_send_events_get_modes;
        libinput_device_config_send_events_set_mode;
        libinput_device_config_tap_get_default_enabled;
        libinput_device_config_tap_get_enabled;
        libinput_device_config_tap_get_finger_count;
        libinput_device_config_tap_set_enabled;
        libinput_device_get_context;
+       libinput_device_get_device_group;
        libinput_device_get_id_product;
        libinput_device_get_id_vendor;
        libinput_device_get_name;
@@ -45,9 +49,13 @@ global:
        libinput_device_get_sysname;
        libinput_device_get_udev_device;
        libinput_device_get_user_data;
-       libinput_device_has_button;
+       libinput_device_group_get_user_data;
+       libinput_device_group_ref;
+       libinput_device_group_set_user_data;
+       libinput_device_group_unref;
        libinput_device_has_capability;
        libinput_device_led_update;
+       libinput_device_pointer_has_button;
        libinput_device_ref;
        libinput_device_set_seat_logical_name;
        libinput_device_set_user_data;
@@ -56,15 +64,15 @@ global:
        libinput_event_destroy;
        libinput_event_device_notify_get_base_event;
        libinput_event_get_context;
-       libinput_event_get_device_notify_event;
        libinput_event_get_device;
+       libinput_event_get_device_notify_event;
        libinput_event_get_keyboard_event;
        libinput_event_get_pointer_event;
        libinput_event_get_touch_event;
        libinput_event_get_type;
        libinput_event_keyboard_get_base_event;
-       libinput_event_keyboard_get_key_state;
        libinput_event_keyboard_get_key;
+       libinput_event_keyboard_get_key_state;
        libinput_event_keyboard_get_seat_key_count;
        libinput_event_keyboard_get_time;
        libinput_event_pointer_get_absolute_x;
@@ -75,8 +83,8 @@ global:
        libinput_event_pointer_get_axis_value;
        libinput_event_pointer_get_axis_value_discrete;
        libinput_event_pointer_get_base_event;
-       libinput_event_pointer_get_button_state;
        libinput_event_pointer_get_button;
+       libinput_event_pointer_get_button_state;
        libinput_event_pointer_get_dx;
        libinput_event_pointer_get_dx_unaccelerated;
        libinput_event_pointer_get_dy;
@@ -116,37 +124,159 @@ global:
        libinput_udev_assign_seat;
        libinput_udev_create_context;
        libinput_unref;
+
 local:
        *;
 };
 
-LIBINPUT_0.9.0 {
+LIBINPUT_0.14.0 {
 global:
-       libinput_device_config_click_get_default_method;
-       libinput_device_config_click_get_method;
-       libinput_device_config_click_get_methods;
-       libinput_device_config_click_set_method;
-} LIBINPUT_0.8.0;
+       libinput_device_config_middle_emulation_get_default_enabled;
+       libinput_device_config_middle_emulation_get_enabled;
+       libinput_device_config_middle_emulation_is_available;
+       libinput_device_config_middle_emulation_set_enabled;
+} LIBINPUT_0.12.0;
 
-LIBINPUT_0.11.0 {
-       libinput_device_get_device_group;
-       libinput_device_group_get_user_data;
-       libinput_device_group_ref;
-       libinput_device_group_set_user_data;
-       libinput_device_group_unref;
+LIBINPUT_0.15.0 {
+global:
+       libinput_device_keyboard_has_key;
+} LIBINPUT_0.14.0;
 
-       libinput_device_pointer_has_button;
-} LIBINPUT_0.9.0;
+LIBINPUT_0.19.0 {
+global:
+       libinput_device_config_tap_set_drag_lock_enabled;
+       libinput_device_config_tap_get_drag_lock_enabled;
+       libinput_device_config_tap_get_default_drag_lock_enabled;
+} LIBINPUT_0.15.0;
+
+LIBINPUT_0.20.0 {
+       libinput_event_gesture_get_angle_delta;
+       libinput_event_gesture_get_base_event;
+       libinput_event_gesture_get_cancelled;
+       libinput_event_gesture_get_dx;
+       libinput_event_gesture_get_dx_unaccelerated;
+       libinput_event_gesture_get_dy;
+       libinput_event_gesture_get_dy_unaccelerated;
+       libinput_event_gesture_get_finger_count;
+       libinput_event_gesture_get_scale;
+       libinput_event_gesture_get_time;
+       libinput_event_get_gesture_event;
+} LIBINPUT_0.19.0;
+
+LIBINPUT_0.21.0 {
+       libinput_device_config_dwt_is_available;
+       libinput_device_config_dwt_set_enabled;
+       libinput_device_config_dwt_get_enabled;
+       libinput_device_config_dwt_get_default_enabled;
+       libinput_event_gesture_get_time_usec;
+       libinput_event_keyboard_get_time_usec;
+       libinput_event_pointer_get_time_usec;
+       libinput_event_touch_get_time_usec;
+} LIBINPUT_0.20.0;
+
+LIBINPUT_1.1 {
+       libinput_device_config_accel_get_profile;
+       libinput_device_config_accel_get_profiles;
+       libinput_device_config_accel_get_default_profile;
+       libinput_device_config_accel_set_profile;
+} LIBINPUT_0.21.0;
+
+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;
+
+LIBINPUT_1.3 {
+       libinput_device_tablet_pad_get_num_buttons;
+       libinput_device_tablet_pad_get_num_rings;
+       libinput_device_tablet_pad_get_num_strips;
+       libinput_event_get_tablet_pad_event;
+       libinput_event_tablet_pad_get_base_event;
+       libinput_event_tablet_pad_get_button_number;
+       libinput_event_tablet_pad_get_button_state;
+       libinput_event_tablet_pad_get_ring_position;
+       libinput_event_tablet_pad_get_ring_number;
+       libinput_event_tablet_pad_get_ring_source;
+       libinput_event_tablet_pad_get_strip_position;
+       libinput_event_tablet_pad_get_strip_number;
+       libinput_event_tablet_pad_get_strip_source;
+       libinput_event_tablet_pad_get_time;
+       libinput_event_tablet_pad_get_time_usec;
+} LIBINPUT_1.2;
+
+LIBINPUT_1.4 {
+       libinput_device_config_rotation_get_angle;
+       libinput_device_config_rotation_get_default_angle;
+       libinput_device_config_rotation_is_available;
+       libinput_device_config_rotation_set_angle;
+       libinput_device_tablet_pad_get_mode_group;
+       libinput_device_tablet_pad_get_num_mode_groups;
+       libinput_event_tablet_pad_get_mode;
+       libinput_event_tablet_pad_get_mode_group;
+       libinput_tablet_pad_mode_group_button_is_toggle;
+       libinput_tablet_pad_mode_group_get_index;
+       libinput_tablet_pad_mode_group_get_mode;
+       libinput_tablet_pad_mode_group_get_num_modes;
+       libinput_tablet_pad_mode_group_get_user_data;
+       libinput_tablet_pad_mode_group_has_button;
+       libinput_tablet_pad_mode_group_has_strip;
+       libinput_tablet_pad_mode_group_has_ring;
+       libinput_tablet_pad_mode_group_ref;
+       libinput_tablet_pad_mode_group_set_user_data;
+       libinput_tablet_pad_mode_group_unref;
+} LIBINPUT_1.3;
 
-LIBINPUT_1.1_unreleased {
-       libinput_event_touch_get_major;
-       libinput_event_touch_get_major_transformed;
-       libinput_event_touch_get_minor;
-       libinput_event_touch_get_minor_transformed;
-       libinput_event_touch_get_orientation;
-       libinput_event_touch_get_pressure;
-       libinput_event_touch_has_major;
-       libinput_event_touch_has_minor;
-       libinput_event_touch_has_orientation;
-       libinput_event_touch_has_pressure;
-} LIBINPUT_0.9.0;
+LIBINPUT_1.5 {
+       libinput_device_config_tap_get_button_map;
+       libinput_device_config_tap_get_default_button_map;
+       libinput_device_config_tap_set_button_map;
+} LIBINPUT_1.4;
index ad0dcfcb927b98e233d1b1a325930de8f2036458..b14d8b67f9a8495e3851f4510f5c41fc8f02fe3e 100644 (file)
@@ -1,23 +1,24 @@
 /*
- * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
@@ -25,6 +26,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <libudev.h>
 
 #include "path.h"
@@ -32,7 +34,6 @@
 
 static const char default_seat[] = "seat0";
 static const char default_seat_name[] = "default";
-static struct list devices_list;
 
 int path_input_process_event(struct libinput_event);
 static void path_seat_destroy(struct libinput_seat *seat);
@@ -102,8 +103,8 @@ path_seat_get_named(struct path_input *input,
        struct path_seat *seat;
 
        list_for_each(seat, &input->base.seat_list, base.link) {
-               if (strcmp(seat->base.physical_name, seat_name_physical) == 0 &&
-                   strcmp(seat->base.logical_name, seat_name_logical) == 0)
+               if (streq(seat->base.physical_name, seat_name_physical) &&
+                   streq(seat->base.logical_name, seat_name_logical))
                        return seat;
        }
 
@@ -198,7 +199,6 @@ path_input_destroy(struct libinput *input)
 {
        struct path_input *path_input = (struct path_input*)input;
        struct path_device *dev, *tmp;
-       struct device_node *dev_node, *tmp_node;
 
        udev_unref(path_input->udev);
 
@@ -207,10 +207,6 @@ path_input_destroy(struct libinput *input)
                free(dev);
        }
 
-       list_for_each_safe(dev_node, tmp_node, &devices_list, link) {
-               free(dev_node->devname);
-               free(dev_node);
-       }
 }
 
 static struct libinput_device *
@@ -221,8 +217,6 @@ path_create_device(struct libinput *libinput,
        struct path_input *input = (struct path_input*)libinput;
        struct path_device *dev;
        struct libinput_device *device;
-       struct device_node *dev_node;
-       const char *name;
 
        dev = zalloc(sizeof *dev);
        if (!dev)
@@ -232,18 +226,6 @@ path_create_device(struct libinput *libinput,
 
        list_insert(&input->path_list, &dev->link);
 
-       name = udev_device_get_devnode(udev_device);
-       if (name)
-       {
-               dev_node = zalloc(sizeof *dev_node);
-               if (dev_node)
-               {
-                       dev_node->devname = strdup(name);
-                       if (dev_node->devname)
-                               list_insert(&devices_list, &dev_node->link);
-               }
-       }
-
        device = path_device_enable(input, udev_device, seat_name);
 
        if (!device) {
@@ -307,8 +289,6 @@ libinput_path_create_context(const struct libinput_interface *interface,
        input->udev = udev;
        list_init(&input->path_list);
 
-       list_init(&devices_list);
-
        return &input->base;
 }
 
@@ -332,7 +312,7 @@ udev_device_from_devnode(struct libinput *libinput,
                dev = udev_device_new_from_devnum(udev, 'c', st.st_rdev);
 
                count++;
-               if (count > 10) {
+               if (count > 200) {
                        log_bug_libinput(libinput,
                                        "udev device never initialized (%s)\n",
                                        devnode);
@@ -363,6 +343,11 @@ libinput_path_add_device(struct libinput *libinput,
                return NULL;
        }
 
+       if (ignore_litest_test_suite_device(udev_device)) {
+               udev_device_unref(udev_device);
+               return NULL;
+       }
+
        device = path_create_device(libinput, udev_device, NULL);
        udev_device_unref(udev_device);
        return device;
@@ -376,7 +361,6 @@ libinput_path_remove_device(struct libinput_device *device)
        struct libinput_seat *seat;
        struct evdev_device *evdev = (struct evdev_device*)device;
        struct path_device *dev;
-       struct device_node *dev_node;
 
        if (libinput->interface_backend != &interface_backend) {
                log_bug_client(libinput, "Mismatching backends.\n");
@@ -392,23 +376,8 @@ libinput_path_remove_device(struct libinput_device *device)
                }
        }
 
-       list_for_each(dev_node, &devices_list, link) {
-               if (strcmp(dev_node->devname, udev_device_get_devnode(evdev->udev_device)) == 0) {
-                       list_remove(&dev_node->link);
-                       free(dev_node->devname);
-                       free(dev_node);
-                       break;
-               }
-       }
-
        seat = device->seat;
        libinput_seat_ref(seat);
        path_disable_device(libinput, evdev);
        libinput_seat_unref(seat);
 }
-
-struct list *
-libinput_path_get_devices(void)
-{
-       return &devices_list;
-}
index 973146ab23f72d5bfcb029cac81f3b11cc627819..c9f677b7bfb7d96ba399db8c8e84e89b6d37d2b3 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef _PATH_H_
index f6c8e427679be2ef347e8abda1837dc7cb2ff45b..c9247942c6dfc042ec194609080423231731f5d2 100644 (file)
@@ -1,25 +1,28 @@
 /*
- * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
+#include "config.h"
+
 #include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
@@ -54,8 +57,8 @@ libinput_timer_arm_timer_fd(struct libinput *libinput)
        }
 
        if (earliest_expire != UINT64_MAX) {
-               its.it_value.tv_sec = earliest_expire / 1000;
-               its.it_value.tv_nsec = (earliest_expire % 1000) * 1000 * 1000;
+               its.it_value.tv_sec = earliest_expire / ms2us(1000);
+               its.it_value.tv_nsec = (earliest_expire % ms2us(1000)) * 1000;
        }
 
        r = timerfd_settime(libinput->timer.fd, TFD_TIMER_ABSTIME, &its, NULL);
@@ -68,7 +71,11 @@ libinput_timer_set(struct libinput_timer *timer, uint64_t expire)
 {
 #ifndef NDEBUG
        uint64_t now = libinput_now(timer->libinput);
-       if (abs(expire - now) > 5000)
+       if (expire < now)
+               log_bug_libinput(timer->libinput,
+                                "timer offset negative (-%" PRIu64 ")\n",
+                                now - expire);
+       else if ((expire - now) > ms2us(5000))
                log_bug_libinput(timer->libinput,
                                 "timer offset more than 5s, now %"
                                 PRIu64 " expire %" PRIu64 "\n",
@@ -101,6 +108,15 @@ libinput_timer_handler(void *data)
        struct libinput *libinput = data;
        struct libinput_timer *timer, *tmp;
        uint64_t now;
+       uint64_t discard;
+       int r;
+
+       r = read(libinput->timer.fd, &discard, sizeof(discard));
+       if (r == -1 && errno != EAGAIN)
+               log_bug_libinput(libinput,
+                                "Error %d reading from timerfd (%s)",
+                                errno,
+                                strerror(errno));
 
        now = libinput_now(libinput);
        if (now == 0)
@@ -119,7 +135,8 @@ libinput_timer_handler(void *data)
 int
 libinput_timer_subsys_init(struct libinput *libinput)
 {
-       libinput->timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+       libinput->timer.fd = timerfd_create(CLOCK_MONOTONIC,
+                                           TFD_CLOEXEC | TFD_NONBLOCK);
        if (libinput->timer.fd < 0)
                return -1;
 
index 5e3b89b0e2d8b4333bb3177104710e790bf4d31b..f8315cfe7b7a6fdbbdf38efa0f523df9d0808cd7 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef TIMER_H
@@ -32,7 +33,7 @@ struct libinput;
 struct libinput_timer {
        struct libinput *libinput;
        struct list link;
-       uint64_t expire; /* in absolute ms CLOCK_MONOTONIC */
+       uint64_t expire; /* in absolute us CLOCK_MONOTONIC */
        void (*timer_func)(uint64_t now, void *timer_func_data);
        void *timer_func_data;
 };
@@ -42,7 +43,7 @@ libinput_timer_init(struct libinput_timer *timer, struct libinput *libinput,
                    void (*timer_func)(uint64_t now, void *timer_func_data),
                    void *timer_func_data);
 
-/* Set timer expire time, in absolute ms CLOCK_MONOTONIC */
+/* Set timer expire time, in absolute us CLOCK_MONOTONIC */
 void
 libinput_timer_set(struct libinput_timer *timer, uint64_t expire);
 
old mode 100755 (executable)
new mode 100644 (file)
index f5d0de4..6bf85de
@@ -1,23 +1,25 @@
 /*
  * Copyright © 2013 Intel Corporation
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
- * Permission to use, copy, modify, distribute, and sell this software and
- * its documentation for any purpose is hereby granted without fee, provided
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
@@ -41,26 +43,6 @@ udev_seat_create(struct udev_input *input,
 static struct udev_seat *
 udev_seat_get_named(struct udev_input *input, const char *seat_name);
 
-static bool
-libinput_path_has_device(struct libinput *libinput, const char *devnode)
-{
-       struct device_node *dev;
-       struct list *dev_list;
-
-       if (!devnode) return false;
-       dev_list = libinput_path_get_devices();
-       if (dev_list->prev == NULL && dev_list->next == NULL) return false;
-
-       list_for_each(dev, dev_list, link) {
-               const char *name = dev->devname;
-               if (!name) break;
-               if (strcmp(name, devnode) == 0)
-                       return true;
-       }
-
-       return false;
-}
-
 static int
 device_added(struct udev_device *udev_device,
             struct udev_input *input,
@@ -77,7 +59,10 @@ device_added(struct udev_device *udev_device,
        if (!device_seat)
                device_seat = default_seat;
 
-       if (strcmp(device_seat, input->seat_id))
+       if (!streq(device_seat, input->seat_id))
+               return 0;
+
+       if (ignore_litest_test_suite_device(udev_device))
                return 0;
 
        devnode = udev_device_get_devnode(udev_device);
@@ -98,11 +83,6 @@ device_added(struct udev_device *udev_device,
                        return -1;
        }
 
-       if (libinput_path_has_device(&input->base, devnode))
-       {
-               log_info(&input->base, "libinput_path already created input device '%s.\n", devnode);
-               return 0;
-       }
        device = evdev_device_create(&seat->base, udev_device);
        libinput_seat_unref(&seat->base);
 
@@ -156,8 +136,8 @@ device_removed(struct udev_device *udev_device, struct udev_input *input)
        list_for_each(seat, &input->base.seat_list, base.link) {
                list_for_each_safe(device, next,
                                   &seat->base.devices_list, base.link) {
-                       if (!strcmp(syspath,
-                                   udev_device_get_syspath(device->udev_device))) {
+                       if (streq(syspath,
+                                 udev_device_get_syspath(device->udev_device))) {
                                log_info(&input->base,
                                         "input device %s, %s removed\n",
                                         device->devname,
@@ -223,9 +203,9 @@ evdev_udev_handler(void *data)
        if (strncmp("event", udev_device_get_sysname(udev_device), 5) != 0)
                goto out;
 
-       if (!strcmp(action, "add"))
+       if (streq(action, "add"))
                device_added(udev_device, input, NULL);
-       else if (!strcmp(action, "remove"))
+       else if (streq(action, "remove"))
                device_removed(udev_device, input);
 
 out:
@@ -270,34 +250,17 @@ udev_input_enable(struct libinput *libinput)
        struct udev_input *input = (struct udev_input*)libinput;
        struct udev *udev = input->udev;
        int fd;
-       unsigned int buf_size = 0;
 
        if (input->udev_monitor)
                return 0;
 
-       char *env;
-
-       if ((env = getenv("UDEV_MONITOR_EVENT_SOURCE")))
-       {
-               log_info(libinput, "udev: event source is %s.\n", env);
-               input->udev_monitor = udev_monitor_new_from_netlink(udev, env);
-       }
-       else
-               input->udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
-
+       input->udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
        if (!input->udev_monitor) {
                log_info(libinput,
                         "udev: failed to create the udev monitor\n");
                return -1;
        }
 
-       env = getenv("UDEV_MONITOR_BUFFER_SIZE");
-       if (env && (buf_size = atoi(env)))
-       {
-               log_info(libinput,"udev: set receive buffer size = %d\n", buf_size);
-               udev_monitor_set_receive_buffer_size(input->udev_monitor, buf_size);
-       }
-
        udev_monitor_filter_add_match_subsystem_devtype(input->udev_monitor,
                        "input", NULL);
 
@@ -370,7 +333,7 @@ udev_seat_get_named(struct udev_input *input, const char *seat_name)
        struct udev_seat *seat;
 
        list_for_each(seat, &input->base.seat_list, base.link) {
-               if (strcmp(seat->base.logical_name, seat_name) == 0)
+               if (streq(seat->base.logical_name, seat_name))
                        return seat;
        }
 
index 372cde16d3d0267309ab299e0e144b29f6e49850..ee54b422f48f9e76c29da8bc21ff7f32a8d78c31 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 Intel Corporation
  *
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef _UDEV_SEAT_H_
index 5743ca4f021f8281e42a224664f3386baff9d36b..7ff12e437cb97a0b95a7be3f7180752e6cae7b3e 100644 (file)
@@ -13,36 +13,74 @@ noinst_LTLIBRARIES = liblitest.la
 liblitest_la_SOURCES = \
        litest.h \
        litest-int.h \
-       litest-alps-semi-mt.c \
-       litest-bcm5974.c \
-       litest-generic-singletouch.c \
-       litest-keyboard.c \
-       litest-mouse.c \
-       litest-ms-surface-cover.c \
-       litest-qemu-usb-tablet.c \
-       litest-synaptics.c \
-       litest-synaptics-hover.c \
-       litest-synaptics-st.c \
-       litest-synaptics-t440.c \
-       litest-synaptics-x1-carbon-3rd.c \
-       litest-trackpoint.c \
-       litest-wacom-touch.c \
-       litest-xen-virtual-pointer.c \
-       litest-vmware-virtual-usb-mouse.c \
+       litest-device-alps-semi-mt.c \
+       litest-device-alps-dualpoint.c \
+       litest-device-anker-mouse-kbd.c \
+       litest-device-apple-internal-keyboard.c \
+       litest-device-apple-magicmouse.c \
+       litest-device-asus-rog-gladius.c \
+       litest-device-atmel-hover.c \
+       litest-device-bcm5974.c \
+       litest-device-cyborg-rat-5.c \
+       litest-device-elantech-touchpad.c \
+       litest-device-generic-singletouch.c \
+       litest-device-huion-pentablet.c \
+       litest-device-keyboard.c \
+       litest-device-keyboard-all-codes.c \
+       litest-device-keyboard-razer-blackwidow.c \
+       litest-device-logitech-trackball.c \
+       litest-device-nexus4-touch-screen.c \
+       litest-device-magic-trackpad.c \
+       litest-device-mouse.c \
+       litest-device-mouse-roccat.c \
+       litest-device-mouse-low-dpi.c \
+       litest-device-mouse-wheel-click-angle.c \
+       litest-device-ms-surface-cover.c \
+       litest-device-protocol-a-touch-screen.c \
+       litest-device-qemu-usb-tablet.c \
+       litest-device-synaptics.c \
+       litest-device-synaptics-hover.c \
+       litest-device-synaptics-i2c.c \
+       litest-device-synaptics-st.c \
+       litest-device-synaptics-t440.c \
+       litest-device-synaptics-x1-carbon-3rd.c \
+       litest-device-trackpoint.c \
+       litest-device-touch-screen.c \
+       litest-device-touchscreen-fuzz.c \
+       litest-device-wacom-bamboo-tablet.c \
+       litest-device-wacom-cintiq-tablet.c \
+       litest-device-wacom-cintiq-13hdt-finger.c \
+       litest-device-wacom-cintiq-13hdt-pad.c \
+       litest-device-wacom-cintiq-13hdt-pen.c \
+       litest-device-wacom-cintiq-24hd.c \
+       litest-device-wacom-cintiq-24hdt-pad.c \
+       litest-device-wacom-ekr.c \
+       litest-device-wacom-hid4800-pen.c \
+       litest-device-wacom-intuos-tablet.c \
+       litest-device-wacom-intuos3-pad.c \
+       litest-device-wacom-intuos5-pad.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 \
+       litest-device-yubikey.c \
        litest.c
 liblitest_la_LIBADD = $(top_builddir)/src/libinput-util.la
+liblitest_la_CFLAGS = $(AM_CFLAGS) \
+             -DLIBINPUT_MODEL_QUIRKS_UDEV_RULES_FILE="\"$(abs_top_builddir)/udev/90-libinput-model-quirks-litest.rules\"" \
+             -DLIBINPUT_MODEL_QUIRKS_UDEV_HWDB_FILE="\"$(abs_top_srcdir)/udev/90-libinput-model-quirks.hwdb\"" \
+             -DLIBINPUT_TEST_DEVICE_RULES_FILE="\"$(abs_top_srcdir)/udev/80-libinput-test-device.rules\""
+if HAVE_LIBUNWIND
+liblitest_la_LIBADD += $(LIBUNWIND_LIBS) -ldl
+liblitest_la_CFLAGS += $(LIBUNWIND_CFLAGS)
+endif
+
+run_tests = libinput-test-suite-runner \
+           test-litest-selftest
 
-run_tests = \
-       test-udev \
-       test-path \
-       test-pointer \
-       test-touch \
-       test-log \
-       test-touchpad \
-       test-trackpoint \
-       test-misc \
-       test-keyboard \
-       test-device
 build_tests = \
        test-build-cxx \
        test-build-linker \
@@ -53,47 +91,35 @@ noinst_PROGRAMS = $(build_tests) $(run_tests)
 noinst_SCRIPTS = symbols-leak-test
 TESTS = $(run_tests) symbols-leak-test
 
-.NOTPARALLEL:
-
-test_udev_SOURCES = udev.c
-test_udev_LDADD = $(TEST_LIBS)
-test_udev_LDFLAGS = -no-install
-
-test_path_SOURCES = path.c
-test_path_LDADD = $(TEST_LIBS)
-test_path_LDFLAGS = -no-install
-
-test_pointer_SOURCES = pointer.c
-test_pointer_LDADD = $(TEST_LIBS)
-test_pointer_LDFLAGS = -no-install
-
-test_touch_SOURCES = touch.c
-test_touch_LDADD = $(TEST_LIBS)
-test_touch_LDFLAGS = -no-install
-
-test_log_SOURCES = log.c
-test_log_LDADD = $(TEST_LIBS)
-test_log_LDFLAGS = -no-install
-
-test_touchpad_SOURCES = touchpad.c
-test_touchpad_LDADD = $(TEST_LIBS)
-test_touchpad_LDFLAGS = -no-install
-
-test_trackpoint_SOURCES = trackpoint.c
-test_trackpoint_LDADD = $(TEST_LIBS)
-test_trackpoint_LDFLAGS = -no-install
-
-test_misc_SOURCES = misc.c
-test_misc_LDADD = $(TEST_LIBS)
-test_misc_LDFLAGS = -no-install
-
-test_keyboard_SOURCES = keyboard.c
-test_keyboard_LDADD = $(TEST_LIBS)
-test_keyboard_LDFLAGS = -no-install
-
-test_device_SOURCES = device.c
-test_device_LDADD = $(TEST_LIBS)
-test_device_LDFLAGS = -no-install
+libinput_test_suite_runner_SOURCES = udev.c \
+                                    path.c \
+                                    pointer.c \
+                                    touch.c \
+                                    log.c \
+                                    tablet.c \
+                                    pad.c \
+                                    touchpad.c \
+                                    touchpad-tap.c \
+                                    touchpad-buttons.c \
+                                    trackpoint.c \
+                                    trackball.c \
+                                    misc.c \
+                                    keyboard.c \
+                                    device.c \
+                                    gestures.c
+
+libinput_test_suite_runner_CFLAGS = $(AM_CFLAGS) -DLIBINPUT_LT_VERSION="\"$(LIBINPUT_LT_VERSION)\""
+libinput_test_suite_runner_LDADD = $(TEST_LIBS)
+libinput_test_suite_runner_LDFLAGS = -no-install
+
+test_litest_selftest_SOURCES = litest-selftest.c litest.c litest-int.h litest.h
+test_litest_selftest_CFLAGS = -DLITEST_DISABLE_BACKTRACE_LOGGING -DLITEST_NO_MAIN $(liblitest_la_CFLAGS)
+test_litest_selftest_LDADD = $(TEST_LIBS)
+test_litest_selftest_LDFLAGS = -no-install
+if HAVE_LIBUNWIND
+test_litest_selftest_LDADD += $(LIBUNWIND_LIBS) -ldl
+test_litest_selftest_CFLAGS += $(LIBUNWIND_CFLAGS)
+endif
 
 # build-test only
 test_build_pedantic_c99_SOURCES = build-pedantic.c
@@ -111,17 +137,21 @@ test_build_linker_LDADD = $(top_builddir)/src/libinput.la $(top_builddir)/src/li
 test_build_cxx_SOURCES = build-cxx.cc
 test_build_cxx_CXXFLAGS = -Wall -Wextra -Wno-unused-parameter $(AM_CXXFLAGS)
 
+AM_TESTS_ENVIRONMENT= LITEST_VERBOSE=1; export LITEST_VERBOSE;
+
 if HAVE_VALGRIND
 VALGRIND_FLAGS=--leak-check=full \
               --quiet \
               --error-exitcode=3 \
               --suppressions=$(srcdir)/valgrind.suppressions
 
-valgrind:
-       $(MAKE) check-TESTS LOG_COMPILER="$(VALGRIND)" LOG_FLAGS="$(VALGRIND_FLAGS)" CK_FORK=no
+valgrind: check-am
+       $(MAKE) check-TESTS TEST_SUITE_LOG="test-suite-valgrind.log" LOG_COMPILER="$(VALGRIND)" LOG_FLAGS="$(VALGRIND_FLAGS)" CK_FORK=no USING_VALGRIND=yes
 
 check: valgrind
 
+DISTCLEANFILES=test-suite-valgrind.log
+
 endif
 endif
 EXTRA_DIST=valgrind.suppressions
index 920fc4abac8a763207eeb52c699a2ede94b73921..f602127de785a73291257e61a6bfb374c27c55ee 100644 (file)
@@ -3,6 +3,7 @@
 /* This is a build-test only */
 
 int
-main(void) {
+main(void)
+{
        return 0;
 }
index 0839bfaed168d83227d22ac9eba6020019174c16..4ed12d959e80c56169db131d48fd2df033adf113 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -64,14 +65,20 @@ START_TEST(device_sendevents_config_touchpad)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput_device *device;
-       uint32_t modes;
+       uint32_t modes, expected;
+
+       expected = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
+
+       /* The wacom devices in the test suite are external */
+       if (libevdev_get_id_vendor(dev->evdev) != VENDOR_ID_WACOM &&
+           libevdev_get_id_bustype(dev->evdev) != BUS_BLUETOOTH)
+               expected |=
+                       LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
 
        device = dev->libinput_device;
 
        modes = libinput_device_config_send_events_get_modes(device);
-       ck_assert_int_eq(modes,
-                        LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE|
-                        LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       ck_assert_int_eq(modes, expected);
 }
 END_TEST
 
@@ -82,6 +89,11 @@ START_TEST(device_sendevents_config_touchpad_superset)
        enum libinput_config_status status;
        uint32_t modes;
 
+       /* The wacom devices in the test suite are external */
+       if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_WACOM ||
+           libevdev_get_id_bustype(dev->evdev) == BUS_BLUETOOTH)
+               return;
+
        device = dev->libinput_device;
 
        modes = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED |
@@ -199,6 +211,85 @@ START_TEST(device_disable_touchpad)
 }
 END_TEST
 
+START_TEST(device_disable_touch)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_device *device;
+       enum libinput_config_status status;
+
+       device = dev->libinput_device;
+
+       litest_drain_events(li);
+
+       status = libinput_device_config_send_events_set_mode(device,
+                       LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       /* no event from disabling */
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+
+       /* no event from resuming */
+       status = libinput_device_config_send_events_set_mode(device,
+                       LIBINPUT_CONFIG_SEND_EVENTS_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(device_disable_touch_during_touch)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_device *device;
+       enum libinput_config_status status;
+       struct libinput_event *event;
+
+       device = dev->libinput_device;
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10, 0);
+       litest_drain_events(li);
+
+       status = libinput_device_config_send_events_set_mode(device,
+                       LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       /* after disabling sendevents we require a touch up */
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_UP);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_FRAME);
+       libinput_event_destroy(event);
+
+       litest_assert_empty_queue(li);
+
+       litest_touch_move_to(dev, 0, 90, 90, 50, 50, 10, 0);
+       litest_touch_up(dev, 0);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+
+       /* no event from resuming */
+       status = libinput_device_config_send_events_set_mode(device,
+                       LIBINPUT_CONFIG_SEND_EVENTS_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
 START_TEST(device_disable_events_pending)
 {
        struct litest_device *dev = litest_current_device();
@@ -284,7 +375,7 @@ START_TEST(device_reenable_syspath_changed)
 {
        struct libinput *li;
        struct litest_device *litest_device;
-       struct libinput_device *device1, *device2;
+       struct libinput_device *device1;
        enum libinput_config_status status;
        struct libinput_event *event;
 
@@ -303,9 +394,6 @@ START_TEST(device_reenable_syspath_changed)
        litest_drain_events(li);
 
        litest_device = litest_add_device(li, LITEST_MOUSE);
-       device2 = litest_device->libinput_device;
-       ck_assert_str_eq(libinput_device_get_sysname(device1),
-                        libinput_device_get_sysname(device2));
 
        status = libinput_device_config_send_events_set_mode(device1,
                        LIBINPUT_CONFIG_SEND_EVENTS_ENABLED);
@@ -659,6 +747,22 @@ START_TEST(device_context)
 }
 END_TEST
 
+static int open_restricted(const char *path, int flags, void *data)
+{
+       int fd;
+       fd = open(path, flags);
+       return fd < 0 ? -errno : fd;
+}
+static void close_restricted(int fd, void *data)
+{
+       close(fd);
+}
+
+const struct libinput_interface simple_interface = {
+       .open_restricted = open_restricted,
+       .close_restricted = close_restricted,
+};
+
 START_TEST(device_group_get)
 {
        struct litest_device *dev = litest_current_device();
@@ -707,18 +811,634 @@ START_TEST(device_group_ref)
 }
 END_TEST
 
-int main (int argc, char **argv)
+START_TEST(device_group_leak)
 {
-       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);
+       struct libinput *li;
+       struct libinput_device *device;
+       struct libevdev_uinput *uinput;
+       struct libinput_device_group *group;
+
+       uinput = litest_create_uinput_device("test device", NULL,
+                                            EV_KEY, BTN_LEFT,
+                                            EV_KEY, BTN_RIGHT,
+                                            EV_REL, REL_X,
+                                            EV_REL, REL_Y,
+                                            -1);
+
+       li = libinput_path_create_context(&simple_interface, NULL);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+
+       group = libinput_device_get_device_group(device);
+       libinput_device_group_ref(group);
+
+       libinput_path_remove_device(device);
+
+       libevdev_uinput_destroy(uinput);
+       libinput_unref(li);
+
+       /* the device group leaks, check valgrind */
+}
+END_TEST
+
+START_TEST(abs_device_no_absx)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+
+       uinput = litest_create_uinput_device("test device", NULL,
+                                            EV_KEY, BTN_LEFT,
+                                            EV_KEY, BTN_RIGHT,
+                                            EV_ABS, ABS_Y,
+                                            -1);
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       litest_restore_log_handler(li);
+       ck_assert(device == NULL);
+       libinput_unref(li);
+
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(abs_device_no_absy)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+
+       uinput = litest_create_uinput_device("test device", NULL,
+                                            EV_KEY, BTN_LEFT,
+                                            EV_KEY, BTN_RIGHT,
+                                            EV_ABS, ABS_X,
+                                            -1);
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       litest_restore_log_handler(li);
+       ck_assert(device == NULL);
+       libinput_unref(li);
+
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(abs_mt_device_no_absy)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+
+       uinput = litest_create_uinput_device("test device", NULL,
+                                            EV_KEY, BTN_LEFT,
+                                            EV_KEY, BTN_RIGHT,
+                                            EV_ABS, ABS_X,
+                                            EV_ABS, ABS_Y,
+                                            EV_ABS, ABS_MT_SLOT,
+                                            EV_ABS, ABS_MT_POSITION_X,
+                                            -1);
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       litest_restore_log_handler(li);
+       ck_assert(device == NULL);
+       libinput_unref(li);
+
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(abs_mt_device_no_absx)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+
+       uinput = litest_create_uinput_device("test device", NULL,
+                                            EV_KEY, BTN_LEFT,
+                                            EV_KEY, BTN_RIGHT,
+                                            EV_ABS, ABS_X,
+                                            EV_ABS, ABS_Y,
+                                            EV_ABS, ABS_MT_SLOT,
+                                            EV_ABS, ABS_MT_POSITION_Y,
+                                            -1);
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       litest_restore_log_handler(li);
+       ck_assert(device == NULL);
+       libinput_unref(li);
+
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+static void
+assert_device_ignored(struct libinput *li, struct input_absinfo *absinfo)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput_device *device;
+
+       uinput = litest_create_uinput_abs_device("test device", NULL,
+                                                absinfo,
+                                                EV_KEY, BTN_LEFT,
+                                                EV_KEY, BTN_RIGHT,
+                                                -1);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       litest_assert_ptr_null(device);
+       libevdev_uinput_destroy(uinput);
+}
+
+START_TEST(abs_device_no_range)
+{
+       struct libinput *li;
+       int code = _i; /* looped test */
+       /* set x/y so libinput doesn't just reject for missing axes */
+       struct input_absinfo absinfo[] = {
+               { ABS_X, 0, 10, 0, 0, 0 },
+               { ABS_Y, 0, 10, 0, 0, 0 },
+               { code, 0, 0, 0, 0, 0 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+
+       assert_device_ignored(li, absinfo);
+
+       litest_restore_log_handler(li);
+       libinput_unref(li);
+}
+END_TEST
+
+START_TEST(abs_mt_device_no_range)
+{
+       struct libinput *li;
+       int code = _i; /* looped test */
+       /* set x/y so libinput doesn't just reject for missing axes */
+       struct input_absinfo absinfo[] = {
+               { ABS_X, 0, 10, 0, 0, 0 },
+               { ABS_Y, 0, 10, 0, 0, 0 },
+               { ABS_MT_SLOT, 0, 10, 0, 0, 0 },
+               { ABS_MT_TRACKING_ID, 0, 255, 0, 0, 0 },
+               { ABS_MT_POSITION_X, 0, 10, 0, 0, 0 },
+               { ABS_MT_POSITION_Y, 0, 10, 0, 0, 0 },
+               { code, 0, 0, 0, 0, 0 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+
+       if (code != ABS_MT_TOOL_TYPE &&
+           code != ABS_MT_TRACKING_ID) /* kernel overrides it */
+               assert_device_ignored(li, absinfo);
+
+       litest_restore_log_handler(li);
+       libinput_unref(li);
+}
+END_TEST
+
+START_TEST(abs_device_missing_res)
+{
+       struct libinput *li;
+       struct input_absinfo absinfo[] = {
+               { ABS_X, 0, 10, 0, 0, 10 },
+               { ABS_Y, 0, 10, 0, 0, 0 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+
+       assert_device_ignored(li, absinfo);
+
+       absinfo[0].resolution = 0;
+       absinfo[1].resolution = 20;
+
+       assert_device_ignored(li, absinfo);
+
+       litest_restore_log_handler(li);
+       libinput_unref(li);
+}
+END_TEST
+
+START_TEST(abs_mt_device_missing_res)
+{
+       struct libinput *li;
+       struct input_absinfo absinfo[] = {
+               { ABS_X, 0, 10, 0, 0, 10 },
+               { ABS_Y, 0, 10, 0, 0, 10 },
+               { ABS_MT_SLOT, 0, 2, 0, 0, 0 },
+               { ABS_MT_TRACKING_ID, 0, 255, 0, 0, 0 },
+               { ABS_MT_POSITION_X, 0, 10, 0, 0, 10 },
+               { ABS_MT_POSITION_Y, 0, 10, 0, 0, 0 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+       assert_device_ignored(li, absinfo);
+
+       absinfo[4].resolution = 0;
+       absinfo[5].resolution = 20;
+
+       assert_device_ignored(li, absinfo);
+
+       litest_restore_log_handler(li);
+       libinput_unref(li);
+
+}
+END_TEST
+
+START_TEST(device_wheel_only)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       ck_assert(libinput_device_has_capability(device,
+                                                LIBINPUT_DEVICE_CAP_POINTER));
+}
+END_TEST
+
+START_TEST(device_accelerometer)
+{
+       struct libinput *li;
+       struct libevdev_uinput *uinput;
+       struct libinput_device *device;
+
+       struct input_absinfo absinfo[] = {
+               { ABS_X, 0, 10, 0, 0, 10 },
+               { ABS_Y, 0, 10, 0, 0, 10 },
+               { ABS_Z, 0, 10, 0, 0, 10 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+
+       li = litest_create_context();
+       litest_disable_log_handler(li);
+
+       uinput = litest_create_uinput_abs_device("test device", NULL,
+                                                absinfo,
+                                                -1);
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       litest_assert_ptr_null(device);
+       libevdev_uinput_destroy(uinput);
+       litest_restore_log_handler(li);
+       libinput_unref(li);
+}
+END_TEST
+
+START_TEST(device_udev_tag_alps)
+{
+       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,
+                                             "LIBINPUT_MODEL_ALPS_TOUCHPAD");
+
+       if (strstr(libinput_device_get_name(device), "ALPS"))
+               ck_assert_notnull(prop);
+       else
+               ck_assert(prop == NULL);
+
+       udev_device_unref(d);
+}
+END_TEST
+
+START_TEST(device_udev_tag_wacom)
+{
+       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,
+                                             "LIBINPUT_MODEL_WACOM_TOUCHPAD");
+
+       if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_WACOM)
+               ck_assert_notnull(prop);
+       else
+               ck_assert(prop == NULL);
+
+       udev_device_unref(d);
+}
+END_TEST
+
+START_TEST(device_udev_tag_apple)
+{
+       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,
+                                             "LIBINPUT_MODEL_WACOM_TOUCHPAD");
+
+       if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_WACOM)
+               ck_assert_notnull(prop);
+       else
+               ck_assert(prop == NULL);
+
+       udev_device_unref(d);
+}
+END_TEST
+
+START_TEST(device_udev_tag_synaptics_serial)
+{
+       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,
+                                             "LIBINPUT_MODEL_SYNAPTICS_SERIAL_TOUCHPAD");
+
+       if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_SYNAPTICS_SERIAL &&
+           libevdev_get_id_product(dev->evdev) == PRODUCT_ID_SYNAPTICS_SERIAL)
+               ck_assert_notnull(prop);
+       else
+               ck_assert(prop == NULL);
+
+       udev_device_unref(d);
+}
+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;
+       struct libinput *li;
+       struct libinput_device *device;
+       int i;
+
+       uinput = litest_create_uinput_device("test device",
+                                            NULL,
+                                            EV_KEY, KEY_A,
+                                            EV_KEY, KEY_B,
+                                            EV_REL, REL_X,
+                                            EV_REL, REL_Y,
+                                            -1);
+       li = litest_create_context();
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       ck_assert(device != NULL);
+
+       litest_disable_log_handler(li);
+       for (i = 0; i < 100; i++) {
+               libevdev_uinput_write_event(uinput, EV_REL, REL_X, 1);
+               libevdev_uinput_write_event(uinput, EV_REL, REL_Y, -1);
+               libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+       litest_restore_log_handler(li);
+
+       libinput_unref(li);
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(device_touchpad_rel)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+       const struct input_absinfo abs[] = {
+               { ABS_X, 0, 10, 0, 0, 10 },
+               { ABS_Y, 0, 10, 0, 0, 10 },
+               { ABS_MT_SLOT, 0, 2, 0, 0, 0 },
+               { ABS_MT_TRACKING_ID, 0, 255, 0, 0, 0 },
+               { ABS_MT_POSITION_X, 0, 10, 0, 0, 10 },
+               { ABS_MT_POSITION_Y, 0, 10, 0, 0, 10 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+       int i;
+
+       uinput = litest_create_uinput_abs_device("test device",
+                                                NULL, abs,
+                                                EV_KEY, BTN_TOOL_FINGER,
+                                                EV_KEY, BTN_TOUCH,
+                                                EV_REL, REL_X,
+                                                EV_REL, REL_Y,
+                                                -1);
+       li = litest_create_context();
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       ck_assert(device != NULL);
+
+       for (i = 0; i < 100; i++) {
+               libevdev_uinput_write_event(uinput, EV_REL, REL_X, 1);
+               libevdev_uinput_write_event(uinput, EV_REL, REL_Y, -1);
+               libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+
+       libinput_unref(li);
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(device_touch_rel)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+       const struct input_absinfo abs[] = {
+               { ABS_X, 0, 10, 0, 0, 10 },
+               { ABS_Y, 0, 10, 0, 0, 10 },
+               { ABS_MT_SLOT, 0, 2, 0, 0, 0 },
+               { ABS_MT_TRACKING_ID, 0, 255, 0, 0, 0 },
+               { ABS_MT_POSITION_X, 0, 10, 0, 0, 10 },
+               { ABS_MT_POSITION_Y, 0, 10, 0, 0, 10 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+       int i;
+
+       uinput = litest_create_uinput_abs_device("test device",
+                                                NULL, abs,
+                                                EV_KEY, BTN_TOUCH,
+                                                EV_REL, REL_X,
+                                                EV_REL, REL_Y,
+                                                -1);
+       li = litest_create_context();
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       ck_assert(device != NULL);
+
+       litest_disable_log_handler(li);
+       for (i = 0; i < 100; i++) {
+               libevdev_uinput_write_event(uinput, EV_REL, REL_X, 1);
+               libevdev_uinput_write_event(uinput, EV_REL, REL_Y, -1);
+               libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+       litest_restore_log_handler(li);
+
+       libinput_unref(li);
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(device_abs_rel)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+       const struct input_absinfo abs[] = {
+               { ABS_X, 0, 10, 0, 0, 10 },
+               { ABS_Y, 0, 10, 0, 0, 10 },
+               { -1, -1, -1, -1, -1, -1 }
+       };
+       int i;
+
+       uinput = litest_create_uinput_abs_device("test device",
+                                                NULL, abs,
+                                                EV_KEY, BTN_TOUCH,
+                                                EV_KEY, BTN_LEFT,
+                                                EV_REL, REL_X,
+                                                EV_REL, REL_Y,
+                                                -1);
+       li = litest_create_context();
+       device = libinput_path_add_device(li,
+                                         libevdev_uinput_get_devnode(uinput));
+       ck_assert(device != NULL);
+
+       for (i = 0; i < 100; i++) {
+               libevdev_uinput_write_event(uinput, EV_REL, REL_X, 1);
+               libevdev_uinput_write_event(uinput, EV_REL, REL_Y, -1);
+               libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+
+       libinput_unref(li);
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(device_quirks_no_abs_mt_y)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *pev;
+       int code;
+
+       litest_drain_events(li);
+
+       litest_event(dev, EV_REL, REL_HWHEEL, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       pev = litest_is_axis_event(event,
+                                  LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
+                                  LIBINPUT_POINTER_AXIS_SOURCE_WHEEL);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(pev));
+
+       for (code = ABS_MISC + 1; code < ABS_MAX; code++) {
+               litest_event(dev, EV_ABS, code, 1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               litest_assert_empty_queue(li);
+       }
+
+}
+END_TEST
+
+START_TEST(device_quirks_cyborg_rat_mode_button)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput *li = dev->libinput;
+
+       ck_assert(!libinput_device_pointer_has_button(device, 0x118));
+       ck_assert(!libinput_device_pointer_has_button(device, 0x119));
+       ck_assert(!libinput_device_pointer_has_button(device, 0x11a));
+
+       litest_drain_events(li);
+
+       litest_event(dev, EV_KEY, 0x118, 0);
+       litest_event(dev, EV_KEY, 0x119, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, 0x119, 0);
+       litest_event(dev, EV_KEY, 0x11a, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, 0x11a, 0);
+       litest_event(dev, EV_KEY, 0x118, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(device_quirks_apple_magicmouse)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       /* ensure we get no events from the touch */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+void
+litest_setup_tests_device(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_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_touch, LITEST_TOUCH, LITEST_ANY);
+       litest_add("device:sendevents", device_disable_touch_during_touch, LITEST_TOUCH, LITEST_ANY);
+       litest_add("device:sendevents", device_disable_touch, LITEST_SINGLE_TOUCH, LITEST_TOUCHPAD);
+       litest_add("device:sendevents", device_disable_touch_during_touch, LITEST_SINGLE_TOUCH, LITEST_TOUCHPAD);
+       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);
@@ -728,12 +1448,38 @@ int main (int argc, char **argv)
        litest_add("device:sendevents", device_disable_release_softbutton, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
        litest_add("device:sendevents", device_disable_topsoftbutton, LITEST_TOPBUTTONPAD, LITEST_ANY);
        litest_add("device:id", device_ids, LITEST_ANY, LITEST_ANY);
-       litest_add_for_device("device:context", device_context, LITEST_SYNAPTICS_CLICKPAD);
+       litest_add_for_device("device:context", device_context, LITEST_SYNAPTICS_CLICKPAD_X220);
 
        litest_add("device:udev", device_get_udev_handle, LITEST_ANY, LITEST_ANY);
 
        litest_add("device:group", device_group_get, LITEST_ANY, LITEST_ANY);
        litest_add_no_device("device:group", device_group_ref);
-
-       return litest_run(argc, argv);
+       litest_add_no_device("device:group", device_group_leak);
+
+       litest_add_no_device("device:invalid devices", abs_device_no_absx);
+       litest_add_no_device("device:invalid devices", abs_device_no_absy);
+       litest_add_no_device("device:invalid devices", abs_mt_device_no_absx);
+       litest_add_no_device("device:invalid devices", abs_mt_device_no_absy);
+       litest_add_ranged_no_device("device:invalid devices", abs_device_no_range, &abs_range);
+       litest_add_ranged_no_device("device:invalid devices", abs_mt_device_no_range, &abs_mt_range);
+       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_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);
+       litest_add_no_device("device:invalid rel events", device_touch_rel);
+       litest_add_no_device("device:invalid rel events", device_abs_rel);
+
+       litest_add_for_device("device:quirks", device_quirks_no_abs_mt_y, LITEST_ANKER_MOUSE_KBD);
+       litest_add_for_device("device:quirks", device_quirks_cyborg_rat_mode_button, LITEST_CYBORG_RAT);
+       litest_add_for_device("device:quirks", device_quirks_apple_magicmouse, LITEST_MAGICMOUSE);
 }
diff --git a/test/gestures.c b/test/gestures.c
new file mode 100644 (file)
index 0000000..0b132c3
--- /dev/null
@@ -0,0 +1,1241 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <check.h>
+#include <libinput.h>
+
+#include "libinput-util.h"
+#include "litest.h"
+
+START_TEST(gestures_cap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       if (libevdev_has_property(dev->evdev, INPUT_PROP_SEMI_MT))
+               ck_assert(!libinput_device_has_capability(device,
+                                         LIBINPUT_DEVICE_CAP_GESTURE));
+       else
+               ck_assert(libinput_device_has_capability(device,
+                                        LIBINPUT_DEVICE_CAP_GESTURE));
+}
+END_TEST
+
+START_TEST(gestures_nocap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       ck_assert(!libinput_device_has_capability(device,
+                                                 LIBINPUT_DEVICE_CAP_GESTURE));
+}
+END_TEST
+
+START_TEST(gestures_swipe_3fg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) < 3)
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 40, 40);
+       litest_touch_down(dev, 1, 50, 40);
+       litest_touch_down(dev, 2, 60, 40);
+       libinput_dispatch(li);
+       litest_touch_move_three_touches(dev,
+                                       40, 40,
+                                       50, 40,
+                                       60, 40,
+                                       dir_x, dir_y,
+                                       10, 2);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                                        3);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                                                3);
+
+               dx = libinput_event_gesture_get_dx(gevent);
+               dy = libinput_event_gesture_get_dy(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               dx = libinput_event_gesture_get_dx_unaccelerated(gevent);
+               dy = libinput_event_gesture_get_dy_unaccelerated(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               libinput_event_destroy(event);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_END,
+                                        3);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_swipe_3fg_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) > 2 ||
+           !libinput_device_has_capability(dev->libinput_device,
+                                           LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 40, 40);
+       litest_touch_down(dev, 1, 50, 40);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+       litest_touch_move_two_touches(dev,
+                                     40, 40,
+                                     50, 40,
+                                     dir_x, dir_y,
+                                     10, 2);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                                        3);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                                                3);
+
+               dx = libinput_event_gesture_get_dx(gevent);
+               dy = libinput_event_gesture_get_dy(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               dx = libinput_event_gesture_get_dx_unaccelerated(gevent);
+               dy = libinput_event_gesture_get_dy_unaccelerated(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               libinput_event_destroy(event);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_END,
+                                        3);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_swipe_4fg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int cardinals[8][2] = {
+               { 0, 3 },
+               { 3, 3 },
+               { 3, 0 },
+               { 3, -3 },
+               { 0, -3 },
+               { -3, -3 },
+               { -3, 0 },
+               { -3, 3 },
+       };
+       int i;
+
+       if (libevdev_get_num_slots(dev->evdev) < 4)
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 40, 40);
+       litest_touch_down(dev, 1, 50, 40);
+       litest_touch_down(dev, 2, 60, 40);
+       litest_touch_down(dev, 3, 70, 40);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+
+               dir_x += cardinals[cardinal][0];
+               dir_y += cardinals[cardinal][1];
+
+               litest_touch_move(dev,
+                                 0,
+                                 40 + dir_x,
+                                 40 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 + dir_x,
+                                 40 + dir_y);
+               litest_touch_move(dev,
+                                 2,
+                                 60 + dir_x,
+                                 40 + dir_y);
+               litest_touch_move(dev,
+                                 3,
+                                 70 + dir_x,
+                                 40 + dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                                        4);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                                                4);
+
+               dx = libinput_event_gesture_get_dx(gevent);
+               dy = libinput_event_gesture_get_dy(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               dx = libinput_event_gesture_get_dx_unaccelerated(gevent);
+               dy = libinput_event_gesture_get_dy_unaccelerated(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               libinput_event_destroy(event);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+       litest_touch_up(dev, 3);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_END,
+                                        4);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_swipe_4fg_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) > 2 ||
+           !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_QUADTAP) ||
+           !libinput_device_has_capability(dev->libinput_device,
+                                           LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 40, 40);
+       litest_touch_down(dev, 1, 50, 40);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+       litest_touch_move_two_touches(dev,
+                                     40, 40,
+                                     50, 40,
+                                     dir_x, dir_y,
+                                     10, 2);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                                        4);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+                                                4);
+
+               dx = libinput_event_gesture_get_dx(gevent);
+               dy = libinput_event_gesture_get_dy(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               dx = libinput_event_gesture_get_dx_unaccelerated(gevent);
+               dy = libinput_event_gesture_get_dy_unaccelerated(gevent);
+               if (dir_x == 0.0)
+                       ck_assert(dx == 0.0);
+               else if (dir_x < 0.0)
+                       ck_assert(dx < 0.0);
+               else if (dir_x > 0.0)
+                       ck_assert(dx > 0.0);
+
+               if (dir_y == 0.0)
+                       ck_assert(dy == 0.0);
+               else if (dir_y < 0.0)
+                       ck_assert(dy < 0.0);
+               else if (dir_y > 0.0)
+                       ck_assert(dy > 0.0);
+
+               libinput_event_destroy(event);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_END,
+                                        4);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_pinch)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int i;
+       double scale, oldscale;
+       double angle;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) < 2 ||
+           !libinput_device_has_capability(dev->libinput_device,
+                                           LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50 + dir_x, 50 + dir_y);
+       litest_touch_down(dev, 1, 50 - dir_x, 50 - dir_y);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+               if (dir_x > 0.0)
+                       dir_x -= 2;
+               else if (dir_x < 0.0)
+                       dir_x += 2;
+               if (dir_y > 0.0)
+                       dir_y -= 2;
+               else if (dir_y < 0.0)
+                       dir_y += 2;
+               litest_touch_move(dev,
+                                 0,
+                                 50 + dir_x,
+                                 50 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 - dir_x,
+                                 50 - dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                        2);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       scale = libinput_event_gesture_get_scale(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       ck_assert(scale == 1.0);
+
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                                                2);
+
+               oldscale = scale;
+               scale = libinput_event_gesture_get_scale(gevent);
+
+               ck_assert(scale < oldscale);
+
+               angle = libinput_event_gesture_get_angle_delta(gevent);
+               ck_assert_double_le(fabs(angle), 1.0);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_END,
+                                        2);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_pinch_3fg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int i;
+       double scale, oldscale;
+       double angle;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) < 3)
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50 + dir_x, 50 + dir_y);
+       litest_touch_down(dev, 1, 50 - dir_x, 50 - dir_y);
+       litest_touch_down(dev, 2, 51 - dir_x, 51 - dir_y);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+               if (dir_x > 0.0)
+                       dir_x -= 2;
+               else if (dir_x < 0.0)
+                       dir_x += 2;
+               if (dir_y > 0.0)
+                       dir_y -= 2;
+               else if (dir_y < 0.0)
+                       dir_y += 2;
+               litest_touch_move(dev,
+                                 0,
+                                 50 + dir_x,
+                                 50 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 - dir_x,
+                                 50 - dir_y);
+               litest_touch_move(dev,
+                                 2,
+                                 51 - dir_x,
+                                 51 - dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                        3);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       scale = libinput_event_gesture_get_scale(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       ck_assert(scale == 1.0);
+
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                                                3);
+
+               oldscale = scale;
+               scale = libinput_event_gesture_get_scale(gevent);
+
+               ck_assert(scale < oldscale);
+
+               angle = libinput_event_gesture_get_angle_delta(gevent);
+               ck_assert_double_le(fabs(angle), 1.0);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_END,
+                                        3);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_pinch_3fg_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int i;
+       double scale, oldscale;
+       double angle;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) > 2 ||
+           !libinput_device_has_capability(dev->libinput_device,
+                                           LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50 + dir_x, 50 + dir_y);
+       litest_touch_down(dev, 1, 50 - dir_x, 50 - dir_y);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+               if (dir_x > 0.0)
+                       dir_x -= 2;
+               else if (dir_x < 0.0)
+                       dir_x += 2;
+               if (dir_y > 0.0)
+                       dir_y -= 2;
+               else if (dir_y < 0.0)
+                       dir_y += 2;
+               litest_touch_move(dev,
+                                 0,
+                                 50 + dir_x,
+                                 50 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 - dir_x,
+                                 50 - dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                        3);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       scale = libinput_event_gesture_get_scale(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       ck_assert(scale == 1.0);
+
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                                                3);
+
+               oldscale = scale;
+               scale = libinput_event_gesture_get_scale(gevent);
+
+               ck_assert(scale < oldscale);
+
+               angle = libinput_event_gesture_get_angle_delta(gevent);
+               ck_assert_double_le(fabs(angle), 1.0);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_END,
+                                        3);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_pinch_4fg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int i;
+       double scale, oldscale;
+       double angle;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) < 4)
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50 + dir_x, 50 + dir_y);
+       litest_touch_down(dev, 1, 50 - dir_x, 50 - dir_y);
+       litest_touch_down(dev, 2, 51 - dir_x, 51 - dir_y);
+       litest_touch_down(dev, 3, 52 - dir_x, 52 - dir_y);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+               if (dir_x > 0.0)
+                       dir_x -= 2;
+               else if (dir_x < 0.0)
+                       dir_x += 2;
+               if (dir_y > 0.0)
+                       dir_y -= 2;
+               else if (dir_y < 0.0)
+                       dir_y += 2;
+               litest_touch_move(dev,
+                                 0,
+                                 50 + dir_x,
+                                 50 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 - dir_x,
+                                 50 - dir_y);
+               litest_touch_move(dev,
+                                 2,
+                                 51 - dir_x,
+                                 51 - dir_y);
+               litest_touch_move(dev,
+                                 3,
+                                 52 - dir_x,
+                                 52 - dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                        4);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       scale = libinput_event_gesture_get_scale(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       ck_assert(scale == 1.0);
+
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                                                4);
+
+               oldscale = scale;
+               scale = libinput_event_gesture_get_scale(gevent);
+
+               ck_assert(scale < oldscale);
+
+               angle = libinput_event_gesture_get_angle_delta(gevent);
+               ck_assert_double_le(fabs(angle), 1.0);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+       litest_touch_up(dev, 3);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_END,
+                                        4);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_pinch_4fg_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int i;
+       double scale, oldscale;
+       double angle;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) > 2 ||
+           !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_QUADTAP) ||
+           !libinput_device_has_capability(dev->libinput_device,
+                                           LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50 + dir_x, 50 + dir_y);
+       litest_touch_down(dev, 1, 50 - dir_x, 50 - dir_y);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+               if (dir_x > 0.0)
+                       dir_x -= 3;
+               else if (dir_x < 0.0)
+                       dir_x += 3;
+               if (dir_y > 0.0)
+                       dir_y -= 3;
+               else if (dir_y < 0.0)
+                       dir_y += 3;
+               litest_touch_move(dev,
+                                 0,
+                                 50 + dir_x,
+                                 50 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 - dir_x,
+                                 50 - dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                        4);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       scale = libinput_event_gesture_get_scale(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       ck_assert(scale == 1.0);
+
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                                                4);
+
+               oldscale = scale;
+               scale = libinput_event_gesture_get_scale(gevent);
+
+               ck_assert(scale < oldscale);
+
+               angle = libinput_event_gesture_get_angle_delta(gevent);
+               ck_assert_double_le(fabs(angle), 1.5);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_END,
+                                        4);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_spread)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       double dx, dy;
+       int cardinal = _i; /* ranged test */
+       double dir_x, dir_y;
+       int i;
+       double scale, oldscale;
+       double angle;
+       int cardinals[8][2] = {
+               { 0, 30 },
+               { 30, 30 },
+               { 30, 0 },
+               { 30, -30 },
+               { 0, -30 },
+               { -30, -30 },
+               { -30, 0 },
+               { -30, 30 },
+       };
+
+       if (libevdev_get_num_slots(dev->evdev) < 2 ||
+           !libinput_device_has_capability(dev->libinput_device,
+                                           LIBINPUT_DEVICE_CAP_GESTURE))
+               return;
+
+       dir_x = cardinals[cardinal][0];
+       dir_y = cardinals[cardinal][1];
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50 + dir_x, 50 + dir_y);
+       litest_touch_down(dev, 1, 50 - dir_x, 50 - dir_y);
+       libinput_dispatch(li);
+
+       for (i = 0; i < 15; i++) {
+               litest_push_event_frame(dev);
+               if (dir_x > 0.0)
+                       dir_x += 1;
+               else if (dir_x < 0.0)
+                       dir_x -= 1;
+               if (dir_y > 0.0)
+                       dir_y += 1;
+               else if (dir_y < 0.0)
+                       dir_y -= 1;
+               litest_touch_move(dev,
+                                 0,
+                                 50 + dir_x,
+                                 50 + dir_y);
+               litest_touch_move(dev,
+                                 1,
+                                 50 - dir_x,
+                                 50 - dir_y);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+                                        2);
+       dx = libinput_event_gesture_get_dx(gevent);
+       dy = libinput_event_gesture_get_dy(gevent);
+       scale = libinput_event_gesture_get_scale(gevent);
+       ck_assert(dx == 0.0);
+       ck_assert(dy == 0.0);
+       ck_assert(scale == 1.0);
+
+       libinput_event_destroy(event);
+
+       while ((event = libinput_get_event(li)) != NULL) {
+               gevent = litest_is_gesture_event(event,
+                                                LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+                                                2);
+               oldscale = scale;
+               scale = libinput_event_gesture_get_scale(gevent);
+               ck_assert(scale > oldscale);
+
+               angle = libinput_event_gesture_get_angle_delta(gevent);
+               ck_assert_double_le(fabs(angle), 1.0);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_PINCH_END,
+                                        2);
+       ck_assert(!libinput_event_gesture_get_cancelled(gevent));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_time_usec)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_gesture *gevent;
+       uint64_t time_usec;
+
+       if (libevdev_get_num_slots(dev->evdev) < 3)
+               return;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 40, 40);
+       litest_touch_down(dev, 1, 50, 40);
+       litest_touch_down(dev, 2, 60, 40);
+       libinput_dispatch(li);
+       litest_touch_move_three_touches(dev,
+                                       40, 40,
+                                       50, 40,
+                                       60, 40,
+                                       0, 30,
+                                       10, 2);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       gevent = litest_is_gesture_event(event,
+                                        LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN,
+                                        3);
+       time_usec = libinput_event_gesture_get_time_usec(gevent);
+       ck_assert_int_eq(libinput_event_gesture_get_time(gevent),
+                        (uint32_t) (time_usec / 1000));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(gestures_3fg_buttonarea_scroll)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_num_slots(dev->evdev) < 3)
+               return;
+
+       litest_enable_buttonareas(dev);
+       litest_enable_2fg_scroll(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 40, 20);
+       litest_touch_down(dev, 1, 30, 20);
+       /* third finger in btnarea */
+       litest_touch_down(dev, 2, 50, 99);
+       libinput_dispatch(li);
+       litest_touch_move_two_touches(dev,
+                                       40, 20,
+                                       30, 20,
+                                       0, 40,
+                                       10, 2);
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
+}
+END_TEST
+
+START_TEST(gestures_3fg_buttonarea_scroll_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_num_slots(dev->evdev) > 2)
+               return;
+
+       litest_enable_buttonareas(dev);
+       litest_enable_2fg_scroll(dev);
+       litest_drain_events(li);
+
+       /* first finger in btnarea */
+       litest_touch_down(dev, 0, 20, 99);
+       litest_touch_down(dev, 1, 30, 20);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_touch_move_to(dev, 1, 30, 20, 30, 70, 10, 1);
+
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
+}
+END_TEST
+
+void
+litest_setup_tests_gestures(void)
+{
+       /* N, NE, ... */
+       struct range cardinals = { 0, 8 };
+
+       litest_add("gestures:cap", gestures_cap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("gestures:cap", gestures_nocap, LITEST_ANY, LITEST_TOUCHPAD);
+
+       litest_add_ranged("gestures:swipe", gestures_swipe_3fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:swipe", gestures_swipe_3fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:swipe", gestures_swipe_4fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:swipe", gestures_swipe_4fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:pinch", gestures_pinch, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:pinch", gestures_pinch_3fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:pinch", gestures_pinch_3fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:pinch", gestures_pinch_4fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:pinch", gestures_pinch_4fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+       litest_add_ranged("gestures:pinch", gestures_spread, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &cardinals);
+
+       litest_add("gestures:swipe", gestures_3fg_buttonarea_scroll, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
+       litest_add("gestures:swipe", gestures_3fg_buttonarea_scroll_btntool, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
+
+       litest_add("gestures:time", gestures_time_usec, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+}
index cb7ad52203124df57f2178a45429832d28a52d5f..780506aaaa0aecc6ee76533c3e6b20db9e55673d 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2014 Jonas Ådahl <jadahl@gmail.com>
  *
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include "config.h"
@@ -61,11 +62,9 @@ START_TEST(keyboard_seat_key_count)
                        continue;
                }
 
-               kev = libinput_event_get_keyboard_event(ev);
-               ck_assert_notnull(kev);
-               ck_assert_int_eq(libinput_event_keyboard_get_key(kev), KEY_A);
-               ck_assert_int_eq(libinput_event_keyboard_get_key_state(kev),
-                                LIBINPUT_KEY_STATE_PRESSED);
+               kev = litest_is_keyboard_event(ev,
+                                              KEY_A,
+                                              LIBINPUT_KEY_STATE_PRESSED);
 
                ++expected_key_button_count;
                seat_key_count =
@@ -175,31 +174,6 @@ START_TEST(keyboard_ignore_no_pressed_release)
 }
 END_TEST
 
-static void
-test_key_event(struct litest_device *dev, unsigned int key, int state)
-{
-       struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       struct libinput_event_keyboard *kevent;
-
-       litest_event(dev, EV_KEY, key, state);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-
-       libinput_dispatch(li);
-
-       event = libinput_get_event(li);
-       ck_assert(event != NULL);
-       ck_assert_int_eq(libinput_event_get_type(event), LIBINPUT_EVENT_KEYBOARD_KEY);
-
-       kevent = libinput_event_get_keyboard_event(event);
-       ck_assert(kevent != NULL);
-       ck_assert_int_eq(libinput_event_keyboard_get_key(kevent), key);
-       ck_assert_int_eq(libinput_event_keyboard_get_key_state(kevent),
-                        state ? LIBINPUT_KEY_STATE_PRESSED :
-                                LIBINPUT_KEY_STATE_RELEASED);
-       libinput_event_destroy(event);
-}
-
 START_TEST(keyboard_key_auto_release)
 {
        struct libinput *libinput;
@@ -244,7 +218,17 @@ START_TEST(keyboard_key_auto_release)
 
        /* Send pressed events, without releasing */
        for (i = 0; i < ARRAY_LENGTH(keys); ++i) {
-               test_key_event(dev, keys[i].code, 1);
+               key = keys[i].code;
+               litest_event(dev, EV_KEY, key, 1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+               libinput_dispatch(libinput);
+
+               event = libinput_get_event(libinput);
+               kevent = litest_is_keyboard_event(event,
+                                                 key,
+                                                 LIBINPUT_KEY_STATE_PRESSED);
+               libinput_event_destroy(event);
        }
 
        litest_drain_events(libinput);
@@ -290,12 +274,119 @@ START_TEST(keyboard_key_auto_release)
 }
 END_TEST
 
-int
-main(int argc, char **argv)
+START_TEST(keyboard_has_key)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       unsigned int code;
+       int evdev_has, libinput_has;
+
+       ck_assert(libinput_device_has_capability(
+                                        device,
+                                        LIBINPUT_DEVICE_CAP_KEYBOARD));
+
+       for (code = 0; code < KEY_CNT; code++) {
+               evdev_has = libevdev_has_event_code(dev->evdev, EV_KEY, code);
+               libinput_has = libinput_device_keyboard_has_key(device, code);
+               ck_assert_int_eq(evdev_has, libinput_has);
+       }
+}
+END_TEST
+
+START_TEST(keyboard_keys_bad_device)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       unsigned int code;
+       int has_key;
+
+       if (libinput_device_has_capability(device,
+                                          LIBINPUT_DEVICE_CAP_KEYBOARD))
+               return;
+
+       for (code = 0; code < KEY_CNT; code++) {
+               has_key = libinput_device_keyboard_has_key(device, code);
+               ck_assert_int_eq(has_key, -1);
+       }
+}
+END_TEST
+
+START_TEST(keyboard_time_usec)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event_keyboard *kev;
+       struct libinput_event *event;
+       uint64_t time_usec;
+
+       if (!libevdev_has_event_code(dev->evdev, EV_KEY, KEY_A))
+               return;
+
+       litest_drain_events(dev->libinput);
+
+       litest_keyboard_key(dev, KEY_A, true);
+
+       litest_wait_for_event(li);
+
+       event = libinput_get_event(li);
+       kev = litest_is_keyboard_event(event,
+                                      KEY_A,
+                                      LIBINPUT_KEY_STATE_PRESSED);
+
+       time_usec = libinput_event_keyboard_get_time_usec(kev);
+       ck_assert_int_eq(libinput_event_keyboard_get_time(kev),
+                        (uint32_t) (time_usec / 1000));
+
+       libinput_event_destroy(event);
+       litest_drain_events(dev->libinput);
+}
+END_TEST
+
+START_TEST(keyboard_no_buttons)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int code;
+       const char *name;
+
+       litest_drain_events(dev->libinput);
+
+       for (code = 0; code < KEY_MAX; code++) {
+               if (!libevdev_has_event_code(dev->evdev, EV_KEY, code))
+                       continue;
+
+               name = libevdev_event_code_get_name(EV_KEY, code);
+               if (!name || !strneq(name, "KEY_", 4))
+                       continue;
+
+               litest_keyboard_key(dev, code, true);
+               litest_keyboard_key(dev, code, false);
+               libinput_dispatch(li);
+
+               event = libinput_get_event(li);
+               litest_is_keyboard_event(event,
+                                        code,
+                                        LIBINPUT_KEY_STATE_PRESSED);
+               libinput_event_destroy(event);
+               event = libinput_get_event(li);
+               litest_is_keyboard_event(event,
+                                        code,
+                                        LIBINPUT_KEY_STATE_RELEASED);
+               libinput_event_destroy(event);
+       }
+}
+END_TEST
+
+void
+litest_setup_tests_keyboard(void)
 {
        litest_add_no_device("keyboard:seat key count", keyboard_seat_key_count);
        litest_add_no_device("keyboard:key counting", keyboard_ignore_no_pressed_release);
        litest_add_no_device("keyboard:key counting", keyboard_key_auto_release);
+       litest_add("keyboard:keys", keyboard_has_key, LITEST_KEYS, LITEST_ANY);
+       litest_add("keyboard:keys", keyboard_keys_bad_device, LITEST_ANY, LITEST_ANY);
+       litest_add("keyboard:time", keyboard_time_usec, LITEST_KEYS, LITEST_ANY);
 
-       return litest_run(argc, argv);
+       litest_add("keyboard:events", keyboard_no_buttons, LITEST_KEYS, LITEST_ANY);
 }
diff --git a/test/litest-alps-semi-mt.c b/test/litest-alps-semi-mt.c
deleted file mode 100644 (file)
index 4d95ac9..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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 <assert.h>
-
-#include "libinput-util.h"
-
-#include "litest.h"
-#include "litest-int.h"
-
-
-static void alps_create(struct litest_device *d);
-
-static void
-litest_alps_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_ALPS_SEMI_MT);
-       litest_set_current_device(d);
-}
-
-static void
-alps_touch_down(struct litest_device *d, unsigned int slot, double x, double y)
-{
-       struct litest_semi_mt *semi_mt = d->private;
-
-       litest_semi_mt_touch_down(d, semi_mt, slot, x, y);
-}
-
-static void
-alps_touch_move(struct litest_device *d, unsigned int slot, double x, double y)
-{
-       struct litest_semi_mt *semi_mt = d->private;
-
-       litest_semi_mt_touch_move(d, semi_mt, slot, x, y);
-}
-
-static void
-alps_touch_up(struct litest_device *d, unsigned int slot)
-{
-       struct litest_semi_mt *semi_mt = d->private;
-
-       litest_semi_mt_touch_up(d, semi_mt, slot);
-}
-
-static struct litest_device_interface interface = {
-       .touch_down = alps_touch_down,
-       .touch_move = alps_touch_move,
-       .touch_up = alps_touch_up,
-};
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0x8,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_MIDDLE,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOUCH,
-       EV_KEY, BTN_TOOL_DOUBLETAP,
-       EV_KEY, BTN_TOOL_TRIPLETAP,
-       EV_KEY, BTN_TOOL_QUADTAP,
-       INPUT_PROP_MAX, INPUT_PROP_POINTER,
-       INPUT_PROP_MAX, INPUT_PROP_SEMI_MT,
-       -1, -1,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 0, 2000, 0, 0, 0 },
-       { ABS_Y, 0, 1400, 0, 0, 0 },
-       { ABS_PRESSURE, 0, 127, 0, 0, 0 },
-       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
-       { ABS_MT_POSITION_X, 0, 2000, 0, 0, 0 },
-       { ABS_MT_POSITION_Y, 0, 1400, 0, 0, 0 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { .value = -1 }
-};
-
-struct litest_test_device litest_alps_device = {
-       .type = LITEST_ALPS_SEMI_MT,
-       .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_SEMI_MT,
-       .shortname = "alps semi-mt",
-       .setup = litest_alps_setup,
-       .interface = &interface,
-       .create = alps_create,
-
-       .name = "AlpsPS/2 ALPS GlidePoint",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
-
-static void
-alps_create(struct litest_device *d)
-{
-       struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt));
-       assert(semi_mt);
-
-       d->private = semi_mt;
-
-       d->uinput = litest_create_uinput_device_from_description(litest_alps_device.name,
-                                                                litest_alps_device.id,
-                                                                absinfo,
-                                                                events);
-       d->interface = &interface;
-}
diff --git a/test/litest-bcm5974.c b/test/litest-bcm5974.c
deleted file mode 100644 (file)
index 035bed2..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright © 2013 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_bcm5974_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_BCM5974);
-       litest_set_current_device(d);
-}
-
-struct input_event down[] = {
-       { .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 = 30  },
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, -4824, 4824, 0, 0, 0 },
-       { ABS_Y, -172, 4290, 0, 0, 0 },
-       { ABS_PRESSURE, 0, 256, 5, 0, 0 },
-       { ABS_TOOL_WIDTH, 0, 16, 0, 0, 0 },
-       { ABS_MT_SLOT, 0, 15, 0, 0, 0 },
-       { ABS_MT_POSITION_X, -4824, 4824, 17, 0, 0 },
-       { ABS_MT_POSITION_Y, -172, 4290, 17, 0, 0 },
-       { ABS_MT_ORIENTATION, -16384, 16384, 3276, 0, 0 },
-       { ABS_MT_TOUCH_MAJOR, 0, 2048, 81, 0, 0 },
-       { ABS_MT_TOUCH_MINOR, 0, 2048, 81, 0, 0 },
-       { ABS_MT_WIDTH_MAJOR, 0, 2048, 81, 0, 0 },
-       { ABS_MT_WIDTH_MINOR, 0, 2048, 81, 0, 0 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x3,
-       .vendor = 0x5ac,
-       .product = 0x249,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOOL_QUINTTAP,
-       EV_KEY, BTN_TOUCH,
-       EV_KEY, BTN_TOOL_DOUBLETAP,
-       EV_KEY, BTN_TOOL_TRIPLETAP,
-       EV_KEY, BTN_TOOL_QUADTAP,
-       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
-       -1, -1
-};
-
-struct litest_test_device litest_bcm5974_device = {
-       .type = LITEST_BCM5974,
-       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD |
-                   LITEST_BUTTON | LITEST_APPLE_CLICKPAD,
-       .shortname = "bcm5974",
-       .setup = litest_bcm5974_setup,
-       .interface = &interface,
-
-       .name = "bcm5974",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-device-alps-dualpoint.c b/test/litest-device-alps-dualpoint.c
new file mode 100644 (file)
index 0000000..5020427
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include "libinput-util.h"
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void alps_dualpoint_create(struct litest_device *d);
+
+static void
+litest_alps_dualpoint_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_ALPS_DUALPOINT);
+       litest_set_current_device(d);
+}
+
+static void
+alps_dualpoint_touch_down(struct litest_device *d, unsigned int slot, double x, double y)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_down(d, semi_mt, slot, x, y);
+}
+
+static void
+alps_dualpoint_touch_move(struct litest_device *d, unsigned int slot, double x, double y)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_move(d, semi_mt, slot, x, y);
+}
+
+static void
+alps_dualpoint_touch_up(struct litest_device *d, unsigned int slot)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_up(d, semi_mt, slot);
+}
+
+static struct litest_device_interface interface = {
+       .touch_down = alps_dualpoint_touch_down,
+       .touch_move = alps_dualpoint_touch_move,
+       .touch_up = alps_dualpoint_touch_up,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x310,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_SEMI_MT,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 2000, 0, 0, 25 },
+       { ABS_Y, 0, 1400, 0, 0, 32 },
+       { ABS_PRESSURE, 0, 127, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 2000, 0, 0, 25 },
+       { ABS_MT_POSITION_Y, 0, 1400, 0, 0, 32 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 }
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"touchpad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n"
+"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest AlpsPS/2 ALPS DualPoint TouchPad\","
+"    ENV{LIBINPUT_MODEL_DELL_TOUCHPAD}=\"1\"\n"
+"\n"
+"LABEL=\"touchpad_end\"";
+
+struct litest_test_device litest_alps_dualpoint_device = {
+       .type = LITEST_ALPS_DUALPOINT,
+       .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_SEMI_MT,
+       .shortname = "alps dualpoint",
+       .setup = litest_alps_dualpoint_setup,
+       .interface = &interface,
+       .create = alps_dualpoint_create,
+
+       .name = "AlpsPS/2 ALPS DualPoint TouchPad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
+
+static void
+alps_dualpoint_create(struct litest_device *d)
+{
+       struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt));
+       assert(semi_mt);
+
+       d->private = semi_mt;
+
+       d->uinput = litest_create_uinput_device_from_description(litest_alps_dualpoint_device.name,
+                                                                litest_alps_dualpoint_device.id,
+                                                                absinfo,
+                                                                events);
+       d->interface = &interface;
+}
diff --git a/test/litest-device-alps-semi-mt.c b/test/litest-device-alps-semi-mt.c
new file mode 100644 (file)
index 0000000..720eaeb
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include "libinput-util.h"
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void alps_create(struct litest_device *d);
+
+static void
+litest_alps_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_ALPS_SEMI_MT);
+       litest_set_current_device(d);
+}
+
+static void
+alps_touch_down(struct litest_device *d, unsigned int slot, double x, double y)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_down(d, semi_mt, slot, x, y);
+}
+
+static void
+alps_touch_move(struct litest_device *d, unsigned int slot, double x, double y)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_move(d, semi_mt, slot, x, y);
+}
+
+static void
+alps_touch_up(struct litest_device *d, unsigned int slot)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_up(d, semi_mt, slot);
+}
+
+static struct litest_device_interface interface = {
+       .touch_down = alps_touch_down,
+       .touch_move = alps_touch_move,
+       .touch_up = alps_touch_up,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x8,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_SEMI_MT,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 2000, 0, 0, 0 },
+       { ABS_Y, 0, 1400, 0, 0, 0 },
+       { ABS_PRESSURE, 0, 127, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 2000, 0, 0, 0 },
+       { ABS_MT_POSITION_Y, 0, 1400, 0, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_alps_device = {
+       .type = LITEST_ALPS_SEMI_MT,
+       .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_SEMI_MT,
+       .shortname = "alps semi-mt",
+       .setup = litest_alps_setup,
+       .interface = &interface,
+       .create = alps_create,
+
+       .name = "AlpsPS/2 ALPS GlidePoint",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
+
+static void
+alps_create(struct litest_device *d)
+{
+       struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt));
+       assert(semi_mt);
+
+       d->private = semi_mt;
+
+       d->uinput = litest_create_uinput_device_from_description(litest_alps_device.name,
+                                                                litest_alps_device.id,
+                                                                absinfo,
+                                                                events);
+       d->interface = &interface;
+}
diff --git a/test/litest-device-anker-mouse-kbd.c b/test/litest-device-anker-mouse-kbd.c
new file mode 100644 (file)
index 0000000..5f39928
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+/* Recording from https://bugs.freedesktop.org/show_bug.cgi?id=93474
+ * This is the keyboard device for this mouse.
+ */
+
+static void litest_anker_mouse_kbd_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_ANKER_MOUSE_KBD);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x4d9,
+       .product = 0xfa50,
+};
+
+static int events[] = {
+       EV_REL, REL_HWHEEL,
+       EV_KEY, KEY_ESC,
+       EV_KEY, KEY_KPMINUS,
+       EV_KEY, KEY_KPPLUS,
+       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_POWER,
+       EV_KEY, KEY_PAUSE,
+       EV_KEY, KEY_STOP,
+       EV_KEY, KEY_PROPS,
+       EV_KEY, KEY_UNDO,
+       EV_KEY, KEY_COPY,
+       EV_KEY, KEY_OPEN,
+       EV_KEY, KEY_PASTE,
+       EV_KEY, KEY_FIND,
+       EV_KEY, KEY_CUT,
+       EV_KEY, KEY_HELP,
+       EV_KEY, KEY_MENU,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_WAKEUP,
+       EV_KEY, KEY_FILE,
+       EV_KEY, KEY_WWW,
+       EV_KEY, KEY_COFFEE,
+       EV_KEY, KEY_MAIL,
+       EV_KEY, KEY_BOOKMARKS,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_EJECTCD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_RECORD,
+       EV_KEY, KEY_REWIND,
+       EV_KEY, KEY_PHONE,
+       EV_KEY, KEY_CONFIG,
+       EV_KEY, KEY_HOMEPAGE,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_EXIT,
+       EV_KEY, KEY_EDIT,
+       EV_KEY, KEY_SCROLLUP,
+       EV_KEY, KEY_SCROLLDOWN,
+       EV_KEY, KEY_NEW,
+       EV_KEY, KEY_REDO,
+       EV_KEY, KEY_CLOSE,
+       EV_KEY, KEY_PLAY,
+       EV_KEY, KEY_FASTFORWARD,
+       EV_KEY, KEY_BASSBOOST,
+       EV_KEY, KEY_PRINT,
+       EV_KEY, KEY_CAMERA,
+       EV_KEY, KEY_CHAT,
+       EV_KEY, KEY_SEARCH,
+       EV_KEY, KEY_FINANCE,
+       EV_KEY, KEY_BRIGHTNESSDOWN,
+       EV_KEY, KEY_BRIGHTNESSUP,
+       EV_KEY, KEY_KBDILLUMTOGGLE,
+       EV_KEY, KEY_SAVE,
+       EV_KEY, KEY_DOCUMENTS,
+       EV_KEY, KEY_UNKNOWN,
+       EV_KEY, KEY_VIDEO_NEXT,
+       EV_KEY, KEY_BRIGHTNESS_AUTO,
+       EV_KEY, BTN_0,
+       EV_KEY, KEY_SELECT,
+       EV_KEY, KEY_GOTO,
+       EV_KEY, KEY_INFO,
+       EV_KEY, KEY_PROGRAM,
+       EV_KEY, KEY_PVR,
+       EV_KEY, KEY_SUBTITLE,
+       EV_KEY, KEY_ZOOM,
+       EV_KEY, KEY_KEYBOARD,
+       EV_KEY, KEY_PC,
+       EV_KEY, KEY_TV,
+       EV_KEY, KEY_TV2,
+       EV_KEY, KEY_VCR,
+       EV_KEY, KEY_VCR2,
+       EV_KEY, KEY_SAT,
+       EV_KEY, KEY_CD,
+       EV_KEY, KEY_TAPE,
+       EV_KEY, KEY_TUNER,
+       EV_KEY, KEY_PLAYER,
+       EV_KEY, KEY_DVD,
+       EV_KEY, KEY_AUDIO,
+       EV_KEY, KEY_VIDEO,
+       EV_KEY, KEY_MEMO,
+       EV_KEY, KEY_CALENDAR,
+       EV_KEY, KEY_RED,
+       EV_KEY, KEY_GREEN,
+       EV_KEY, KEY_YELLOW,
+       EV_KEY, KEY_BLUE,
+       EV_KEY, KEY_CHANNELUP,
+       EV_KEY, KEY_CHANNELDOWN,
+       EV_KEY, KEY_LAST,
+       EV_KEY, KEY_NEXT,
+       EV_KEY, KEY_RESTART,
+       EV_KEY, KEY_SLOW,
+       EV_KEY, KEY_SHUFFLE,
+       EV_KEY, KEY_PREVIOUS,
+       EV_KEY, KEY_VIDEOPHONE,
+       EV_KEY, KEY_GAMES,
+       EV_KEY, KEY_ZOOMIN,
+       EV_KEY, KEY_ZOOMOUT,
+       EV_KEY, KEY_ZOOMRESET,
+       EV_KEY, KEY_WORDPROCESSOR,
+       EV_KEY, KEY_EDITOR,
+       EV_KEY, KEY_SPREADSHEET,
+       EV_KEY, KEY_GRAPHICSEDITOR,
+       EV_KEY, KEY_PRESENTATION,
+       EV_KEY, KEY_DATABASE,
+       EV_KEY, KEY_NEWS,
+       EV_KEY, KEY_VOICEMAIL,
+       EV_KEY, KEY_ADDRESSBOOK,
+       EV_KEY, KEY_MESSENGER,
+       EV_KEY, KEY_DISPLAYTOGGLE,
+       EV_KEY, KEY_SPELLCHECK,
+       EV_KEY, KEY_LOGOFF,
+       EV_KEY, KEY_MEDIA_REPEAT,
+       EV_KEY, KEY_IMAGES,
+       EV_KEY, KEY_BUTTONCONFIG,
+       EV_KEY, KEY_TASKMANAGER,
+       EV_KEY, KEY_JOURNAL,
+       EV_KEY, KEY_CONTROLPANEL,
+       EV_KEY, KEY_APPSELECT,
+       EV_KEY, KEY_SCREENSAVER,
+       EV_KEY, KEY_VOICECOMMAND,
+       EV_KEY, KEY_BRIGHTNESS_MIN,
+       EV_KEY, KEY_BRIGHTNESS_MAX,
+       EV_KEY, KEY_KBDINPUTASSIST_PREV,
+       EV_KEY, KEY_KBDINPUTASSIST_NEXT,
+       EV_KEY, KEY_KBDINPUTASSIST_PREVGROUP,
+       EV_KEY, KEY_KBDINPUTASSIST_NEXTGROUP,
+       EV_KEY, KEY_KBDINPUTASSIST_ACCEPT,
+       EV_KEY, KEY_KBDINPUTASSIST_CANCEL,
+       EV_MSC, MSC_SCAN,
+       -1 , -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_VOLUME, 0, 4096, 0, 0, 0 },
+       { ABS_MISC, 0, 255, 0, 0, 0 },
+       { 0x29, 0, 255, 0, 0, 0 },
+       { 0x2a, 0, 255, 0, 0, 0 },
+       { 0x2b, 0, 255, 0, 0, 0 },
+       { 0x2c, 0, 255, 0, 0, 0 },
+       { 0x2d, 0, 255, 0, 0, 0 },
+       { 0x2e, 0, 255, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 255, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0, 0 },
+       { ABS_MT_TOUCH_MINOR, 0, 255, 0, 0, 0 },
+       { ABS_MT_WIDTH_MINOR, 0, 255, 0, 0, 0 },
+       { ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0, 0 },
+       { ABS_MT_ORIENTATION, 0, 255, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 255, 0, 0, 0 },
+       { .value = -1 },
+};
+
+struct litest_test_device litest_anker_mouse_kbd_device = {
+       .type = LITEST_ANKER_MOUSE_KBD,
+       .features = LITEST_KEYS | LITEST_WHEEL,
+       .shortname = "anker_kbd",
+       .setup = litest_anker_mouse_kbd_setup,
+       .interface = NULL,
+
+       .name = "USB Laser Game Mouse",
+       .id = &input_id,
+       .absinfo = absinfo,
+       .events = events,
+};
diff --git a/test/litest-device-apple-internal-keyboard.c b/test/litest-device-apple-internal-keyboard.c
new file mode 100644 (file)
index 0000000..c24403d
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_apple_keyboard_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_APPLE_KEYBOARD);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x5ac,
+       .product = 0x273,
+};
+
+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_ZENKAKUHANKAKU,
+       EV_KEY, KEY_102ND,
+       EV_KEY, KEY_F11,
+       EV_KEY, KEY_F12,
+       EV_KEY, KEY_RO,
+       EV_KEY, KEY_KATAKANA,
+       EV_KEY, KEY_HIRAGANA,
+       EV_KEY, KEY_HENKAN,
+       EV_KEY, KEY_KATAKANAHIRAGANA,
+       EV_KEY, KEY_MUHENKAN,
+       EV_KEY, KEY_KPJPCOMMA,
+       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_POWER,
+       EV_KEY, KEY_KPEQUAL,
+       EV_KEY, KEY_PAUSE,
+       EV_KEY, KEY_SCALE,
+       EV_KEY, KEY_KPCOMMA,
+       EV_KEY, KEY_HANGEUL,
+       EV_KEY, KEY_HANJA,
+       EV_KEY, KEY_YEN,
+       EV_KEY, KEY_LEFTMETA,
+       EV_KEY, KEY_RIGHTMETA,
+       EV_KEY, KEY_COMPOSE,
+       EV_KEY, KEY_STOP,
+       EV_KEY, KEY_AGAIN,
+       EV_KEY, KEY_PROPS,
+       EV_KEY, KEY_UNDO,
+       EV_KEY, KEY_FRONT,
+       EV_KEY, KEY_COPY,
+       EV_KEY, KEY_OPEN,
+       EV_KEY, KEY_PASTE,
+       EV_KEY, KEY_FIND,
+       EV_KEY, KEY_CUT,
+       EV_KEY, KEY_HELP,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_WWW,
+       EV_KEY, KEY_COFFEE,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_EJECTCD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_REWIND,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_EDIT,
+       EV_KEY, KEY_SCROLLUP,
+       EV_KEY, KEY_SCROLLDOWN,
+       EV_KEY, KEY_KPLEFTPAREN,
+       EV_KEY, KEY_KPRIGHTPAREN,
+       EV_KEY, KEY_F13,
+       EV_KEY, KEY_F14,
+       EV_KEY, KEY_F15,
+       EV_KEY, KEY_F16,
+       EV_KEY, KEY_F17,
+       EV_KEY, KEY_F18,
+       EV_KEY, KEY_F19,
+       EV_KEY, KEY_F20,
+       EV_KEY, KEY_F21,
+       EV_KEY, KEY_F22,
+       EV_KEY, KEY_F23,
+       EV_KEY, KEY_F24,
+       EV_KEY, KEY_DASHBOARD,
+       EV_KEY, KEY_FASTFORWARD,
+       EV_KEY, KEY_BRIGHTNESSDOWN,
+       EV_KEY, KEY_BRIGHTNESSUP,
+       EV_KEY, KEY_SWITCHVIDEOMODE,
+       EV_KEY, KEY_KBDILLUMTOGGLE,
+       EV_KEY, KEY_KBDILLUMDOWN,
+       EV_KEY, KEY_KBDILLUMUP,
+       EV_KEY, KEY_UNKNOWN,
+       EV_KEY, KEY_FN,
+       EV_MSC, MSC_SCAN,
+
+       EV_LED, LED_NUML,
+       EV_LED, LED_CAPSL,
+       EV_LED, LED_SCROLLL,
+       EV_LED, LED_COMPOSE,
+       EV_LED, LED_KANA,
+       -1, -1
+};
+
+struct litest_test_device litest_apple_keyboard_device = {
+       .type = LITEST_APPLE_KEYBOARD,
+       .features = LITEST_KEYS,
+       .shortname = "apple_keyboard",
+       .setup = litest_apple_keyboard_setup,
+       .interface = NULL,
+
+       .name = "Apple Inc. Apple Internal Keyboard / Trackpad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = NULL,
+};
diff --git a/test/litest-device-apple-magicmouse.c b/test/litest-device-apple-magicmouse.c
new file mode 100644 (file)
index 0000000..ce6fc78
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2016 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_magicmouse_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MAGICMOUSE);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 272 },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 400 },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 272 },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 400 },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x5,
+       .vendor = 0x5ac,
+       .product = 0x30d,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_WHEEL,
+       EV_REL, REL_HWHEEL,
+       -1 , -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_MT_SLOT, 0, 15, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 1020, 0, 0, 0 },
+       { ABS_MT_TOUCH_MINOR, 0, 1020, 0, 0, 0 },
+       { ABS_MT_ORIENTATION, -31, 32, 1, 0, 0 },
+       { ABS_MT_POSITION_X, -1100, 1258, 4, 0, 26 },
+       { ABS_MT_POSITION_Y, -1589, 2047, 4, 0, 26 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_magicmouse_device = {
+       .type = LITEST_MAGICMOUSE,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
+       .shortname = "magicmouse",
+       .setup = litest_magicmouse_setup,
+       .interface = &interface,
+
+       .name = "Apple Magic Mouse",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-asus-rog-gladius.c b/test/litest-device-asus-rog-gladius.c
new file mode 100644 (file)
index 0000000..a44396f
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+/* Note: this is the second event node of this mouse only, the first event
+ * node is just a normal mouse */
+
+static void litest_mouse_gladius_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MOUSE_GLADIUS);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x0b05,
+       .product = 0x181a,
+};
+
+static int events[] = {
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_HWHEEL,
+       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_ZENKAKUHANKAKU,
+       EV_KEY, KEY_102ND,
+       EV_KEY, KEY_F11,
+       EV_KEY, KEY_F12,
+       EV_KEY, KEY_RO,
+       EV_KEY, KEY_KATAKANA,
+       EV_KEY, KEY_HIRAGANA,
+       EV_KEY, KEY_HENKAN,
+       EV_KEY, KEY_KATAKANAHIRAGANA,
+       EV_KEY, KEY_MUHENKAN,
+       EV_KEY, KEY_KPJPCOMMA,
+       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_POWER,
+       EV_KEY, KEY_KPEQUAL,
+       EV_KEY, KEY_PAUSE,
+       EV_KEY, KEY_KPCOMMA,
+       EV_KEY, KEY_HANGEUL,
+       EV_KEY, KEY_HANJA,
+       EV_KEY, KEY_YEN,
+       EV_KEY, KEY_LEFTMETA,
+       EV_KEY, KEY_RIGHTMETA,
+       EV_KEY, KEY_COMPOSE,
+       EV_KEY, KEY_STOP,
+       EV_KEY, KEY_AGAIN,
+       EV_KEY, KEY_PROPS,
+       EV_KEY, KEY_UNDO,
+       EV_KEY, KEY_FRONT,
+       EV_KEY, KEY_COPY,
+       EV_KEY, KEY_OPEN,
+       EV_KEY, KEY_PASTE,
+       EV_KEY, KEY_FIND,
+       EV_KEY, KEY_CUT,
+       EV_KEY, KEY_HELP,
+       EV_KEY, KEY_MENU,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_FILE,
+       EV_KEY, KEY_WWW,
+       EV_KEY, KEY_COFFEE,
+       EV_KEY, KEY_MAIL,
+       EV_KEY, KEY_BOOKMARKS,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_EJECTCD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_RECORD,
+       EV_KEY, KEY_REWIND,
+       EV_KEY, KEY_PHONE,
+       EV_KEY, KEY_CONFIG,
+       EV_KEY, KEY_HOMEPAGE,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_EXIT,
+       EV_KEY, KEY_EDIT,
+       EV_KEY, KEY_SCROLLUP,
+       EV_KEY, KEY_SCROLLDOWN,
+       EV_KEY, KEY_KPLEFTPAREN,
+       EV_KEY, KEY_KPRIGHTPAREN,
+       EV_KEY, KEY_NEW,
+       EV_KEY, KEY_REDO,
+       EV_KEY, KEY_F13,
+       EV_KEY, KEY_F14,
+       EV_KEY, KEY_F15,
+       EV_KEY, KEY_F16,
+       EV_KEY, KEY_F17,
+       EV_KEY, KEY_F18,
+       EV_KEY, KEY_F19,
+       EV_KEY, KEY_F20,
+       EV_KEY, KEY_F21,
+       EV_KEY, KEY_F22,
+       EV_KEY, KEY_F23,
+       EV_KEY, KEY_F24,
+       EV_KEY, KEY_CLOSE,
+       EV_KEY, KEY_PLAY,
+       EV_KEY, KEY_FASTFORWARD,
+       EV_KEY, KEY_BASSBOOST,
+       EV_KEY, KEY_PRINT,
+       EV_KEY, KEY_CAMERA,
+       EV_KEY, KEY_CHAT,
+       EV_KEY, KEY_SEARCH,
+       EV_KEY, KEY_FINANCE,
+       EV_KEY, KEY_CANCEL,
+       EV_KEY, KEY_BRIGHTNESSDOWN,
+       EV_KEY, KEY_BRIGHTNESSUP,
+       EV_KEY, KEY_KBDILLUMTOGGLE,
+       EV_KEY, KEY_SEND,
+       EV_KEY, KEY_REPLY,
+       EV_KEY, KEY_FORWARDMAIL,
+       EV_KEY, KEY_SAVE,
+       EV_KEY, KEY_DOCUMENTS,
+       EV_KEY, KEY_UNKNOWN,
+       EV_KEY, KEY_VIDEO_NEXT,
+       EV_KEY, KEY_BRIGHTNESS_AUTO,
+       EV_KEY, BTN_0,
+       EV_KEY, KEY_SELECT,
+       EV_KEY, KEY_GOTO,
+       EV_KEY, KEY_INFO,
+       EV_KEY, KEY_PROGRAM,
+       EV_KEY, KEY_PVR,
+       EV_KEY, KEY_SUBTITLE,
+       EV_KEY, KEY_ZOOM,
+       EV_KEY, KEY_KEYBOARD,
+       EV_KEY, KEY_PC,
+       EV_KEY, KEY_TV,
+       EV_KEY, KEY_TV2,
+       EV_KEY, KEY_VCR,
+       EV_KEY, KEY_VCR2,
+       EV_KEY, KEY_SAT,
+       EV_KEY, KEY_CD,
+       EV_KEY, KEY_TAPE,
+       EV_KEY, KEY_TUNER,
+       EV_KEY, KEY_PLAYER,
+       EV_KEY, KEY_DVD,
+       EV_KEY, KEY_AUDIO,
+       EV_KEY, KEY_VIDEO,
+       EV_KEY, KEY_MEMO,
+       EV_KEY, KEY_CALENDAR,
+       EV_KEY, KEY_RED,
+       EV_KEY, KEY_GREEN,
+       EV_KEY, KEY_YELLOW,
+       EV_KEY, KEY_BLUE,
+       EV_KEY, KEY_CHANNELUP,
+       EV_KEY, KEY_CHANNELDOWN,
+       EV_KEY, KEY_LAST,
+       EV_KEY, KEY_NEXT,
+       EV_KEY, KEY_RESTART,
+       EV_KEY, KEY_SLOW,
+       EV_KEY, KEY_SHUFFLE,
+       EV_KEY, KEY_PREVIOUS,
+       EV_KEY, KEY_VIDEOPHONE,
+       EV_KEY, KEY_GAMES,
+       EV_KEY, KEY_ZOOMIN,
+       EV_KEY, KEY_ZOOMOUT,
+       EV_KEY, KEY_ZOOMRESET,
+       EV_KEY, KEY_WORDPROCESSOR,
+       EV_KEY, KEY_EDITOR,
+       EV_KEY, KEY_SPREADSHEET,
+       EV_KEY, KEY_GRAPHICSEDITOR,
+       EV_KEY, KEY_PRESENTATION,
+       EV_KEY, KEY_DATABASE,
+       EV_KEY, KEY_NEWS,
+       EV_KEY, KEY_VOICEMAIL,
+       EV_KEY, KEY_ADDRESSBOOK,
+       EV_KEY, KEY_MESSENGER,
+       EV_KEY, KEY_DISPLAYTOGGLE,
+       EV_KEY, KEY_SPELLCHECK,
+       EV_KEY, KEY_LOGOFF,
+       EV_KEY, KEY_MEDIA_REPEAT,
+       EV_KEY, KEY_IMAGES,
+       EV_KEY, KEY_BUTTONCONFIG,
+       EV_KEY, KEY_TASKMANAGER,
+       EV_KEY, KEY_JOURNAL,
+       EV_KEY, KEY_CONTROLPANEL,
+       EV_KEY, KEY_APPSELECT,
+       EV_KEY, KEY_SCREENSAVER,
+       EV_KEY, KEY_VOICECOMMAND,
+       EV_KEY, KEY_BRIGHTNESS_MIN,
+       EV_KEY, KEY_BRIGHTNESS_MAX,
+       EV_LED, LED_NUML,
+       EV_LED, LED_CAPSL,
+       EV_LED, LED_SCROLLL,
+       EV_LED, LED_COMPOSE,
+       EV_LED, LED_KANA,
+       -1 , -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_VOLUME, 0, 668, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_mouse_gladius_device = {
+       .type = LITEST_MOUSE_GLADIUS,
+       .features = LITEST_RELATIVE | LITEST_WHEEL | LITEST_KEYS,
+       .shortname = "mouse_gladius",
+       .setup = litest_mouse_gladius_setup,
+       .interface = NULL,
+
+       .name = "ASUS ROG GLADIUS",
+       .id = &input_id,
+       .absinfo = absinfo,
+       .events = events,
+};
diff --git a/test/litest-device-atmel-hover.c b/test/litest-device-atmel-hover.c
new file mode 100644 (file)
index 0000000..1691549
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include "libinput-util.h"
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+atmel_hover_create(struct litest_device *d);
+
+static void
+litest_atmel_hover_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_ATMEL_HOVER);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event up[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 },
+       { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = 1 },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = 0  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .touch_up_events = up,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x18,
+       .vendor = 0x0,
+       .product = 0x0,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 960, 0, 0, 10 },
+       { ABS_Y, 0, 540, 0, 0, 10 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0, 0 },
+       { ABS_MT_ORIENTATION, 0, 255, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 960, 0, 0, 10 },
+       { ABS_MT_POSITION_Y, 0, 540, 0, 0, 10 },
+       { ABS_MT_TOOL_TYPE, 0, 2, 0, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_MT_DISTANCE, 0, 1, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_atmel_hover_device = {
+       .type = LITEST_ATMEL_HOVER,
+       .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_CLICKPAD | LITEST_HOVER,
+       .shortname = "atmel hover",
+       .setup = litest_atmel_hover_setup,
+       .interface = &interface,
+       .create = atmel_hover_create,
+
+       .name = "Atmel maXTouch Touchpad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
+
+static void
+atmel_hover_create(struct litest_device *d)
+{
+       struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt));
+       assert(semi_mt);
+
+       d->private = semi_mt;
+
+       d->uinput = litest_create_uinput_device_from_description(
+                       litest_atmel_hover_device.name,
+                       litest_atmel_hover_device.id,
+                       absinfo,
+                       events);
+       d->interface = &interface;
+}
diff --git a/test/litest-device-bcm5974.c b/test/litest-device-bcm5974.c
new file mode 100644 (file)
index 0000000..cbf2a74
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_bcm5974_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_BCM5974);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_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:
+       case ABS_MT_PRESSURE:
+               *value = 30;
+               return 0;
+       }
+       return 1;
+}
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+
+       .get_axis_default = get_axis_default,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, -4824, 4824, 0, 0, 0 },
+       { ABS_Y, -172, 4290, 0, 0, 0 },
+       { ABS_PRESSURE, 0, 256, 5, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 16, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 15, 0, 0, 0 },
+       { ABS_MT_POSITION_X, -4824, 4824, 17, 0, 0 },
+       { ABS_MT_POSITION_Y, -172, 4290, 17, 0, 0 },
+       { ABS_MT_ORIENTATION, -16384, 16384, 3276, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 2048, 81, 0, 0 },
+       { ABS_MT_TOUCH_MINOR, 0, 2048, 81, 0, 0 },
+       { ABS_MT_WIDTH_MAJOR, 0, 2048, 81, 0, 0 },
+       { ABS_MT_WIDTH_MINOR, 0, 2048, 81, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x5ac,
+       .product = 0x249,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       -1, -1
+};
+
+struct litest_test_device litest_bcm5974_device = {
+       .type = LITEST_BCM5974,
+       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD |
+                   LITEST_BUTTON | LITEST_APPLE_CLICKPAD,
+       .shortname = "bcm5974",
+       .setup = litest_bcm5974_setup,
+       .interface = &interface,
+
+       .name = "bcm5974",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-cyborg-rat-5.c b/test/litest-device-cyborg-rat-5.c
new file mode 100644 (file)
index 0000000..a1db77a
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_cyborg_rat_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_CYBORG_RAT);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x6a3,
+       .product = 0xcd5,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_KEY, BTN_SIDE,
+       EV_KEY, BTN_EXTRA,
+       EV_KEY, BTN_FORWARD,
+       EV_KEY, BTN_TASK,
+       EV_KEY, 0x118,
+       EV_KEY, 0x119,
+       EV_KEY, 0x11a,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_WHEEL,
+       -1 , -1,
+};
+
+struct litest_test_device litest_cyborg_rat_device = {
+       .type = LITEST_CYBORG_RAT,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
+       .shortname = "cyborg_rat",
+       .setup = litest_cyborg_rat_setup,
+       .interface = NULL,
+
+       .name = "Saitek Cyborg R.A.T.5 Mouse",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+};
diff --git a/test/litest-device-elantech-touchpad.c b/test/litest-device-elantech-touchpad.c
new file mode 100644 (file)
index 0000000..abf58d1
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_elantech_touchpad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_ELANTECH_TOUCHPAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_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_PRESSURE:
+       case ABS_MT_PRESSURE:
+               *value = 30;
+               return 0;
+       }
+       return 1;
+}
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+
+       .get_axis_default = get_axis_default,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0xe,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1280, 0, 0, 0 },
+       { ABS_Y, 0, 704, 0, 0, 0 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 1280, 0, 0, 0 },
+       { ABS_MT_POSITION_Y, 0, 704, 0, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_elantech_touchpad_device = {
+       .type = LITEST_ELANTECH_TOUCHPAD,
+       .features = LITEST_TOUCHPAD | LITEST_BUTTON,
+       .shortname = "elantech",
+       .setup = litest_elantech_touchpad_setup,
+       .interface = &interface,
+
+       .name = "ETPS/2 Elantech Touchpad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-generic-singletouch.c b/test/litest-device-generic-singletouch.c
new file mode 100644 (file)
index 0000000..c34c62d
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_generic_singletouch_touch_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_GENERIC_SINGLETOUCH);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 10000, 20000, 0, 0, 10 },
+       { ABS_Y, -2000, 2000, 0, 0, 9 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x01,
+       .vendor = 0x02,
+       .product = 0x03,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1,
+};
+
+struct litest_test_device litest_generic_singletouch_device = {
+       .type = LITEST_GENERIC_SINGLETOUCH,
+       .features = LITEST_SINGLE_TOUCH,
+       .shortname = "generic-singletouch",
+       .setup = litest_generic_singletouch_touch_setup,
+       .interface = &interface,
+
+       .name = "generic_singletouch",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-huion-pentablet.c b/test/litest-device-huion-pentablet.c
new file mode 100644 (file)
index 0000000..e030f20
--- /dev/null
@@ -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-keyboard-all-codes.c b/test/litest-device-keyboard-all-codes.c
new file mode 100644 (file)
index 0000000..fffd0a6
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void all_codes_create(struct litest_device *d);
+
+static void litest_keyboard_all_codes_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_KEYBOARD);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x1,
+       .product = 0x1,
+};
+
+struct litest_test_device litest_keyboard_all_codes_device = {
+       .type = LITEST_KEYBOARD_ALL_CODES,
+       .features = LITEST_KEYS,
+       .shortname = "keyboard all event codes",
+       .setup = litest_keyboard_all_codes_setup,
+       .interface = NULL,
+       .create = all_codes_create,
+
+       .name = "All event codes keyboard",
+       .id = &input_id,
+       .events = NULL,
+       .absinfo = NULL,
+};
+
+static void
+all_codes_create(struct litest_device *d)
+{
+       int events[KEY_MAX * 2 + 2];
+       int code, idx;
+
+       for (idx = 0, code = 0; code < KEY_MAX; code++) {
+               events[idx++] = EV_KEY;
+               events[idx++] = code;
+       }
+       events[idx++] = -1;
+       events[idx++] = -1;
+
+       d->uinput = litest_create_uinput_device_from_description(litest_keyboard_all_codes_device.name,
+                                                                litest_keyboard_all_codes_device.id,
+                                                                NULL,
+                                                                events);
+}
diff --git a/test/litest-device-keyboard-razer-blackwidow.c b/test/litest-device-keyboard-razer-blackwidow.c
new file mode 100644 (file)
index 0000000..7e9823a
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+/* Recording from https://bugs.freedesktop.org/show_bug.cgi?id=89783
+ * This is the second of 4 devices exported by this keyboard, the first is
+ * just a basic keyboard that is identical to the normal litest-keyboard.c
+ * file.
+ */
+
+static void litest_blackwidow_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_KEYBOARD_BLACKWIDOW);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x1532,
+       .product = 0x11b,
+};
+
+static int events[] = {
+       EV_REL, REL_HWHEEL,
+       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_ZENKAKUHANKAKU,
+       EV_KEY, KEY_102ND,
+       EV_KEY, KEY_F11,
+       EV_KEY, KEY_F12,
+       EV_KEY, KEY_RO,
+       EV_KEY, KEY_KATAKANA,
+       EV_KEY, KEY_HIRAGANA,
+       EV_KEY, KEY_HENKAN,
+       EV_KEY, KEY_KATAKANAHIRAGANA,
+       EV_KEY, KEY_MUHENKAN,
+       EV_KEY, KEY_KPJPCOMMA,
+       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_POWER,
+       EV_KEY, KEY_KPEQUAL,
+       EV_KEY, KEY_PAUSE,
+       EV_KEY, KEY_KPCOMMA,
+       EV_KEY, KEY_HANGEUL,
+       EV_KEY, KEY_HANJA,
+       EV_KEY, KEY_YEN,
+       EV_KEY, KEY_LEFTMETA,
+       EV_KEY, KEY_RIGHTMETA,
+       EV_KEY, KEY_COMPOSE,
+       EV_KEY, KEY_STOP,
+       EV_KEY, KEY_AGAIN,
+       EV_KEY, KEY_PROPS,
+       EV_KEY, KEY_UNDO,
+       EV_KEY, KEY_FRONT,
+       EV_KEY, KEY_COPY,
+       EV_KEY, KEY_OPEN,
+       EV_KEY, KEY_PASTE,
+       EV_KEY, KEY_FIND,
+       EV_KEY, KEY_CUT,
+       EV_KEY, KEY_HELP,
+       EV_KEY, KEY_MENU,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_WAKEUP,
+       EV_KEY, KEY_FILE,
+       EV_KEY, KEY_WWW,
+       EV_KEY, KEY_COFFEE,
+       EV_KEY, KEY_MAIL,
+       EV_KEY, KEY_BOOKMARKS,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_EJECTCD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_RECORD,
+       EV_KEY, KEY_REWIND,
+       EV_KEY, KEY_PHONE,
+       EV_KEY, KEY_CONFIG,
+       EV_KEY, KEY_HOMEPAGE,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_EXIT,
+       EV_KEY, KEY_EDIT,
+       EV_KEY, KEY_SCROLLUP,
+       EV_KEY, KEY_SCROLLDOWN,
+       EV_KEY, KEY_KPLEFTPAREN,
+       EV_KEY, KEY_KPRIGHTPAREN,
+       EV_KEY, KEY_NEW,
+       EV_KEY, KEY_F13,
+       EV_KEY, KEY_F14,
+       EV_KEY, KEY_F15,
+       EV_KEY, KEY_F16,
+       EV_KEY, KEY_F17,
+       EV_KEY, KEY_F18,
+       EV_KEY, KEY_F19,
+       EV_KEY, KEY_F20,
+       EV_KEY, KEY_F21,
+       EV_KEY, KEY_F22,
+       EV_KEY, KEY_F23,
+       EV_KEY, KEY_F24,
+       EV_KEY, KEY_CLOSE,
+       EV_KEY, KEY_PLAY,
+       EV_KEY, KEY_FASTFORWARD,
+       EV_KEY, KEY_BASSBOOST,
+       EV_KEY, KEY_PRINT,
+       EV_KEY, KEY_CAMERA,
+       EV_KEY, KEY_CHAT,
+       EV_KEY, KEY_SEARCH,
+       EV_KEY, KEY_FINANCE,
+       EV_KEY, KEY_BRIGHTNESSDOWN,
+       EV_KEY, KEY_BRIGHTNESSUP,
+       EV_KEY, KEY_KBDILLUMTOGGLE,
+       EV_KEY, KEY_SAVE,
+       EV_KEY, KEY_DOCUMENTS,
+       EV_KEY, KEY_UNKNOWN,
+       EV_KEY, KEY_VIDEO_NEXT,
+       EV_KEY, KEY_BRIGHTNESS_AUTO,
+       EV_KEY, BTN_0,
+       EV_KEY, KEY_SELECT,
+       EV_KEY, KEY_GOTO,
+       EV_KEY, KEY_INFO,
+       EV_KEY, KEY_PROGRAM,
+       EV_KEY, KEY_PVR,
+       EV_KEY, KEY_SUBTITLE,
+       EV_KEY, KEY_ZOOM,
+       EV_KEY, KEY_KEYBOARD,
+       EV_KEY, KEY_PC,
+       EV_KEY, KEY_TV,
+       EV_KEY, KEY_TV2,
+       EV_KEY, KEY_VCR,
+       EV_KEY, KEY_VCR2,
+       EV_KEY, KEY_SAT,
+       EV_KEY, KEY_CD,
+       EV_KEY, KEY_TAPE,
+       EV_KEY, KEY_TUNER,
+       EV_KEY, KEY_PLAYER,
+       EV_KEY, KEY_DVD,
+       EV_KEY, KEY_AUDIO,
+       EV_KEY, KEY_VIDEO,
+       EV_KEY, KEY_MEMO,
+       EV_KEY, KEY_CALENDAR,
+       EV_KEY, KEY_RED,
+       EV_KEY, KEY_GREEN,
+       EV_KEY, KEY_YELLOW,
+       EV_KEY, KEY_BLUE,
+       EV_KEY, KEY_CHANNELUP,
+       EV_KEY, KEY_CHANNELDOWN,
+       EV_KEY, KEY_LAST,
+       EV_KEY, KEY_NEXT,
+       EV_KEY, KEY_RESTART,
+       EV_KEY, KEY_SLOW,
+       EV_KEY, KEY_SHUFFLE,
+       EV_KEY, KEY_PREVIOUS,
+       EV_KEY, KEY_VIDEOPHONE,
+       EV_KEY, KEY_GAMES,
+       EV_KEY, KEY_ZOOMIN,
+       EV_KEY, KEY_ZOOMOUT,
+       EV_KEY, KEY_ZOOMRESET,
+       EV_KEY, KEY_WORDPROCESSOR,
+       EV_KEY, KEY_EDITOR,
+       EV_KEY, KEY_SPREADSHEET,
+       EV_KEY, KEY_GRAPHICSEDITOR,
+       EV_KEY, KEY_PRESENTATION,
+       EV_KEY, KEY_DATABASE,
+       EV_KEY, KEY_NEWS,
+       EV_KEY, KEY_VOICEMAIL,
+       EV_KEY, KEY_ADDRESSBOOK,
+       EV_KEY, KEY_MESSENGER,
+       EV_KEY, KEY_DISPLAYTOGGLE,
+       EV_KEY, KEY_SPELLCHECK,
+       EV_KEY, KEY_LOGOFF,
+       EV_KEY, KEY_MEDIA_REPEAT,
+       EV_KEY, KEY_IMAGES,
+       EV_KEY, KEY_BUTTONCONFIG,
+       EV_KEY, KEY_TASKMANAGER,
+       EV_KEY, KEY_JOURNAL,
+       EV_KEY, KEY_CONTROLPANEL,
+       EV_KEY, KEY_APPSELECT,
+       EV_KEY, KEY_SCREENSAVER,
+       EV_KEY, KEY_VOICECOMMAND,
+       EV_KEY, KEY_BRIGHTNESS_MIN,
+       EV_KEY, KEY_BRIGHTNESS_MAX,
+       EV_MSC, MSC_SCAN,
+       -1 , -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_VOLUME, 0, 572, 0, 0, 0 },
+       { ABS_MISC, 0, 255, 0, 0, 0 },
+       { 0x29, 0, 255, 0, 0, 0 },
+       { 0x2a, 0, 255, 0, 0, 0 },
+       { 0x2b, 0, 255, 0, 0, 0 },
+       { 0x2c, 0, 255, 0, 0, 0 },
+       { 0x2d, 0, 255, 0, 0, 0 },
+       { 0x2e, 0, 255, 0, 0, 0 },
+       { 0x2f, 0, 255, 0, 0, 0 },
+       { 0x30, 0, 255, 0, 0, 0 },
+       { 0x31, 0, 255, 0, 0, 0 },
+       { 0x32, 0, 255, 0, 0, 0 },
+       { 0x33, 0, 255, 0, 0, 0 },
+       { 0x34, 0, 255, 0, 0, 0 },
+       { 0x35, 0, 255, 0, 0, 0 },
+       { 0x36, 0, 255, 0, 0, 0 },
+       { 0x37, 0, 255, 0, 0, 0 },
+       { 0x38, 0, 255, 0, 0, 0 },
+       { 0x39, 0, 255, 0, 0, 0 },
+       { 0x3a, 0, 255, 0, 0, 0 },
+       { 0x3b, 0, 255, 0, 0, 0 },
+       { 0x3c, 0, 255, 0, 0, 0 },
+       { 0x3d, 0, 255, 0, 0, 0 },
+       { 0x3e, 0, 255, 0, 0, 0 },
+       { 0x3f, 0, 255, 0, 0, 0 },
+       { .value = -1 },
+};
+
+struct litest_test_device litest_keyboard_blackwidow_device = {
+       .type = LITEST_KEYBOARD_BLACKWIDOW,
+       .features = LITEST_KEYS | LITEST_WHEEL,
+       .shortname = "blackwidow",
+       .setup = litest_blackwidow_setup,
+       .interface = NULL,
+
+       .name = "Razer Razer BlackWidow 2013",
+       .id = &input_id,
+       .absinfo = absinfo,
+       .events = events,
+};
diff --git a/test/litest-device-keyboard.c b/test/litest-device-keyboard.c
new file mode 100644 (file)
index 0000000..e8b9c13
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_keyboard_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_KEYBOARD);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x1,
+       .product = 0x1,
+};
+
+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_ZENKAKUHANKAKU,
+       EV_KEY, KEY_102ND,
+       EV_KEY, KEY_F11,
+       EV_KEY, KEY_F12,
+       EV_KEY, KEY_RO,
+       EV_KEY, KEY_KATAKANA,
+       EV_KEY, KEY_HIRAGANA,
+       EV_KEY, KEY_HENKAN,
+       EV_KEY, KEY_KATAKANAHIRAGANA,
+       EV_KEY, KEY_MUHENKAN,
+       EV_KEY, KEY_KPJPCOMMA,
+       EV_KEY, KEY_KPENTER,
+       EV_KEY, KEY_RIGHTCTRL,
+       EV_KEY, KEY_KPSLASH,
+       EV_KEY, KEY_SYSRQ,
+       EV_KEY, KEY_RIGHTALT,
+       EV_KEY, KEY_LINEFEED,
+       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_MACRO,
+       EV_KEY, KEY_MUTE,
+       EV_KEY, KEY_VOLUMEDOWN,
+       EV_KEY, KEY_VOLUMEUP,
+       EV_KEY, KEY_POWER,
+       EV_KEY, KEY_KPEQUAL,
+       EV_KEY, KEY_KPPLUSMINUS,
+       EV_KEY, KEY_PAUSE,
+       /* EV_KEY,  KEY_SCALE, */
+       EV_KEY, KEY_KPCOMMA,
+       EV_KEY, KEY_HANGEUL,
+       EV_KEY, KEY_HANJA,
+       EV_KEY, KEY_YEN,
+       EV_KEY, KEY_LEFTMETA,
+       EV_KEY, KEY_RIGHTMETA,
+       EV_KEY, KEY_COMPOSE,
+       EV_KEY, KEY_STOP,
+
+       EV_KEY, KEY_MENU,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SETUP,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_WAKEUP,
+       EV_KEY, KEY_SCREENLOCK,
+       EV_KEY, KEY_DIRECTION,
+       EV_KEY, KEY_CYCLEWINDOWS,
+       EV_KEY, KEY_MAIL,
+       EV_KEY, KEY_BOOKMARKS,
+       EV_KEY, KEY_COMPUTER,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_HOMEPAGE,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_F14,
+       EV_KEY, KEY_F15,
+       EV_KEY, KEY_SEARCH,
+       EV_KEY, KEY_MEDIA,
+       EV_KEY, KEY_FN,
+       -1, -1,
+};
+
+struct litest_test_device litest_keyboard_device = {
+       .type = LITEST_KEYBOARD,
+       .features = LITEST_KEYS,
+       .shortname = "default keyboard",
+       .setup = litest_keyboard_setup,
+       .interface = NULL,
+
+       .name = "AT Translated Set 2 keyboard",
+       .id = &input_id,
+       .events = events,
+       .absinfo = NULL,
+};
diff --git a/test/litest-device-logitech-trackball.c b/test/litest-device-logitech-trackball.c
new file mode 100644 (file)
index 0000000..1a5d896
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_logitech_trackball_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_LOGITECH_TRACKBALL);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x46d,
+       .product = 0xc408,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_KEY, BTN_SIDE,
+       EV_KEY, BTN_EXTRA,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       -1 , -1,
+};
+
+struct litest_test_device litest_logitech_trackball_device = {
+       .type = LITEST_LOGITECH_TRACKBALL,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_TRACKBALL,
+       .shortname = "logitech trackball",
+       .setup = litest_logitech_trackball_setup,
+       .interface = NULL,
+
+       .name = "Logitech USB Trackball",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+};
diff --git a/test/litest-device-magic-trackpad.c b/test/litest-device-magic-trackpad.c
new file mode 100644 (file)
index 0000000..588d6f5
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_magicpad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MAGIC_TRACKPAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 272 },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 400 },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = 272 },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = 400 },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, -2909, 3167, 4, 0, 46 },
+       { ABS_Y, -2456, 2565, 4, 0, 45 },
+       { ABS_MT_SLOT, 0, 15, 0, 0, 0 },
+       { ABS_MT_POSITION_X, -2909, 3167, 4, 0, 46 },
+       { ABS_MT_POSITION_Y, -2456, 2565, 4, 0, 45 },
+       { ABS_MT_ORIENTATION, -31, 32, 1, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 1020, 4, 0, 0 },
+       { ABS_MT_TOUCH_MINOR, 0, 1020, 4, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x5,
+       .vendor = 0x5ac,
+       .product = 0x30e,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       -1, -1
+};
+
+struct litest_test_device litest_magicpad_device = {
+       .type = LITEST_MAGIC_TRACKPAD,
+       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD |
+                   LITEST_BUTTON | LITEST_APPLE_CLICKPAD,
+       .shortname = "magic trackpad",
+       .setup = litest_magicpad_setup,
+       .interface = &interface,
+
+       .name = "Apple Wireless Trackpad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-mouse-low-dpi.c b/test/litest-device-mouse-low-dpi.c
new file mode 100644 (file)
index 0000000..dccf40f
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_mouse_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MOUSE_LOW_DPI);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x1,
+       .product = 0x1,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_WHEEL,
+       -1 , -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"touchpad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n"
+"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Low DPI Mouse*\",\\\n"
+"    ENV{MOUSE_DPI}=\"400@125\"\n"
+"\n"
+"LABEL=\"touchpad_end\"";
+
+struct litest_test_device litest_mouse_low_dpi_device = {
+       .type = LITEST_MOUSE_LOW_DPI,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
+       .shortname = "low-dpi mouse",
+       .setup = litest_mouse_setup,
+       .interface = NULL,
+
+       .name = "Low DPI Mouse",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-mouse-roccat.c b/test/litest-device-mouse-roccat.c
new file mode 100644 (file)
index 0000000..013374e
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_mouse_roccat_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MOUSE_ROCCAT);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x1e7d,
+       .product = 0x2e22,
+};
+
+static int events[] = {
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_WHEEL,
+       EV_REL, REL_HWHEEL,
+       EV_REL, REL_DIAL,
+       EV_KEY, KEY_ESC,
+       EV_KEY, KEY_ENTER,
+       EV_KEY, KEY_KPMINUS,
+       EV_KEY, KEY_KPPLUS,
+       EV_KEY, KEY_UP,
+       EV_KEY, KEY_LEFT,
+       EV_KEY, KEY_RIGHT,
+       EV_KEY, KEY_DOWN,
+       EV_KEY, KEY_MUTE,
+       EV_KEY, KEY_VOLUMEDOWN,
+       EV_KEY, KEY_VOLUMEUP,
+       EV_KEY, KEY_POWER,
+       EV_KEY, KEY_PAUSE,
+       EV_KEY, KEY_STOP,
+       EV_KEY, KEY_PROPS,
+       EV_KEY, KEY_UNDO,
+       EV_KEY, KEY_COPY,
+       EV_KEY, KEY_OPEN,
+       EV_KEY, KEY_PASTE,
+       EV_KEY, KEY_FIND,
+       EV_KEY, KEY_CUT,
+       EV_KEY, KEY_HELP,
+       EV_KEY, KEY_MENU,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_FILE,
+       EV_KEY, KEY_WWW,
+       EV_KEY, KEY_COFFEE,
+       EV_KEY, KEY_MAIL,
+       EV_KEY, KEY_BOOKMARKS,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_EJECTCD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_RECORD,
+       EV_KEY, KEY_REWIND,
+       EV_KEY, KEY_PHONE,
+       EV_KEY, KEY_CONFIG,
+       EV_KEY, KEY_HOMEPAGE,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_EXIT,
+       EV_KEY, KEY_SCROLLUP,
+       EV_KEY, KEY_SCROLLDOWN,
+       EV_KEY, KEY_NEW,
+       EV_KEY, KEY_CLOSE,
+       EV_KEY, KEY_PLAY,
+       EV_KEY, KEY_FASTFORWARD,
+       EV_KEY, KEY_BASSBOOST,
+       EV_KEY, KEY_PRINT,
+       EV_KEY, KEY_CAMERA,
+       EV_KEY, KEY_CHAT,
+       EV_KEY, KEY_SEARCH,
+       EV_KEY, KEY_FINANCE,
+       EV_KEY, KEY_BRIGHTNESSDOWN,
+       EV_KEY, KEY_BRIGHTNESSUP,
+       EV_KEY, KEY_KBDILLUMTOGGLE,
+       EV_KEY, KEY_SAVE,
+       EV_KEY, KEY_DOCUMENTS,
+       EV_KEY, KEY_UNKNOWN,
+       EV_KEY, KEY_VIDEO_NEXT,
+       EV_KEY, KEY_BRIGHTNESS_AUTO,
+       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, KEY_SELECT,
+       EV_KEY, KEY_GOTO,
+       EV_KEY, KEY_INFO,
+       EV_KEY, KEY_PROGRAM,
+       EV_KEY, KEY_PVR,
+       EV_KEY, KEY_SUBTITLE,
+       EV_KEY, KEY_ZOOM,
+       EV_KEY, KEY_KEYBOARD,
+       EV_KEY, KEY_PC,
+       EV_KEY, KEY_TV,
+       EV_KEY, KEY_TV2,
+       EV_KEY, KEY_VCR,
+       EV_KEY, KEY_VCR2,
+       EV_KEY, KEY_SAT,
+       EV_KEY, KEY_CD,
+       EV_KEY, KEY_TAPE,
+       EV_KEY, KEY_TUNER,
+       EV_KEY, KEY_PLAYER,
+       EV_KEY, KEY_DVD,
+       EV_KEY, KEY_AUDIO,
+       EV_KEY, KEY_VIDEO,
+       EV_KEY, KEY_MEMO,
+       EV_KEY, KEY_CALENDAR,
+       EV_KEY, KEY_RED,
+       EV_KEY, KEY_GREEN,
+       EV_KEY, KEY_YELLOW,
+       EV_KEY, KEY_BLUE,
+       EV_KEY, KEY_CHANNELUP,
+       EV_KEY, KEY_CHANNELDOWN,
+       EV_KEY, KEY_LAST,
+       EV_KEY, KEY_NEXT,
+       EV_KEY, KEY_RESTART,
+       EV_KEY, KEY_SLOW,
+       EV_KEY, KEY_SHUFFLE,
+       EV_KEY, KEY_PREVIOUS,
+       EV_KEY, KEY_VIDEOPHONE,
+       EV_KEY, KEY_GAMES,
+       EV_KEY, KEY_ZOOMIN,
+       EV_KEY, KEY_ZOOMOUT,
+       EV_KEY, KEY_ZOOMRESET,
+       EV_KEY, KEY_WORDPROCESSOR,
+       EV_KEY, KEY_EDITOR,
+       EV_KEY, KEY_SPREADSHEET,
+       EV_KEY, KEY_GRAPHICSEDITOR,
+       EV_KEY, KEY_PRESENTATION,
+       EV_KEY, KEY_DATABASE,
+       EV_KEY, KEY_NEWS,
+       EV_KEY, KEY_VOICEMAIL,
+       EV_KEY, KEY_ADDRESSBOOK,
+       EV_KEY, KEY_MESSENGER,
+       EV_KEY, KEY_DISPLAYTOGGLE,
+       EV_KEY, KEY_SPELLCHECK,
+       EV_KEY, KEY_LOGOFF,
+       EV_KEY, KEY_MEDIA_REPEAT,
+       EV_KEY, KEY_IMAGES,
+       EV_KEY, KEY_BUTTONCONFIG,
+       EV_KEY, KEY_TASKMANAGER,
+       EV_KEY, KEY_JOURNAL,
+       EV_KEY, KEY_CONTROLPANEL,
+       EV_KEY, KEY_APPSELECT,
+       EV_KEY, KEY_SCREENSAVER,
+       EV_KEY, KEY_VOICECOMMAND,
+       EV_KEY, KEY_BRIGHTNESS_MIN,
+       EV_KEY, KEY_BRIGHTNESS_MAX,
+       -1 , -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_VOLUME, 0, 572, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { ABS_MISC + 1, 0, 0, 0, 0, 0 },
+       { ABS_MISC + 2, 0, 0, 0, 0, 0 },
+       { ABS_MISC + 3, 0, 0, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_mouse_roccat_device = {
+       .type = LITEST_MOUSE_ROCCAT,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL | LITEST_KEYS,
+       .shortname = "mouse_roccat",
+       .setup = litest_mouse_roccat_setup,
+       .interface = NULL,
+
+       .name = "ROCCAT ROCCAT Kone XTD",
+       .id = &input_id,
+       .absinfo = absinfo,
+       .events = events,
+};
diff --git a/test/litest-device-mouse-wheel-click-angle.c b/test/litest-device-mouse-wheel-click-angle.c
new file mode 100644 (file)
index 0000000..fd2e225
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_mouse_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MOUSE_WHEEL_CLICK_ANGLE);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x1234,
+       .product = 0x5678,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_WHEEL,
+       -1 , -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"wheel_click_angle_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"wheel_click_angle_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wheel Click Angle Mouse*\",\\\n"
+"    ENV{MOUSE_WHEEL_CLICK_ANGLE}=\"-7\"\n"
+"    ENV{MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL}=\"13\"\n"
+"\n"
+"LABEL=\"wheel_click_angle_end\"";
+
+struct litest_test_device litest_mouse_wheel_click_angle_device = {
+       .type = LITEST_MOUSE_WHEEL_CLICK_ANGLE,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
+       .shortname = "mouse-wheelclickangle",
+       .setup = litest_mouse_setup,
+       .interface = NULL,
+
+       .name = "Wheel Click Angle Mouse",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-mouse.c b/test/litest-device-mouse.c
new file mode 100644 (file)
index 0000000..bae9863
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_mouse_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MOUSE);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x17ef,
+       .product = 0x6019,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_WHEEL,
+       -1 , -1,
+};
+
+struct litest_test_device litest_mouse_device = {
+       .type = LITEST_MOUSE,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
+       .shortname = "mouse",
+       .setup = litest_mouse_setup,
+       .interface = NULL,
+
+       .name = "Lenovo Optical USB Mouse",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+};
diff --git a/test/litest-device-ms-surface-cover.c b/test/litest-device-ms-surface-cover.c
new file mode 100644 (file)
index 0000000..fdd9681
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_ms_surface_cover_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_MS_SURFACE_COVER);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+/* We define down/move so that we can emulate fake touches on this device,
+   to make sure nothing crashes. */
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1022, 0, 0, 12 },
+       { ABS_Y, 0, 487, 0, 0, 12 },
+       { ABS_VOLUME, 0, 1023, 0, 0, 0 },
+       { ABS_MISC, 0, 255, 0, 0, 0 },
+       { 41, 0, 255, 0, 0, 0 },
+       { 42, -127, 127, 0, 0, 0 },
+       { 43, -127, 127, 0, 0, 0 },
+       { 44, -127, 127, 0, 0, 0 },
+       { 45, -127, 127, 0, 0, 0 },
+       { 46, -127, 127, 0, 0, 0 },
+       { 47, -127, 127, 0, 0, 0 },
+       /* ABS_MT range overlap starts here */
+       { 48, -127, 127, 0, 0, 0 }, /* ABS_MT_SLOT */
+       { 49, -127, 127, 0, 0, 0 },
+       { 50, -127, 127, 0, 0, 0 },
+       { 51, -127, 127, 0, 0, 0 },
+       { 52, -127, 127, 0, 0, 0 },
+       { 53, -127, 127, 0, 0, 0 },
+       { 54, -127, 127, 0, 0, 0 },
+       { 55, -127, 127, 0, 0, 0 },
+       { 56, -127, 127, 0, 0, 0 },
+       { 57, -127, 127, 0, 0, 0 },
+       { 58, -127, 127, 0, 0, 0 },
+       { 59, -127, 127, 0, 0, 0 },
+       { 60, -127, 127, 0, 0, 0 },
+       { 61, -127, 127, 0, 0, 0 }, /* ABS_MT_TOOL_Y */
+       { 62, -127, 127, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x45e,
+       .product = 0x7dc,
+};
+
+static int events[] = {
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       EV_REL, REL_HWHEEL,
+       EV_REL, REL_DIAL,
+       EV_REL, REL_WHEEL,
+       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_RO,
+       EV_KEY, KEY_HENKAN,
+       EV_KEY, KEY_KATAKANAHIRAGANA,
+       EV_KEY, KEY_MUHENKAN,
+       EV_KEY, KEY_KPJPCOMMA,
+       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_POWER,
+       EV_KEY, KEY_KPEQUAL,
+       EV_KEY, KEY_PAUSE,
+       EV_KEY, KEY_KPCOMMA,
+       EV_KEY, KEY_HANGEUL,
+       EV_KEY, KEY_HANJA,
+       EV_KEY, KEY_YEN,
+       EV_KEY, KEY_LEFTMETA,
+       EV_KEY, KEY_RIGHTMETA,
+       EV_KEY, KEY_COMPOSE,
+       EV_KEY, KEY_STOP,
+       EV_KEY, KEY_AGAIN,
+       EV_KEY, KEY_PROPS,
+       EV_KEY, KEY_UNDO,
+       EV_KEY, KEY_FRONT,
+       EV_KEY, KEY_COPY,
+       EV_KEY, KEY_OPEN,
+       EV_KEY, KEY_PASTE,
+       EV_KEY, KEY_FIND,
+       EV_KEY, KEY_CUT,
+       EV_KEY, KEY_HELP,
+       EV_KEY, KEY_MENU,
+       EV_KEY, KEY_CALC,
+       EV_KEY, KEY_SLEEP,
+       EV_KEY, KEY_FILE,
+       EV_KEY, KEY_WWW,
+       EV_KEY, KEY_COFFEE,
+       EV_KEY, KEY_MAIL,
+       EV_KEY, KEY_BOOKMARKS,
+       EV_KEY, KEY_BACK,
+       EV_KEY, KEY_FORWARD,
+       EV_KEY, KEY_EJECTCD,
+       EV_KEY, KEY_NEXTSONG,
+       EV_KEY, KEY_PLAYPAUSE,
+       EV_KEY, KEY_PREVIOUSSONG,
+       EV_KEY, KEY_STOPCD,
+       EV_KEY, KEY_RECORD,
+       EV_KEY, KEY_REWIND,
+       EV_KEY, KEY_PHONE,
+       EV_KEY, KEY_CONFIG,
+       EV_KEY, KEY_HOMEPAGE,
+       EV_KEY, KEY_REFRESH,
+       EV_KEY, KEY_EXIT,
+       EV_KEY, KEY_EDIT,
+       EV_KEY, KEY_SCROLLUP,
+       EV_KEY, KEY_SCROLLDOWN,
+       EV_KEY, KEY_NEW,
+       EV_KEY, KEY_REDO,
+       EV_KEY, KEY_F13,
+       EV_KEY, KEY_F14,
+       EV_KEY, KEY_F15,
+       EV_KEY, KEY_F16,
+       EV_KEY, KEY_F17,
+       EV_KEY, KEY_F18,
+       EV_KEY, KEY_F19,
+       EV_KEY, KEY_F20,
+       EV_KEY, KEY_F21,
+       EV_KEY, KEY_F22,
+       EV_KEY, KEY_F23,
+       EV_KEY, KEY_F24,
+       EV_KEY, KEY_CLOSE,
+       EV_KEY, KEY_PLAY,
+       EV_KEY, KEY_FASTFORWARD,
+       EV_KEY, KEY_BASSBOOST,
+       EV_KEY, KEY_PRINT,
+       EV_KEY, KEY_CAMERA,
+       EV_KEY, KEY_CHAT,
+       EV_KEY, KEY_SEARCH,
+       EV_KEY, KEY_FINANCE,
+       EV_KEY, KEY_CANCEL,
+       EV_KEY, KEY_BRIGHTNESSDOWN,
+       EV_KEY, KEY_BRIGHTNESSUP,
+       EV_KEY, KEY_KBDILLUMTOGGLE,
+       EV_KEY, KEY_SEND,
+       EV_KEY, KEY_REPLY,
+       EV_KEY, KEY_FORWARDMAIL,
+       EV_KEY, KEY_SAVE,
+       EV_KEY, KEY_DOCUMENTS,
+       EV_KEY, KEY_UNKNOWN,
+       EV_KEY, KEY_VIDEO_NEXT,
+       EV_KEY, KEY_BRIGHTNESS_ZERO,
+       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, KEY_SELECT,
+       EV_KEY, KEY_GOTO,
+       EV_KEY, KEY_INFO,
+       EV_KEY, KEY_PROGRAM,
+       EV_KEY, KEY_PVR,
+       EV_KEY, KEY_SUBTITLE,
+       EV_KEY, KEY_ZOOM,
+       EV_KEY, KEY_KEYBOARD,
+       EV_KEY, KEY_PC,
+       EV_KEY, KEY_TV,
+       EV_KEY, KEY_TV2,
+       EV_KEY, KEY_VCR,
+       EV_KEY, KEY_VCR2,
+       EV_KEY, KEY_SAT,
+       EV_KEY, KEY_CD,
+       EV_KEY, KEY_TAPE,
+       EV_KEY, KEY_TUNER,
+       EV_KEY, KEY_PLAYER,
+       EV_KEY, KEY_DVD,
+       EV_KEY, KEY_AUDIO,
+       EV_KEY, KEY_VIDEO,
+       EV_KEY, KEY_MEMO,
+       EV_KEY, KEY_CALENDAR,
+       EV_KEY, KEY_RED,
+       EV_KEY, KEY_GREEN,
+       EV_KEY, KEY_YELLOW,
+       EV_KEY, KEY_BLUE,
+       EV_KEY, KEY_CHANNELUP,
+       EV_KEY, KEY_CHANNELDOWN,
+       EV_KEY, KEY_LAST,
+       EV_KEY, KEY_NEXT,
+       EV_KEY, KEY_RESTART,
+       EV_KEY, KEY_SLOW,
+       EV_KEY, KEY_SHUFFLE,
+       EV_KEY, KEY_PREVIOUS,
+       EV_KEY, KEY_VIDEOPHONE,
+       EV_KEY, KEY_GAMES,
+       EV_KEY, KEY_ZOOMIN,
+       EV_KEY, KEY_ZOOMOUT,
+       EV_KEY, KEY_ZOOMRESET,
+       EV_KEY, KEY_WORDPROCESSOR,
+       EV_KEY, KEY_EDITOR,
+       EV_KEY, KEY_SPREADSHEET,
+       EV_KEY, KEY_GRAPHICSEDITOR,
+       EV_KEY, KEY_PRESENTATION,
+       EV_KEY, KEY_DATABASE,
+       EV_KEY, KEY_NEWS,
+       EV_KEY, KEY_VOICEMAIL,
+       EV_KEY, KEY_ADDRESSBOOK,
+       EV_KEY, KEY_MESSENGER,
+       EV_KEY, KEY_DISPLAYTOGGLE,
+       EV_KEY, KEY_SPELLCHECK,
+       EV_KEY, KEY_LOGOFF,
+       EV_KEY, KEY_MEDIA_REPEAT,
+       EV_KEY, KEY_IMAGES,
+       EV_KEY, 576,
+       EV_KEY, 577,
+       EV_KEY, 578,
+       EV_KEY, 579,
+       EV_KEY, 580,
+       EV_KEY, 581,
+       EV_KEY, 582,
+       EV_KEY, 592,
+       EV_KEY, 593,
+       EV_KEY, 608,
+       EV_KEY, 609,
+       EV_KEY, 610,
+       EV_KEY, 611,
+       EV_KEY, 612,
+       EV_KEY, 613,
+       EV_LED, LED_NUML,
+       EV_LED, LED_CAPSL,
+       EV_LED, LED_SCROLLL,
+       -1, -1,
+};
+
+struct litest_test_device litest_ms_surface_cover_device = {
+       .type = LITEST_MS_SURFACE_COVER,
+       .features = LITEST_KEYS | LITEST_ABSOLUTE | LITEST_RELATIVE | LITEST_FAKE_MT | LITEST_BUTTON | LITEST_WHEEL,
+       .shortname = "MS surface cover",
+       .setup = litest_ms_surface_cover_setup,
+       .interface = &interface,
+
+       .name = "Microsoft Surface Type Cover",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-nexus4-touch-screen.c b/test/litest-device-nexus4-touch-screen.c
new file mode 100644 (file)
index 0000000..d193005
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2015 Canonical, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_nexus4_setup(void)
+{
+       struct litest_device *d =
+               litest_create_device(LITEST_NEXUS4_TOUCH_SCREEN);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1500, 0, 0, 0 },
+       { ABS_Y, 0, 2500, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 1500, 0, 0, 0 },
+       { ABS_MT_POSITION_Y, 0, 2500, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 15, 1, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x1,
+       .vendor = 0x43e,
+       .product = 0x26,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1
+};
+
+struct litest_test_device litest_nexus4_device = {
+       .type = LITEST_NEXUS4_TOUCH_SCREEN,
+       .features = LITEST_TOUCH|LITEST_ELLIPSE,
+       .shortname = "nexus4",
+       .setup = litest_nexus4_setup,
+       .interface = &interface,
+
+       .name = "Nexus 4 touch screen",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-protocol-a-touch-screen.c b/test/litest-device-protocol-a-touch-screen.c
new file mode 100644 (file)
index 0000000..e3b67c4
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_protocol_a_touch_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_PROTOCOL_A_SCREEN);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_MT_REPORT, .value = 0 },
+       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_MT_REPORT, .value = 0 },
+       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 32767, 0, 0, 0 },
+       { ABS_Y, 0, 32767, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 32767, 0, 0, 0 },
+       { ABS_MT_POSITION_Y, 0, 32767, 0, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 1, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x18,
+       .vendor = 0xeef,
+       .product = 0x20,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1,
+};
+
+struct litest_test_device litest_protocol_a_screen = {
+       .type = LITEST_PROTOCOL_A_SCREEN,
+       .features = LITEST_PROTOCOL_A,
+       .shortname = "protocol A",
+       .setup = litest_protocol_a_touch_setup,
+       .interface = &interface,
+
+       .name = "Protocol A touch screen",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-qemu-usb-tablet.c b/test/litest-device-qemu-usb-tablet.c
new file mode 100644 (file)
index 0000000..b9a6523
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+#include <assert.h>
+
+static void
+litest_qemu_tablet_touch_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_QEMU_TABLET);
+       litest_set_current_device(d);
+}
+
+static void touch_down(struct litest_device *d, unsigned int slot,
+                      double x, double y)
+{
+       assert(slot == 0);
+
+       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
+       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static void touch_move(struct litest_device *d, unsigned int slot,
+                      double x, double y)
+{
+       assert(slot == 0);
+
+       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
+       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static void touch_up(struct litest_device *d, unsigned int slot)
+{
+       assert(slot == 0);
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static struct litest_device_interface interface = {
+       .touch_down = touch_down,
+       .touch_move = touch_move,
+       .touch_up = touch_up,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 32767, 0, 0, 0 },
+       { ABS_Y, 0, 32767, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x03,
+       .vendor = 0x627,
+       .product = 0x01,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_REL, REL_WHEEL,
+       -1, -1,
+};
+
+struct litest_test_device litest_qemu_tablet_device = {
+       .type = LITEST_QEMU_TABLET,
+       .features = LITEST_WHEEL | LITEST_BUTTON | LITEST_ABSOLUTE,
+       .shortname = "qemu tablet",
+       .setup = litest_qemu_tablet_touch_setup,
+       .interface = &interface,
+
+       .name = "QEMU 0.12.1 QEMU USB Tablet",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-synaptics-hover.c b/test/litest-device-synaptics-hover.c
new file mode 100644 (file)
index 0000000..2429565
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <assert.h>
+
+#include "libinput-util.h"
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+synaptics_hover_create(struct litest_device *d);
+
+static void
+litest_synaptics_hover_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_set_current_device(d);
+}
+
+static void
+synaptics_hover_touch_down(struct litest_device *d, unsigned int slot, double x, double y)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_down(d, semi_mt, slot, x, y);
+}
+
+static void
+synaptics_hover_touch_move(struct litest_device *d, unsigned int slot, double x, double y)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_move(d, semi_mt, slot, x, y);
+}
+
+static void
+synaptics_hover_touch_up(struct litest_device *d, unsigned int slot)
+{
+       struct litest_semi_mt *semi_mt = d->private;
+
+       litest_semi_mt_touch_up(d, semi_mt, slot);
+}
+
+static struct litest_device_interface interface = {
+       .touch_down = synaptics_hover_touch_down,
+       .touch_move = synaptics_hover_touch_move,
+       .touch_up = synaptics_hover_touch_up,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x7,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_SEMI_MT,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 1472, 5472, 0, 0, 60 },
+       { ABS_Y, 1408, 4498, 0, 0, 85 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 1472, 5472, 0, 0, 60 },
+       { ABS_MT_POSITION_Y, 1408, 4498, 0, 0, 85 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 }
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"synaptics_semi_mt_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"synaptics_semi_mt_end\"\n"
+"\n"
+"ATTRS{name}==\"SynPS/2 Synaptics TouchPad\",\\\n"
+"    ENV{LIBINPUT_MODEL_JUMPING_SEMI_MT}=\"1\"\n"
+"\n"
+"LABEL=\"synaptics_semi_mt_end\"";
+
+struct litest_test_device litest_synaptics_hover_device = {
+       .type = LITEST_SYNAPTICS_HOVER_SEMI_MT,
+       .features = LITEST_TOUCHPAD | LITEST_SEMI_MT | LITEST_BUTTON,
+       .shortname = "synaptics hover",
+       .setup = litest_synaptics_hover_setup,
+       .interface = &interface,
+       .create = synaptics_hover_create,
+
+       .name = "SynPS/2 Synaptics TouchPad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
+
+static void
+synaptics_hover_create(struct litest_device *d)
+{
+       struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt));
+       assert(semi_mt);
+
+       d->private = semi_mt;
+
+       d->uinput = litest_create_uinput_device_from_description(
+                       litest_synaptics_hover_device.name,
+                       litest_synaptics_hover_device.id,
+                       absinfo,
+                       events);
+       d->interface = &interface;
+}
diff --git a/test/litest-device-synaptics-i2c.c b/test/litest-device-synaptics-i2c.c
new file mode 100644 (file)
index 0000000..be826a4
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_synaptics_i2c_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_I2C);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x18,
+       .vendor = 0x6cb,
+       .product = 0x76ad,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1216, 0, 0, 12 },
+       { ABS_Y, 0, 680, 0, 0, 12 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 1216, 0, 0, 12 },
+       { ABS_MT_POSITION_Y, 0, 680, 0, 0, 12 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 }
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"touchpad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n"
+"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest DLL0704:01 06CB:76AD Touchpad\","
+"    ENV{LIBINPUT_MODEL_DELL_TOUCHPAD}=\"1\"\n"
+"\n"
+"LABEL=\"touchpad_end\"";
+
+struct litest_test_device litest_synaptics_i2c_device = {
+       .type = LITEST_SYNAPTICS_I2C,
+       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON,
+       .shortname = "synaptics-i2c",
+       .setup = litest_synaptics_i2c_setup,
+       .interface = &interface,
+
+       .name = "DLL0704:01 06CB:76AD Touchpad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-synaptics-st.c b/test/litest-device-synaptics-st.c
new file mode 100644 (file)
index 0000000..d2bd1ae
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_synaptics_touchpad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_TOUCHPAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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 = 30  },
+       { .type = EV_ABS, .code = ABS_TOOL_WIDTH, .value = 7  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+struct input_event up[] = {
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .touch_up_events = up,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 1472, 5472, 0, 0, 75 },
+       { ABS_Y, 1408, 4448, 0, 0, 129 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x7,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOUCH,
+       -1, -1,
+};
+
+struct litest_test_device litest_synaptics_touchpad_device = {
+       .type = LITEST_SYNAPTICS_TOUCHPAD,
+       .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_SINGLE_TOUCH,
+       .shortname = "synaptics ST",
+       .setup = litest_synaptics_touchpad_setup,
+       .interface = &interface,
+
+       .name = "SynPS/2 Synaptics TouchPad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-synaptics-t440.c b/test/litest-device-synaptics-t440.c
new file mode 100644 (file)
index 0000000..fa56185
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_synaptics_t440_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_TOPBUTTONPAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_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:
+       case ABS_MT_PRESSURE:
+               *value = 30;
+               return 0;
+       }
+       return 1;
+}
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+
+       .get_axis_default = get_axis_default,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x7,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       INPUT_PROP_MAX, INPUT_PROP_TOPBUTTONPAD,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 1024, 5112, 0, 0, 42 },
+       { ABS_Y, 2024, 4832, 0, 0, 42 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 1024, 5112, 0, 0, 42 },
+       { ABS_MT_POSITION_Y, 2024, 4832, 0, 0, 42 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_synaptics_t440_device = {
+       .type = LITEST_SYNAPTICS_TOPBUTTONPAD,
+       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON | LITEST_TOPBUTTONPAD,
+       .shortname = "synaptics t440",
+       .setup = litest_synaptics_t440_setup,
+       .interface = &interface,
+
+       .name = "SynPS/2 Synaptics TouchPad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-synaptics-x1-carbon-3rd.c b/test/litest-device-synaptics-x1-carbon-3rd.c
new file mode 100644 (file)
index 0000000..23d9c5b
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_synaptics_carbon3rd_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_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:
+       case ABS_MT_PRESSURE:
+               *value = 30;
+               return 0;
+       }
+       return 1;
+}
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+
+       .get_axis_default = get_axis_default,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x7,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       EV_KEY, BTN_0,
+       EV_KEY, BTN_1,
+       EV_KEY, BTN_2,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 1266, 5676, 0, 0, 45 },
+       { ABS_Y, 1096, 4758, 0, 0, 68 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 1266, 5676, 0, 0, 45 },
+       { ABS_MT_POSITION_Y, 1096, 4758, 0, 0, 68 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { .value = -1 }
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"touchpad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n"
+"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest SynPS/2 Synaptics TouchPad X1C3rd\","
+"    ENV{LIBINPUT_MODEL_LENOVO_T450_TOUCHPAD}=\"1\"\n"
+"\n"
+"LABEL=\"touchpad_end\"";
+
+struct litest_test_device litest_synaptics_carbon3rd_device = {
+       .type = LITEST_SYNAPTICS_TRACKPOINT_BUTTONS,
+       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON,
+       .shortname = "synaptics carbon3rd",
+       .setup = litest_synaptics_carbon3rd_setup,
+       .interface = &interface,
+
+       .name = "SynPS/2 Synaptics TouchPad X1C3rd",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-synaptics.c b/test/litest-device-synaptics.c
new file mode 100644 (file)
index 0000000..3186a3a
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_synaptics_clickpad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_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_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN  },
+       { .type = EV_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:
+       case ABS_MT_PRESSURE:
+               *value = 30;
+               return 0;
+       }
+       return 1;
+}
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+
+       .get_axis_default = get_axis_default,
+};
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0x11,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
+       -1, -1,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 1472, 5472, 0, 0, 75 },
+       { ABS_Y, 1408, 4448, 0, 0, 129 },
+       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 1472, 5472, 0, 0, 75 },
+       { ABS_MT_POSITION_Y, 1408, 4448, 0, 0, 129 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { .value = -1 }
+};
+
+struct litest_test_device litest_synaptics_clickpad_device = {
+       .type = LITEST_SYNAPTICS_CLICKPAD_X220,
+       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON,
+       .shortname = "synaptics",
+       .setup = litest_synaptics_clickpad_setup,
+       .interface = &interface,
+
+       .name = "SynPS/2 Synaptics TouchPad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-touch-screen.c b/test/litest-device-touch-screen.c
new file mode 100644 (file)
index 0000000..e91458a
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2015 Canonical, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_generic_mt_setup(void)
+{
+       struct litest_device *d =
+               litest_create_device(LITEST_GENERIC_MULTITOUCH_SCREEN);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MAJOR, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TOUCH_MINOR, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1500, 0, 0, 0 },
+       { ABS_Y, 0, 2500, 0, 0, 0 },
+       { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 1500, 0, 0, 0 },
+       { ABS_MT_POSITION_Y, 0, 2500, 0, 0, 0 },
+       { ABS_MT_ORIENTATION, -256, 255, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 255, 1, 0, 0 },
+       { ABS_MT_TOUCH_MINOR, 0, 255, 1, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x1,
+       .vendor = 0x0,
+       .product = 0x25,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1
+};
+
+struct litest_test_device litest_generic_multitouch_screen_device = {
+       .type = LITEST_GENERIC_MULTITOUCH_SCREEN,
+       .features = LITEST_TOUCH|LITEST_ELLIPSE,
+       .shortname = "generic-mt",
+       .setup = litest_generic_mt_setup,
+       .interface = &interface,
+
+       .name = "generic-mt",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-touchscreen-fuzz.c b/test/litest-device-touchscreen-fuzz.c
new file mode 100644 (file)
index 0000000..6f374c5
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2015 Canonical, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_fuzz_mt_setup(void)
+{
+       struct litest_device *d =
+               litest_create_device(LITEST_MULTITOUCH_FUZZ_SCREEN);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_PRESSURE, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1500, 10, 0, 0 },
+       { ABS_Y, 0, 2500, 10, 0, 0 },
+       { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 1500, 10, 0, 0 },
+       { ABS_MT_POSITION_Y, 0, 2500, 10, 0, 0 },
+       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x1,
+       .vendor = 0x0,
+       .product = 0x25,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1
+};
+
+struct litest_test_device litest_multitouch_fuzz_screen_device = {
+       .type = LITEST_MULTITOUCH_FUZZ_SCREEN,
+       .features = LITEST_TOUCH,
+       .shortname = "touchscreen-fuzz",
+       .setup = litest_fuzz_mt_setup,
+       .interface = &interface,
+
+       .name = "touchscreen with fuzz",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-trackpoint.c b/test/litest-device-trackpoint.c
new file mode 100644 (file)
index 0000000..5d72249
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_trackpoint_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_TRACKPOINT);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x11,
+       .vendor = 0x2,
+       .product = 0xa,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_REL, REL_X,
+       EV_REL, REL_Y,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       INPUT_PROP_MAX, INPUT_PROP_POINTING_STICK,
+       -1, -1,
+};
+
+struct litest_test_device litest_trackpoint_device = {
+       .type = LITEST_TRACKPOINT,
+       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_POINTINGSTICK,
+       .shortname = "trackpoint",
+       .setup = litest_trackpoint_setup,
+       .interface = NULL,
+
+       .name = "TPPS/2 IBM TrackPoint",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+
+};
diff --git a/test/litest-device-vmware-virtual-usb-mouse.c b/test/litest-device-vmware-virtual-usb-mouse.c
new file mode 100644 (file)
index 0000000..c92c566
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+#include <assert.h>
+
+static void
+litest_vmware_virtmouse_touch_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_VMWARE_VIRTMOUSE);
+       litest_set_current_device(d);
+}
+
+static void touch_down(struct litest_device *d, unsigned int slot,
+                      double x, double y)
+{
+       assert(slot == 0);
+
+       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
+       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static void touch_move(struct litest_device *d, unsigned int slot,
+                      double x, double y)
+{
+       assert(slot == 0);
+
+       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
+       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static void touch_up(struct litest_device *d, unsigned int slot)
+{
+       assert(slot == 0);
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static struct litest_device_interface interface = {
+       .touch_down = touch_down,
+       .touch_move = touch_move,
+       .touch_up = touch_up,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 32767, 0, 0, 0 },
+       { ABS_Y, 0, 32767, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x03,
+       .vendor = 0xe0f,
+       .product = 0x03,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_KEY, BTN_SIDE,
+       EV_KEY, BTN_EXTRA,
+       EV_KEY, BTN_FORWARD,
+       EV_KEY, BTN_BACK,
+       EV_KEY, BTN_TASK,
+       EV_KEY, 280,
+       EV_KEY, 281,
+       EV_KEY, 282,
+       EV_KEY, 283,
+       EV_KEY, 284,
+       EV_KEY, 285,
+       EV_KEY, 286,
+       EV_KEY, 287,
+       EV_REL, REL_WHEEL,
+       EV_REL, REL_HWHEEL,
+       -1, -1,
+};
+
+struct litest_test_device litest_vmware_virtmouse_device = {
+       .type = LITEST_VMWARE_VIRTMOUSE,
+       .features = LITEST_WHEEL | LITEST_BUTTON | LITEST_ABSOLUTE,
+       .shortname = "vmware virtmouse",
+       .setup = litest_vmware_virtmouse_touch_setup,
+       .interface = &interface,
+
+       .name = "VMware VMware Virtual USB Mouse",
+       .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 (file)
index 0000000..d1f5fc9
--- /dev/null
@@ -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-13hdt-finger.c b/test/litest-device-wacom-cintiq-13hdt-finger.c
new file mode 100644 (file)
index 0000000..9c82de0
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2016 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_13HDT_FINGER);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_ORIENTATION, .value = 0 },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 2937, 0, 0, 10 },
+       { ABS_Y, 0, 1652, 0, 0, 10 },
+       { ABS_MT_SLOT, 0, 9, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 2937, 0, 0, 0 },
+       { ABS_MT_WIDTH_MAJOR, 0, 2937, 0, 0, 0 },
+       { ABS_MT_WIDTH_MINOR, 0, 1652, 0, 0, 0 },
+       { ABS_MT_ORIENTATION, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 2937, 0, 0, 10 },
+       { ABS_MT_POSITION_Y, 0, 1652, 0, 0, 10 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0x335,
+       .version = 0x110,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"rule_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"rule_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Cintiq 13 HD touch Finger*\",\\\n"
+"    ENV{LIBINPUT_DEVICE_GROUP}=\"wacom-13hdt-group\"\n"
+"\n"
+"LABEL=\"rule_end\"";
+
+struct litest_test_device litest_wacom_cintiq_13hdt_finger_device = {
+       .type = LITEST_WACOM_CINTIQ_13HDT_FINGER,
+       .features = LITEST_TOUCH,
+       .shortname = "wacom-cintiq-13hdt-finger",
+       .setup = litest_wacom_cintiq_tablet_setup,
+       .interface = &interface,
+
+       .name = "Wacom Cintiq 13 HD touch Finger",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-cintiq-13hdt-pad.c b/test/litest-device-wacom-cintiq-13hdt-pad.c
new file mode 100644 (file)
index 0000000..cc4add4
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2016 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_pad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_CINTIQ_13HDT_PAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event ring_start[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 15 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_change[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_end[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = 0 },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 0 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .pad_ring_start_events = ring_start,
+       .pad_ring_change_events = ring_change,
+       .pad_ring_end_events = ring_end,
+};
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1, 0, 0, 0 },
+       { ABS_Y, 0, 1, 0, 0, 0 },
+       { ABS_WHEEL, 0, 71, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0x33,
+       .version = 0x110,
+};
+
+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_STYLUS,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"pad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"pad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Cintiq 13 HD touch Pad*\",\\\n"
+"    ENV{ID_INPUT_TABLET_PAD}=\"1\",\\\n"
+"    ENV{LIBINPUT_DEVICE_GROUP}=\"wacom-13hdt-group\"\n"
+"\n"
+"LABEL=\"pad_end\"";
+
+struct litest_test_device litest_wacom_cintiq_13hdt_pad_device = {
+       .type = LITEST_WACOM_CINTIQ_13HDT_PAD,
+       .features = LITEST_TABLET_PAD | LITEST_RING,
+       .shortname = "wacom-cintiq-13hdt-pad",
+       .setup = litest_wacom_cintiq_pad_setup,
+       .interface = &interface,
+
+       .name = "Wacom Cintiq 13 HD touch Pad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-cintiq-13hdt-pen.c b/test/litest-device-wacom-cintiq-13hdt-pen.c
new file mode 100644 (file)
index 0000000..084d59a
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright © 2016 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_13HDT_PEN);
+       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, 400, 59152, 4, 0, 200 },
+       { ABS_Y, 400, 33448, 4, 0, 200 },
+       { ABS_Z, -900, 899, 0, 0, 287 },
+       { ABS_WHEEL, 0, 1023, 0, 0, 0 },
+       { ABS_THROTTLE, 0, 71, 0, 0, 0 },
+       { ABS_PRESSURE, 0, 2047, 0, 0, 0 },
+       { ABS_DISTANCE, 0, 63, 1, 0, 0 },
+       { ABS_TILT_X, -64, 63, 1, 0, 57 },
+       { ABS_TILT_Y, -64, 63, 1, 0, 57 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0x333,
+       .version = 0x110,
+};
+
+static int events[] = {
+       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,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"rule_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"rule_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Cintiq 13 HD touch Pen*\",\\\n"
+"    ENV{LIBINPUT_DEVICE_GROUP}=\"wacom-13hdt-group\"\n"
+"\n"
+"LABEL=\"rule_end\"";
+
+struct litest_test_device litest_wacom_cintiq_13hdt_pen_device = {
+       .type = LITEST_WACOM_CINTIQ_13HDT_PEN,
+       .features = LITEST_TABLET | LITEST_DISTANCE | LITEST_TOOL_SERIAL | LITEST_TILT,
+       .shortname = "wacom-cintiq-13hdt-pen-tablet",
+       .setup = litest_wacom_cintiq_tablet_setup,
+       .interface = &interface,
+
+       .name = "Wacom Cintiq 13 HD touch Pen",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-cintiq-24hd.c b/test/litest-device-wacom-cintiq-24hd.c
new file mode 100644 (file)
index 0000000..07f27f9
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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_24HD);
+       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, 400, 104080, 4, 0, 200 },
+       { ABS_Y, 400, 65200, 4, 0, 200 },
+       { ABS_Z, -900, 899, 0, 0, 0 },
+       { ABS_WHEEL, 0, 1023, 0, 0, 0 },
+       { ABS_THROTTLE, 0, 71, 0, 0, 0 },
+       { ABS_PRESSURE, 0, 2047, 0, 0, 0 },
+       { ABS_DISTANCE, 0, 63, 0, 0, 0 },
+       { ABS_TILT_X, -64, 63, 0, 0, 57 },
+       { ABS_TILT_Y, -64, 63, 0, 0, 57 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0xf4,
+       .version = 0x113,
+};
+
+static int events[] = {
+       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_24hd_device = {
+       .type = LITEST_WACOM_CINTIQ_24HD,
+       .features = LITEST_TABLET | LITEST_DISTANCE | LITEST_TOOL_SERIAL | LITEST_TILT,
+       .shortname = "wacom-cintiq-24hd-tablet",
+       .setup = litest_wacom_cintiq_tablet_setup,
+       .interface = &interface,
+
+       .name = "Wacom Cintiq 24 HD Pen",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-wacom-cintiq-24hdt-pad.c b/test/litest-device-wacom-cintiq-24hdt-pad.c
new file mode 100644 (file)
index 0000000..d20fbd5
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright © 2016 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 <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_wacom_cintiq_pad_setup(void)
+{
+       struct litest_device *d;
+
+       d = litest_create_device(LITEST_WACOM_CINTIQ_24HDT_PAD);
+
+       litest_set_current_device(d);
+}
+
+static void
+litest_wacom_cintiq_pad_teardown(void)
+{
+       litest_generic_device_teardown();
+}
+
+static struct input_event down[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event ring_start[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 15 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_change[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_end[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = 0 },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 0 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .pad_ring_start_events = ring_start,
+       .pad_ring_change_events = ring_change,
+       .pad_ring_end_events = ring_end,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1, 0, 0, 0 },
+       { ABS_Y, 0, 1, 0, 0, 0 },
+       { ABS_WHEEL, 0, 71, 0, 0, 0 },
+       { ABS_THROTTLE, 0, 71, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0xf8,
+       .version = 0x110,
+};
+
+static int events[] = {
+       EV_KEY, KEY_PROG1,
+       EV_KEY, KEY_PROG2,
+       EV_KEY, KEY_PROG3,
+       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_SOUTH,
+       EV_KEY, BTN_EAST,
+       EV_KEY, BTN_C,
+       EV_KEY, BTN_NORTH,
+       EV_KEY, BTN_WEST,
+       EV_KEY, BTN_Z,
+       EV_KEY, BTN_STYLUS,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"pad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"pad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Cintiq 24 HD touch Pad*\",\\\n"
+"    ENV{ID_INPUT_TABLET_PAD}=\"1\"\n"
+"\n"
+"LABEL=\"pad_end\"";
+
+struct litest_test_device litest_wacom_cintiq_24hdt_pad_device = {
+       .type = LITEST_WACOM_CINTIQ_24HDT_PAD,
+       .features = LITEST_TABLET_PAD | LITEST_RING,
+       .shortname = "wacom-cintiq-24hdt-pad",
+       .setup = litest_wacom_cintiq_pad_setup,
+       .teardown = litest_wacom_cintiq_pad_teardown,
+       .interface = &interface,
+
+       .name = "Wacom Cintiq 24 HD touch Pad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-cintiq-tablet.c b/test/litest-device-wacom-cintiq-tablet.c
new file mode 100644 (file)
index 0000000..4685668
--- /dev/null
@@ -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-ekr.c b/test/litest-device-wacom-ekr.c
new file mode 100644 (file)
index 0000000..fa73927
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2016 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_ekr_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_EKR);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event ring_start[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 15 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_change[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_end[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = 0 },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 0 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .pad_ring_start_events = ring_start,
+       .pad_ring_change_events = ring_change,
+       .pad_ring_end_events = ring_end,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1, 0, 0, 0 },
+       { ABS_Y, 0, 1, 0, 0, 0 },
+       { ABS_WHEEL, 0, 71, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0x331,
+};
+
+static int events[] = {
+       EV_KEY, BTN_0,
+       EV_KEY, BTN_1,
+       EV_KEY, BTN_2,
+       EV_KEY, BTN_3,
+       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_BASE,
+       EV_KEY, BTN_BASE2,
+       EV_KEY, BTN_SOUTH,
+       EV_KEY, BTN_EAST,
+       EV_KEY, BTN_C,
+       EV_KEY, BTN_NORTH,
+       EV_KEY, BTN_WEST,
+       EV_KEY, BTN_Z,
+       EV_KEY, BTN_STYLUS,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"pad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"pad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Express Key Remote Pad*\",\\\n"
+"    ENV{ID_INPUT_TABLET_PAD}=\"1\"\n"
+"\n"
+"LABEL=\"pad_end\"";
+
+struct litest_test_device litest_wacom_ekr_device = {
+       .type = LITEST_WACOM_EKR,
+       .features = LITEST_TABLET_PAD | LITEST_RING,
+       .shortname = "wacom-ekr",
+       .setup = litest_wacom_ekr_setup,
+       .interface = &interface,
+
+       .name = "Wacom Express Key Remote Pad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-hid4800-pen.c b/test/litest-device-wacom-hid4800-pen.c
new file mode 100644 (file)
index 0000000..b1a6071
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2016 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_hid4800_tablet_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_HID4800_PEN);
+       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_MSC, .code = MSC_SERIAL, .value = 297797542 },
+       { .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_MSC, .code = MSC_SERIAL, .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_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_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, 21696, 4, 0, 100 },
+       { ABS_Y, 0, 13560, 4, 0, 100 },
+       { ABS_PRESSURE, 0, 2047, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x18,
+       .vendor = 0x56a,
+       .product = 0x4800,
+       .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,
+       EV_MSC, MSC_SERIAL,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1,
+};
+
+struct litest_test_device litest_wacom_hid4800_tablet_device = {
+       .type = LITEST_WACOM_HID4800_PEN,
+       .features = LITEST_TABLET,
+       .shortname = "wacom-hid4800-tablet",
+       .setup = litest_wacom_hid4800_tablet_setup,
+       .interface = &interface,
+
+       .name = "Wacom HID 4800 Pen",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-wacom-intuos-finger.c b/test/litest-device-wacom-intuos-finger.c
new file mode 100644 (file)
index 0000000..8014588
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_wacom_finger_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_FINGER);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 4096, 0, 0, 18 },
+       { ABS_Y, 0, 4096, 0, 0, 29 },
+       { ABS_MT_SLOT, 0, 15, 0, 0, 0 },
+       { ABS_MT_TOUCH_MAJOR, 0, 4096, 0, 0, 0 },
+       { ABS_MT_TOUCH_MINOR, 0, 4096, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 4096, 0, 0, 18 },
+       { ABS_MT_POSITION_Y, 0, 4096, 0, 0, 29 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0x27,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOOL_FINGER,
+       EV_KEY, BTN_TOOL_QUINTTAP,
+       EV_KEY, BTN_TOUCH,
+       EV_KEY, BTN_TOOL_DOUBLETAP,
+       EV_KEY, BTN_TOOL_TRIPLETAP,
+       EV_KEY, BTN_TOOL_QUADTAP,
+       INPUT_PROP_MAX, INPUT_PROP_POINTER,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"rule_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"rule_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Intuos5 touch M Finger*\",\\\n"
+"    ENV{LIBINPUT_DEVICE_GROUP}=\"wacom-i5-group\"\n"
+"\n"
+"LABEL=\"rule_end\"";
+
+struct litest_test_device litest_wacom_finger_device = {
+       .type = LITEST_WACOM_FINGER,
+       .features = LITEST_TOUCHPAD,
+       .shortname = "wacom-finger",
+       .setup = litest_wacom_finger_setup,
+       .interface = &interface,
+
+       .name = "Wacom Intuos5 touch M Finger",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-intuos-tablet.c b/test/litest-device-wacom-intuos-tablet.c
new file mode 100644 (file)
index 0000000..00eac7a
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"rule_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"rule_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Intuos5 touch M Pen*\",\\\n"
+"    ENV{LIBINPUT_DEVICE_GROUP}=\"wacom-i5-group\"\n"
+"\n"
+"LABEL=\"rule_end\"";
+
+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,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-intuos3-pad.c b/test/litest-device-wacom-intuos3-pad.c
new file mode 100644 (file)
index 0000000..0622ddc
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2016 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_intuos3_pad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_INTUOS3_PAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event strip_start[] = {
+       { .type = EV_ABS, .code = ABS_RX, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 15 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event strip_change[] = {
+       { .type = EV_ABS, .code = ABS_RX, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event strip_end[] = {
+       { .type = EV_ABS, .code = ABS_RX, .value = 0 },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 0 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .pad_strip_start_events = strip_start,
+       .pad_strip_change_events = strip_change,
+       .pad_strip_end_events = strip_end,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1, 0, 0, 0 },
+       { ABS_Y, 0, 1, 0, 0, 0 },
+       { ABS_RX, 0, 4096, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0xb7,
+};
+
+static int events[] = {
+       EV_KEY, BTN_0,
+       EV_KEY, BTN_1,
+       EV_KEY, BTN_2,
+       EV_KEY, BTN_3,
+       EV_KEY, BTN_STYLUS,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"pad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"pad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Intuos3 4x6 Pad*\",\\\n"
+"    ENV{ID_INPUT_TABLET_PAD}=\"1\"\n"
+"\n"
+"LABEL=\"pad_end\"";
+
+struct litest_test_device litest_wacom_intuos3_pad_device = {
+       .type = LITEST_WACOM_INTUOS3_PAD,
+       .features = LITEST_TABLET_PAD | LITEST_STRIP,
+       .shortname = "wacom-intuos3-pad",
+       .setup = litest_wacom_intuos3_pad_setup,
+       .interface = &interface,
+
+       .name = "Wacom Intuos3 4x6 Pad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-intuos5-pad.c b/test/litest-device-wacom-intuos5-pad.c
new file mode 100644 (file)
index 0000000..cacc5c7
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2016 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_intuos5_pad_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_INTUOS5_PAD);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event ring_start[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 15 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_change[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct input_event ring_end[] = {
+       { .type = EV_ABS, .code = ABS_WHEEL, .value = 0 },
+       { .type = EV_ABS, .code = ABS_MISC, .value = 0 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+} ;
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+       .pad_ring_start_events = ring_start,
+       .pad_ring_change_events = ring_change,
+       .pad_ring_end_events = ring_end,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 1, 0, 0, 0 },
+       { ABS_Y, 0, 1, 0, 0, 0 },
+       { ABS_WHEEL, 0, 71, 0, 0, 0 },
+       { ABS_MISC, 0, 0, 0, 0, 10 },
+       { .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_STYLUS,
+       -1, -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"pad_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"pad_end\"\n"
+"\n"
+"ATTRS{name}==\"litest Wacom Intuos5 touch M Pad*\",\\\n"
+"    ENV{LIBINPUT_DEVICE_GROUP}=\"wacom-i5-group\",\\\n"
+"    ENV{ID_INPUT_TABLET_PAD}=\"1\"\n"
+"\n"
+"LABEL=\"pad_end\"";
+
+struct litest_test_device litest_wacom_intuos5_pad_device = {
+       .type = LITEST_WACOM_INTUOS5_PAD,
+       .features = LITEST_TABLET_PAD | LITEST_RING,
+       .shortname = "wacom-pad",
+       .setup = litest_wacom_intuos5_pad_setup,
+       .interface = &interface,
+
+       .name = "Wacom Intuos5 touch M Pad",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-wacom-isdv4-tablet.c b/test/litest-device-wacom-isdv4-tablet.c
new file mode 100644 (file)
index 0000000..400fb57
--- /dev/null
@@ -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-wacom-touch.c b/test/litest-device-wacom-touch.c
new file mode 100644 (file)
index 0000000..e7da39c
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void
+litest_wacom_touch_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WACOM_TOUCH);
+       litest_set_current_device(d);
+}
+
+static struct input_event down[] = {
+       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct input_event move[] = {
+       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
+       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
+       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+       { .type = -1, .code = -1 },
+};
+
+static struct litest_device_interface interface = {
+       .touch_down_events = down,
+       .touch_move_events = move,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 2776, 0, 0, 10 },
+       { ABS_Y, 0, 1569, 0, 0, 9 },
+       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
+       { ABS_MT_POSITION_X, 0, 2776, 0, 0, 10 },
+       { ABS_MT_POSITION_Y, 0, 1569, 0, 0, 9 },
+       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x56a,
+       .product = 0xe6,
+};
+
+static int events[] = {
+       EV_KEY, BTN_TOUCH,
+       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
+       -1, -1,
+};
+
+struct litest_test_device litest_wacom_touch_device = {
+       .type = LITEST_WACOM_TOUCH,
+       .features = LITEST_TOUCH,
+       .shortname = "wacom-touch",
+       .setup = litest_wacom_touch_setup,
+       .interface = &interface,
+
+       .name = "Wacom ISDv4 E6 Finger",
+       .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 (file)
index 0000000..880ddf3
--- /dev/null
@@ -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-device-wheel-only.c b/test/litest-device-wheel-only.c
new file mode 100644 (file)
index 0000000..c5b9fb0
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_wheel_only_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_WHEEL_ONLY);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x1,
+       .product = 0x2,
+};
+
+static int events[] = {
+       EV_REL, REL_WHEEL,
+       -1 , -1,
+};
+
+static const char udev_rule[] =
+"ACTION==\"remove\", GOTO=\"wheel_only_end\"\n"
+"KERNEL!=\"event*\", GOTO=\"wheel_only_end\"\n"
+"\n"
+"ATTRS{name}==\"litest wheel only device*\",\\\n"
+"    ENV{ID_INPUT_KEY}=\"1\"\n"
+"\n"
+"LABEL=\"wheel_only_end\"";
+
+struct litest_test_device litest_wheel_only_device = {
+       .type = LITEST_WHEEL_ONLY,
+       .features = LITEST_WHEEL,
+       .shortname = "wheel only",
+       .setup = litest_wheel_only_setup,
+       .interface = NULL,
+
+       .name = "wheel only device",
+       .id = &input_id,
+       .absinfo = NULL,
+       .events = events,
+       .udev_rule = udev_rule,
+};
diff --git a/test/litest-device-xen-virtual-pointer.c b/test/litest-device-xen-virtual-pointer.c
new file mode 100644 (file)
index 0000000..b4457c2
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+#include <assert.h>
+
+static void
+litest_xen_virtual_pointer_touch_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_XEN_VIRTUAL_POINTER);
+       litest_set_current_device(d);
+}
+
+static void touch_down(struct litest_device *d, unsigned int slot,
+                      double x, double y)
+{
+       assert(slot == 0);
+
+       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
+       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static void touch_move(struct litest_device *d, unsigned int slot,
+                      double x, double y)
+{
+       assert(slot == 0);
+
+       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
+       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static void touch_up(struct litest_device *d, unsigned int slot)
+{
+       assert(slot == 0);
+       litest_event(d, EV_SYN, SYN_REPORT, 0);
+}
+
+static struct litest_device_interface interface = {
+       .touch_down = touch_down,
+       .touch_move = touch_move,
+       .touch_up = touch_up,
+};
+
+static struct input_absinfo absinfo[] = {
+       { ABS_X, 0, 800, 0, 0, 0 },
+       { ABS_Y, 0, 800, 0, 0, 0 },
+       { .value = -1 },
+};
+
+static struct input_id input_id = {
+       .bustype = 0x01,
+       .vendor = 0x5853,
+       .product = 0xfffe,
+};
+
+static int events[] = {
+       EV_KEY, BTN_LEFT,
+       EV_KEY, BTN_RIGHT,
+       EV_KEY, BTN_MIDDLE,
+       EV_KEY, BTN_SIDE,
+       EV_KEY, BTN_EXTRA,
+       EV_KEY, BTN_FORWARD,
+       EV_KEY, BTN_BACK,
+       EV_KEY, BTN_TASK,
+       EV_REL, REL_WHEEL,
+       -1, -1,
+};
+
+struct litest_test_device litest_xen_virtual_pointer_device = {
+       .type = LITEST_XEN_VIRTUAL_POINTER,
+       .features = LITEST_WHEEL | LITEST_BUTTON | LITEST_ABSOLUTE,
+       .shortname = "xen pointer",
+       .setup = litest_xen_virtual_pointer_touch_setup,
+       .interface = &interface,
+
+       .name = "Xen Virtual Pointer",
+       .id = &input_id,
+       .events = events,
+       .absinfo = absinfo,
+};
diff --git a/test/litest-device-yubikey.c b/test/litest-device-yubikey.c
new file mode 100644 (file)
index 0000000..d2c1d4c
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "litest.h"
+#include "litest-int.h"
+
+static void litest_yubikey_setup(void)
+{
+       struct litest_device *d = litest_create_device(LITEST_YUBIKEY);
+       litest_set_current_device(d);
+}
+
+static struct input_id input_id = {
+       .bustype = 0x3,
+       .vendor = 0x1050,
+       .product = 0x10,
+};
+
+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_PAUSE,
+       EV_KEY, KEY_LEFTMETA,
+       EV_KEY, KEY_RIGHTMETA,
+       EV_KEY, KEY_COMPOSE,
+
+       EV_LED, LED_NUML,
+       EV_LED, LED_CAPSL,
+       EV_LED, LED_SCROLLL,
+       EV_LED, LED_COMPOSE,
+       EV_LED, LED_KANA,
+       -1, -1,
+};
+
+struct litest_test_device litest_yubikey_device = {
+       .type = LITEST_YUBIKEY,
+       .features = LITEST_KEYS,
+       .shortname = "yubikey",
+       .setup = litest_yubikey_setup,
+       .interface = NULL,
+
+       .name = "Yubico Yubico Yubikey II",
+       .id = &input_id,
+       .events = events,
+       .absinfo = NULL,
+};
diff --git a/test/litest-generic-singletouch.c b/test/litest-generic-singletouch.c
deleted file mode 100644 (file)
index 1a1a6c6..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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_generic_singletouch_touch_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_GENERIC_SINGLETOUCH);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 10000, 20000, 0, 0, 10 },
-       { ABS_Y, -2000, 2000, 0, 0, 9 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x01,
-       .vendor = 0x02,
-       .product = 0x03,
-};
-
-static int events[] = {
-       EV_KEY, BTN_TOUCH,
-       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
-       -1, -1,
-};
-
-struct litest_test_device litest_generic_singletouch_device = {
-       .type = LITEST_GENERIC_SINGLETOUCH,
-       .features = LITEST_SINGLE_TOUCH,
-       .shortname = "generic-singletouch",
-       .setup = litest_generic_singletouch_touch_setup,
-       .interface = &interface,
-
-       .name = "generic_singletouch",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
index 12746f1bd6e34b7f695b46ed2fd5db53b6eabd60..1a9060d9664c59914d111c92f30312d8016ef8a3 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #if HAVE_CONFIG_H
@@ -78,6 +79,12 @@ struct litest_device_interface {
        void (*touch_move)(struct litest_device *d, unsigned int slot, double x, double y);
        void (*touch_up)(struct litest_device *d, unsigned int slot);
 
+       /**
+        * Default value for the given EV_ABS axis.
+        * @return 0 on success, nonzero otherwise
+        */
+       int (*get_axis_default)(struct litest_device *d, unsigned int code, int32_t *value);
+
        /**
         * Set of of events to execute on touch down, terminated by a .type
         * and .code value of -1. If the event value is LITEST_AUTO_ASSIGN,
@@ -90,8 +97,32 @@ struct litest_device_interface {
        struct input_event *touch_move_events;
        struct input_event *touch_up_events;
 
-       int min[2];
-       int max[2];
+       /**
+        * 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;
+
+       /**
+        * Pad events, LITEST_AUTO_ASSIGN is allowed on event values
+        * for ABS_WHEEL
+        */
+       struct input_event *pad_ring_start_events;
+       struct input_event *pad_ring_change_events;
+       struct input_event *pad_ring_end_events;
+
+       /**
+        * Pad events, LITEST_AUTO_ASSIGN is allowed on event values
+        * for ABS_RX
+        */
+       struct input_event *pad_strip_start_events;
+       struct input_event *pad_strip_change_events;
+       struct input_event *pad_strip_end_events;
+
+       int min[2]; /* x/y axis minimum */
+       int max[2]; /* x/y axis maximum */
 };
 
 void litest_set_current_device(struct litest_device *device);
diff --git a/test/litest-keyboard.c b/test/litest-keyboard.c
deleted file mode 100644 (file)
index 6a9accd..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright © 2013 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_keyboard_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_KEYBOARD);
-       litest_set_current_device(d);
-}
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x1,
-       .product = 0x1,
-};
-
-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_ZENKAKUHANKAKU,
-       EV_KEY, KEY_102ND,
-       EV_KEY, KEY_F11,
-       EV_KEY, KEY_F12,
-       EV_KEY, KEY_RO,
-       EV_KEY, KEY_KATAKANA,
-       EV_KEY, KEY_HIRAGANA,
-       EV_KEY, KEY_HENKAN,
-       EV_KEY, KEY_KATAKANAHIRAGANA,
-       EV_KEY, KEY_MUHENKAN,
-       EV_KEY, KEY_KPJPCOMMA,
-       EV_KEY, KEY_KPENTER,
-       EV_KEY, KEY_RIGHTCTRL,
-       EV_KEY, KEY_KPSLASH,
-       EV_KEY, KEY_SYSRQ,
-       EV_KEY, KEY_RIGHTALT,
-       EV_KEY, KEY_LINEFEED,
-       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_MACRO,
-       EV_KEY, KEY_MUTE,
-       EV_KEY, KEY_VOLUMEDOWN,
-       EV_KEY, KEY_VOLUMEUP,
-       EV_KEY, KEY_POWER,
-       EV_KEY, KEY_KPEQUAL,
-       EV_KEY, KEY_KPPLUSMINUS,
-       EV_KEY, KEY_PAUSE,
-       /* EV_KEY,  KEY_SCALE, */
-       EV_KEY, KEY_KPCOMMA,
-       EV_KEY, KEY_HANGEUL,
-       EV_KEY, KEY_HANJA,
-       EV_KEY, KEY_YEN,
-       EV_KEY, KEY_LEFTMETA,
-       EV_KEY, KEY_RIGHTMETA,
-       EV_KEY, KEY_COMPOSE,
-       EV_KEY, KEY_STOP,
-
-       EV_KEY, KEY_MENU,
-       EV_KEY, KEY_CALC,
-       EV_KEY, KEY_SETUP,
-       EV_KEY, KEY_SLEEP,
-       EV_KEY, KEY_WAKEUP,
-       EV_KEY, KEY_SCREENLOCK,
-       EV_KEY, KEY_DIRECTION,
-       EV_KEY, KEY_CYCLEWINDOWS,
-       EV_KEY, KEY_MAIL,
-       EV_KEY, KEY_BOOKMARKS,
-       EV_KEY, KEY_COMPUTER,
-       EV_KEY, KEY_BACK,
-       EV_KEY, KEY_FORWARD,
-       EV_KEY, KEY_NEXTSONG,
-       EV_KEY, KEY_PLAYPAUSE,
-       EV_KEY, KEY_PREVIOUSSONG,
-       EV_KEY, KEY_STOPCD,
-       EV_KEY, KEY_HOMEPAGE,
-       EV_KEY, KEY_REFRESH,
-       EV_KEY, KEY_F14,
-       EV_KEY, KEY_F15,
-       EV_KEY, KEY_SEARCH,
-       EV_KEY, KEY_MEDIA,
-       EV_KEY, KEY_FN,
-       -1, -1,
-};
-
-struct litest_test_device litest_keyboard_device = {
-       .type = LITEST_KEYBOARD,
-       .features = LITEST_KEYS,
-       .shortname = "default keyboard",
-       .setup = litest_keyboard_setup,
-       .interface = NULL,
-
-       .name = "AT Translated Set 2 keyboard",
-       .id = &input_id,
-       .events = events,
-       .absinfo = NULL,
-};
diff --git a/test/litest-mouse.c b/test/litest-mouse.c
deleted file mode 100644 (file)
index 3f68521..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright © 2013 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_mouse_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_MOUSE);
-       litest_set_current_device(d);
-}
-
-static struct input_id input_id = {
-       .bustype = 0x3,
-       .vendor = 0x17ef,
-       .product = 0x6019,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_MIDDLE,
-       EV_REL, REL_X,
-       EV_REL, REL_Y,
-       EV_REL, REL_WHEEL,
-       -1 , -1,
-};
-
-struct litest_test_device litest_mouse_device = {
-       .type = LITEST_MOUSE,
-       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_WHEEL,
-       .shortname = "mouse",
-       .setup = litest_mouse_setup,
-       .interface = NULL,
-
-       .name = "Lenovo Optical USB Mouse",
-       .id = &input_id,
-       .absinfo = NULL,
-       .events = events,
-};
diff --git a/test/litest-ms-surface-cover.c b/test/litest-ms-surface-cover.c
deleted file mode 100644 (file)
index 5b9ec23..0000000
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * 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_ms_surface_cover_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_MS_SURFACE_COVER);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-/* We define down/move so that we can emulate fake touches on this device,
-   to make sure nothing crashes. */
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_VOLUME, 0, 1023, 0, 0, 0 },
-       { ABS_MISC, 0, 255, 0, 0, 0 },
-       { 41, 0, 255, 0, 0, 0 },
-       { 42, -127, 127, 0, 0, 0 },
-       { 43, -127, 127, 0, 0, 0 },
-       { 44, -127, 127, 0, 0, 0 },
-       { 45, -127, 127, 0, 0, 0 },
-       { 46, -127, 127, 0, 0, 0 },
-       { 47, -127, 127, 0, 0, 0 },
-       /* ABS_MT range overlap starts here */
-       { 48, -127, 127, 0, 0, 0 }, /* ABS_MT_SLOT */
-       { 49, -127, 127, 0, 0, 0 },
-       { 50, -127, 127, 0, 0, 0 },
-       { 51, -127, 127, 0, 0, 0 },
-       { 52, -127, 127, 0, 0, 0 },
-       { 53, -127, 127, 0, 0, 0 },
-       { 54, -127, 127, 0, 0, 0 },
-       { 55, -127, 127, 0, 0, 0 },
-       { 56, -127, 127, 0, 0, 0 },
-       { 57, -127, 127, 0, 0, 0 },
-       { 58, -127, 127, 0, 0, 0 },
-       { 59, -127, 127, 0, 0, 0 },
-       { 60, -127, 127, 0, 0, 0 },
-       { 61, -127, 127, 0, 0, 0 }, /* ABS_MT_TOOL_Y */
-       { 62, -127, 127, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x3,
-       .vendor = 0x45e,
-       .product = 0x7a9,
-};
-
-static int events[] = {
-       EV_REL, REL_X,
-       EV_REL, REL_Y,
-       EV_REL, REL_HWHEEL,
-       EV_REL, REL_DIAL,
-       EV_REL, REL_WHEEL,
-       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_RO,
-       EV_KEY, KEY_HENKAN,
-       EV_KEY, KEY_KATAKANAHIRAGANA,
-       EV_KEY, KEY_MUHENKAN,
-       EV_KEY, KEY_KPJPCOMMA,
-       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_POWER,
-       EV_KEY, KEY_KPEQUAL,
-       EV_KEY, KEY_PAUSE,
-       EV_KEY, KEY_KPCOMMA,
-       EV_KEY, KEY_HANGEUL,
-       EV_KEY, KEY_HANJA,
-       EV_KEY, KEY_YEN,
-       EV_KEY, KEY_LEFTMETA,
-       EV_KEY, KEY_RIGHTMETA,
-       EV_KEY, KEY_COMPOSE,
-       EV_KEY, KEY_STOP,
-       EV_KEY, KEY_AGAIN,
-       EV_KEY, KEY_PROPS,
-       EV_KEY, KEY_UNDO,
-       EV_KEY, KEY_FRONT,
-       EV_KEY, KEY_COPY,
-       EV_KEY, KEY_OPEN,
-       EV_KEY, KEY_PASTE,
-       EV_KEY, KEY_FIND,
-       EV_KEY, KEY_CUT,
-       EV_KEY, KEY_HELP,
-       EV_KEY, KEY_MENU,
-       EV_KEY, KEY_CALC,
-       EV_KEY, KEY_SLEEP,
-       EV_KEY, KEY_FILE,
-       EV_KEY, KEY_WWW,
-       EV_KEY, KEY_COFFEE,
-       EV_KEY, KEY_MAIL,
-       EV_KEY, KEY_BOOKMARKS,
-       EV_KEY, KEY_BACK,
-       EV_KEY, KEY_FORWARD,
-       EV_KEY, KEY_EJECTCD,
-       EV_KEY, KEY_NEXTSONG,
-       EV_KEY, KEY_PLAYPAUSE,
-       EV_KEY, KEY_PREVIOUSSONG,
-       EV_KEY, KEY_STOPCD,
-       EV_KEY, KEY_RECORD,
-       EV_KEY, KEY_REWIND,
-       EV_KEY, KEY_PHONE,
-       EV_KEY, KEY_CONFIG,
-       EV_KEY, KEY_HOMEPAGE,
-       EV_KEY, KEY_REFRESH,
-       EV_KEY, KEY_EXIT,
-       EV_KEY, KEY_EDIT,
-       EV_KEY, KEY_SCROLLUP,
-       EV_KEY, KEY_SCROLLDOWN,
-       EV_KEY, KEY_NEW,
-       EV_KEY, KEY_REDO,
-       EV_KEY, KEY_F13,
-       EV_KEY, KEY_F14,
-       EV_KEY, KEY_F15,
-       EV_KEY, KEY_F16,
-       EV_KEY, KEY_F17,
-       EV_KEY, KEY_F18,
-       EV_KEY, KEY_F19,
-       EV_KEY, KEY_F20,
-       EV_KEY, KEY_F21,
-       EV_KEY, KEY_F22,
-       EV_KEY, KEY_F23,
-       EV_KEY, KEY_F24,
-       EV_KEY, KEY_CLOSE,
-       EV_KEY, KEY_PLAY,
-       EV_KEY, KEY_FASTFORWARD,
-       EV_KEY, KEY_BASSBOOST,
-       EV_KEY, KEY_PRINT,
-       EV_KEY, KEY_CAMERA,
-       EV_KEY, KEY_CHAT,
-       EV_KEY, KEY_SEARCH,
-       EV_KEY, KEY_FINANCE,
-       EV_KEY, KEY_CANCEL,
-       EV_KEY, KEY_BRIGHTNESSDOWN,
-       EV_KEY, KEY_BRIGHTNESSUP,
-       EV_KEY, KEY_KBDILLUMTOGGLE,
-       EV_KEY, KEY_SEND,
-       EV_KEY, KEY_REPLY,
-       EV_KEY, KEY_FORWARDMAIL,
-       EV_KEY, KEY_SAVE,
-       EV_KEY, KEY_DOCUMENTS,
-       EV_KEY, KEY_UNKNOWN,
-       EV_KEY, KEY_VIDEO_NEXT,
-       EV_KEY, KEY_BRIGHTNESS_ZERO,
-       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, KEY_SELECT,
-       EV_KEY, KEY_GOTO,
-       EV_KEY, KEY_INFO,
-       EV_KEY, KEY_PROGRAM,
-       EV_KEY, KEY_PVR,
-       EV_KEY, KEY_SUBTITLE,
-       EV_KEY, KEY_ZOOM,
-       EV_KEY, KEY_KEYBOARD,
-       EV_KEY, KEY_PC,
-       EV_KEY, KEY_TV,
-       EV_KEY, KEY_TV2,
-       EV_KEY, KEY_VCR,
-       EV_KEY, KEY_VCR2,
-       EV_KEY, KEY_SAT,
-       EV_KEY, KEY_CD,
-       EV_KEY, KEY_TAPE,
-       EV_KEY, KEY_TUNER,
-       EV_KEY, KEY_PLAYER,
-       EV_KEY, KEY_DVD,
-       EV_KEY, KEY_AUDIO,
-       EV_KEY, KEY_VIDEO,
-       EV_KEY, KEY_MEMO,
-       EV_KEY, KEY_CALENDAR,
-       EV_KEY, KEY_RED,
-       EV_KEY, KEY_GREEN,
-       EV_KEY, KEY_YELLOW,
-       EV_KEY, KEY_BLUE,
-       EV_KEY, KEY_CHANNELUP,
-       EV_KEY, KEY_CHANNELDOWN,
-       EV_KEY, KEY_LAST,
-       EV_KEY, KEY_NEXT,
-       EV_KEY, KEY_RESTART,
-       EV_KEY, KEY_SLOW,
-       EV_KEY, KEY_SHUFFLE,
-       EV_KEY, KEY_PREVIOUS,
-       EV_KEY, KEY_VIDEOPHONE,
-       EV_KEY, KEY_GAMES,
-       EV_KEY, KEY_ZOOMIN,
-       EV_KEY, KEY_ZOOMOUT,
-       EV_KEY, KEY_ZOOMRESET,
-       EV_KEY, KEY_WORDPROCESSOR,
-       EV_KEY, KEY_EDITOR,
-       EV_KEY, KEY_SPREADSHEET,
-       EV_KEY, KEY_GRAPHICSEDITOR,
-       EV_KEY, KEY_PRESENTATION,
-       EV_KEY, KEY_DATABASE,
-       EV_KEY, KEY_NEWS,
-       EV_KEY, KEY_VOICEMAIL,
-       EV_KEY, KEY_ADDRESSBOOK,
-       EV_KEY, KEY_MESSENGER,
-       EV_KEY, KEY_DISPLAYTOGGLE,
-       EV_KEY, KEY_SPELLCHECK,
-       EV_KEY, KEY_LOGOFF,
-       EV_KEY, KEY_MEDIA_REPEAT,
-       EV_KEY, KEY_IMAGES,
-       EV_KEY, 576,
-       EV_KEY, 577,
-       EV_KEY, 578,
-       EV_KEY, 579,
-       EV_KEY, 580,
-       EV_KEY, 581,
-       EV_KEY, 582,
-       EV_KEY, 592,
-       EV_KEY, 593,
-       EV_KEY, 608,
-       EV_KEY, 609,
-       EV_KEY, 610,
-       EV_KEY, 611,
-       EV_KEY, 612,
-       EV_KEY, 613,
-       EV_LED, LED_NUML,
-       EV_LED, LED_CAPSL,
-       EV_LED, LED_SCROLLL,
-       EV_REP, REP_DELAY,
-       -1, -1,
-};
-
-struct litest_test_device litest_ms_surface_cover_device = {
-       .type = LITEST_MS_SURFACE_COVER,
-       .features = LITEST_KEYBOARD | LITEST_RELATIVE | LITEST_FAKE_MT,
-       .shortname = "MS surface cover",
-       .setup = litest_ms_surface_cover_setup,
-       .interface = &interface,
-
-       .name = "MICROSOFT SAM",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-qemu-usb-tablet.c b/test/litest-qemu-usb-tablet.c
deleted file mode 100644 (file)
index 4f03e24..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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"
-#include <assert.h>
-
-static void
-litest_qemu_tablet_touch_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_QEMU_TABLET);
-       litest_set_current_device(d);
-}
-
-static void touch_down(struct litest_device *d, unsigned int slot,
-                      double x, double y)
-{
-       assert(slot == 0);
-
-       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
-       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static void touch_move(struct litest_device *d, unsigned int slot,
-                      double x, double y)
-{
-       assert(slot == 0);
-
-       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
-       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static void touch_up(struct litest_device *d, unsigned int slot)
-{
-       assert(slot == 0);
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static struct litest_device_interface interface = {
-       .touch_down = touch_down,
-       .touch_move = touch_move,
-       .touch_up = touch_up,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 0, 32767, 0, 0, 0 },
-       { ABS_Y, 0, 32767, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x03,
-       .vendor = 0x627,
-       .product = 0x01,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_MIDDLE,
-       EV_REL, REL_WHEEL,
-       -1, -1,
-};
-
-struct litest_test_device litest_qemu_tablet_device = {
-       .type = LITEST_QEMU_TABLET,
-       .features = LITEST_WHEEL | LITEST_BUTTON | LITEST_ABSOLUTE,
-       .shortname = "qemu tablet",
-       .setup = litest_qemu_tablet_touch_setup,
-       .interface = &interface,
-
-       .name = "QEMU 0.12.1 QEMU USB Tablet",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-selftest.c b/test/litest-selftest.c
new file mode 100644 (file)
index 0000000..47d5ef1
--- /dev/null
@@ -0,0 +1,435 @@
+#include <config.h>
+
+#include <check.h>
+#include <signal.h>
+
+#include "litest.h"
+
+START_TEST(litest_assert_trigger)
+{
+       litest_assert(1 == 2);
+}
+END_TEST
+
+START_TEST(litest_assert_notrigger)
+{
+       litest_assert(1 == 1);
+}
+END_TEST
+
+START_TEST(litest_assert_msg_trigger)
+{
+       litest_assert_msg(1 == 2, "1 is not 2\n");
+}
+END_TEST
+
+START_TEST(litest_assert_msg_NULL_trigger)
+{
+       litest_assert_msg(1 == 2, NULL);
+}
+END_TEST
+
+START_TEST(litest_assert_msg_notrigger)
+{
+       litest_assert_msg(1 == 1, "1 is not 2\n");
+       litest_assert_msg(1 == 1, NULL);
+}
+END_TEST
+
+START_TEST(litest_abort_msg_trigger)
+{
+       litest_abort_msg("message\n");
+}
+END_TEST
+
+START_TEST(litest_abort_msg_NULL_trigger)
+{
+       litest_abort_msg(NULL);
+}
+END_TEST
+
+START_TEST(litest_int_eq_trigger)
+{
+       int a = 10;
+       int b = 20;
+       litest_assert_int_eq(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_eq_notrigger)
+{
+       int a = 10;
+       int b = 10;
+       litest_assert_int_eq(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_ne_trigger)
+{
+       int a = 10;
+       int b = 10;
+       litest_assert_int_ne(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_ne_notrigger)
+{
+       int a = 10;
+       int b = 20;
+       litest_assert_int_ne(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_lt_trigger_eq)
+{
+       int a = 10;
+       int b = 10;
+       litest_assert_int_lt(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_lt_trigger_gt)
+{
+       int a = 11;
+       int b = 10;
+       litest_assert_int_lt(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_lt_notrigger)
+{
+       int a = 10;
+       int b = 11;
+       litest_assert_int_lt(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_le_trigger)
+{
+       int a = 11;
+       int b = 10;
+       litest_assert_int_le(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_le_notrigger)
+{
+       int a = 10;
+       int b = 11;
+       int c = 10;
+       litest_assert_int_le(a, b);
+       litest_assert_int_le(a, c);
+}
+END_TEST
+
+START_TEST(litest_int_gt_trigger_eq)
+{
+       int a = 10;
+       int b = 10;
+       litest_assert_int_gt(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_gt_trigger_lt)
+{
+       int a = 9;
+       int b = 10;
+       litest_assert_int_gt(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_gt_notrigger)
+{
+       int a = 10;
+       int b = 9;
+       litest_assert_int_gt(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_ge_trigger)
+{
+       int a = 9;
+       int b = 10;
+       litest_assert_int_ge(a, b);
+}
+END_TEST
+
+START_TEST(litest_int_ge_notrigger)
+{
+       int a = 10;
+       int b = 9;
+       int c = 10;
+       litest_assert_int_ge(a, b);
+       litest_assert_int_ge(a, c);
+}
+END_TEST
+
+START_TEST(litest_ptr_eq_notrigger)
+{
+       int v = 10;
+       int *a = &v;
+       int *b = &v;
+       int *c = NULL;
+       int *d = NULL;
+
+       litest_assert_ptr_eq(a, b);
+       litest_assert_ptr_eq(c, d);
+}
+END_TEST
+
+START_TEST(litest_ptr_eq_trigger)
+{
+       int v = 10;
+       int v2 = 11;
+       int *a = &v;
+       int *b = &v2;
+
+       litest_assert_ptr_eq(a, b);
+}
+END_TEST
+
+START_TEST(litest_ptr_eq_trigger_NULL)
+{
+       int v = 10;
+       int *a = &v;
+       int *b = NULL;
+
+       litest_assert_ptr_eq(a, b);
+}
+END_TEST
+
+START_TEST(litest_ptr_eq_trigger_NULL2)
+{
+       int v = 10;
+       int *a = &v;
+       int *b = NULL;
+
+       litest_assert_ptr_eq(b, a);
+}
+END_TEST
+
+START_TEST(litest_ptr_ne_trigger)
+{
+       int v = 10;
+       int *a = &v;
+       int *b = &v;
+
+       litest_assert_ptr_ne(a, b);
+}
+END_TEST
+
+START_TEST(litest_ptr_ne_trigger_NULL)
+{
+       int *a = NULL;
+
+       litest_assert_ptr_ne(a, NULL);
+}
+END_TEST
+
+START_TEST(litest_ptr_ne_trigger_NULL2)
+{
+       int *a = NULL;
+
+       litest_assert_ptr_ne(NULL, a);
+}
+END_TEST
+
+START_TEST(litest_ptr_ne_notrigger)
+{
+       int v1 = 10;
+       int v2 = 10;
+       int *a = &v1;
+       int *b = &v2;
+       int *c = NULL;
+
+       litest_assert_ptr_ne(a, b);
+       litest_assert_ptr_ne(a, c);
+       litest_assert_ptr_ne(c, b);
+}
+END_TEST
+
+START_TEST(litest_ptr_null_notrigger)
+{
+       int *a = NULL;
+
+       litest_assert_ptr_null(a);
+       litest_assert_ptr_null(NULL);
+}
+END_TEST
+
+START_TEST(litest_ptr_null_trigger)
+{
+       int v;
+       int *a = &v;
+
+       litest_assert_ptr_null(a);
+}
+END_TEST
+
+START_TEST(litest_ptr_notnull_notrigger)
+{
+       int v;
+       int *a = &v;
+
+       litest_assert_ptr_notnull(a);
+}
+END_TEST
+
+START_TEST(litest_ptr_notnull_trigger)
+{
+       int *a = NULL;
+
+       litest_assert_ptr_notnull(a);
+}
+END_TEST
+
+START_TEST(litest_ptr_notnull_trigger_NULL)
+{
+       litest_assert_ptr_notnull(NULL);
+}
+END_TEST
+
+START_TEST(ck_double_eq_and_ne)
+{
+       ck_assert_double_eq(0.4,0.4);
+       ck_assert_double_eq(0.4,0.4 + 1E-6);
+       ck_assert_double_ne(0.4,0.4 + 1E-3);
+}
+END_TEST
+
+START_TEST(ck_double_lt_gt)
+{
+       ck_assert_double_lt(12.0,13.0);
+       ck_assert_double_gt(15.4,13.0);
+       ck_assert_double_le(12.0,12.0);
+       ck_assert_double_le(12.0,20.0);
+       ck_assert_double_ge(12.0,12.0);
+       ck_assert_double_ge(20.0,12.0);
+}
+END_TEST
+
+START_TEST(ck_double_eq_fails)
+{
+       ck_assert_double_eq(0.41,0.4);
+}
+END_TEST
+
+START_TEST(ck_double_ne_fails)
+{
+       ck_assert_double_ne(0.4 + 1E-7,0.4);
+}
+END_TEST
+
+START_TEST(ck_double_lt_fails)
+{
+       ck_assert_double_lt(6,5);
+}
+END_TEST
+
+START_TEST(ck_double_gt_fails)
+{
+       ck_assert_double_gt(5,6);
+}
+END_TEST
+
+START_TEST(ck_double_le_fails)
+{
+       ck_assert_double_le(6,5);
+}
+END_TEST
+
+START_TEST(ck_double_ge_fails)
+{
+       ck_assert_double_ge(5,6);
+}
+END_TEST
+
+static Suite *
+litest_assert_macros_suite(void)
+{
+       TCase *tc;
+       Suite *s;
+
+       s = suite_create("litest:assert macros");
+       tc = tcase_create("assert");
+       tcase_add_test_raise_signal(tc, litest_assert_trigger, SIGABRT);
+       tcase_add_test(tc, litest_assert_notrigger);
+       tcase_add_test_raise_signal(tc, litest_assert_msg_trigger, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_assert_msg_NULL_trigger, SIGABRT);
+       tcase_add_test(tc, litest_assert_msg_notrigger);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("abort");
+       tcase_add_test_raise_signal(tc, litest_abort_msg_trigger, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_abort_msg_NULL_trigger, SIGABRT);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("int comparison ");
+       tcase_add_test_raise_signal(tc, litest_int_eq_trigger, SIGABRT);
+       tcase_add_test(tc, litest_int_eq_notrigger);
+       tcase_add_test_raise_signal(tc, litest_int_ne_trigger, SIGABRT);
+       tcase_add_test(tc, litest_int_ne_notrigger);
+       tcase_add_test_raise_signal(tc, litest_int_le_trigger, SIGABRT);
+       tcase_add_test(tc, litest_int_le_notrigger);
+       tcase_add_test_raise_signal(tc, litest_int_lt_trigger_gt, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_int_lt_trigger_eq, SIGABRT);
+       tcase_add_test(tc, litest_int_lt_notrigger);
+       tcase_add_test_raise_signal(tc, litest_int_ge_trigger, SIGABRT);
+       tcase_add_test(tc, litest_int_ge_notrigger);
+       tcase_add_test_raise_signal(tc, litest_int_gt_trigger_eq, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_int_gt_trigger_lt, SIGABRT);
+       tcase_add_test(tc, litest_int_gt_notrigger);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("pointer comparison ");
+       tcase_add_test_raise_signal(tc, litest_ptr_eq_trigger, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_ptr_eq_trigger_NULL, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_ptr_eq_trigger_NULL2, SIGABRT);
+       tcase_add_test(tc, litest_ptr_eq_notrigger);
+       tcase_add_test_raise_signal(tc, litest_ptr_ne_trigger, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_ptr_ne_trigger_NULL, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_ptr_ne_trigger_NULL2, SIGABRT);
+       tcase_add_test(tc, litest_ptr_ne_notrigger);
+       tcase_add_test_raise_signal(tc, litest_ptr_null_trigger, SIGABRT);
+       tcase_add_test(tc, litest_ptr_null_notrigger);
+       tcase_add_test_raise_signal(tc, litest_ptr_notnull_trigger, SIGABRT);
+       tcase_add_test_raise_signal(tc, litest_ptr_notnull_trigger_NULL, SIGABRT);
+       tcase_add_test(tc, litest_ptr_notnull_notrigger);
+       suite_add_tcase(s, tc);
+
+       tc = tcase_create("double comparison ");
+       tcase_add_test(tc, ck_double_eq_and_ne);
+       tcase_add_test(tc, ck_double_lt_gt);
+       tcase_add_exit_test(tc, ck_double_eq_fails, 1);
+       tcase_add_exit_test(tc, ck_double_ne_fails, 1);
+       tcase_add_exit_test(tc, ck_double_lt_fails, 1);
+       tcase_add_exit_test(tc, ck_double_gt_fails, 1);
+       tcase_add_exit_test(tc, ck_double_le_fails, 1);
+       tcase_add_exit_test(tc, ck_double_ge_fails, 1);
+       suite_add_tcase(s, tc);
+
+       return s;
+}
+
+int
+main (int argc, char **argv)
+{
+       int nfailed;
+       Suite *s;
+       SRunner *sr;
+
+        /* when running under valgrind we're using nofork mode, so a signal
+         * raised by a test will fail in valgrind. There's nothing to
+         * memcheck here anyway, so just skip the valgrind test */
+        if (getenv("USING_VALGRIND"))
+            return EXIT_SUCCESS;
+
+       s = litest_assert_macros_suite();
+        sr = srunner_create(s);
+
+       srunner_run_all(sr, CK_ENV);
+       nfailed = srunner_ntests_failed(sr);
+       srunner_free(sr);
+
+       return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/litest-synaptics-hover.c b/test/litest-synaptics-hover.c
deleted file mode 100644 (file)
index 7ba56bc..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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 <assert.h>
-
-#include "libinput-util.h"
-
-#include "litest.h"
-#include "litest-int.h"
-
-static void
-synaptics_hover_create(struct litest_device *d);
-
-static void
-litest_synaptics_hover_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_HOVER_SEMI_MT);
-       litest_set_current_device(d);
-}
-
-static void
-synaptics_hover_touch_down(struct litest_device *d, unsigned int slot, double x, double y)
-{
-       struct litest_semi_mt *semi_mt = d->private;
-
-       litest_semi_mt_touch_down(d, semi_mt, slot, x, y);
-}
-
-static void
-synaptics_hover_touch_move(struct litest_device *d, unsigned int slot, double x, double y)
-{
-       struct litest_semi_mt *semi_mt = d->private;
-
-       litest_semi_mt_touch_move(d, semi_mt, slot, x, y);
-}
-
-static void
-synaptics_hover_touch_up(struct litest_device *d, unsigned int slot)
-{
-       struct litest_semi_mt *semi_mt = d->private;
-
-       litest_semi_mt_touch_up(d, semi_mt, slot);
-}
-
-static struct litest_device_interface interface = {
-       .touch_down = synaptics_hover_touch_down,
-       .touch_move = synaptics_hover_touch_move,
-       .touch_up = synaptics_hover_touch_up,
-};
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0x7,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOUCH,
-       EV_KEY, BTN_TOOL_DOUBLETAP,
-       EV_KEY, BTN_TOOL_TRIPLETAP,
-       INPUT_PROP_MAX, INPUT_PROP_POINTER,
-       INPUT_PROP_MAX, INPUT_PROP_SEMI_MT,
-       -1, -1,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 1472, 5472, 0, 0, 60 },
-       { ABS_Y, 1408, 4498, 0, 0, 85 },
-       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
-       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
-       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
-       { ABS_MT_POSITION_X, 1472, 5472, 0, 0, 60 },
-       { ABS_MT_POSITION_Y, 1408, 4498, 0, 0, 85 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { .value = -1 }
-};
-
-struct litest_test_device litest_synaptics_hover_device = {
-       .type = LITEST_SYNAPTICS_HOVER_SEMI_MT,
-       .features = LITEST_TOUCHPAD | LITEST_SEMI_MT | LITEST_BUTTON,
-       .shortname = "synaptics hover",
-       .setup = litest_synaptics_hover_setup,
-       .interface = &interface,
-       .create = synaptics_hover_create,
-
-       .name = "SynPS/2 Synaptics TouchPad",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
-
-static void
-synaptics_hover_create(struct litest_device *d)
-{
-       struct litest_semi_mt *semi_mt = zalloc(sizeof(*semi_mt));
-       assert(semi_mt);
-
-       d->private = semi_mt;
-
-       d->uinput = litest_create_uinput_device_from_description(
-                       litest_synaptics_hover_device.name,
-                       litest_synaptics_hover_device.id,
-                       absinfo,
-                       events);
-       d->interface = &interface;
-}
diff --git a/test/litest-synaptics-st.c b/test/litest-synaptics-st.c
deleted file mode 100644 (file)
index 9f69332..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright © 2013 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_synaptics_touchpad_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_TOUCHPAD);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .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 = 30  },
-       { .type = EV_ABS, .code = ABS_TOOL_WIDTH, .value = 7  },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-struct input_event up[] = {
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-       .touch_up_events = up,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 1472, 5472, 0, 0, 75 },
-       { ABS_Y, 1408, 4448, 0, 0, 129 },
-       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
-       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0x7,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOUCH,
-       -1, -1,
-};
-
-struct litest_test_device litest_synaptics_touchpad_device = {
-       .type = LITEST_SYNAPTICS_TOUCHPAD,
-       .features = LITEST_TOUCHPAD | LITEST_BUTTON | LITEST_SINGLE_TOUCH,
-       .shortname = "synaptics ST",
-       .setup = litest_synaptics_touchpad_setup,
-       .interface = &interface,
-
-       .name = "SynPS/2 Synaptics TouchPad",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-synaptics-t440.c b/test/litest-synaptics-t440.c
deleted file mode 100644 (file)
index 65a0ad4..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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_synaptics_t440_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_TOPBUTTONPAD);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .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 = 30  },
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN  },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0x7,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOOL_QUINTTAP,
-       EV_KEY, BTN_TOUCH,
-       EV_KEY, BTN_TOOL_DOUBLETAP,
-       EV_KEY, BTN_TOOL_TRIPLETAP,
-       EV_KEY, BTN_TOOL_QUADTAP,
-       INPUT_PROP_MAX, INPUT_PROP_POINTER,
-       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
-       INPUT_PROP_MAX, INPUT_PROP_TOPBUTTONPAD,
-       -1, -1,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 1024, 5112, 0, 0, 42 },
-       { ABS_Y, 2024, 4832, 0, 0, 42 },
-       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
-       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
-       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
-       { ABS_MT_POSITION_X, 1024, 5112, 0, 0, 42 },
-       { ABS_MT_POSITION_Y, 2024, 4832, 0, 0, 42 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
-       { .value = -1 }
-};
-
-struct litest_test_device litest_synaptics_t440_device = {
-       .type = LITEST_SYNAPTICS_TOPBUTTONPAD,
-       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON | LITEST_TOPBUTTONPAD,
-       .shortname = "synaptics t440",
-       .setup = litest_synaptics_t440_setup,
-       .interface = &interface,
-
-       .name = "SynPS/2 Synaptics TouchPad",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-synaptics-x1-carbon-3rd.c b/test/litest-synaptics-x1-carbon-3rd.c
deleted file mode 100644 (file)
index 67d6f46..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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_synaptics_carbon3rd_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .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 = 30  },
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN  },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0x7,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOOL_QUINTTAP,
-       EV_KEY, BTN_TOUCH,
-       EV_KEY, BTN_TOOL_DOUBLETAP,
-       EV_KEY, BTN_TOOL_TRIPLETAP,
-       EV_KEY, BTN_TOOL_QUADTAP,
-       EV_KEY, BTN_0,
-       EV_KEY, BTN_1,
-       EV_KEY, BTN_2,
-       INPUT_PROP_MAX, INPUT_PROP_POINTER,
-       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
-       -1, -1,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 1266, 5676, 0, 0, 45 },
-       { ABS_Y, 1096, 4758, 0, 0, 68 },
-       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
-       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
-       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
-       { ABS_MT_POSITION_X, 1266, 5676, 0, 0, 45 },
-       { ABS_MT_POSITION_Y, 1096, 4758, 0, 0, 68 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
-       { .value = -1 }
-};
-
-static const char udev_rule[] =
-"ACTION==\"remove\", GOTO=\"touchpad_end\"\n"
-"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n"
-"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n"
-"\n"
-"ATTRS{name}==\"litest*X1C3rd*\",\\\n"
-"    ENV{TOUCHPAD_HAS_TRACKPOINT_BUTTONS}=\"1\"\n"
-"\n"
-"LABEL=\"touchpad_end\"";
-
-struct litest_test_device litest_synaptics_carbon3rd_device = {
-       .type = LITEST_SYNAPTICS_TRACKPOINT_BUTTONS,
-       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON,
-       .shortname = "synaptics carbon3rd",
-       .setup = litest_synaptics_carbon3rd_setup,
-       .interface = &interface,
-
-       .name = "SynPS/2 Synaptics TouchPad X1C3rd",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-       .udev_rule = udev_rule,
-};
diff --git a/test/litest-synaptics.c b/test/litest-synaptics.c
deleted file mode 100644 (file)
index 5565e63..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright © 2013 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_synaptics_clickpad_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_SYNAPTICS_CLICKPAD);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .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 = 30  },
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN  },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0x11,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_TOOL_FINGER,
-       EV_KEY, BTN_TOOL_QUINTTAP,
-       EV_KEY, BTN_TOUCH,
-       EV_KEY, BTN_TOOL_DOUBLETAP,
-       EV_KEY, BTN_TOOL_TRIPLETAP,
-       EV_KEY, BTN_TOOL_QUADTAP,
-       INPUT_PROP_MAX, INPUT_PROP_POINTER,
-       INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD,
-       -1, -1,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 1472, 5472, 0, 0, 75 },
-       { ABS_Y, 1408, 4448, 0, 0, 129 },
-       { ABS_PRESSURE, 0, 255, 0, 0, 0 },
-       { ABS_TOOL_WIDTH, 0, 15, 0, 0, 0 },
-       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
-       { ABS_MT_POSITION_X, 1472, 5472, 0, 0, 75 },
-       { ABS_MT_POSITION_Y, 1408, 4448, 0, 0, 129 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { ABS_MT_PRESSURE, 0, 255, 0, 0, 0 },
-       { .value = -1 }
-};
-
-struct litest_test_device litest_synaptics_clickpad_device = {
-       .type = LITEST_SYNAPTICS_CLICKPAD,
-       .features = LITEST_TOUCHPAD | LITEST_CLICKPAD | LITEST_BUTTON,
-       .shortname = "synaptics",
-       .setup = litest_synaptics_clickpad_setup,
-       .interface = &interface,
-
-       .name = "SynPS/2 Synaptics TouchPad",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-trackpoint.c b/test/litest-trackpoint.c
deleted file mode 100644 (file)
index 09f8b70..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright © 2013 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_trackpoint_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_TRACKPOINT);
-       litest_set_current_device(d);
-}
-
-static struct input_id input_id = {
-       .bustype = 0x11,
-       .vendor = 0x2,
-       .product = 0xa,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_MIDDLE,
-       EV_REL, REL_X,
-       EV_REL, REL_Y,
-       INPUT_PROP_MAX, INPUT_PROP_POINTER,
-       INPUT_PROP_MAX, INPUT_PROP_POINTING_STICK,
-       -1, -1,
-};
-
-struct litest_test_device litest_trackpoint_device = {
-       .type = LITEST_TRACKPOINT,
-       .features = LITEST_RELATIVE | LITEST_BUTTON | LITEST_POINTINGSTICK,
-       .shortname = "trackpoint",
-       .setup = litest_trackpoint_setup,
-       .interface = NULL,
-
-       .name = "TPPS/2 IBM TrackPoint",
-       .id = &input_id,
-       .absinfo = NULL,
-       .events = events,
-
-};
diff --git a/test/litest-vmware-virtual-usb-mouse.c b/test/litest-vmware-virtual-usb-mouse.c
deleted file mode 100644 (file)
index c4ed368..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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"
-#include <assert.h>
-
-static void
-litest_vmware_virtmouse_touch_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_VMWARE_VIRTMOUSE);
-       litest_set_current_device(d);
-}
-
-static void touch_down(struct litest_device *d, unsigned int slot,
-                      double x, double y)
-{
-       assert(slot == 0);
-
-       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
-       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static void touch_move(struct litest_device *d, unsigned int slot,
-                      double x, double y)
-{
-       assert(slot == 0);
-
-       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
-       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static void touch_up(struct litest_device *d, unsigned int slot)
-{
-       assert(slot == 0);
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static struct litest_device_interface interface = {
-       .touch_down = touch_down,
-       .touch_move = touch_move,
-       .touch_up = touch_up,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 0, 32767, 0, 0, 0 },
-       { ABS_Y, 0, 32767, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x03,
-       .vendor = 0xe0f,
-       .product = 0x03,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_MIDDLE,
-       EV_KEY, BTN_SIDE,
-       EV_KEY, BTN_EXTRA,
-       EV_KEY, BTN_FORWARD,
-       EV_KEY, BTN_BACK,
-       EV_KEY, BTN_TASK,
-       EV_KEY, 280,
-       EV_KEY, 281,
-       EV_KEY, 282,
-       EV_KEY, 283,
-       EV_KEY, 284,
-       EV_KEY, 285,
-       EV_KEY, 286,
-       EV_KEY, 287,
-       EV_REL, REL_WHEEL,
-       EV_REL, REL_HWHEEL,
-       -1, -1,
-};
-
-struct litest_test_device litest_vmware_virtmouse_device = {
-       .type = LITEST_VMWARE_VIRTMOUSE,
-       .features = LITEST_WHEEL | LITEST_BUTTON | LITEST_ABSOLUTE,
-       .shortname = "vmware virtmouse",
-       .setup = litest_vmware_virtmouse_touch_setup,
-       .interface = &interface,
-
-       .name = "VMware VMware Virtual USB Mouse",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-wacom-touch.c b/test/litest-wacom-touch.c
deleted file mode 100644 (file)
index 49f3e64..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright © 2013 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_touch_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_WACOM_TOUCH);
-       litest_set_current_device(d);
-}
-
-static struct input_event down[] = {
-       { .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_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct input_event move[] = {
-       { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = LITEST_AUTO_ASSIGN },
-       { .type = EV_KEY, .code = BTN_TOUCH, .value = 1 },
-       { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
-       { .type = -1, .code = -1 },
-};
-
-static struct litest_device_interface interface = {
-       .touch_down_events = down,
-       .touch_move_events = move,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 0, 2776, 0, 0, 10 },
-       { ABS_Y, 0, 1569, 0, 0, 9 },
-       { ABS_MT_SLOT, 0, 1, 0, 0, 0 },
-       { ABS_MT_POSITION_X, 0, 2776, 0, 0, 10 },
-       { ABS_MT_POSITION_Y, 0, 1569, 0, 0, 9 },
-       { ABS_MT_TRACKING_ID, 0, 65535, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x3,
-       .vendor = 0x56a,
-       .product = 0xe6,
-};
-
-static int events[] = {
-       EV_KEY, BTN_TOUCH,
-       INPUT_PROP_MAX, INPUT_PROP_DIRECT,
-       -1, -1,
-};
-
-struct litest_test_device litest_wacom_touch_device = {
-       .type = LITEST_WACOM_TOUCH,
-       .features = LITEST_TOUCH,
-       .shortname = "wacom-touch",
-       .setup = litest_wacom_touch_setup,
-       .interface = &interface,
-
-       .name = "Wacom ISDv4 E6 Finger",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
diff --git a/test/litest-xen-virtual-pointer.c b/test/litest-xen-virtual-pointer.c
deleted file mode 100644 (file)
index 25891a7..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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"
-#include <assert.h>
-
-static void
-litest_xen_virtual_pointer_touch_setup(void)
-{
-       struct litest_device *d = litest_create_device(LITEST_XEN_VIRTUAL_POINTER);
-       litest_set_current_device(d);
-}
-
-static void touch_down(struct litest_device *d, unsigned int slot,
-                      double x, double y)
-{
-       assert(slot == 0);
-
-       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
-       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static void touch_move(struct litest_device *d, unsigned int slot,
-                      double x, double y)
-{
-       assert(slot == 0);
-
-       litest_event(d, EV_ABS, ABS_X, litest_scale(d, ABS_X, x));
-       litest_event(d, EV_ABS, ABS_Y, litest_scale(d, ABS_Y, y));
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static void touch_up(struct litest_device *d, unsigned int slot)
-{
-       assert(slot == 0);
-       litest_event(d, EV_SYN, SYN_REPORT, 0);
-}
-
-static struct litest_device_interface interface = {
-       .touch_down = touch_down,
-       .touch_move = touch_move,
-       .touch_up = touch_up,
-};
-
-static struct input_absinfo absinfo[] = {
-       { ABS_X, 0, 800, 0, 0, 0 },
-       { ABS_Y, 0, 800, 0, 0, 0 },
-       { .value = -1 },
-};
-
-static struct input_id input_id = {
-       .bustype = 0x01,
-       .vendor = 0x5853,
-       .product = 0xfffe,
-};
-
-static int events[] = {
-       EV_KEY, BTN_LEFT,
-       EV_KEY, BTN_RIGHT,
-       EV_KEY, BTN_MIDDLE,
-       EV_KEY, BTN_SIDE,
-       EV_KEY, BTN_EXTRA,
-       EV_KEY, BTN_FORWARD,
-       EV_KEY, BTN_BACK,
-       EV_KEY, BTN_TASK,
-       EV_REL, REL_WHEEL,
-       -1, -1,
-};
-
-struct litest_test_device litest_xen_virtual_pointer_device = {
-       .type = LITEST_XEN_VIRTUAL_POINTER,
-       .features = LITEST_WHEEL | LITEST_BUTTON | LITEST_ABSOLUTE,
-       .shortname = "xen pointer",
-       .setup = litest_xen_virtual_pointer_touch_setup,
-       .interface = &interface,
-
-       .name = "Xen Virtual Pointer",
-       .id = &input_id,
-       .events = events,
-       .absinfo = absinfo,
-};
index 16d92399c854a7c28df0e55fa69a2765d6b10231..4c301b584d1eaace1dbf73486294f59f6faca767 100644 (file)
@@ -1,23 +1,25 @@
 /*
  * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2013 Marcin Slusarz <marcin.slusarz@gmail.com>
  *
- * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #if HAVE_CONFIG_H
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <getopt.h>
 #include <poll.h>
+#include <signal.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include "linux/input.h"
 #include <sys/ptrace.h>
+#include <sys/sendfile.h>
 #include <sys/timerfd.h>
 #include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <libudev.h>
 
 #include "litest.h"
 #include "litest-int.h"
 
 #define UDEV_RULES_D "/run/udev/rules.d"
 #define UDEV_RULE_PREFIX "99-litest-"
-
+#define UDEV_HWDB_D "/etc/udev/hwdb.d"
+#define UDEV_MODEL_QUIRKS_RULE_FILE UDEV_RULES_D \
+       "/91-litest-model-quirks-REMOVEME.rules"
+#define UDEV_MODEL_QUIRKS_HWDB_FILE UDEV_HWDB_D \
+       "/91-litest-model-quirks-REMOVEME.hwdb"
+#define UDEV_TEST_DEVICE_RULE_FILE UDEV_RULES_D \
+       "/91-litest-test-device-REMOVEME.rules"
+
+static int jobs = 8;
 static int in_debugger = -1;
 static int verbose = 0;
+const char *filter_test = NULL;
+const char *filter_device = NULL;
+const char *filter_group = NULL;
+
+struct created_file {
+       struct list link;
+       char *path;
+};
+
+struct list created_files_list; /* list of all files to remove at the end of
+                                  the test run */
+
+static void litest_init_udev_rules(struct list *created_files_list);
+static void litest_remove_udev_rules(struct list *created_files_list);
+
+/* defined for the litest selftest */
+#ifndef LITEST_DISABLE_BACKTRACE_LOGGING
+#define litest_log(...) fprintf(stderr, __VA_ARGS__)
+#define litest_vlog(format_, args_) vfprintf(stderr, format_, args_)
+#else
+#define litest_log(...) { /* __VA_ARGS__ */ }
+#define litest_vlog(...) { /* __VA_ARGS__ */ }
+#endif
+
+#ifdef HAVE_LIBUNWIND
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#include <dlfcn.h>
+
+static char cwd[PATH_MAX];
+
+static bool
+litest_backtrace_get_lineno(const char *executable,
+                           unw_word_t addr,
+                           char *file_return,
+                           int *line_return)
+{
+#if HAVE_ADDR2LINE
+       FILE* f;
+       char buffer[PATH_MAX];
+       char *s;
+       unsigned int i;
+
+       if (!cwd[0]) {
+               if (getcwd(cwd, sizeof(cwd)) == NULL)
+                       cwd[0] = 0; /* contents otherwise undefined. */
+       }
+
+       sprintf (buffer,
+                ADDR2LINE " -C -e %s -i %lx",
+                executable,
+                (unsigned long) addr);
+
+       f = popen(buffer, "r");
+       if (f == NULL) {
+               litest_log("Failed to execute: %s\n", buffer);
+               return false;
+       }
+
+       buffer[0] = '?';
+       if (fgets(buffer, sizeof(buffer), f) == NULL) {
+               pclose(f);
+               return false;
+       }
+       pclose(f);
+
+       if (buffer[0] == '?')
+               return false;
+
+       s = strrchr(buffer, ':');
+       if (!s)
+               return false;
+
+       *s = '\0';
+       s++;
+       sscanf(s, "%d", line_return);
+
+       /* now strip cwd from buffer */
+       s = buffer;
+       i = 0;
+       while(i < strlen(cwd) && *s != '\0' && cwd[i] == *s) {
+               *s = '\0';
+               s++;
+               i++;
+       }
+
+       if (i > 0)
+               *(--s) = '.';
+       strcpy(file_return, s);
+
+       return true;
+#else /* HAVE_ADDR2LINE */
+       return false;
+#endif
+}
+
+static void
+litest_backtrace(void)
+{
+       unw_cursor_t cursor;
+       unw_context_t context;
+       unw_word_t off;
+       unw_proc_info_t pip;
+       int ret;
+       char procname[256];
+       Dl_info dlinfo;
+       /* filename and i are unused ifdef LITEST_SHUTUP */
+       const char *filename __attribute__((unused));
+       int i __attribute__((unused)) = 0;
+
+       pip.unwind_info = NULL;
+       ret = unw_getcontext(&context);
+       if (ret) {
+               litest_log("unw_getcontext failed: %s [%d]\n",
+                          unw_strerror(ret),
+                          ret);
+               return;
+       }
+
+       ret = unw_init_local(&cursor, &context);
+       if (ret) {
+               litest_log("unw_init_local failed: %s [%d]\n",
+                          unw_strerror(ret),
+                          ret);
+               return;
+       }
+
+       litest_log("\nBacktrace:\n");
+       ret = unw_step(&cursor);
+       while (ret > 0) {
+               char file[PATH_MAX];
+               int line;
+               bool have_lineno = false;
+
+               ret = unw_get_proc_info(&cursor, &pip);
+               if (ret) {
+                       litest_log("unw_get_proc_info failed: %s [%d]\n",
+                                  unw_strerror(ret),
+                                  ret);
+                       break;
+               }
+
+               ret = unw_get_proc_name(&cursor, procname, 256, &off);
+               if (ret && ret != -UNW_ENOMEM) {
+                       if (ret != -UNW_EUNSPEC)
+                               litest_log("unw_get_proc_name failed: %s [%d]\n",
+                                          unw_strerror(ret),
+                                          ret);
+                       procname[0] = '?';
+                       procname[1] = 0;
+               }
+
+               if (dladdr((void *)(pip.start_ip + off), &dlinfo) &&
+                   dlinfo.dli_fname &&
+                   *dlinfo.dli_fname) {
+                       filename = dlinfo.dli_fname;
+                       have_lineno = litest_backtrace_get_lineno(filename,
+                                                                 (pip.start_ip + off),
+                                                                 file,
+                                                                 &line);
+               } else {
+                       filename = "?";
+               }
+
+               if (have_lineno) {
+                       litest_log("%d: %s() (%s:%d)\n",
+                                  i,
+                                  procname,
+                                  file,
+                                  line);
+               } else  {
+                       litest_log("%d: %s (%s%s+%#x) [%p]\n",
+                                  i,
+                                  filename,
+                                  procname,
+                                  ret == -UNW_ENOMEM ? "..." : "",
+                                  (int)off,
+                                  (void *)(pip.start_ip + off));
+               }
+
+               i++;
+               ret = unw_step(&cursor);
+               if (ret < 0)
+                       litest_log("unw_step failed: %s [%d]\n",
+                                  unw_strerror(ret),
+                                  ret);
+       }
+       litest_log("\n");
+}
+#else /* HAVE_LIBUNWIND */
+static inline void
+litest_backtrace(void)
+{
+       /* thou shall install libunwind */
+}
+#endif
+
+void
+litest_fail_condition(const char *file,
+                     int line,
+                     const char *func,
+                     const char *condition,
+                     const char *message,
+                     ...)
+{
+       litest_log("FAILED: %s\n", condition);
+
+       if (message) {
+               va_list args;
+               va_start(args, message);
+               litest_vlog(message, args);
+               va_end(args);
+       }
+
+       litest_log("in %s() (%s:%d)\n", func, file, line);
+       litest_backtrace();
+       abort();
+}
+
+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)
+{
+       litest_log("FAILED COMPARISON: %s %s %s\n", astr, operator, bstr);
+       litest_log("Resolved to: %d %s %d\n", a, operator, b);
+       litest_log("in %s() (%s:%d)\n", func, file, line);
+       litest_backtrace();
+       abort();
+}
+
+void
+litest_fail_comparison_ptr(const char *file,
+                          int line,
+                          const char *func,
+                          const char *comparison)
+{
+       litest_log("FAILED COMPARISON: %s\n", comparison);
+       litest_log("in %s() (%s:%d)\n", func, file, line);
+       litest_backtrace();
+       abort();
+}
 
 struct test {
        struct list node;
@@ -63,15 +326,18 @@ struct suite {
        struct list tests;
        char *name;
        Suite *suite;
+       bool used;
 };
 
 static struct litest_device *current_device;
 
-struct litest_device *litest_current_device(void) {
+struct litest_device *litest_current_device(void)
+{
        return current_device;
 }
 
-void litest_set_current_device(struct litest_device *device) {
+void litest_set_current_device(struct litest_device *device)
+{
        current_device = device;
 }
 
@@ -89,6 +355,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;
@@ -96,6 +366,41 @@ extern struct litest_test_device litest_xen_virtual_pointer_device;
 extern struct litest_test_device litest_vmware_virtmouse_device;
 extern struct litest_test_device litest_synaptics_hover_device;
 extern struct litest_test_device litest_synaptics_carbon3rd_device;
+extern struct litest_test_device litest_protocol_a_screen;
+extern struct litest_test_device litest_wacom_finger_device;
+extern struct litest_test_device litest_keyboard_blackwidow_device;
+extern struct litest_test_device litest_wheel_only_device;
+extern struct litest_test_device litest_mouse_roccat_device;
+extern struct litest_test_device litest_ms_surface_cover_device;
+extern struct litest_test_device litest_logitech_trackball_device;
+extern struct litest_test_device litest_atmel_hover_device;
+extern struct litest_test_device litest_alps_dualpoint_device;
+extern struct litest_test_device litest_mouse_low_dpi_device;
+extern struct litest_test_device litest_generic_multitouch_screen_device;
+extern struct litest_test_device litest_nexus4_device;
+extern struct litest_test_device litest_magicpad_device;
+extern struct litest_test_device litest_elantech_touchpad_device;
+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;
+extern struct litest_test_device litest_cyborg_rat_device;
+extern struct litest_test_device litest_yubikey_device;
+extern struct litest_test_device litest_synaptics_i2c_device;
+extern struct litest_test_device litest_wacom_cintiq_24hd_device;
+extern struct litest_test_device litest_multitouch_fuzz_screen_device;
+extern struct litest_test_device litest_wacom_intuos3_pad_device;
+extern struct litest_test_device litest_wacom_intuos5_pad_device;
+extern struct litest_test_device litest_keyboard_all_codes_device;
+extern struct litest_test_device litest_magicmouse_device;
+extern struct litest_test_device litest_wacom_ekr_device;
+extern struct litest_test_device litest_wacom_cintiq_24hdt_pad_device;
+extern struct litest_test_device litest_wacom_cintiq_13hdt_finger_device;
+extern struct litest_test_device litest_wacom_cintiq_13hdt_pen_device;
+extern struct litest_test_device litest_wacom_cintiq_13hdt_pad_device;
+extern struct litest_test_device litest_wacom_hid4800_tablet_device;
 
 struct litest_test_device* devices[] = {
        &litest_synaptics_clickpad_device,
@@ -106,6 +411,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,
@@ -113,73 +422,95 @@ struct litest_test_device* devices[] = {
        &litest_vmware_virtmouse_device,
        &litest_synaptics_hover_device,
        &litest_synaptics_carbon3rd_device,
+       &litest_protocol_a_screen,
+       &litest_wacom_finger_device,
+       &litest_keyboard_blackwidow_device,
+       &litest_wheel_only_device,
+       &litest_mouse_roccat_device,
+       &litest_ms_surface_cover_device,
+       &litest_logitech_trackball_device,
+       &litest_atmel_hover_device,
+       &litest_alps_dualpoint_device,
+       &litest_mouse_low_dpi_device,
+       &litest_generic_multitouch_screen_device,
+       &litest_nexus4_device,
+       &litest_magicpad_device,
+       &litest_elantech_touchpad_device,
+       &litest_mouse_gladius_device,
+       &litest_mouse_wheel_click_angle_device,
+       &litest_apple_keyboard_device,
+       &litest_anker_mouse_kbd_device,
+       &litest_waltop_tablet_device,
+       &litest_huion_tablet_device,
+       &litest_cyborg_rat_device,
+       &litest_yubikey_device,
+       &litest_synaptics_i2c_device,
+       &litest_wacom_cintiq_24hd_device,
+       &litest_multitouch_fuzz_screen_device,
+       &litest_wacom_intuos3_pad_device,
+       &litest_wacom_intuos5_pad_device,
+       &litest_keyboard_all_codes_device,
+       &litest_magicmouse_device,
+       &litest_wacom_ekr_device,
+       &litest_wacom_cintiq_24hdt_pad_device,
+       &litest_wacom_cintiq_13hdt_finger_device,
+       &litest_wacom_cintiq_13hdt_pen_device,
+       &litest_wacom_cintiq_13hdt_pad_device,
+       &litest_wacom_hid4800_tablet_device,
        NULL,
 };
 
 static struct list all_tests;
 
-static void
-litest_reload_udev_rules(void)
+static inline void
+litest_system(const char *command)
 {
-       system("udevadm control --reload-rules");
-}
+       int ret;
 
-static int
-litest_udev_rule_filter(const struct dirent *entry)
-{
-       return strncmp(entry->d_name,
-                      UDEV_RULE_PREFIX,
-                      strlen(UDEV_RULE_PREFIX)) == 0;
+       ret = system(command);
+
+       if (ret == -1) {
+               litest_abort_msg("Failed to execute: %s", command);
+       } else if (WIFEXITED(ret)) {
+               if (WEXITSTATUS(ret))
+                       litest_abort_msg("'%s' failed with %d",
+                                        command,
+                                        WEXITSTATUS(ret));
+       } else if (WIFSIGNALED(ret)) {
+               litest_abort_msg("'%s' terminated with signal %d",
+                                command,
+                                WTERMSIG(ret));
+       }
 }
 
 static void
-litest_drop_udev_rules(void)
+litest_reload_udev_rules(void)
 {
-       int n;
-       int rc;
-       struct dirent **entries;
-       char path[PATH_MAX];
-
-       n = scandir(UDEV_RULES_D,
-                   &entries,
-                   litest_udev_rule_filter,
-                   alphasort);
-       if (n < 0)
-               return;
-
-       while (n--) {
-               rc = snprintf(path, sizeof(path),
-                             "%s/%s",
-                             UDEV_RULES_D,
-                             entries[n]->d_name);
-               if (rc > 0 &&
-                   (size_t)rc == strlen(UDEV_RULES_D) +
-                           strlen(entries[n]->d_name) + 1)
-                       unlink(path);
-               else
-                       fprintf(stderr,
-                               "Failed to delete %s. Remaining tests are unreliable\n",
-                               entries[n]->d_name);
-               free(entries[n]);
-       }
-       free(entries);
-
-       litest_reload_udev_rules();
+       litest_system("udevadm control --reload-rules");
+       litest_system("udevadm hwdb --update");
 }
 
 static void
 litest_add_tcase_for_device(struct suite *suite,
+                           const char *funcname,
                            void *func,
-                           const struct litest_test_device *dev)
+                           const struct litest_test_device *dev,
+                           const struct range *range)
 {
        struct test *t;
        const char *test_name = dev->shortname;
 
        list_for_each(t, &suite->tests, node) {
-               if (strcmp(t->name, test_name) != 0)
+               if (!streq(t->name, test_name))
                        continue;
 
-               tcase_add_test(t->tc, func);
+               if (range)
+                       tcase_add_loop_test(t->tc,
+                                           func,
+                                           range->lower,
+                                           range->upper);
+               else
+                       tcase_add_test(t->tc, func);
                return;
        }
 
@@ -188,30 +519,38 @@ litest_add_tcase_for_device(struct suite *suite,
        t->name = strdup(test_name);
        t->tc = tcase_create(test_name);
        list_insert(&suite->tests, &t->node);
-       /* we can't guarantee that we clean up properly if a test fails, the
-          udev rules used for a previous test may still be in place. Add an
-          unchecked fixture to always clean up all rules before/after a
-          test case completes */
-       tcase_add_unchecked_fixture(t->tc,
-                                   litest_drop_udev_rules,
-                                   litest_drop_udev_rules);
        tcase_add_checked_fixture(t->tc, dev->setup,
                                  dev->teardown ? dev->teardown : litest_generic_device_teardown);
-       tcase_add_test(t->tc, func);
+       if (range)
+               tcase_add_loop_test(t->tc,
+                                   func,
+                                   range->lower,
+                                   range->upper);
+       else
+               tcase_add_test(t->tc, func);
        suite_add_tcase(suite->suite, t->tc);
 }
 
 static void
-litest_add_tcase_no_device(struct suite *suite, void *func)
+litest_add_tcase_no_device(struct suite *suite,
+                          void *func,
+                          const struct range *range)
 {
        struct test *t;
        const char *test_name = "no device";
 
+       if (filter_device &&
+           fnmatch(filter_device, test_name, 0) != 0)
+               return;
+
        list_for_each(t, &suite->tests, node) {
-               if (strcmp(t->name, test_name) != 0)
+               if (!streq(t->name, test_name))
                        continue;
 
-               tcase_add_test(t->tc, func);
+               if (range)
+                       tcase_add_loop_test(t->tc, func, range->lower, range->upper);
+               else
+                       tcase_add_test(t->tc, func);
                return;
        }
 
@@ -224,40 +563,6 @@ litest_add_tcase_no_device(struct suite *suite, void *func)
        suite_add_tcase(suite->suite, t->tc);
 }
 
-static void
-litest_add_tcase(struct suite *suite, void *func,
-                enum litest_device_feature required,
-                enum litest_device_feature excluded)
-{
-       struct litest_test_device **dev = devices;
-
-       assert(required >= LITEST_DISABLE_DEVICE);
-       assert(excluded >= LITEST_DISABLE_DEVICE);
-
-       if (required == LITEST_DISABLE_DEVICE &&
-           excluded == LITEST_DISABLE_DEVICE) {
-               litest_add_tcase_no_device(suite, func);
-       } else if (required != LITEST_ANY || excluded != LITEST_ANY) {
-               while (*dev) {
-                       if (((*dev)->features & required) == required &&
-                           ((*dev)->features & excluded) == 0)
-                               litest_add_tcase_for_device(suite, func, *dev);
-                       dev++;
-               }
-       } else {
-               while (*dev) {
-                       litest_add_tcase_for_device(suite, func, *dev);
-                       dev++;
-               }
-       }
-}
-
-void
-litest_add_no_device(const char *name, void *func)
-{
-       litest_add(name, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE);
-}
-
 static struct suite *
 get_suite(const char *name)
 {
@@ -267,7 +572,7 @@ get_suite(const char *name)
                list_init(&all_tests);
 
        list_for_each(s, &all_tests, node) {
-               if (strcmp(s->name, name) == 0)
+               if (streq(s->name, name))
                        return s;
        }
 
@@ -275,6 +580,7 @@ get_suite(const char *name)
        assert(s != NULL);
        s->name = strdup(name);
        s->suite = suite_create(s->name);
+       s->used = false;
 
        list_init(&s->tests);
        list_insert(&all_tests, &s->node);
@@ -282,78 +588,172 @@ get_suite(const char *name)
        return s;
 }
 
-void
-litest_add(const char *name,
-          void *func,
-          enum litest_device_feature required,
-          enum litest_device_feature excluded)
-{
-       litest_add_tcase(get_suite(name), func, required, excluded);
-}
-
-void
-litest_add_for_device(const char *name,
-                     void *func,
-                     enum litest_device_type type)
+static void
+litest_add_tcase(const char *suite_name,
+                const char *funcname,
+                void *func,
+                enum litest_device_feature required,
+                enum litest_device_feature excluded,
+                const struct range *range)
 {
-       struct suite *s;
        struct litest_test_device **dev = devices;
+       struct suite *suite;
+       bool added = false;
 
-       assert(type < LITEST_NO_DEVICE);
+       assert(required >= LITEST_DISABLE_DEVICE);
+       assert(excluded >= LITEST_DISABLE_DEVICE);
 
-       s = get_suite(name);
-       while (*dev) {
-               if ((*dev)->type == type) {
-                       litest_add_tcase_for_device(s, func, *dev);
-                       return;
+       if (filter_test &&
+           fnmatch(filter_test, funcname, 0) != 0)
+               return;
+
+       if (filter_group &&
+           fnmatch(filter_group, suite_name, 0) != 0)
+               return;
+
+       suite = get_suite(suite_name);
+
+       if (required == LITEST_DISABLE_DEVICE &&
+           excluded == LITEST_DISABLE_DEVICE) {
+               litest_add_tcase_no_device(suite, func, range);
+               added = true;
+       } else if (required != LITEST_ANY || excluded != LITEST_ANY) {
+               for (; *dev; dev++) {
+                       if (filter_device &&
+                           fnmatch(filter_device, (*dev)->shortname, 0) != 0)
+                               continue;
+                       if (((*dev)->features & required) != required ||
+                           ((*dev)->features & excluded) != 0)
+                               continue;
+
+                       litest_add_tcase_for_device(suite,
+                                                   funcname,
+                                                   func,
+                                                   *dev,
+                                                   range);
+                       added = true;
+               }
+       } else {
+               for (; *dev; dev++) {
+                       if (filter_device &&
+                           fnmatch(filter_device, (*dev)->shortname, 0) != 0)
+                               continue;
+
+                       litest_add_tcase_for_device(suite,
+                                                   funcname,
+                                                   func,
+                                                   *dev,
+                                                   range);
+                       added = true;
                }
-               dev++;
        }
 
-       ck_abort_msg("Invalid test device type");
+       if (!added &&
+           filter_test == NULL &&
+           filter_device == NULL &&
+           filter_group == NULL) {
+               fprintf(stderr, "Test '%s' does not match any devices. Aborting.\n", funcname);
+               abort();
+       }
 }
 
-static int
-is_debugger_attached(void)
+void
+_litest_add_no_device(const char *name, const char *funcname, void *func)
 {
-       int status;
-       int rc;
-       int pid = fork();
+       _litest_add(name, funcname, func, LITEST_DISABLE_DEVICE, LITEST_DISABLE_DEVICE);
+}
 
-       if (pid == -1)
-               return 0;
+void
+_litest_add_ranged_no_device(const char *name,
+                            const char *funcname,
+                            void *func,
+                            const struct range *range)
+{
+       _litest_add_ranged(name,
+                          funcname,
+                          func,
+                          LITEST_DISABLE_DEVICE,
+                          LITEST_DISABLE_DEVICE,
+                          range);
+}
 
-       if (pid == 0) {
-               int ppid = getppid();
-               if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) {
-                       waitpid(ppid, NULL, 0);
-                       ptrace(PTRACE_CONT, NULL, NULL);
-                       ptrace(PTRACE_DETACH, ppid, NULL, NULL);
-                       rc = 0;
-               } else {
-                       rc = 1;
-               }
-               _exit(rc);
-       } else {
-               waitpid(pid, &status, 0);
-               rc = WEXITSTATUS(status);
-       }
+void
+_litest_add(const char *name,
+           const char *funcname,
+           void *func,
+           enum litest_device_feature required,
+           enum litest_device_feature excluded)
+{
+       _litest_add_ranged(name,
+                          funcname,
+                          func,
+                          required,
+                          excluded,
+                          NULL);
+}
 
-       return rc;
+void
+_litest_add_ranged(const char *name,
+                  const char *funcname,
+                  void *func,
+                  enum litest_device_feature required,
+                  enum litest_device_feature excluded,
+                  const struct range *range)
+{
+       litest_add_tcase(name, funcname, func, required, excluded, range);
 }
 
-static void
-litest_list_tests(struct list *tests)
+void
+_litest_add_for_device(const char *name,
+                      const char *funcname,
+                      void *func,
+                      enum litest_device_type type)
+{
+       _litest_add_ranged_for_device(name, funcname, func, type, NULL);
+}
+
+void
+_litest_add_ranged_for_device(const char *name,
+                             const char *funcname,
+                             void *func,
+                             enum litest_device_type type,
+                             const struct range *range)
 {
        struct suite *s;
+       struct litest_test_device **dev = devices;
+       bool device_filtered = false;
 
-       list_for_each(s, tests, node) {
-               struct test *t;
-               printf("%s:\n", s->name);
-               list_for_each(t, &s->tests, node) {
-                       printf("        %s\n", t->name);
+       assert(type < LITEST_NO_DEVICE);
+
+       if (filter_test &&
+           fnmatch(filter_test, funcname, 0) != 0)
+               return;
+
+       if (filter_group &&
+           fnmatch(filter_group, name, 0) != 0)
+               return;
+
+       s = get_suite(name);
+       for (; *dev; dev++) {
+               if (filter_device &&
+                   fnmatch(filter_device, (*dev)->shortname, 0) != 0) {
+                       device_filtered = true;
+                       continue;
+               }
+
+               if ((*dev)->type == type) {
+                       litest_add_tcase_for_device(s,
+                                                   funcname,
+                                                   func,
+                                                   *dev,
+                                                   range);
+                       return;
                }
        }
+
+       /* only abort if no filter was set, that's a bug */
+       if (!device_filtered)
+               litest_abort_msg("Invalid test device type");
 }
 
 static void
@@ -368,16 +768,45 @@ litest_log_handler(struct libinput *libinput,
        case LIBINPUT_LOG_PRIORITY_INFO: priority = "info"; break;
        case LIBINPUT_LOG_PRIORITY_ERROR: priority = "error"; break;
        case LIBINPUT_LOG_PRIORITY_DEBUG: priority = "debug"; break;
+       default:
+                 abort();
        }
 
        fprintf(stderr, "litest %s: ", priority);
        vfprintf(stderr, format, args);
+
+       if (strstr(format, "client bug: ") ||
+           strstr(format, "libinput bug: "))
+               litest_abort_msg("libinput bug triggered, aborting.\n");
+}
+
+static char *
+litest_init_device_udev_rules(struct litest_test_device *dev);
+
+static void
+litest_init_all_device_udev_rules(struct list *created_files)
+{
+       struct litest_test_device **dev = devices;
+
+       while (*dev) {
+               char *udev_file;
+
+               udev_file = litest_init_device_udev_rules(*dev);
+               if (udev_file) {
+                       struct created_file *file = zalloc(sizeof(*file));
+                       litest_assert(file);
+                       file->path = udev_file;
+                       list_insert(created_files, &file->link);
+               }
+               dev++;
+       }
 }
 
 static int
 open_restricted(const char *path, int flags, void *userdata)
 {
-       return open(path, flags);
+       int fd = open(path, flags);
+       return fd < 0 ? -errno : fd;
 }
 
 static void
@@ -391,57 +820,61 @@ struct libinput_interface interface = {
        .close_restricted = close_restricted,
 };
 
-static const struct option opts[] = {
-       { "list", 0, 0, 'l' },
-       { "verbose", 0, 0, 'v' },
-       { 0, 0, 0, 0}
-};
+static void
+litest_signal(int sig)
+{
+       struct created_file *f, *tmp;
 
-int
-litest_run(int argc, char **argv) {
+       list_for_each_safe(f, tmp, &created_files_list, link) {
+               list_remove(&f->link);
+               unlink(f->path);
+               /* in the sighandler, we can't free */
+       }
+
+       if (fork() == 0) {
+               /* child, we can run system() */
+               litest_reload_udev_rules();
+               exit(0);
+       }
+
+       exit(1);
+}
+
+static inline void
+litest_setup_sighandler(int sig)
+{
+       struct sigaction act, oact;
+       int rc;
+
+       sigemptyset(&act.sa_mask);
+       sigaddset(&act.sa_mask, sig);
+       act.sa_flags = 0;
+       act.sa_handler = litest_signal;
+       rc = sigaction(sig, &act, &oact);
+       litest_assert_int_ne(rc, -1);
+}
+
+static void
+litest_free_test_list(struct list *tests)
+{
        struct suite *s, *snext;
-       int failed;
        SRunner *sr = NULL;
 
-       if (in_debugger == -1) {
-               in_debugger = is_debugger_attached();
-               if (in_debugger)
-                       setenv("CK_FORK", "no", 0);
-       }
+       /* quirk needed for check: test suites can only get freed by adding
+        * them to a test runner and freeing the runner. Without this,
+        * valgrind complains */
+       list_for_each(s, tests, node) {
+               if (s->used)
+                       continue;
 
-       list_for_each(s, &all_tests, node) {
                if (!sr)
                        sr = srunner_create(s->suite);
                else
                        srunner_add_suite(sr, s->suite);
        }
-
-       while(1) {
-               int c;
-               int option_index = 0;
-
-               c = getopt_long(argc, argv, "", opts, &option_index);
-               if (c == -1)
-                       break;
-               switch(c) {
-                       case 'l':
-                               litest_list_tests(&all_tests);
-                               return 0;
-                       case 'v':
-                               verbose = 1;
-                               break;
-                       default:
-                               fprintf(stderr, "usage: %s [--list]\n", argv[0]);
-                               return 1;
-
-               }
-       }
-
-       srunner_run_all(sr, CK_ENV);
-       failed = srunner_ntests_failed(sr);
        srunner_free(sr);
 
-       list_for_each_safe(s, snext, &all_tests, node) {
+       list_for_each_safe(s, snext, tests, node) {
                struct test *t, *tnext;
 
                list_for_each_safe(t, tnext, &s->tests, node) {
@@ -454,6 +887,98 @@ litest_run(int argc, char **argv) {
                free(s->name);
                free(s);
        }
+}
+
+static int
+litest_run_suite(char *argv0, struct list *tests, int which, int max)
+{
+       int failed = 0;
+       SRunner *sr = NULL;
+       struct suite *s;
+       int argvlen = strlen(argv0);
+       int count = -1;
+
+       if (max > 1)
+               snprintf(argv0, argvlen, "libinput-test-%-50d", which);
+
+       list_for_each(s, tests, node) {
+               ++count;
+               if (max != 1 && (count % max) != which) {
+                       continue;
+               }
+
+               if (!sr)
+                       sr = srunner_create(s->suite);
+               else
+                       srunner_add_suite(sr, s->suite);
+
+               s->used = true;
+       }
+
+       if (!sr)
+               return 0;
+
+       srunner_run_all(sr, CK_ENV);
+       failed = srunner_ntests_failed(sr);
+       srunner_free(sr);
+       return failed;
+}
+
+static int
+litest_fork_subtests(char *argv0, struct list *tests, int max_forks)
+{
+       int failed = 0;
+       int status;
+       pid_t pid;
+       int f;
+
+       for (f = 0; f < max_forks; f++) {
+               pid = fork();
+               if (pid == 0) {
+                       failed = litest_run_suite(argv0, tests, f, max_forks);
+                       litest_free_test_list(&all_tests);
+                       exit(failed);
+                       /* child always exits here */
+               }
+       }
+
+       /* parent process only */
+       while (wait(&status) != -1 && errno != ECHILD) {
+               if (WEXITSTATUS(status) != 0)
+                       failed = 1;
+       }
+
+       return failed;
+}
+
+static inline int
+litest_run(int argc, char **argv)
+{
+       int failed = 0;
+
+       list_init(&created_files_list);
+
+       if (list_empty(&all_tests)) {
+               fprintf(stderr,
+                       "Error: filters are too strict, no tests to run.\n");
+               return 1;
+       }
+
+       if (getenv("LITEST_VERBOSE"))
+               verbose = 1;
+
+       litest_init_udev_rules(&created_files_list);
+
+       litest_setup_sighandler(SIGINT);
+
+       if (jobs == 1)
+               failed = litest_run_suite(argv[0], &all_tests, 1, 1);
+       else
+               failed = litest_fork_subtests(argv[0], &all_tests, jobs);
+
+       litest_free_test_list(&all_tests);
+
+       litest_remove_udev_rules(&created_files_list);
 
        return failed;
 }
@@ -470,13 +995,13 @@ merge_absinfo(const struct input_absinfo *orig,
                return NULL;
 
        abs = calloc(sz, sizeof(*abs));
-       ck_assert(abs != NULL);
+       litest_assert(abs != NULL);
 
        nelem = 0;
        while (orig[nelem].value != -1) {
                abs[nelem] = orig[nelem];
                nelem++;
-               ck_assert_int_lt(nelem, sz);
+               litest_assert_int_lt(nelem, sz);
        }
 
        /* just append, if the same axis is present twice, libevdev will
@@ -484,10 +1009,10 @@ merge_absinfo(const struct input_absinfo *orig,
        i = 0;
        while (override && override[i].value != -1) {
                abs[nelem++] = override[i++];
-               ck_assert_int_lt(nelem, sz);
+               litest_assert_int_lt(nelem, sz);
        }
 
-       ck_assert_int_lt(nelem, sz);
+       litest_assert_int_lt(nelem, sz);
        abs[nelem].value = -1;
 
        return abs;
@@ -504,13 +1029,13 @@ merge_events(const int *orig, const int *override)
                return NULL;
 
        events = calloc(sz, sizeof(int));
-       ck_assert(events != NULL);
+       litest_assert(events != NULL);
 
        nelem = 0;
        while (orig[nelem] != -1) {
                events[nelem] = orig[nelem];
                nelem++;
-               ck_assert_int_lt(nelem, sz);
+               litest_assert_int_lt(nelem, sz);
        }
 
        /* just append, if the same axis is present twice, libevdev will
@@ -518,46 +1043,133 @@ merge_events(const int *orig, const int *override)
        i = 0;
        while (override && override[i] != -1) {
                events[nelem++] = override[i++];
-               ck_assert_int_le(nelem, sz);
+               litest_assert_int_le(nelem, sz);
        }
 
-       ck_assert_int_lt(nelem, sz);
+       litest_assert_int_lt(nelem, sz);
        events[nelem] = -1;
 
        return events;
 }
 
-static char *
-litest_init_udev_rules(struct litest_test_device *dev)
+static inline struct created_file *
+litest_copy_file(const char *dest, const char *src, const char *header)
 {
-       int rc;
-       FILE *f;
-       char *path;
+       int in, out, length;
+       struct created_file *file;
 
-       if (!dev->udev_rule)
-               return NULL;
+       file = zalloc(sizeof(*file));
+       litest_assert(file);
+       file->path = strdup(dest);
+       litest_assert(file->path);
+
+       out = open(dest, O_CREAT|O_WRONLY, 0644);
+       litest_assert_int_gt(out, -1);
+
+       if (header) {
+               length = strlen(header);
+               litest_assert_int_eq(write(out, header, length), length);
+       }
+
+       in = open(src, O_RDONLY);
+       litest_assert_int_gt(in, -1);
+       /* lazy, just check for error and empty file copy */
+       litest_assert_int_gt(sendfile(out, in, NULL, 40960), 0);
+       close(out);
+       close(in);
+
+       return file;
+}
+
+static inline void
+litest_install_model_quirks(struct list *created_files_list)
+{
+       const char *warning =
+                        "#################################################################\n"
+                        "# WARNING: REMOVE THIS FILE\n"
+                        "# This is a run-time file for the libinput test suite and\n"
+                        "# should be removed on exit. If the test-suite is not currently \n"
+                        "# running, remove this file and update your hwdb: \n"
+                        "#       sudo udevadm hwdb --update\n"
+                        "#################################################################\n\n";
+       struct created_file *file;
+
+       file = litest_copy_file(UDEV_MODEL_QUIRKS_RULE_FILE,
+                               LIBINPUT_MODEL_QUIRKS_UDEV_RULES_FILE,
+                               warning);
+       list_insert(created_files_list, &file->link);
+
+       file = litest_copy_file(UDEV_MODEL_QUIRKS_HWDB_FILE,
+                               LIBINPUT_MODEL_QUIRKS_UDEV_HWDB_FILE,
+                               warning);
+       list_insert(created_files_list, &file->link);
+
+       file = litest_copy_file(UDEV_TEST_DEVICE_RULE_FILE,
+                               LIBINPUT_TEST_DEVICE_RULES_FILE,
+                               warning);
+       list_insert(created_files_list, &file->link);
+}
+
+static void
+litest_init_udev_rules(struct list *created_files)
+{
+       int rc;
 
        rc = mkdir(UDEV_RULES_D, 0755);
        if (rc == -1 && errno != EEXIST)
                ck_abort_msg("Failed to create udev rules directory (%s)\n",
                             strerror(errno));
 
-       rc = asprintf(&path,
+       rc = mkdir(UDEV_HWDB_D, 0755);
+       if (rc == -1 && errno != EEXIST)
+               ck_abort_msg("Failed to create udev hwdb directory (%s)\n",
+                            strerror(errno));
+
+       litest_install_model_quirks(created_files);
+       litest_init_all_device_udev_rules(created_files);
+       litest_reload_udev_rules();
+}
+
+static void
+litest_remove_udev_rules(struct list *created_files_list)
+{
+       struct created_file *f, *tmp;
+
+       list_for_each_safe(f, tmp, created_files_list, link) {
+               list_remove(&f->link);
+               unlink(f->path);
+               free(f->path);
+               free(f);
+       }
+
+       litest_reload_udev_rules();
+}
+
+static char *
+litest_init_device_udev_rules(struct litest_test_device *dev)
+{
+       int rc;
+       FILE *f;
+       char *path = NULL;
+
+       if (!dev->udev_rule)
+               return NULL;
+
+       rc = xasprintf(&path,
                      "%s/%s%s.rules",
                      UDEV_RULES_D,
                      UDEV_RULE_PREFIX,
                      dev->shortname);
-       ck_assert_int_eq(rc,
-                        strlen(UDEV_RULES_D) +
-                        strlen(UDEV_RULE_PREFIX) +
-                        strlen(dev->shortname) + 7);
+       litest_assert_int_eq(rc,
+                            (int)(
+                                  strlen(UDEV_RULES_D) +
+                                  strlen(UDEV_RULE_PREFIX) +
+                                  strlen(dev->shortname) + 7));
        f = fopen(path, "w");
-       ck_assert_notnull(f);
-       ck_assert_int_ge(fputs(dev->udev_rule, f), 0);
+       litest_assert_notnull(f);
+       litest_assert_int_ge(fputs(dev->udev_rule, f), 0);
        fclose(f);
 
-       litest_reload_udev_rules();
-
        return path;
 }
 
@@ -574,7 +1186,6 @@ litest_create(enum litest_device_type which,
        const struct input_id *id;
        struct input_absinfo *abs;
        int *events;
-       char *udev_file;
 
        dev = devices;
        while (*dev) {
@@ -587,18 +1198,13 @@ litest_create(enum litest_device_type which,
                ck_abort_msg("Invalid device type %d\n", which);
 
        d = zalloc(sizeof(*d));
-       ck_assert(d != NULL);
-
-       udev_file = litest_init_udev_rules(*dev);
+       litest_assert(d != NULL);
 
        /* device has custom create method */
        if ((*dev)->create) {
                (*dev)->create(d);
                if (abs_override || events_override) {
-                       if (udev_file)
-                               unlink(udev_file);
-                       ck_abort_msg("Custom create cannot"
-                                    "be overridden");
+                       litest_abort_msg("Custom create cannot be overridden");
                }
 
                return d;
@@ -614,7 +1220,6 @@ litest_create(enum litest_device_type which,
                                                                 abs,
                                                                 events);
        d->interface = (*dev)->interface;
-       d->udev_rule_file = udev_file;
        free(abs);
        free(events);
 
@@ -627,7 +1232,7 @@ litest_create_context(void)
 {
        struct libinput *libinput =
                libinput_path_create_context(&interface, NULL);
-       ck_assert_notnull(libinput);
+       litest_assert_notnull(libinput);
 
        libinput_log_set_handler(libinput, litest_log_handler);
        if (verbose)
@@ -636,6 +1241,18 @@ litest_create_context(void)
        return libinput;
 }
 
+void
+litest_disable_log_handler(struct libinput *libinput)
+{
+       libinput_log_set_handler(libinput, NULL);
+}
+
+void
+litest_restore_log_handler(struct libinput *libinput)
+{
+       libinput_log_set_handler(libinput, litest_log_handler);
+}
+
 struct litest_device *
 litest_add_device_with_overrides(struct libinput *libinput,
                                 enum litest_device_type which,
@@ -656,16 +1273,16 @@ litest_add_device_with_overrides(struct libinput *libinput,
                          events_override);
 
        path = libevdev_uinput_get_devnode(d->uinput);
-       ck_assert(path != NULL);
+       litest_assert(path != NULL);
        fd = open(path, O_RDWR|O_NONBLOCK);
-       ck_assert_int_ne(fd, -1);
+       litest_assert_int_ne(fd, -1);
 
        rc = libevdev_new_from_fd(fd, &d->evdev);
-       ck_assert_int_eq(rc, 0);
+       litest_assert_int_eq(rc, 0);
 
        d->libinput = libinput;
        d->libinput_device = libinput_path_add_device(d->libinput, path);
-       ck_assert(d->libinput_device != NULL);
+       litest_assert(d->libinput_device != NULL);
        libinput_device_ref(d->libinput_device);
 
        if (d->interface) {
@@ -733,16 +1350,11 @@ litest_delete_device(struct litest_device *d)
        if (!d)
                return;
 
-       if (d->udev_rule_file) {
-               unlink(d->udev_rule_file);
-               free(d->udev_rule_file);
-               d->udev_rule_file = NULL;
-       }
-
        libinput_device_unref(d->libinput_device);
        libinput_path_remove_device(d->libinput_device);
        if (d->owns_context)
                libinput_unref(d->libinput);
+       close(libevdev_get_fd(d->evdev));
        libevdev_free(d->evdev);
        libevdev_uinput_destroy(d->uinput);
        free(d->private);
@@ -760,15 +1372,249 @@ litest_event(struct litest_device *d, unsigned int type,
                return;
 
        ret = libevdev_uinput_write_event(d->uinput, type, code, value);
-       ck_assert_int_eq(ret, 0);
+       litest_assert_int_eq(ret, 0);
+}
+
+static bool
+axis_replacement_value(struct litest_device *d,
+                      struct axis_replacement *axes,
+                      int32_t evcode,
+                      int32_t *value)
+{
+       struct axis_replacement *axis = axes;
+
+       if (!axes)
+               return false;
+
+       while (axis->evcode != -1) {
+               if (axis->evcode == evcode) {
+                       *value = litest_scale(d, evcode, axis->value);
+                       return true;
+               }
+               axis++;
+       }
+
+       return false;
+}
+
+int
+litest_auto_assign_value(struct litest_device *d,
+                        const struct input_event *ev,
+                        int slot, double x, double y,
+                        struct axis_replacement *axes,
+                        bool touching)
+{
+       static int tracking_id;
+       int value = ev->value;
+
+       if (value != LITEST_AUTO_ASSIGN || ev->type != EV_ABS)
+               return value;
+
+       switch (ev->code) {
+       case ABS_X:
+       case ABS_MT_POSITION_X:
+               value = litest_scale(d, ABS_X, x);
+               break;
+       case ABS_Y:
+       case ABS_MT_POSITION_Y:
+               value = litest_scale(d, ABS_Y, y);
+               break;
+       case ABS_MT_TRACKING_ID:
+               value = ++tracking_id;
+               break;
+       case ABS_MT_SLOT:
+               value = slot;
+               break;
+       case ABS_MT_DISTANCE:
+               value = touching ? 0 : 1;
+               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 void
+send_btntool(struct litest_device *d, bool hover)
+{
+       litest_event(d, EV_KEY, BTN_TOUCH, d->ntouches_down != 0 && !hover);
+       litest_event(d, EV_KEY, BTN_TOOL_FINGER, d->ntouches_down == 1);
+       litest_event(d, EV_KEY, BTN_TOOL_DOUBLETAP, d->ntouches_down == 2);
+       litest_event(d, EV_KEY, BTN_TOOL_TRIPLETAP, d->ntouches_down == 3);
+       litest_event(d, EV_KEY, BTN_TOOL_QUADTAP, d->ntouches_down == 4);
+       litest_event(d, EV_KEY, BTN_TOOL_QUINTTAP, d->ntouches_down == 5);
+}
+
+static void
+litest_slot_start(struct litest_device *d,
+                 unsigned int slot,
+                 double x,
+                 double y,
+                 struct axis_replacement *axes,
+                 bool touching)
+{
+       struct input_event *ev;
+
+       assert(d->ntouches_down >= 0);
+       d->ntouches_down++;
+
+       send_btntool(d, !touching);
+
+       if (d->interface->touch_down) {
+               d->interface->touch_down(d, slot, x, y);
+               return;
+       }
+
+       ev = d->interface->touch_down_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               int value = litest_auto_assign_value(d,
+                                                    ev,
+                                                    slot,
+                                                    x,
+                                                    y,
+                                                    axes,
+                                                    touching);
+               if (value != LITEST_AUTO_ASSIGN)
+                       litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+void
+litest_touch_down(struct litest_device *d,
+                 unsigned int slot,
+                 double x,
+                 double y)
+{
+       litest_slot_start(d, slot, x, y, NULL, true);
+}
+
+void
+litest_touch_down_extended(struct litest_device *d,
+                          unsigned int slot,
+                          double x,
+                          double y,
+                          struct axis_replacement *axes)
+{
+       litest_slot_start(d, slot, x, y, axes, true);
+}
+
+void
+litest_touch_up(struct litest_device *d, unsigned int slot)
+{
+       struct input_event *ev;
+       struct input_event up[] = {
+               { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+               { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 },
+               { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
+               { .type = -1, .code = -1 }
+       };
+
+       litest_assert_int_gt(d->ntouches_down, 0);
+       d->ntouches_down--;
+
+       send_btntool(d, false);
+
+       if (d->interface->touch_up) {
+               d->interface->touch_up(d, slot);
+               return;
+       } else if (d->interface->touch_up_events) {
+               ev = d->interface->touch_up_events;
+       } else
+               ev = up;
+
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               int value = litest_auto_assign_value(d,
+                                                    ev,
+                                                    slot,
+                                                    0,
+                                                    0,
+                                                    NULL,
+                                                    false);
+               litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+static void
+litest_slot_move(struct litest_device *d,
+                unsigned int slot,
+                double x,
+                double y,
+                struct axis_replacement *axes,
+                bool touching)
+{
+       struct input_event *ev;
+
+       if (d->interface->touch_move) {
+               d->interface->touch_move(d, slot, x, y);
+               return;
+       }
+
+       ev = d->interface->touch_move_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               int value = litest_auto_assign_value(d,
+                                                    ev,
+                                                    slot,
+                                                    x,
+                                                    y,
+                                                    axes,
+                                                    touching);
+               if (value != LITEST_AUTO_ASSIGN)
+                       litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+void
+litest_touch_move(struct litest_device *d,
+                 unsigned int slot,
+                 double x,
+                 double y)
+{
+       litest_slot_move(d, slot, x, y, NULL, true);
+}
+
+void
+litest_touch_move_extended(struct litest_device *d,
+                          unsigned int slot,
+                          double x,
+                          double y,
+                          struct axis_replacement *axes)
+{
+       litest_slot_move(d, slot, x, y, axes, true);
+}
+
+void
+litest_touch_move_to(struct litest_device *d,
+                    unsigned int slot,
+                    double x_from, double y_from,
+                    double x_to, double y_to,
+                    int steps, int sleep_ms)
+{
+       for (int i = 0; i < steps - 1; i++) {
+               litest_touch_move(d, slot,
+                                 x_from + (x_to - x_from)/steps * i,
+                                 y_from + (y_to - y_from)/steps * i);
+               if (sleep_ms) {
+                       libinput_dispatch(d->libinput);
+                       msleep(sleep_ms);
+                       libinput_dispatch(d->libinput);
+               }
+       }
+       litest_touch_move(d, slot, x_to, y_to);
 }
 
-int
-litest_auto_assign_value(struct litest_device *d,
+static int
+auto_assign_tablet_value(struct litest_device *d,
                         const struct input_event *ev,
-                        int slot, double x, double y)
+                        int x, int y,
+                        struct axis_replacement *axes)
 {
-       static int tracking_id;
        int value = ev->value;
 
        if (value != LITEST_AUTO_ASSIGN || ev->type != EV_ABS)
@@ -776,74 +1622,146 @@ litest_auto_assign_value(struct litest_device *d,
 
        switch (ev->code) {
        case ABS_X:
-       case ABS_MT_POSITION_X:
                value = litest_scale(d, ABS_X, x);
                break;
        case ABS_Y:
-       case ABS_MT_POSITION_Y:
                value = litest_scale(d, ABS_Y, y);
                break;
-       case ABS_MT_TRACKING_ID:
-               value = ++tracking_id;
-               break;
-       case ABS_MT_SLOT:
-               value = slot;
+       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 void
-send_btntool(struct litest_device *d)
+static int
+tablet_ignore_event(const struct input_event *ev, int value)
 {
-       litest_event(d, EV_KEY, BTN_TOUCH, d->ntouches_down != 0);
-       litest_event(d, EV_KEY, BTN_TOOL_FINGER, d->ntouches_down == 1);
-       litest_event(d, EV_KEY, BTN_TOOL_DOUBLETAP, d->ntouches_down == 2);
-       litest_event(d, EV_KEY, BTN_TOOL_TRIPLETAP, d->ntouches_down == 3);
-       litest_event(d, EV_KEY, BTN_TOOL_QUADTAP, d->ntouches_down == 4);
-       litest_event(d, EV_KEY, BTN_TOOL_QUINTTAP, d->ntouches_down == 5);
+       return value == -1 && (ev->code == ABS_PRESSURE || ev->code == ABS_DISTANCE);
 }
 
 void
-litest_touch_down(struct litest_device *d, unsigned int slot,
-                 double x, double y)
+litest_tablet_proximity_in(struct litest_device *d, int x, int y, struct axis_replacement *axes)
 {
        struct input_event *ev;
 
-       assert(d->ntouches_down >= 0);
-       d->ntouches_down++;
+       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++;
+       }
+}
 
-       send_btntool(d);
+void
+litest_tablet_proximity_out(struct litest_device *d)
+{
+       struct input_event *ev;
 
-       if (d->interface->touch_down) {
-               d->interface->touch_down(d, slot, x, y);
-               return;
+       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++;
        }
+}
 
-       ev = d->interface->touch_down_events;
+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 = litest_auto_assign_value(d, ev, slot, x, y);
-               litest_event(d, ev->type, ev->code, value);
+               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_up(struct litest_device *d, unsigned int slot)
+litest_touch_move_two_touches(struct litest_device *d,
+                             double x0, double y0,
+                             double x1, double y1,
+                             double dx, double dy,
+                             int steps, int sleep_ms)
+{
+       for (int i = 0; i < steps - 1; i++) {
+               litest_push_event_frame(d);
+               litest_touch_move(d, 0, x0 + dx / steps * i,
+                                       y0 + dy / steps * i);
+               litest_touch_move(d, 1, x1 + dx / steps * i,
+                                       y1 + dy / steps * i);
+               litest_pop_event_frame(d);
+               if (sleep_ms) {
+                       libinput_dispatch(d->libinput);
+                       msleep(sleep_ms);
+               }
+               libinput_dispatch(d->libinput);
+       }
+       litest_push_event_frame(d);
+       litest_touch_move(d, 0, x0 + dx, y0 + dy);
+       litest_touch_move(d, 1, x1 + dx, y1 + dy);
+       litest_pop_event_frame(d);
+}
+
+void
+litest_touch_move_three_touches(struct litest_device *d,
+                               double x0, double y0,
+                               double x1, double y1,
+                               double x2, double y2,
+                               double dx, double dy,
+                               int steps, int sleep_ms)
+{
+       for (int i = 0; i < steps - 1; i++) {
+               litest_touch_move(d, 0, x0 + dx / steps * i,
+                                       y0 + dy / steps * i);
+               litest_touch_move(d, 1, x1 + dx / steps * i,
+                                       y1 + dy / steps * i);
+               litest_touch_move(d, 2, x2 + dx / steps * i,
+                                       y2 + dy / steps * i);
+               if (sleep_ms) {
+                       libinput_dispatch(d->libinput);
+                       msleep(sleep_ms);
+                       libinput_dispatch(d->libinput);
+               }
+       }
+       litest_touch_move(d, 0, x0 + dx, y0 + dy);
+       litest_touch_move(d, 1, x1 + dx, y1 + dy);
+       litest_touch_move(d, 2, x2 + dx, y2 + dy);
+}
+
+void
+litest_hover_start(struct litest_device *d,
+                  unsigned int slot,
+                  double x,
+                  double y)
+{
+       litest_slot_start(d, slot, x, y, NULL, 0);
+}
+
+void
+litest_hover_end(struct litest_device *d, unsigned int slot)
 {
        struct input_event *ev;
        struct input_event up[] = {
                { .type = EV_ABS, .code = ABS_MT_SLOT, .value = LITEST_AUTO_ASSIGN },
+               { .type = EV_ABS, .code = ABS_MT_DISTANCE, .value = 1 },
                { .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1 },
                { .type = EV_SYN, .code = SYN_REPORT, .value = 0 },
                { .type = -1, .code = -1 }
        };
 
-       assert(d->ntouches_down > 0);
+       litest_assert_int_gt(d->ntouches_down, 0);
        d->ntouches_down--;
 
-       send_btntool(d);
+       send_btntool(d, true);
 
        if (d->interface->touch_up) {
                d->interface->touch_up(d, slot);
@@ -854,40 +1772,28 @@ litest_touch_up(struct litest_device *d, unsigned int slot)
                ev = up;
 
        while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
-               int value = litest_auto_assign_value(d, ev, slot, 0, 0);
+               int value = litest_auto_assign_value(d, ev, slot, 0, 0, NULL, false);
                litest_event(d, ev->type, ev->code, value);
                ev++;
        }
 }
 
 void
-litest_touch_move(struct litest_device *d, unsigned int slot,
+litest_hover_move(struct litest_device *d, unsigned int slot,
                  double x, double y)
 {
-       struct input_event *ev;
-
-       if (d->interface->touch_move) {
-               d->interface->touch_move(d, slot, x, y);
-               return;
-       }
-
-       ev = d->interface->touch_move_events;
-       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
-               int value = litest_auto_assign_value(d, ev, slot, x, y);
-               litest_event(d, ev->type, ev->code, value);
-               ev++;
-       }
+       litest_slot_move(d, slot, x, y, NULL, false);
 }
 
 void
-litest_touch_move_to(struct litest_device *d,
+litest_hover_move_to(struct litest_device *d,
                     unsigned int slot,
                     double x_from, double y_from,
                     double x_to, double y_to,
                     int steps, int sleep_ms)
 {
        for (int i = 0; i < steps - 1; i++) {
-               litest_touch_move(d, slot,
+               litest_hover_move(d, slot,
                                  x_from + (x_to - x_from)/steps * i,
                                  y_from + (y_to - y_from)/steps * i);
                if (sleep_ms) {
@@ -896,7 +1802,33 @@ litest_touch_move_to(struct litest_device *d,
                        libinput_dispatch(d->libinput);
                }
        }
-       litest_touch_move(d, slot, x_to, y_to);
+       litest_hover_move(d, slot, x_to, y_to);
+}
+
+void
+litest_hover_move_two_touches(struct litest_device *d,
+                             double x0, double y0,
+                             double x1, double y1,
+                             double dx, double dy,
+                             int steps, int sleep_ms)
+{
+       for (int i = 0; i < steps - 1; i++) {
+               litest_push_event_frame(d);
+               litest_hover_move(d, 0, x0 + dx / steps * i,
+                                       y0 + dy / steps * i);
+               litest_hover_move(d, 1, x1 + dx / steps * i,
+                                       y1 + dy / steps * i);
+               litest_pop_event_frame(d);
+               if (sleep_ms) {
+                       libinput_dispatch(d->libinput);
+                       msleep(sleep_ms);
+                       libinput_dispatch(d->libinput);
+               }
+       }
+       litest_push_event_frame(d);
+       litest_hover_move(d, 0, x0 + dx, y0 + dy);
+       litest_hover_move(d, 1, x1 + dx, y1 + dy);
+       litest_pop_event_frame(d);
 }
 
 void
@@ -941,17 +1873,153 @@ litest_keyboard_key(struct litest_device *d, unsigned int key, bool is_press)
        litest_button_click(d, key, is_press);
 }
 
+static int
+litest_scale_axis(const struct litest_device *d,
+                 unsigned int axis,
+                 double val)
+{
+       const struct input_absinfo *abs;
+
+       litest_assert_double_ge(val, 0.0);
+       litest_assert_double_le(val, 100.0);
+
+       abs = libevdev_get_abs_info(d->evdev, axis);
+       litest_assert_notnull(abs);
+
+       return (abs->maximum - abs->minimum) * val/100.0 + abs->minimum;
+}
+
+static inline int
+litest_scale_range(int min, int max, double val)
+{
+       litest_assert_int_ge((int)val, 0);
+       litest_assert_int_le((int)val, 100);
+
+       return (max - min) * val/100.0 + min;
+}
+
 int
 litest_scale(const struct litest_device *d, unsigned int axis, double val)
 {
        int min, max;
-       ck_assert_int_ge(val, 0);
-       ck_assert_int_le(val, 100);
-       ck_assert_int_le(axis, ABS_Y);
+       litest_assert_double_ge(val, 0.0);
+       litest_assert_double_le(val, 100.0);
 
-       min = d->interface->min[axis];
-       max = d->interface->max[axis];
-       return (max - min) * val/100.0 + min;
+       if (axis <= ABS_Y) {
+               min = d->interface->min[axis];
+               max = d->interface->max[axis];
+
+               return litest_scale_range(min, max, val);
+       } else {
+               return litest_scale_axis(d, axis, val);
+       }
+}
+
+static inline int
+auto_assign_pad_value(struct litest_device *dev,
+                     struct input_event *ev,
+                     double value)
+{
+       const struct input_absinfo *abs;
+
+       if (ev->value != LITEST_AUTO_ASSIGN ||
+           ev->type != EV_ABS)
+               return value;
+
+       abs = libevdev_get_abs_info(dev->evdev, ev->code);
+       litest_assert_notnull(abs);
+
+       if (ev->code == ABS_RX || ev->code == ABS_RY) {
+               double min = abs->minimum != 0 ? log2(abs->minimum) : 0,
+                      max = abs->maximum != 0 ? log2(abs->maximum) : 0;
+
+               /* Value 0 is reserved for finger up, so a value of 0% is
+                * actually 1 */
+               if (value == 0.0) {
+                       return 1;
+               } else {
+                       value = litest_scale_range(min, max, value);
+                       return pow(2, value);
+               }
+       } else {
+               return litest_scale_range(abs->minimum, abs->maximum, value);
+       }
+}
+
+void
+litest_pad_ring_start(struct litest_device *d, double value)
+{
+       struct input_event *ev;
+
+       ev = d->interface->pad_ring_start_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               value = auto_assign_pad_value(d, ev, value);
+               litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+void
+litest_pad_ring_change(struct litest_device *d, double value)
+{
+       struct input_event *ev;
+
+       ev = d->interface->pad_ring_change_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               value = auto_assign_pad_value(d, ev, value);
+               litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+void
+litest_pad_ring_end(struct litest_device *d)
+{
+       struct input_event *ev;
+
+       ev = d->interface->pad_ring_end_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               litest_event(d, ev->type, ev->code, ev->value);
+               ev++;
+       }
+}
+
+void
+litest_pad_strip_start(struct litest_device *d, double value)
+{
+       struct input_event *ev;
+
+       ev = d->interface->pad_strip_start_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               value = auto_assign_pad_value(d, ev, value);
+               litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+void
+litest_pad_strip_change(struct litest_device *d, double value)
+{
+       struct input_event *ev;
+
+       ev = d->interface->pad_strip_change_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               value = auto_assign_pad_value(d, ev, value);
+               litest_event(d, ev->type, ev->code, value);
+               ev++;
+       }
+}
+
+void
+litest_pad_strip_end(struct litest_device *d)
+{
+       struct input_event *ev;
+
+       ev = d->interface->pad_strip_end_events;
+       while (ev && (int16_t)ev->type != -1 && (int16_t)ev->code != -1) {
+               litest_event(d, ev->type, ev->code, ev->value);
+               ev++;
+       }
 }
 
 void
@@ -967,6 +2035,7 @@ litest_wait_for_event_of_type(struct libinput *li, ...)
        enum libinput_event_type types[32] = {LIBINPUT_EVENT_NONE};
        size_t ntypes = 0;
        enum libinput_event_type type;
+       struct pollfd fds;
 
        va_start(args, li);
        type = va_arg(args, int);
@@ -978,12 +2047,16 @@ litest_wait_for_event_of_type(struct libinput *li, ...)
        }
        va_end(args);
 
+       fds.fd = libinput_get_fd(li);
+       fds.events = POLLIN;
+       fds.revents = 0;
+
        while (1) {
                size_t i;
                struct libinput_event *event;
 
                while ((type = libinput_next_event_type(li)) == LIBINPUT_EVENT_NONE) {
-                       msleep(10);
+                       poll(&fds, 1, -1);
                        libinput_dispatch(li);
                }
 
@@ -1014,11 +2087,11 @@ litest_drain_events(struct libinput *li)
 }
 
 static const char *
-litest_event_type_str(struct libinput_event *event)
+litest_event_type_str(enum libinput_event_type type)
 {
        const char *str = NULL;
 
-       switch (libinput_event_get_type(event)) {
+       switch (type) {
        case LIBINPUT_EVENT_NONE:
                abort();
        case LIBINPUT_EVENT_DEVICE_ADDED:
@@ -1057,14 +2130,61 @@ litest_event_type_str(struct libinput_event *event)
        case LIBINPUT_EVENT_TOUCH_FRAME:
                str = "TOUCH FRAME";
                break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+               str = "GESTURE SWIPE START";
+               break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+               str = "GESTURE SWIPE UPDATE";
+               break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+               str = "GESTURE SWIPE END";
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+               str = "GESTURE PINCH START";
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+               str = "GESTURE PINCH UPDATE";
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_END:
+               str = "GESTURE PINCH END";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
+               str = "TABLET TOOL AXIS";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
+               str = "TABLET TOOL PROX";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_TIP:
+               str = "TABLET TOOL TIP";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
+               str = "TABLET TOOL BUTTON";
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+               str = "TABLET PAD BUTTON";
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_RING:
+               str = "TABLET PAD RING";
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+               str = "TABLET PAD STRIP";
+               break;
        }
        return str;
 }
 
+static const char *
+litest_event_get_type_str(struct libinput_event *event)
+{
+       return litest_event_type_str(libinput_event_get_type(event));
+}
+
 static void
 litest_print_event(struct libinput_event *event)
 {
        struct libinput_event_pointer *p;
+       struct libinput_event_tablet_tool *t;
+       struct libinput_event_tablet_pad *pad;
        struct libinput_device *dev;
        enum libinput_event_type type;
        double x, y;
@@ -1075,7 +2195,7 @@ litest_print_event(struct libinput_event *event)
        fprintf(stderr,
                "device %s type %s ",
                libinput_device_get_sysname(dev),
-               litest_event_type_str(event));
+               litest_event_get_type_str(event));
        switch (type) {
        case LIBINPUT_EVENT_POINTER_MOTION:
                p = libinput_event_get_pointer_event(event);
@@ -1098,12 +2218,53 @@ litest_print_event(struct libinput_event *event)
                break;
        case LIBINPUT_EVENT_POINTER_AXIS:
                p = libinput_event_get_pointer_event(event);
-               fprintf(stderr,
-                       "vert %.f horiz %.2f",
-                       libinput_event_pointer_get_axis_value(p,
-                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL),
-                       libinput_event_pointer_get_axis_value(p,
-                               LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL));
+               x = 0.0;
+               y = 0.0;
+               if (libinput_event_pointer_has_axis(p,
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL))
+                       y = libinput_event_pointer_get_axis_value(p,
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               if (libinput_event_pointer_has_axis(p,
+                               LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
+                       x = libinput_event_pointer_get_axis_value(p,
+                               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",
+                       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",
+                       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",
+                       libinput_event_tablet_tool_get_button(t),
+                       libinput_event_tablet_tool_get_button_state(t));
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+               pad = libinput_event_get_tablet_pad_event(event);
+               fprintf(stderr, "button %d state %d",
+                       libinput_event_tablet_pad_get_button_number(pad),
+                       libinput_event_tablet_pad_get_button_state(pad));
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_RING:
+               pad = libinput_event_get_tablet_pad_event(event);
+               fprintf(stderr, "ring %d position %.2f source %d",
+                       libinput_event_tablet_pad_get_ring_number(pad),
+                       libinput_event_tablet_pad_get_ring_position(pad),
+                       libinput_event_tablet_pad_get_ring_source(pad));
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+               pad = libinput_event_get_tablet_pad_event(event);
+               fprintf(stderr, "strip %d position %.2f source %d",
+                       libinput_event_tablet_pad_get_ring_number(pad),
+                       libinput_event_tablet_pad_get_ring_position(pad),
+                       libinput_event_tablet_pad_get_ring_source(pad));
                break;
        default:
                break;
@@ -1112,6 +2273,23 @@ litest_print_event(struct libinput_event *event)
        fprintf(stderr, "\n");
 }
 
+void
+litest_assert_event_type(struct libinput_event *event,
+                        enum libinput_event_type want)
+{
+       if (libinput_event_get_type(event) == want)
+               return;
+
+       fprintf(stderr,
+               "FAILED EVENT TYPE: have %s (%d) but want %s (%d)\n",
+               litest_event_get_type_str(event),
+               libinput_event_get_type(event),
+               litest_event_type_str(want),
+               want);
+       litest_backtrace();
+       abort();
+}
+
 void
 litest_assert_empty_queue(struct libinput *li)
 {
@@ -1128,14 +2306,14 @@ litest_assert_empty_queue(struct libinput *li)
                libinput_dispatch(li);
        }
 
-       ck_assert(empty_queue);
+       litest_assert(empty_queue);
 }
 
-struct libevdev_uinput *
-litest_create_uinput_device_from_description(const char *name,
-                                            const struct input_id *id,
-                                            const struct input_absinfo *abs_info,
-                                            const int *events)
+static struct libevdev_uinput *
+litest_create_uinput(const char *name,
+                    const struct input_id *id,
+                    const struct input_absinfo *abs_info,
+                    const int *events)
 {
        struct libevdev_uinput *uinput;
        struct libevdev *dev;
@@ -1145,7 +2323,7 @@ litest_create_uinput_device_from_description(const char *name,
        const struct input_absinfo default_abs = {
                .value = 0,
                .minimum = 0,
-               .maximum = 0xffff,
+               .maximum = 100,
                .fuzz = 0,
                .flat = 0,
                .resolution = 100
@@ -1154,7 +2332,7 @@ litest_create_uinput_device_from_description(const char *name,
        const char *devnode;
 
        dev = libevdev_new();
-       ck_assert(dev != NULL);
+       litest_assert(dev != NULL);
 
        snprintf(buf, sizeof(buf), "litest %s", name);
        libevdev_set_name(dev, buf);
@@ -1167,9 +2345,13 @@ litest_create_uinput_device_from_description(const char *name,
 
        abs = abs_info;
        while (abs && abs->value != -1) {
-               rc = libevdev_enable_event_code(dev, EV_ABS,
-                                               abs->value, abs);
-               ck_assert_int_eq(rc, 0);
+               struct input_absinfo a = *abs;
+
+               /* abs_info->value is used for the code and may be outside
+                  of [min, max] */
+               a.value = abs->minimum;
+               rc = libevdev_enable_event_code(dev, EV_ABS, abs->value, &a);
+               litest_assert_int_eq(rc, 0);
                abs++;
        }
 
@@ -1182,7 +2364,7 @@ litest_create_uinput_device_from_description(const char *name,
                        rc = libevdev_enable_event_code(dev, type, code,
                                                        type == EV_ABS ? &default_abs : NULL);
                }
-               ck_assert_int_eq(rc, 0);
+               litest_assert_int_eq(rc, 0);
        }
 
        rc = libevdev_uinput_create_from_device(dev,
@@ -1192,34 +2374,95 @@ litest_create_uinput_device_from_description(const char *name,
           http://cgit.freedesktop.org/libevdev/commit/?id=debe9b030c8069cdf78307888ef3b65830b25122 */
        if (rc == -EBADF)
                rc = -EACCES;
-       ck_assert_msg(rc == 0, "Failed to create uinput device: %s", strerror(-rc));
+       litest_assert_msg(rc == 0, "Failed to create uinput device: %s", strerror(-rc));
 
        libevdev_free(dev);
 
-       /* uinput does not yet support setting the resolution, so we set it
-        * afterwards. This is of course racy as hell but the way we
-        * _generally_ use this function by the time libinput uses the
-        * device, we're finished here */
-
        devnode = libevdev_uinput_get_devnode(uinput);
-       ck_assert_notnull(devnode);
+       litest_assert_notnull(devnode);
        fd = open(devnode, O_RDONLY);
-       ck_assert_int_gt(fd, -1);
+       litest_assert_int_gt(fd, -1);
        rc = libevdev_new_from_fd(fd, &dev);
-       ck_assert_int_eq(rc, 0);
+       litest_assert_int_eq(rc, 0);
 
+       /* uinput does not yet support setting the resolution, so we set it
+        * afterwards. This is of course racy as hell but the way we
+        * _generally_ use this function by the time libinput uses the
+        * device, we're finished here */
        abs = abs_info;
        while (abs && abs->value != -1) {
                if (abs->resolution != 0) {
+                       if (libevdev_get_abs_resolution(dev, abs->value) ==
+                           abs->resolution)
+                               break;
+
                        rc = libevdev_kernel_set_abs_info(dev,
                                                          abs->value,
                                                          abs);
-                       ck_assert_int_eq(rc, 0);
+                       litest_assert_int_eq(rc, 0);
+               }
+               abs++;
+       }
+       close(fd);
+       libevdev_free(dev);
+
+       return uinput;
+}
+
+struct libevdev_uinput *
+litest_create_uinput_device_from_description(const char *name,
+                                            const struct input_id *id,
+                                            const struct input_absinfo *abs_info,
+                                            const int *events)
+{
+       struct libevdev_uinput *uinput;
+       const char *syspath;
+
+       struct udev *udev;
+       struct udev_monitor *udev_monitor;
+       struct udev_device *udev_device;
+       const char *udev_action;
+       const char *udev_syspath = NULL;
+       int rc;
+
+       udev = udev_new();
+       litest_assert_notnull(udev);
+       udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
+       litest_assert_notnull(udev_monitor);
+       udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "input",
+                                                       NULL);
+       /* remove O_NONBLOCK */
+       rc = fcntl(udev_monitor_get_fd(udev_monitor), F_SETFL, 0);
+       litest_assert_int_ne(rc, -1);
+       litest_assert_int_eq(udev_monitor_enable_receiving(udev_monitor),
+                            0);
+
+       uinput = litest_create_uinput(name, id, abs_info, events);
+
+       syspath = libevdev_uinput_get_syspath(uinput);
+
+       /* blocking, we don't want to continue until udev is ready */
+       while (1) {
+               udev_device = udev_monitor_receive_device(udev_monitor);
+               litest_assert_notnull(udev_device);
+               udev_action = udev_device_get_action(udev_device);
+               if (strcmp(udev_action, "add") != 0) {
+                       udev_device_unref(udev_device);
+                       continue;
                }
-               abs++;
+
+               udev_syspath = udev_device_get_syspath(udev_device);
+               if (udev_syspath && streq(udev_syspath, syspath))
+                       break;
+
+               udev_device_unref(udev_device);
        }
-       close(fd);
-       libevdev_free(dev);
+
+       litest_assert(udev_device_get_property_value(udev_device, "ID_INPUT"));
+
+       udev_device_unref(udev_device);
+       udev_monitor_unref(udev_monitor);
+       udev_unref(udev);
 
        return uinput;
 }
@@ -1238,7 +2481,7 @@ litest_create_uinput_abs_device_v(const char *name,
               (code = va_arg(args, int)) != -1) {
                *event++ = type;
                *event++ = code;
-               ck_assert(event < &events[ARRAY_LENGTH(events) - 2]);
+               litest_assert(event < &events[ARRAY_LENGTH(events) - 2]);
        }
 
        *event++ = -1;
@@ -1277,27 +2520,277 @@ litest_create_uinput_device(const char *name, struct input_id *id, ...)
        return uinput;
 }
 
+struct libinput_event_pointer*
+litest_is_button_event(struct libinput_event *event,
+                      unsigned int button,
+                      enum libinput_button_state state)
+{
+       struct libinput_event_pointer *ptrev;
+       enum libinput_event_type type = LIBINPUT_EVENT_POINTER_BUTTON;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+       ptrev = libinput_event_get_pointer_event(event);
+       litest_assert_int_eq(libinput_event_pointer_get_button(ptrev),
+                            button);
+       litest_assert_int_eq(libinput_event_pointer_get_button_state(ptrev),
+                            state);
+
+       return ptrev;
+}
+
+struct libinput_event_pointer *
+litest_is_axis_event(struct libinput_event *event,
+                    enum libinput_pointer_axis axis,
+                    enum libinput_pointer_axis_source source)
+{
+       struct libinput_event_pointer *ptrev;
+       enum libinput_event_type type = LIBINPUT_EVENT_POINTER_AXIS;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+       ptrev = libinput_event_get_pointer_event(event);
+       litest_assert(libinput_event_pointer_has_axis(ptrev, axis));
+
+       if (source != 0)
+               litest_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
+                                    source);
+
+       return ptrev;
+}
+
+struct libinput_event_pointer *
+litest_is_motion_event(struct libinput_event *event)
+{
+       struct libinput_event_pointer *ptrev;
+       enum libinput_event_type type = LIBINPUT_EVENT_POINTER_MOTION;
+       double x, y, ux, uy;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+       ptrev = libinput_event_get_pointer_event(event);
+
+       x = libinput_event_pointer_get_dx(ptrev);
+       y = libinput_event_pointer_get_dy(ptrev);
+       ux = libinput_event_pointer_get_dx_unaccelerated(ptrev);
+       uy = libinput_event_pointer_get_dy_unaccelerated(ptrev);
+
+       /* No 0 delta motion events */
+       litest_assert(x != 0.0 || y != 0.0 ||
+                     ux != 0.0 || uy != 0.0);
+
+       return ptrev;
+}
+
 void
 litest_assert_button_event(struct libinput *li, unsigned int button,
                           enum libinput_button_state state)
 {
        struct libinput_event *event;
-       struct libinput_event_pointer *ptrev;
 
        litest_wait_for_event(li);
        event = libinput_get_event(li);
 
-       ck_assert(event != NULL);
-       ck_assert_int_eq(libinput_event_get_type(event),
-                        LIBINPUT_EVENT_POINTER_BUTTON);
-       ptrev = libinput_event_get_pointer_event(event);
-       ck_assert_int_eq(libinput_event_pointer_get_button(ptrev),
-                        button);
-       ck_assert_int_eq(libinput_event_pointer_get_button_state(ptrev),
-                        state);
+       litest_is_button_event(event, button, state);
+
+       libinput_event_destroy(event);
+}
+
+struct libinput_event_touch *
+litest_is_touch_event(struct libinput_event *event,
+                     enum libinput_event_type type)
+{
+       struct libinput_event_touch *touch;
+
+       litest_assert(event != NULL);
+
+       if (type == 0)
+               type = libinput_event_get_type(event);
+
+       switch (type) {
+       case LIBINPUT_EVENT_TOUCH_DOWN:
+       case LIBINPUT_EVENT_TOUCH_UP:
+       case LIBINPUT_EVENT_TOUCH_MOTION:
+       case LIBINPUT_EVENT_TOUCH_FRAME:
+               litest_assert_event_type(event, type);
+               break;
+       default:
+               ck_abort_msg("%s: invalid touch type %d\n", __func__, type);
+       }
+
+       touch = libinput_event_get_touch_event(event);
+
+       return touch;
+}
+
+struct libinput_event_keyboard *
+litest_is_keyboard_event(struct libinput_event *event,
+                        unsigned int key,
+                        enum libinput_key_state state)
+{
+       struct libinput_event_keyboard *kevent;
+       enum libinput_event_type type = LIBINPUT_EVENT_KEYBOARD_KEY;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+
+       kevent = libinput_event_get_keyboard_event(event);
+       litest_assert(kevent != NULL);
+
+       litest_assert_int_eq(libinput_event_keyboard_get_key(kevent), key);
+       litest_assert_int_eq(libinput_event_keyboard_get_key_state(kevent),
+                            state);
+       return kevent;
+}
+
+struct libinput_event_gesture *
+litest_is_gesture_event(struct libinput_event *event,
+                       enum libinput_event_type type,
+                       int nfingers)
+{
+       struct libinput_event_gesture *gevent;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+
+       gevent = libinput_event_get_gesture_event(event);
+       litest_assert(gevent != NULL);
+
+       if (nfingers != -1)
+               litest_assert_int_eq(libinput_event_gesture_get_finger_count(gevent),
+                                    nfingers);
+       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_event_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_event_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_event_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);
 }
 
+struct libinput_event_tablet_pad *
+litest_is_pad_button_event(struct libinput_event *event,
+                          unsigned int button,
+                          enum libinput_button_state state)
+{
+       struct libinput_event_tablet_pad *p;
+       enum libinput_event_type type = LIBINPUT_EVENT_TABLET_PAD_BUTTON;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+
+       p = libinput_event_get_tablet_pad_event(event);
+       litest_assert(p != NULL);
+
+       litest_assert_int_eq(libinput_event_tablet_pad_get_button_number(p),
+                            button);
+
+       return p;
+}
+
+struct libinput_event_tablet_pad *
+litest_is_pad_ring_event(struct libinput_event *event,
+                        unsigned int number,
+                        enum libinput_tablet_pad_ring_axis_source source)
+{
+       struct libinput_event_tablet_pad *p;
+       enum libinput_event_type type = LIBINPUT_EVENT_TABLET_PAD_RING;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+       p = libinput_event_get_tablet_pad_event(event);
+
+       litest_assert_int_eq(libinput_event_tablet_pad_get_ring_number(p),
+                            number);
+       litest_assert_int_eq(libinput_event_tablet_pad_get_ring_source(p),
+                            source);
+
+       return p;
+}
+
+struct libinput_event_tablet_pad *
+litest_is_pad_strip_event(struct libinput_event *event,
+                         unsigned int number,
+                         enum libinput_tablet_pad_strip_axis_source source)
+{
+       struct libinput_event_tablet_pad *p;
+       enum libinput_event_type type = LIBINPUT_EVENT_TABLET_PAD_STRIP;
+
+       litest_assert(event != NULL);
+       litest_assert_event_type(event, type);
+       p = libinput_event_get_tablet_pad_event(event);
+
+       litest_assert_int_eq(libinput_event_tablet_pad_get_strip_number(p),
+                            number);
+       litest_assert_int_eq(libinput_event_tablet_pad_get_strip_source(p),
+                            source);
+
+       return p;
+}
+
+void
+litest_assert_pad_button_event(struct libinput *li,
+                              unsigned int button,
+                              enum libinput_button_state state)
+{
+       struct libinput_event *event;
+       struct libinput_event_tablet_pad *pev;
+
+       litest_wait_for_event(li);
+       event = libinput_get_event(li);
+
+       pev = litest_is_pad_button_event(event, button, state);
+       libinput_event_destroy(libinput_event_tablet_pad_get_base_event(pev));
+}
+
 void
 litest_assert_scroll(struct libinput *li,
                     enum libinput_pointer_axis axis,
@@ -1305,35 +2798,29 @@ litest_assert_scroll(struct libinput *li,
 {
        struct libinput_event *event, *next_event;
        struct libinput_event_pointer *ptrev;
+       int value;
 
        event = libinput_get_event(li);
        next_event = libinput_get_event(li);
-       ck_assert(next_event != NULL); /* At least 1 scroll + stop scroll */
+       litest_assert(next_event != NULL); /* At least 1 scroll + stop scroll */
 
        while (event) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               ptrev = libinput_event_get_pointer_event(event);
-               ck_assert(ptrev != NULL);
+               ptrev = litest_is_axis_event(event, axis, 0);
 
                if (next_event) {
+                       value = libinput_event_pointer_get_axis_value(ptrev,
+                                                                     axis);
                        /* Normal scroll event, check dir */
                        if (minimum_movement > 0) {
-                               ck_assert_int_ge(
-                                       libinput_event_pointer_get_axis_value(ptrev,
-                                                                             axis),
-                                       minimum_movement);
+                               litest_assert_int_ge(value, minimum_movement);
                        } else {
-                               ck_assert_int_le(
-                                       libinput_event_pointer_get_axis_value(ptrev,
-                                                                             axis),
-                                       minimum_movement);
+                               litest_assert_int_le(value, minimum_movement);
                        }
                } else {
                        /* Last scroll event, must be 0 */
-                       ck_assert_int_eq(
+                       ck_assert_double_eq(
                                libinput_event_pointer_get_axis_value(ptrev, axis),
-                               0);
+                               0.0);
                }
                libinput_event_destroy(event);
                event = next_event;
@@ -1351,23 +2838,62 @@ litest_assert_only_typed_events(struct libinput *li,
 
        libinput_dispatch(li);
        event = libinput_get_event(li);
-       ck_assert_notnull(event);
+       litest_assert_notnull(event);
 
        while (event) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                type);
+               litest_assert_int_eq(libinput_event_get_type(event),
+                                     type);
                libinput_event_destroy(event);
                libinput_dispatch(li);
                event = libinput_get_event(li);
        }
 }
 
+void
+litest_assert_touch_sequence(struct libinput *li)
+{
+       struct libinput_event *event;
+
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_DOWN);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_FRAME);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       do {
+               litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_MOTION);
+               libinput_event_destroy(event);
+
+               event = libinput_get_event(li);
+               litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_FRAME);
+               libinput_event_destroy(event);
+
+               event = libinput_get_event(li);
+               litest_assert_notnull(event);
+       } while (libinput_event_get_type(event) != LIBINPUT_EVENT_TOUCH_UP);
+
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_UP);
+       libinput_event_destroy(event);
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_FRAME);
+       libinput_event_destroy(event);
+}
+
 void
 litest_timeout_tap(void)
 {
        msleep(200);
 }
 
+void
+litest_timeout_tapndrag(void)
+{
+       msleep(520);
+}
+
 void
 litest_timeout_softbuttons(void)
 {
@@ -1380,6 +2906,48 @@ litest_timeout_buttonscroll(void)
        msleep(300);
 }
 
+void
+litest_timeout_finger_switch(void)
+{
+       msleep(120);
+}
+
+void
+litest_timeout_edgescroll(void)
+{
+       msleep(300);
+}
+
+void
+litest_timeout_middlebutton(void)
+{
+       msleep(70);
+}
+
+void
+litest_timeout_dwt_short(void)
+{
+       msleep(220);
+}
+
+void
+litest_timeout_dwt_long(void)
+{
+       msleep(520);
+}
+
+void
+litest_timeout_gesture(void)
+{
+       msleep(120);
+}
+
+void
+litest_timeout_trackpoint(void)
+{
+       msleep(320);
+}
+
 void
 litest_push_event_frame(struct litest_device *dev)
 {
@@ -1404,11 +2972,11 @@ send_abs_xy(struct litest_device *d, double x, double y)
        e.type = EV_ABS;
        e.code = ABS_X;
        e.value = LITEST_AUTO_ASSIGN;
-       val = litest_auto_assign_value(d, &e, 0, x, y);
+       val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
        litest_event(d, EV_ABS, ABS_X, val);
 
        e.code = ABS_Y;
-       val = litest_auto_assign_value(d, &e, 0, x, y);
+       val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
        litest_event(d, EV_ABS, ABS_Y, val);
 }
 
@@ -1421,12 +2989,12 @@ send_abs_mt_xy(struct litest_device *d, double x, double y)
        e.type = EV_ABS;
        e.code = ABS_MT_POSITION_X;
        e.value = LITEST_AUTO_ASSIGN;
-       val = litest_auto_assign_value(d, &e, 0, x, y);
+       val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
        litest_event(d, EV_ABS, ABS_MT_POSITION_X, val);
 
        e.code = ABS_MT_POSITION_Y;
        e.value = LITEST_AUTO_ASSIGN;
-       val = litest_auto_assign_value(d, &e, 0, x, y);
+       val = litest_auto_assign_value(d, &e, 0, x, y, NULL, true);
        litest_event(d, EV_ABS, ABS_MT_POSITION_Y, val);
 }
 
@@ -1535,3 +3103,172 @@ litest_semi_mt_touch_up(struct litest_device *d,
 
        litest_event(d, EV_SYN, SYN_REPORT, 0);
 }
+
+enum litest_mode {
+       LITEST_MODE_ERROR,
+       LITEST_MODE_TEST,
+       LITEST_MODE_LIST,
+};
+
+static inline enum litest_mode
+litest_parse_argv(int argc, char **argv)
+{
+       enum {
+               OPT_FILTER_TEST,
+               OPT_FILTER_DEVICE,
+               OPT_FILTER_GROUP,
+               OPT_JOBS,
+               OPT_LIST,
+               OPT_VERBOSE,
+       };
+       static const struct option opts[] = {
+               { "filter-test", 1, 0, OPT_FILTER_TEST },
+               { "filter-device", 1, 0, OPT_FILTER_DEVICE },
+               { "filter-group", 1, 0, OPT_FILTER_GROUP },
+               { "jobs", 1, 0, OPT_JOBS },
+               { "list", 0, 0, OPT_LIST },
+               { "verbose", 0, 0, OPT_VERBOSE },
+               { 0, 0, 0, 0}
+       };
+
+       enum {
+               JOBS_DEFAULT,
+               JOBS_SINGLE,
+               JOBS_CUSTOM
+       } want_jobs = JOBS_DEFAULT;
+
+       if (in_debugger)
+               want_jobs = JOBS_SINGLE;
+
+       while(1) {
+               int c;
+               int option_index = 0;
+
+               c = getopt_long(argc, argv, "j:", opts, &option_index);
+               if (c == -1)
+                       break;
+               switch(c) {
+               case OPT_FILTER_TEST:
+                       filter_test = optarg;
+                       if (want_jobs == JOBS_DEFAULT)
+                               want_jobs = JOBS_SINGLE;
+                       break;
+               case OPT_FILTER_DEVICE:
+                       filter_device = optarg;
+                       if (want_jobs == JOBS_DEFAULT)
+                               want_jobs = JOBS_SINGLE;
+                       break;
+               case OPT_FILTER_GROUP:
+                       filter_group = optarg;
+                       if (want_jobs == JOBS_DEFAULT)
+                               want_jobs = JOBS_SINGLE;
+                       break;
+               case 'j':
+               case OPT_JOBS:
+                       jobs = atoi(optarg);
+                       want_jobs = JOBS_CUSTOM;
+                       break;
+               case OPT_LIST:
+                       return LITEST_MODE_LIST;
+               case OPT_VERBOSE:
+                       verbose = 1;
+                       break;
+               default:
+                       fprintf(stderr, "usage: %s [--list]\n", argv[0]);
+                       return LITEST_MODE_ERROR;
+               }
+       }
+
+       if (want_jobs == JOBS_SINGLE)
+               jobs = 1;
+
+       return LITEST_MODE_TEST;
+}
+
+#ifndef LITEST_NO_MAIN
+static int
+is_debugger_attached(void)
+{
+       int status;
+       int rc;
+       int pid = fork();
+
+       if (pid == -1)
+               return 0;
+
+       if (pid == 0) {
+               int ppid = getppid();
+               if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0) {
+                       waitpid(ppid, NULL, 0);
+                       ptrace(PTRACE_CONT, NULL, NULL);
+                       ptrace(PTRACE_DETACH, ppid, NULL, NULL);
+                       rc = 0;
+               } else {
+                       rc = 1;
+               }
+               _exit(rc);
+       } else {
+               waitpid(pid, &status, 0);
+               rc = WEXITSTATUS(status);
+       }
+
+       return rc;
+}
+
+static void
+litest_list_tests(struct list *tests)
+{
+       struct suite *s;
+
+       list_for_each(s, tests, node) {
+               struct test *t;
+               printf("%s:\n", s->name);
+               list_for_each(t, &s->tests, node) {
+                       printf("        %s\n", t->name);
+               }
+       }
+}
+
+int
+main(int argc, char **argv)
+{
+       enum litest_mode mode;
+
+       list_init(&all_tests);
+
+       setenv("CK_DEFAULT_TIMEOUT", "30", 0);
+       setenv("LIBINPUT_RUNNING_TEST_SUITE", "1", 1);
+
+       in_debugger = is_debugger_attached();
+       if (in_debugger)
+               setenv("CK_FORK", "no", 0);
+
+       mode = litest_parse_argv(argc, argv);
+       if (mode == LITEST_MODE_ERROR)
+               return EXIT_FAILURE;
+
+       litest_setup_tests_udev();
+       litest_setup_tests_path();
+       litest_setup_tests_pointer();
+       litest_setup_tests_touch();
+       litest_setup_tests_log();
+       litest_setup_tests_tablet();
+       litest_setup_tests_pad();
+       litest_setup_tests_touchpad();
+       litest_setup_tests_touchpad_tap();
+       litest_setup_tests_touchpad_buttons();
+       litest_setup_tests_trackpoint();
+       litest_setup_tests_trackball();
+       litest_setup_tests_misc();
+       litest_setup_tests_keyboard();
+       litest_setup_tests_device();
+       litest_setup_tests_gestures();
+
+       if (mode == LITEST_MODE_LIST) {
+               litest_list_tests(&all_tests);
+               return EXIT_SUCCESS;
+       }
+
+       return litest_run(argc, argv);
+}
+#endif
index 60ec4587976e9f5a815d3022a46a7e339b154470..460235549d905db675046d7bd1afd71692287999 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #if HAVE_CONFIG_H
 #include <libevdev/libevdev.h>
 #include <libevdev/libevdev-uinput.h>
 #include <libinput.h>
+#include <math.h>
+
+extern void litest_setup_tests_udev(void);
+extern void litest_setup_tests_path(void);
+extern void litest_setup_tests_pointer(void);
+extern void litest_setup_tests_touch(void);
+extern void litest_setup_tests_log(void);
+extern void litest_setup_tests_tablet(void);
+extern void litest_setup_tests_pad(void);
+extern void litest_setup_tests_touchpad(void);
+extern void litest_setup_tests_touchpad_tap(void);
+extern void litest_setup_tests_touchpad_buttons(void);
+extern void litest_setup_tests_trackpoint(void);
+extern void litest_setup_tests_trackball(void);
+extern void litest_setup_tests_misc(void);
+extern void litest_setup_tests_keyboard(void);
+extern void litest_setup_tests_device(void);
+extern void litest_setup_tests_gestures(void);
+
+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)) \
+                       litest_fail_condition(__FILE__, __LINE__, __func__, \
+                                             #cond, NULL); \
+       } while(0)
+
+#define litest_assert_msg(cond, ...) \
+       do { \
+               if (!(cond)) \
+                       litest_fail_condition(__FILE__, __LINE__, __func__, \
+                                             #cond, __VA_ARGS__); \
+       } while(0)
+
+#define litest_abort_msg(...) \
+       litest_fail_condition(__FILE__, __LINE__, __func__, \
+                             "aborting", __VA_ARGS__); \
+
+#define litest_assert_notnull(cond) \
+       do { \
+               if ((cond) == NULL) \
+                       litest_fail_condition(__FILE__, __LINE__, __func__, \
+                                             #cond, " expected to be not NULL\n"); \
+       } while(0)
+
+#define litest_assert_comparison_int_(a_, op_, b_) \
+       do { \
+               __typeof__(a_) _a = a_; \
+               __typeof__(b_) _b = b_; \
+               if (trunc(_a) != _a || trunc(_b) != _b) \
+                       litest_abort_msg("litest_assert_int_* used for non-integer value\n"); \
+               if (!((_a) op_ (_b))) \
+                       litest_fail_comparison_int(__FILE__, __LINE__, __func__,\
+                                                  #op_, _a, _b, \
+                                                  #a_, #b_); \
+       } while(0)
+
+#define litest_assert_int_eq(a_, b_) \
+       litest_assert_comparison_int_(a_, ==, b_)
+
+#define litest_assert_int_ne(a_, b_) \
+       litest_assert_comparison_int_(a_, !=, b_)
+
+#define litest_assert_int_lt(a_, b_) \
+       litest_assert_comparison_int_(a_, <, b_)
+
+#define litest_assert_int_le(a_, b_) \
+       litest_assert_comparison_int_(a_, <=, b_)
+
+#define litest_assert_int_ge(a_, b_) \
+       litest_assert_comparison_int_(a_, >=, b_)
+
+#define litest_assert_int_gt(a_, b_) \
+       litest_assert_comparison_int_(a_, >, b_)
+
+#define litest_assert_comparison_ptr_(a_, op_, b_) \
+       do { \
+               __typeof__(a_) _a = a_; \
+               __typeof__(b_) _b = b_; \
+               if (!((_a) op_ (_b))) \
+                       litest_fail_comparison_ptr(__FILE__, __LINE__, __func__,\
+                                                  #a_ " " #op_ " " #b_); \
+       } while(0)
+
+#define litest_assert_ptr_eq(a_, b_) \
+       litest_assert_comparison_ptr_(a_, ==, b_)
+
+#define litest_assert_ptr_ne(a_, b_) \
+       litest_assert_comparison_ptr_(a_, !=, b_)
+
+#define litest_assert_ptr_null(a_) \
+       litest_assert_comparison_ptr_(a_, ==, NULL)
+
+#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,
-       LITEST_SYNAPTICS_TOUCHPAD = -3,
-       LITEST_SYNAPTICS_TOPBUTTONPAD = -4,
-       LITEST_BCM5974 = -5,
-       LITEST_KEYBOARD = -6,
-       LITEST_TRACKPOINT = -7,
-       LITEST_MOUSE = -8,
-       LITEST_WACOM_TOUCH = -9,
-       LITEST_ALPS_SEMI_MT = -10,
-       LITEST_GENERIC_SINGLETOUCH = -11,
-       LITEST_MS_SURFACE_COVER = -12,
-       LITEST_QEMU_TABLET = -13,
-       LITEST_XEN_VIRTUAL_POINTER = -14,
-       LITEST_VMWARE_VIRTMOUSE = -15,
-       LITEST_SYNAPTICS_HOVER_SEMI_MT = -16,
-       LITEST_SYNAPTICS_TRACKPOINT_BUTTONS = -17,
+       LITEST_SYNAPTICS_CLICKPAD_X220 = -1000,
+       LITEST_SYNAPTICS_TOUCHPAD,
+       LITEST_SYNAPTICS_TOPBUTTONPAD,
+       LITEST_BCM5974,
+       LITEST_KEYBOARD,
+       LITEST_TRACKPOINT,
+       LITEST_MOUSE,
+       LITEST_WACOM_TOUCH,
+       LITEST_ALPS_SEMI_MT,
+       LITEST_GENERIC_SINGLETOUCH,
+       LITEST_MS_SURFACE_COVER,
+       LITEST_QEMU_TABLET,
+       LITEST_XEN_VIRTUAL_POINTER,
+       LITEST_VMWARE_VIRTMOUSE,
+       LITEST_SYNAPTICS_HOVER_SEMI_MT,
+       LITEST_SYNAPTICS_TRACKPOINT_BUTTONS,
+       LITEST_PROTOCOL_A_SCREEN,
+       LITEST_WACOM_FINGER,
+       LITEST_KEYBOARD_BLACKWIDOW,
+       LITEST_WHEEL_ONLY,
+       LITEST_MOUSE_ROCCAT,
+       LITEST_LOGITECH_TRACKBALL,
+       LITEST_ATMEL_HOVER,
+       LITEST_ALPS_DUALPOINT,
+       LITEST_MOUSE_LOW_DPI,
+       LITEST_GENERIC_MULTITOUCH_SCREEN,
+       LITEST_NEXUS4_TOUCH_SCREEN,
+       LITEST_MAGIC_TRACKPAD,
+       LITEST_ELANTECH_TOUCHPAD,
+       LITEST_MOUSE_GLADIUS,
+       LITEST_MOUSE_WHEEL_CLICK_ANGLE,
+       LITEST_APPLE_KEYBOARD,
+       LITEST_ANKER_MOUSE_KBD,
+       LITEST_WACOM_BAMBOO,
+       LITEST_WACOM_CINTIQ,
+       LITEST_WACOM_INTUOS,
+       LITEST_WACOM_ISDV4,
+       LITEST_WALTOP,
+       LITEST_HUION_TABLET,
+       LITEST_CYBORG_RAT,
+       LITEST_YUBIKEY,
+       LITEST_SYNAPTICS_I2C,
+       LITEST_WACOM_CINTIQ_24HD,
+       LITEST_MULTITOUCH_FUZZ_SCREEN,
+       LITEST_WACOM_INTUOS3_PAD,
+       LITEST_WACOM_INTUOS5_PAD,
+       LITEST_KEYBOARD_ALL_CODES,
+       LITEST_MAGICMOUSE,
+       LITEST_WACOM_EKR,
+       LITEST_WACOM_CINTIQ_24HDT_PAD,
+       LITEST_WACOM_CINTIQ_13HDT_PEN,
+       LITEST_WACOM_CINTIQ_13HDT_PAD,
+       LITEST_WACOM_CINTIQ_13HDT_FINGER,
+       LITEST_WACOM_HID4800_PEN,
 };
 
 enum litest_device_feature {
@@ -70,6 +243,17 @@ enum litest_device_feature {
        LITEST_POINTINGSTICK = 1 << 11,
        LITEST_FAKE_MT = 1 << 12,
        LITEST_ABSOLUTE = 1 << 13,
+       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,
+       LITEST_TABLET_PAD = 1 << 21,
+       LITEST_RING = 1 << 22,
+       LITEST_STRIP = 1 << 23,
+       LITEST_TRACKBALL = 1 << 24,
 };
 
 struct litest_device {
@@ -84,25 +268,94 @@ struct litest_device {
        bool skip_ev_syn;
 
        void *private; /* device-specific data */
+};
 
-       char *udev_rule_file;
+struct axis_replacement {
+       int32_t evcode;
+       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++)
+ */
+struct range {
+       int lower; /* inclusive */
+       int upper; /* exclusive */
 };
 
 struct libinput *litest_create_context(void);
+void litest_disable_log_handler(struct libinput *libinput);
+void litest_restore_log_handler(struct libinput *libinput);
 
-void litest_add(const char *name, void *func,
-               enum litest_device_feature required_feature,
-               enum litest_device_feature excluded_feature);
+#define litest_add(name_, func_, ...) \
+       _litest_add(name_, #func_, func_, __VA_ARGS__)
+#define litest_add_ranged(name_, func_, ...) \
+       _litest_add_ranged(name_, #func_, func_, __VA_ARGS__)
+#define litest_add_for_device(name_, func_, ...) \
+       _litest_add_for_device(name_, #func_, func_, __VA_ARGS__)
+#define litest_add_ranged_for_device(name_, func_, ...) \
+       _litest_add_ranged_for_device(name_, #func_, func_, __VA_ARGS__)
+#define litest_add_no_device(name_, func_) \
+       _litest_add_no_device(name_, #func_, func_)
+#define litest_add_ranged_no_device(name_, func_, ...) \
+       _litest_add_ranged_no_device(name_, #func_, func_, __VA_ARGS__)
+void
+_litest_add(const char *name,
+           const char *funcname,
+           void *func,
+           enum litest_device_feature required_feature,
+           enum litest_device_feature excluded_feature);
+void
+_litest_add_ranged(const char *name,
+                  const char *funcname,
+                  void *func,
+                  enum litest_device_feature required,
+                  enum litest_device_feature excluded,
+                  const struct range *range);
+void
+_litest_add_for_device(const char *name,
+                      const char *funcname,
+                      void *func,
+                      enum litest_device_type type);
+void
+_litest_add_ranged_for_device(const char *name,
+                             const char *funcname,
+                             void *func,
+                             enum litest_device_type type,
+                             const struct range *range);
+void
+_litest_add_no_device(const char *name,
+                     const char *funcname,
+                     void *func);
 void
-litest_add_for_device(const char *name,
-                     void *func,
-                     enum litest_device_type type);
-void litest_add_no_device(const char *name, void *func);
+_litest_add_ranged_no_device(const char *name,
+                            const char *funcname,
+                            void *func,
+                            const struct range *range);
 
-int litest_run(int argc, char **argv);
-struct litest_device * litest_create_device(enum litest_device_type which);
-struct litest_device * litest_add_device(struct libinput *libinput,
-                                        enum litest_device_type which);
+struct litest_device *
+litest_create_device(enum litest_device_type which);
+
+struct litest_device *
+litest_add_device(struct libinput *libinput,
+                 enum litest_device_type which);
 struct libevdev_uinput *
 litest_create_uinput_device_from_description(const char *name,
                                             const struct input_id *id,
@@ -122,67 +375,290 @@ litest_add_device_with_overrides(struct libinput *libinput,
                                 const struct input_absinfo *abs_override,
                                 const int *events_override);
 
-struct litest_device *litest_current_device(void);
-void litest_delete_device(struct litest_device *d);
-int litest_handle_events(struct litest_device *d);
-
-void litest_event(struct litest_device *t,
-                 unsigned int type,
-                 unsigned int code,
-                 int value);
-int litest_auto_assign_value(struct litest_device *d,
-                            const struct input_event *ev,
-                            int slot, double x, double y);
-void litest_touch_up(struct litest_device *d, unsigned int slot);
-void litest_touch_move(struct litest_device *d,
-                      unsigned int slot,
-                      double x,
-                      double y);
-void litest_touch_down(struct litest_device *d,
+struct litest_device *
+litest_current_device(void);
+
+void
+litest_delete_device(struct litest_device *d);
+
+int
+litest_handle_events(struct litest_device *d);
+
+void
+litest_event(struct litest_device *t,
+            unsigned int type,
+            unsigned int code,
+            int value);
+int
+litest_auto_assign_value(struct litest_device *d,
+                        const struct input_event *ev,
+                        int slot, double x, double y,
+                        struct axis_replacement *axes,
+                        bool touching);
+void
+litest_touch_up(struct litest_device *d, unsigned int slot);
+
+void
+litest_touch_move(struct litest_device *d,
+                 unsigned int slot,
+                 double x,
+                 double y);
+
+void
+litest_touch_move_extended(struct litest_device *d,
+                          unsigned int slot,
+                          double x,
+                          double y,
+                          struct axis_replacement *axes);
+
+void
+litest_touch_down(struct litest_device *d,
+                 unsigned int slot,
+                 double x,
+                 double y);
+
+void
+litest_touch_down_extended(struct litest_device *d,
+                          unsigned int slot,
+                          double x,
+                          double y,
+                          struct axis_replacement *axes);
+
+void
+litest_touch_move_to(struct litest_device *d,
+                    unsigned int slot,
+                    double x_from, double y_from,
+                    double x_to, double y_to,
+                    int steps, int sleep_ms);
+
+void
+litest_touch_move_two_touches(struct litest_device *d,
+                             double x0, double y0,
+                             double x1, double y1,
+                             double dx, double dy,
+                             int steps, int sleep_ms);
+
+void
+litest_touch_move_three_touches(struct litest_device *d,
+                               double x0, double y0,
+                               double x1, double y1,
+                               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_pad_ring_start(struct litest_device *d, double value);
+
+void
+litest_pad_ring_change(struct litest_device *d, double value);
+
+void
+litest_pad_ring_end(struct litest_device *d);
+
+void
+litest_pad_strip_start(struct litest_device *d, double value);
+
+void
+litest_pad_strip_change(struct litest_device *d, double value);
+
+void
+litest_pad_strip_end(struct litest_device *d);
+
+void
+litest_hover_start(struct litest_device *d,
+                  unsigned int slot,
+                  double x,
+                  double y);
+
+void
+litest_hover_end(struct litest_device *d, unsigned int slot);
+
+void litest_hover_move(struct litest_device *d,
                       unsigned int slot,
                       double x,
                       double y);
-void litest_touch_move_to(struct litest_device *d,
-                         unsigned int slot,
-                         double x_from, double y_from,
-                         double x_to, double y_to,
-                         int steps, int sleep_ms);
-void litest_button_click(struct litest_device *d,
-                        unsigned int button,
-                        bool is_press);
-void litest_button_scroll(struct litest_device *d,
-                        unsigned int button,
-                        double dx, double dy);
-void litest_keyboard_key(struct litest_device *d,
+
+void
+litest_hover_move_to(struct litest_device *d,
+                    unsigned int slot,
+                    double x_from, double y_from,
+                    double x_to, double y_to,
+                    int steps, int sleep_ms);
+
+void
+litest_hover_move_two_touches(struct litest_device *d,
+                             double x0, double y0,
+                             double x1, double y1,
+                             double dx, double dy,
+                             int steps, int sleep_ms);
+
+void
+litest_button_click(struct litest_device *d,
+                   unsigned int button,
+                   bool is_press);
+
+void
+litest_button_scroll(struct litest_device *d,
+                    unsigned int button,
+                    double dx, double dy);
+
+void
+litest_keyboard_key(struct litest_device *d,
+                   unsigned int key,
+                   bool is_press);
+
+void
+litest_wait_for_event(struct libinput *li);
+
+void
+litest_wait_for_event_of_type(struct libinput *li, ...);
+
+void
+litest_drain_events(struct libinput *li);
+
+void
+litest_assert_event_type(struct libinput_event *event,
+                        enum libinput_event_type want);
+
+void
+litest_assert_empty_queue(struct libinput *li);
+
+void
+litest_assert_touch_sequence(struct libinput *li);
+
+struct libinput_event_pointer *
+litest_is_button_event(struct libinput_event *event,
+                      unsigned int button,
+                      enum libinput_button_state state);
+
+struct libinput_event_pointer *
+litest_is_axis_event(struct libinput_event *event,
+                    enum libinput_pointer_axis axis,
+                    enum libinput_pointer_axis_source source);
+
+struct libinput_event_pointer *
+litest_is_motion_event(struct libinput_event *event);
+
+struct libinput_event_touch *
+litest_is_touch_event(struct libinput_event *event,
+                     enum libinput_event_type type);
+
+struct libinput_event_keyboard *
+litest_is_keyboard_event(struct libinput_event *event,
                         unsigned int key,
-                        bool is_press);
-void litest_wait_for_event(struct libinput *li);
-void litest_wait_for_event_of_type(struct libinput *li, ...);
-void litest_drain_events(struct libinput *li);
-void litest_assert_empty_queue(struct libinput *li);
-void litest_assert_button_event(struct libinput *li,
-                               unsigned int button,
-                               enum libinput_button_state state);
-void litest_assert_scroll(struct libinput *li,
-                         enum libinput_pointer_axis axis,
-                         int minimum_movement);
-void litest_assert_only_typed_events(struct libinput *li,
-                                    enum libinput_event_type type);
-
-struct libevdev_uinput * litest_create_uinput_device(const char *name,
-                                                    struct input_id *id,
-                                                    ...);
-struct libevdev_uinput * litest_create_uinput_abs_device(const char *name,
-                                                        struct input_id *id,
-                                                        const struct input_absinfo *abs,
-                                                        ...);
-
-void litest_timeout_tap(void);
-void litest_timeout_softbuttons(void);
-void litest_timeout_buttonscroll(void);
-
-void litest_push_event_frame(struct litest_device *dev);
-void litest_pop_event_frame(struct litest_device *dev);
+                        enum libinput_key_state state);
+
+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);
+
+struct libinput_event_tablet_pad *
+litest_is_pad_button_event(struct libinput_event *event,
+                          unsigned int button,
+                          enum libinput_button_state state);
+struct libinput_event_tablet_pad *
+litest_is_pad_ring_event(struct libinput_event *event,
+                        unsigned int number,
+                        enum libinput_tablet_pad_ring_axis_source source);
+struct libinput_event_tablet_pad *
+litest_is_pad_strip_event(struct libinput_event *event,
+                         unsigned int number,
+                         enum libinput_tablet_pad_strip_axis_source source);
+
+void
+litest_assert_button_event(struct libinput *li,
+                          unsigned int button,
+                          enum libinput_button_state state);
+
+void
+litest_assert_scroll(struct libinput *li,
+                    enum libinput_pointer_axis axis,
+                    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);
+
+void
+litest_assert_pad_button_event(struct libinput *li,
+                                   unsigned int button,
+                                   enum libinput_button_state state);
+struct libevdev_uinput *
+litest_create_uinput_device(const char *name,
+                           struct input_id *id,
+                           ...);
+
+struct libevdev_uinput *
+litest_create_uinput_abs_device(const char *name,
+                               struct input_id *id,
+                               const struct input_absinfo *abs,
+                               ...);
+
+void
+litest_timeout_tap(void);
+
+void
+litest_timeout_tapndrag(void);
+
+void
+litest_timeout_softbuttons(void);
+
+void
+litest_timeout_buttonscroll(void);
+
+void
+litest_timeout_edgescroll(void);
+
+void
+litest_timeout_finger_switch(void);
+
+void
+litest_timeout_middlebutton(void);
+
+void
+litest_timeout_dwt_short(void);
+
+void
+litest_timeout_dwt_long(void);
+
+void
+litest_timeout_gesture(void);
+
+void
+litest_timeout_trackpoint(void);
+
+void
+litest_push_event_frame(struct litest_device *dev);
+
+void
+litest_pop_event_frame(struct litest_device *dev);
 
 /* this is a semi-mt device, so we keep track of the touches that the tests
  * send and modify them so that the first touch is always slot 0 and sends
@@ -199,20 +675,243 @@ struct litest_semi_mt {
        } touches[2];
 };
 
-void litest_semi_mt_touch_down(struct litest_device *d,
-                              struct litest_semi_mt *semi_mt,
-                              unsigned int slot,
-                              double x, double y);
-void litest_semi_mt_touch_move(struct litest_device *d,
-                              struct litest_semi_mt *semi_mt,
-                              unsigned int slot,
-                              double x, double y);
-void litest_semi_mt_touch_up(struct litest_device *d,
-                            struct litest_semi_mt *semi_mt,
-                            unsigned int slot);
+void
+litest_semi_mt_touch_down(struct litest_device *d,
+                         struct litest_semi_mt *semi_mt,
+                         unsigned int slot,
+                         double x, double y);
+
+void
+litest_semi_mt_touch_move(struct litest_device *d,
+                         struct litest_semi_mt *semi_mt,
+                         unsigned int slot,
+                         double x, double y);
+
+void
+litest_semi_mt_touch_up(struct litest_device *d,
+                       struct litest_semi_mt *semi_mt,
+                       unsigned int slot);
 
 #ifndef ck_assert_notnull
 #define ck_assert_notnull(ptr) ck_assert_ptr_ne(ptr, NULL)
 #endif
 
+static inline void
+litest_enable_tap(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_enabled(device,
+                                                       LIBINPUT_CONFIG_TAP_ENABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_disable_tap(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_enabled(device,
+                                                       LIBINPUT_CONFIG_TAP_DISABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_set_tap_map(struct libinput_device *device,
+                  enum libinput_config_tap_button_map map)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_enable_tap_drag(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_drag_enabled(device,
+                                                            LIBINPUT_CONFIG_DRAG_ENABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_disable_tap_drag(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_drag_enabled(device,
+                                                            LIBINPUT_CONFIG_DRAG_DISABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline bool
+litest_has_2fg_scroll(struct litest_device *dev)
+{
+       struct libinput_device *device = dev->libinput_device;
+
+       return !!(libinput_device_config_scroll_get_methods(device) &
+                 LIBINPUT_CONFIG_SCROLL_2FG);
+}
+
+static inline void
+litest_enable_2fg_scroll(struct litest_device *dev)
+{
+       enum libinput_config_status status, expected;
+       struct libinput_device *device = dev->libinput_device;
+
+       status = libinput_device_config_scroll_set_method(device,
+                                         LIBINPUT_CONFIG_SCROLL_2FG);
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_enable_edge_scroll(struct litest_device *dev)
+{
+       enum libinput_config_status status, expected;
+       struct libinput_device *device = dev->libinput_device;
+
+       status = libinput_device_config_scroll_set_method(device,
+                                         LIBINPUT_CONFIG_SCROLL_EDGE);
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_enable_clickfinger(struct litest_device *dev)
+{
+       enum libinput_config_status status, expected;
+       struct libinput_device *device = dev->libinput_device;
+
+       status = libinput_device_config_click_set_method(device,
+                                LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_enable_buttonareas(struct litest_device *dev)
+{
+       enum libinput_config_status status, expected;
+       struct libinput_device *device = dev->libinput_device;
+
+       status = libinput_device_config_click_set_method(device,
+                                LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_enable_drag_lock(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_disable_drag_lock(struct libinput_device *device)
+{
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_enable_middleemu(struct litest_device *dev)
+{
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                                                    LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+static inline void
+litest_disable_middleemu(struct litest_device *dev)
+{
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status, expected;
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                                                    LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+
+       litest_assert_int_eq(status, expected);
+}
+
+#define CK_DOUBLE_EQ_EPSILON 1E-3
+#define ck_assert_double_eq(X,Y)  \
+       do { \
+               double _ck_x = X; \
+               double _ck_y = Y; \
+               ck_assert_msg(fabs(_ck_x - _ck_y) < CK_DOUBLE_EQ_EPSILON, \
+                             "Assertion '" #X " == " #Y \
+                             "' failed: "#X"==%f, "#Y"==%f", \
+                             _ck_x, \
+                             _ck_y); \
+       } while (0)
+
+#define ck_assert_double_ne(X,Y)  \
+       do { \
+               double _ck_x = X; \
+               double _ck_y = Y; \
+               ck_assert_msg(fabs(_ck_x - _ck_y) > CK_DOUBLE_EQ_EPSILON, \
+                             "Assertion '" #X " != " #Y \
+                             "' failed: "#X"==%f, "#Y"==%f", \
+                             _ck_x, \
+                             _ck_y); \
+       } while (0)
+
+#define _ck_assert_double_eq(X, OP, Y)  \
+       do { \
+               double _ck_x = X; \
+               double _ck_y = Y; \
+               ck_assert_msg(_ck_x OP _ck_y || \
+                             fabs(_ck_x - _ck_y) < CK_DOUBLE_EQ_EPSILON, \
+                             "Assertion '" #X#OP#Y \
+                             "' failed: "#X"==%f, "#Y"==%f", \
+                             _ck_x, \
+                             _ck_y); \
+       } while (0)
+
+#define _ck_assert_double_ne(X, OP,Y) \
+       do { \
+               double _ck_x = X; \
+               double _ck_y = Y; \
+               ck_assert_msg(_ck_x OP _ck_y && \
+                             fabs(_ck_x - _ck_y) > CK_DOUBLE_EQ_EPSILON, \
+                             "Assertion '" #X#OP#Y \
+                             "' failed: "#X"==%f, "#Y"==%f", \
+                             _ck_x, \
+                             _ck_y); \
+       } while (0)
+#define ck_assert_double_lt(X, Y) _ck_assert_double_ne(X, <, Y)
+#define ck_assert_double_le(X, Y) _ck_assert_double_eq(X, <=, Y)
+#define ck_assert_double_gt(X, Y) _ck_assert_double_ne(X, >, Y)
+#define ck_assert_double_ge(X, Y) _ck_assert_double_eq(X, >=, Y)
 #endif /* LITEST_H */
index a56af1511a4cc6834492c405a7863cbf71f44d71..9dca1520b6852b29ca9a02da95f5ee284e4cddac 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -26,7 +27,6 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <libinput.h>
-#include <libudev.h>
 #include <unistd.h>
 
 #include "litest.h"
@@ -45,7 +45,7 @@ static void close_restricted(int fd, void *data)
        close(fd);
 }
 
-const struct libinput_interface simple_interface = {
+static const struct libinput_interface simple_interface = {
        .open_restricted = open_restricted,
        .close_restricted = close_restricted,
 };
@@ -58,8 +58,8 @@ simple_log_handler(struct libinput *libinput,
 {
        log_handler_called++;
        if (log_handler_context)
-               ck_assert(libinput == log_handler_context);
-       ck_assert(format != NULL);
+               litest_assert_ptr_eq(libinput, log_handler_context);
+       litest_assert_notnull(format);
 }
 
 START_TEST(log_default_priority)
@@ -140,11 +140,11 @@ START_TEST(log_priority)
 }
 END_TEST
 
-int main (int argc, char **argv) {
+void
+litest_setup_tests_log(void)
+{
        litest_add_no_device("log:defaults", log_default_priority);
        litest_add_no_device("log:logging", log_handler_invoked);
        litest_add_no_device("log:logging", log_handler_NULL);
        litest_add_no_device("log:logging", log_priority);
-
-       return litest_run(argc, argv);
 }
index 5b8191f2a4078c66d1d0973b467871a803d124d9..791ebc3774d6f042e6efb8ac71abd990219a624f 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -30,6 +31,7 @@
 #include <unistd.h>
 
 #include "litest.h"
+#include "libinput-util.h"
 
 static int open_restricted(const char *path, int flags, void *data)
 {
@@ -41,7 +43,7 @@ static void close_restricted(int fd, void *data)
        close(fd);
 }
 
-const struct libinput_interface simple_interface = {
+static const struct libinput_interface simple_interface = {
        .open_restricted = open_restricted,
        .close_restricted = close_restricted,
 };
@@ -64,7 +66,7 @@ create_simple_test_device(const char *name, ...)
        };
 
        evdev = libevdev_new();
-       ck_assert(evdev != NULL);
+       litest_assert_notnull(evdev);
        libevdev_set_name(evdev, name);
 
        va_start(args, name);
@@ -82,7 +84,7 @@ create_simple_test_device(const char *name, ...)
        rc = libevdev_uinput_create_from_device(evdev,
                                                LIBEVDEV_UINPUT_OPEN_MANAGED,
                                                &uinput);
-       ck_assert_int_eq(rc, 0);
+       litest_assert_int_eq(rc, 0);
        libevdev_free(evdev);
 
        return uinput;
@@ -126,9 +128,14 @@ START_TEST(event_conversion_device_notify)
                        else if (type == LIBINPUT_EVENT_DEVICE_REMOVED)
                                device_removed++;
 
+                       litest_disable_log_handler(li);
                        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);
+                       ck_assert(libinput_event_get_gesture_event(event) == NULL);
+                       ck_assert(libinput_event_get_tablet_tool_event(event) == NULL);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
                }
 
                libinput_event_destroy(event);
@@ -178,9 +185,14 @@ START_TEST(event_conversion_pointer)
                        else if (type == LIBINPUT_EVENT_POINTER_BUTTON)
                                button++;
 
+                       litest_disable_log_handler(li);
                        ck_assert(libinput_event_get_device_notify_event(event) == NULL);
                        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);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
                }
                libinput_event_destroy(event);
        }
@@ -224,9 +236,14 @@ START_TEST(event_conversion_pointer_abs)
                        else if (type == LIBINPUT_EVENT_POINTER_BUTTON)
                                button++;
 
+                       litest_disable_log_handler(li);
                        ck_assert(libinput_event_get_device_notify_event(event) == NULL);
                        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);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
                }
                libinput_event_destroy(event);
        }
@@ -263,9 +280,14 @@ START_TEST(event_conversion_key)
 
                        key++;
 
+                       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_touch_event(event) == NULL);
+                       ck_assert(libinput_event_get_gesture_event(event) == NULL);
+                       ck_assert(libinput_event_get_tablet_tool_event(event) == NULL);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
                }
                libinput_event_destroy(event);
        }
@@ -309,9 +331,14 @@ START_TEST(event_conversion_touch)
 
                        touch++;
 
+                       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_gesture_event(event) == NULL);
+                       ck_assert(libinput_event_get_tablet_tool_event(event) == NULL);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
                }
                libinput_event_destroy(event);
        }
@@ -320,6 +347,182 @@ START_TEST(event_conversion_touch)
 }
 END_TEST
 
+START_TEST(event_conversion_gesture)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int gestures = 0;
+       int i;
+
+       libinput_dispatch(li);
+
+       litest_touch_down(dev, 0, 70, 30);
+       litest_touch_down(dev, 1, 30, 70);
+       for (i = 0; i < 8; i++) {
+               litest_push_event_frame(dev);
+               litest_touch_move(dev, 0, 70 - i * 5, 30 + i * 5);
+               litest_touch_move(dev, 1, 30 + i * 5, 70 - i * 5);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+       }
+
+       while ((event = libinput_get_event(li))) {
+               enum libinput_event_type type;
+               type = libinput_event_get_type(event);
+
+               if (type >= LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN &&
+                   type <= LIBINPUT_EVENT_GESTURE_PINCH_END) {
+                       struct libinput_event_gesture *g;
+                       struct libinput_event *base;
+                       g = libinput_event_get_gesture_event(event);
+                       base = libinput_event_gesture_get_base_event(g);
+                       ck_assert(event == base);
+
+                       gestures++;
+
+                       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);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
+               }
+               libinput_event_destroy(event);
+       }
+
+       ck_assert_int_gt(gestures, 0);
+}
+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);
+                       ck_assert(libinput_event_get_tablet_pad_event(event) == NULL);
+                       litest_restore_log_handler(li);
+               }
+               libinput_event_destroy(event);
+       }
+
+       ck_assert_int_gt(events, 0);
+}
+END_TEST
+
+START_TEST(event_conversion_tablet_pad)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int events = 0;
+
+       litest_button_click(dev, BTN_0, true);
+       litest_pad_ring_start(dev, 10);
+       litest_pad_ring_end(dev);
+
+       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_PAD_BUTTON &&
+                   type <= LIBINPUT_EVENT_TABLET_PAD_STRIP) {
+                       struct libinput_event_tablet_pad *p;
+                       struct libinput_event *base;
+
+                       p = libinput_event_get_tablet_pad_event(event);
+                       base = libinput_event_tablet_pad_get_base_event(p);
+                       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);
+                       ck_assert(libinput_event_get_tablet_tool_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;
@@ -443,7 +646,7 @@ START_TEST(ratelimit_helpers)
        unsigned int i, j;
 
        /* 10 attempts every 100ms */
-       ratelimit_init(&rl, 100, 10);
+       ratelimit_init(&rl, ms2us(100), 10);
 
        for (j = 0; j < 3; ++j) {
                /* a burst of 9 attempts must succeed */
@@ -547,21 +750,216 @@ START_TEST(wheel_click_parser)
 }
 END_TEST
 
-int main (int argc, char **argv) {
+struct parser_test_float {
+       char *tag;
+       double expected_value;
+};
+
+START_TEST(trackpoint_accel_parser)
+{
+       struct parser_test_float tests[] = {
+               { "0.5", 0.5 },
+               { "1.0", 1.0 },
+               { "2.0", 2.0 },
+               { "fail1.0", 0.0 },
+               { "1.0fail", 0.0 },
+               { "0,5", 0.0 },
+               { NULL, 0.0 }
+       };
+       int i;
+       double accel;
+
+       for (i = 0; tests[i].tag != NULL; i++) {
+               accel = parse_trackpoint_accel_property(tests[i].tag);
+               ck_assert(accel == tests[i].expected_value);
+       }
+}
+END_TEST
+
+struct parser_test_dimension {
+       char *tag;
+       bool success;
+       int x, y;
+};
+
+START_TEST(dimension_prop_parser)
+{
+       struct parser_test_dimension tests[] = {
+               { "10x10", true, 10, 10 },
+               { "1x20", true, 1, 20 },
+               { "1x8000", true, 1, 8000 },
+               { "238492x428210", true, 238492, 428210 },
+               { "0x0", true, 0, 0 },
+               { "-10x10", false, 0, 0 },
+               { "-1", false, 0, 0 },
+               { "1x-99", false, 0, 0 },
+               { "0", false, 0, 0 },
+               { "100", false, 0, 0 },
+               { "", false, 0, 0 },
+               { "abd", false, 0, 0 },
+               { "xabd", false, 0, 0 },
+               { "0xaf", false, 0, 0 },
+               { "0x0x", true, 0, 0 },
+               { "x10", false, 0, 0 },
+               { NULL, false, 0, 0 }
+       };
+       int i;
+       size_t x, y;
+       bool success;
+
+       for (i = 0; tests[i].tag != NULL; i++) {
+               x = y = 0xad;
+               success = parse_dimension_property(tests[i].tag, &x, &y);
+               ck_assert(success == tests[i].success);
+               if (success) {
+                       ck_assert_int_eq(x, tests[i].x);
+                       ck_assert_int_eq(y, tests[i].y);
+               } else {
+                       ck_assert_int_eq(x, 0xad);
+                       ck_assert_int_eq(y, 0xad);
+               }
+       }
+}
+END_TEST
+
+START_TEST(time_conversion)
+{
+       ck_assert_int_eq(us(10), 10);
+       ck_assert_int_eq(ns2us(10000), 10);
+       ck_assert_int_eq(ms2us(10), 10000);
+       ck_assert_int_eq(s2us(1), 1000000);
+       ck_assert_int_eq(us2ms(10000), 10);
+}
+END_TEST
+
+static int open_restricted_leak(const char *path, int flags, void *data)
+{
+       return *(int*)data;
+}
+
+static void close_restricted_leak(int fd, void *data)
+{
+       /* noop */
+}
+
+const struct libinput_interface leak_interface = {
+       .open_restricted = open_restricted_leak,
+       .close_restricted = close_restricted_leak,
+};
+
+static void
+simple_log_handler(struct libinput *libinput,
+                  enum libinput_log_priority priority,
+                  const char *format,
+                  va_list args)
+{
+       vfprintf(stderr, format, args);
+}
+
+START_TEST(fd_no_event_leak)
+{
+       struct libevdev_uinput *uinput;
+       struct libinput *li;
+       struct libinput_device *device;
+       int fd = -1;
+       const char *path;
+       struct libinput_event *event;
+
+       uinput = create_simple_test_device("litest test device",
+                                          EV_REL, REL_X,
+                                          EV_REL, REL_Y,
+                                          EV_KEY, BTN_LEFT,
+                                          EV_KEY, BTN_MIDDLE,
+                                          EV_KEY, BTN_LEFT,
+                                          -1, -1);
+       path = libevdev_uinput_get_devnode(uinput);
+
+       fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC);
+       ck_assert_int_gt(fd, -1);
+
+       li = libinput_path_create_context(&leak_interface, &fd);
+       libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);
+       libinput_log_set_handler(li, simple_log_handler);
+
+       /* Add the device, trigger an event, then remove it again.
+        * Without it, we get a SYN_DROPPED immediately and no events.
+        */
+       device = libinput_path_add_device(li, path);
+       libevdev_uinput_write_event(uinput, EV_REL, REL_X, 1);
+       libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
+       libinput_path_remove_device(device);
+       libinput_dispatch(li);
+       litest_drain_events(li);
+
+       /* Device is removed, but fd is still open. Queue an event, add a
+        * new device with the same fd, the queued event must be discarded
+        * by libinput */
+       libevdev_uinput_write_event(uinput, EV_REL, REL_Y, 1);
+       libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       libinput_path_add_device(li, path);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert_int_eq(libinput_event_get_type(event),
+                        LIBINPUT_EVENT_DEVICE_ADDED);
+       libinput_event_destroy(event);
+
+       litest_assert_empty_queue(li);
+
+       close(fd);
+       libinput_unref(li);
+       libevdev_uinput_destroy(uinput);
+}
+END_TEST
+
+START_TEST(library_version)
+{
+       const char *version = LIBINPUT_LT_VERSION;
+       int C, R, A;
+       int rc;
+
+       rc = sscanf(version, "%d:%d:%d", &C, &R, &A);
+       ck_assert_int_eq(rc, 3);
+
+       ck_assert_int_ge(C, 17);
+       ck_assert_int_ge(R, 0);
+       ck_assert_int_ge(A, 7);
+
+       /* Binary compatibility broken? */
+       ck_assert(R != 0 || A != 0);
+
+       /* The first stable API in 0.12 had 10:0:0  */
+       ck_assert_int_eq(C - A, 10);
+}
+END_TEST
+
+void
+litest_setup_tests_misc(void)
+{
        litest_add_no_device("events:conversion", event_conversion_device_notify);
        litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE);
        litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE);
        litest_add_for_device("events:conversion", event_conversion_pointer_abs, LITEST_XEN_VIRTUAL_POINTER);
        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_for_device("events:conversion", event_conversion_tablet_pad, LITEST_WACOM_INTUOS5_PAD);
+       litest_add_no_device("misc:bitfield_helpers", bitfield_helpers);
 
        litest_add_no_device("context:refcount", context_ref_counting);
        litest_add_no_device("config:status string", config_status_string);
 
        litest_add_no_device("misc:matrix", matrix_helpers);
        litest_add_no_device("misc:ratelimit", ratelimit_helpers);
-       litest_add_no_device("misc:dpi parser", dpi_parser);
-       litest_add_no_device("misc:wheel click parser", wheel_click_parser);
+       litest_add_no_device("misc:parser", dpi_parser);
+       litest_add_no_device("misc:parser", wheel_click_parser);
+       litest_add_no_device("misc:parser", trackpoint_accel_parser);
+       litest_add_no_device("misc:parser", dimension_prop_parser);
+       litest_add_no_device("misc:time", time_conversion);
+
+       litest_add_no_device("misc:fd", fd_no_event_leak);
 
-       return litest_run(argc, argv);
+       litest_add_no_device("misc:library_version", library_version);
 }
diff --git a/test/pad.c b/test/pad.c
new file mode 100644 (file)
index 0000000..e2651e6
--- /dev/null
@@ -0,0 +1,663 @@
+/*
+ * Copyright © 2016 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.
+ */
+
+#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 "litest.h"
+
+START_TEST(pad_cap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       ck_assert(libinput_device_has_capability(device,
+                                                LIBINPUT_DEVICE_CAP_TABLET_PAD));
+
+}
+END_TEST
+
+START_TEST(pad_no_cap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       ck_assert(!libinput_device_has_capability(device,
+                                                 LIBINPUT_DEVICE_CAP_TABLET_PAD));
+}
+END_TEST
+
+START_TEST(pad_num_buttons)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       unsigned int code;
+       unsigned int nbuttons = 0;
+
+       for (code = BTN_0; code < KEY_MAX; code++) {
+               /* BTN_STYLUS is set for compatibility reasons but not
+                * actually hooked up */
+               if (code == BTN_STYLUS)
+                       continue;
+
+               if (libevdev_has_event_code(dev->evdev, EV_KEY, code))
+                       nbuttons++;
+       }
+
+       ck_assert_int_eq(libinput_device_tablet_pad_get_num_buttons(device),
+                        nbuttons);
+}
+END_TEST
+
+START_TEST(pad_button)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       unsigned int code;
+       unsigned int expected_number = 0;
+       struct libinput_event *ev;
+       struct libinput_event_tablet_pad *pev;
+
+       litest_drain_events(li);
+
+       for (code = BTN_0; code < KEY_MAX; code++) {
+               if (!libevdev_has_event_code(dev->evdev, EV_KEY, code))
+                       continue;
+
+               litest_button_click(dev, code, 1);
+               litest_button_click(dev, code, 0);
+               libinput_dispatch(li);
+
+               switch (code) {
+               case BTN_STYLUS:
+                       litest_assert_empty_queue(li);
+                       continue;
+               default:
+                       break;
+               }
+
+               ev = libinput_get_event(li);
+               pev = litest_is_pad_button_event(ev,
+                                                expected_number,
+                                                LIBINPUT_BUTTON_STATE_PRESSED);
+               ev = libinput_event_tablet_pad_get_base_event(pev);
+               libinput_event_destroy(ev);
+
+               ev = libinput_get_event(li);
+               pev = litest_is_pad_button_event(ev,
+                                                expected_number,
+                                                LIBINPUT_BUTTON_STATE_RELEASED);
+               ev = libinput_event_tablet_pad_get_base_event(pev);
+               libinput_event_destroy(ev);
+
+               expected_number++;
+       }
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(pad_has_ring)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       int nrings;
+
+       nrings = libinput_device_tablet_pad_get_num_rings(device);
+       ck_assert_int_ge(nrings, 1);
+}
+END_TEST
+
+START_TEST(pad_ring)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_tablet_pad *pev;
+       int val;
+       double degrees, expected;
+
+       litest_pad_ring_start(dev, 10);
+
+       litest_drain_events(li);
+
+       /* Wacom's 0 value is at 275 degrees */
+       expected = 270;
+
+       for (val = 0; val < 100; val += 10) {
+               litest_pad_ring_change(dev, val);
+               libinput_dispatch(li);
+
+               ev = libinput_get_event(li);
+               pev = litest_is_pad_ring_event(ev,
+                                              0,
+                                              LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER);
+
+               degrees = libinput_event_tablet_pad_get_ring_position(pev);
+               ck_assert_double_ge(degrees, 0.0);
+               ck_assert_double_lt(degrees, 360.0);
+
+               /* rounding errors, mostly caused by small physical range */
+               ck_assert_double_ge(degrees, expected - 2);
+               ck_assert_double_le(degrees, expected + 2);
+
+               libinput_event_destroy(ev);
+
+               expected = fmod(degrees + 36, 360);
+       }
+
+       litest_pad_ring_end(dev);
+}
+END_TEST
+
+START_TEST(pad_ring_finger_up)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_tablet_pad *pev;
+       double degrees;
+
+       litest_pad_ring_start(dev, 10);
+
+       litest_drain_events(li);
+
+       litest_pad_ring_end(dev);
+       libinput_dispatch(li);
+
+       ev = libinput_get_event(li);
+       pev = litest_is_pad_ring_event(ev,
+                                      0,
+                                      LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER);
+
+       degrees = libinput_event_tablet_pad_get_ring_position(pev);
+       ck_assert_double_eq(degrees, -1.0);
+       libinput_event_destroy(ev);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(pad_has_strip)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       int nstrips;
+
+       nstrips = libinput_device_tablet_pad_get_num_strips(device);
+       ck_assert_int_ge(nstrips, 1);
+}
+END_TEST
+
+START_TEST(pad_strip)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_tablet_pad *pev;
+       int val;
+       double pos, expected;
+
+       litest_pad_strip_start(dev, 10);
+
+       litest_drain_events(li);
+
+       expected = 0;
+
+       /* 9.5 works with the generic axis scaling without jumping over a
+        * value. */
+       for (val = 0; val < 100; val += 9.5) {
+               litest_pad_strip_change(dev, val);
+               libinput_dispatch(li);
+
+               ev = libinput_get_event(li);
+               pev = litest_is_pad_strip_event(ev,
+                                               0,
+                                               LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER);
+
+               pos = libinput_event_tablet_pad_get_strip_position(pev);
+               ck_assert_double_ge(pos, 0.0);
+               ck_assert_double_lt(pos, 1.0);
+
+               /* rounding errors, mostly caused by small physical range */
+               ck_assert_double_ge(pos, expected - 0.02);
+               ck_assert_double_le(pos, expected + 0.02);
+
+               libinput_event_destroy(ev);
+
+               expected = pos + 0.08;
+       }
+
+       litest_pad_strip_change(dev, 100);
+       libinput_dispatch(li);
+
+       ev = libinput_get_event(li);
+       pev = litest_is_pad_strip_event(ev,
+                                          0,
+                                          LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER);
+       pos = libinput_event_tablet_pad_get_strip_position(pev);
+       ck_assert_double_eq(pos, 1.0);
+       libinput_event_destroy(ev);
+
+       litest_pad_strip_end(dev);
+}
+END_TEST
+
+START_TEST(pad_strip_finger_up)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_tablet_pad *pev;
+       double pos;
+
+       litest_pad_strip_start(dev, 10);
+       litest_drain_events(li);
+
+       litest_pad_strip_end(dev);
+       libinput_dispatch(li);
+
+       ev = libinput_get_event(li);
+       pev = litest_is_pad_strip_event(ev,
+                                       0,
+                                       LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER);
+
+       pos = libinput_event_tablet_pad_get_strip_position(pev);
+       ck_assert_double_eq(pos, -1.0);
+       libinput_event_destroy(ev);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(pad_left_handed_default)
+{
+#if HAVE_LIBWACOM
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+
+       ck_assert(libinput_device_config_left_handed_is_available(device));
+
+       ck_assert_int_eq(libinput_device_config_left_handed_get_default(device),
+                        0);
+       ck_assert_int_eq(libinput_device_config_left_handed_get(device),
+                        0);
+
+       status = libinput_device_config_left_handed_set(dev->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       ck_assert_int_eq(libinput_device_config_left_handed_get(device),
+                        1);
+       ck_assert_int_eq(libinput_device_config_left_handed_get_default(device),
+                        0);
+
+       status = libinput_device_config_left_handed_set(dev->libinput_device, 0);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       ck_assert_int_eq(libinput_device_config_left_handed_get(device),
+                        0);
+       ck_assert_int_eq(libinput_device_config_left_handed_get_default(device),
+                        0);
+
+#endif
+}
+END_TEST
+
+START_TEST(pad_no_left_handed)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+
+       ck_assert(!libinput_device_config_left_handed_is_available(device));
+
+       ck_assert_int_eq(libinput_device_config_left_handed_get_default(device),
+                        0);
+       ck_assert_int_eq(libinput_device_config_left_handed_get(device),
+                        0);
+
+       status = libinput_device_config_left_handed_set(dev->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+       ck_assert_int_eq(libinput_device_config_left_handed_get(device),
+                        0);
+       ck_assert_int_eq(libinput_device_config_left_handed_get_default(device),
+                        0);
+
+       status = libinput_device_config_left_handed_set(dev->libinput_device, 0);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+       ck_assert_int_eq(libinput_device_config_left_handed_get(device),
+                        0);
+       ck_assert_int_eq(libinput_device_config_left_handed_get_default(device),
+                        0);
+}
+END_TEST
+
+START_TEST(pad_left_handed_ring)
+{
+#if HAVE_LIBWACOM
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_tablet_pad *pev;
+       int val;
+       double degrees, expected;
+
+       libinput_device_config_left_handed_set(dev->libinput_device, 1);
+
+       litest_pad_ring_start(dev, 10);
+
+       litest_drain_events(li);
+
+       /* Wacom's 0 value is at 275 degrees -> 90 in left-handed mode*/
+       expected = 90;
+
+       for (val = 0; val < 100; val += 10) {
+               litest_pad_ring_change(dev, val);
+               libinput_dispatch(li);
+
+               ev = libinput_get_event(li);
+               pev = litest_is_pad_ring_event(ev,
+                                              0,
+                                              LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER);
+
+               degrees = libinput_event_tablet_pad_get_ring_position(pev);
+               ck_assert_double_ge(degrees, 0.0);
+               ck_assert_double_lt(degrees, 360.0);
+
+               /* rounding errors, mostly caused by small physical range */
+               ck_assert_double_ge(degrees, expected - 2);
+               ck_assert_double_le(degrees, expected + 2);
+
+               libinput_event_destroy(ev);
+
+               expected = fmod(degrees + 36, 360);
+       }
+
+       litest_pad_ring_end(dev);
+#endif
+}
+END_TEST
+
+START_TEST(pad_mode_groups)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group *group;
+       int ngroups;
+       int i;
+
+       ngroups = libinput_device_tablet_pad_get_num_mode_groups(device);
+       ck_assert_int_eq(ngroups, 1);
+
+       for (i = 0; i < ngroups; i++) {
+               group = libinput_device_tablet_pad_get_mode_group(device, i);
+               ck_assert_notnull(group);
+               ck_assert_int_eq(libinput_tablet_pad_mode_group_get_index(group),
+                                i);
+       }
+
+       group = libinput_device_tablet_pad_get_mode_group(device, ngroups);
+       ck_assert(group == NULL);
+       group = libinput_device_tablet_pad_get_mode_group(device, ngroups + 1);
+       ck_assert(group == NULL);
+}
+END_TEST
+
+START_TEST(pad_mode_groups_userdata)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group *group;
+       int rc;
+       void *userdata = &rc;
+
+       group = libinput_device_tablet_pad_get_mode_group(device, 0);
+       ck_assert(libinput_tablet_pad_mode_group_get_user_data(group) ==
+                 NULL);
+       libinput_tablet_pad_mode_group_set_user_data(group, userdata);
+       ck_assert(libinput_tablet_pad_mode_group_get_user_data(group) ==
+                 &rc);
+
+       libinput_tablet_pad_mode_group_set_user_data(group, NULL);
+       ck_assert(libinput_tablet_pad_mode_group_get_user_data(group) ==
+                 NULL);
+}
+END_TEST
+
+START_TEST(pad_mode_groups_ref)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group *group, *g;
+
+       group = libinput_device_tablet_pad_get_mode_group(device, 0);
+       g = libinput_tablet_pad_mode_group_ref(group);
+       ck_assert_ptr_eq(g, group);
+
+       /* We don't expect this to be freed. Any leaks should be caught by
+        * valgrind. */
+       g = libinput_tablet_pad_mode_group_unref(group);
+       ck_assert_ptr_eq(g, group);
+}
+END_TEST
+
+START_TEST(pad_mode_group_mode)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group *group;
+       int ngroups;
+       unsigned int nmodes, mode;
+
+       ngroups = libinput_device_tablet_pad_get_num_mode_groups(device);
+       ck_assert_int_ge(ngroups, 1);
+
+       group = libinput_device_tablet_pad_get_mode_group(device, 0);
+
+       nmodes = libinput_tablet_pad_mode_group_get_num_modes(group);
+       ck_assert_int_eq(nmodes, 1);
+
+       mode = libinput_tablet_pad_mode_group_get_mode(group);
+       ck_assert_int_lt(mode, nmodes);
+}
+END_TEST
+
+START_TEST(pad_mode_group_has)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group *group;
+       int ngroups, nbuttons, nrings, nstrips;
+       int i, b, r, s;
+
+       ngroups = libinput_device_tablet_pad_get_num_mode_groups(device);
+       ck_assert_int_ge(ngroups, 1);
+
+       nbuttons = libinput_device_tablet_pad_get_num_buttons(device);
+       nrings = libinput_device_tablet_pad_get_num_rings(device);
+       nstrips = libinput_device_tablet_pad_get_num_strips(device);
+
+       for (b = 0; b < nbuttons; b++) {
+               bool found = false;
+               for (i = 0; i < ngroups; i++) {
+                       group = libinput_device_tablet_pad_get_mode_group(device,
+                                                                         i);
+                       if (libinput_tablet_pad_mode_group_has_button(group,
+                                                                     b)) {
+                               ck_assert(!found);
+                               found = true;
+                       }
+               }
+               ck_assert(found);
+       }
+
+       for (s = 0; s < nstrips; s++) {
+               bool found = false;
+               for (i = 0; i < ngroups; i++) {
+                       group = libinput_device_tablet_pad_get_mode_group(device,
+                                                                         i);
+                       if (libinput_tablet_pad_mode_group_has_strip(group,
+                                                                    s)) {
+                               ck_assert(!found);
+                               found = true;
+                       }
+               }
+               ck_assert(found);
+       }
+
+       for (r = 0; r < nrings; r++) {
+               bool found = false;
+               for (i = 0; i < ngroups; i++) {
+                       group = libinput_device_tablet_pad_get_mode_group(device,
+                                                                         i);
+                       if (libinput_tablet_pad_mode_group_has_ring(group,
+                                                                   r)) {
+                               ck_assert(!found);
+                               found = true;
+                       }
+               }
+               ck_assert(found);
+       }
+}
+END_TEST
+
+START_TEST(pad_mode_group_has_invalid)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group* group;
+       int ngroups, nbuttons, nrings, nstrips;
+       int i;
+       int rc;
+
+       ngroups = libinput_device_tablet_pad_get_num_mode_groups(device);
+       ck_assert_int_ge(ngroups, 1);
+
+       nbuttons = libinput_device_tablet_pad_get_num_buttons(device);
+       nrings = libinput_device_tablet_pad_get_num_rings(device);
+       nstrips = libinput_device_tablet_pad_get_num_strips(device);
+
+       for (i = 0; i < ngroups; i++) {
+               group = libinput_device_tablet_pad_get_mode_group(device, i);
+               rc = libinput_tablet_pad_mode_group_has_button(group,
+                                                              nbuttons);
+               ck_assert_int_eq(rc, 0);
+               rc = libinput_tablet_pad_mode_group_has_button(group,
+                                                              nbuttons + 1);
+               ck_assert_int_eq(rc, 0);
+               rc = libinput_tablet_pad_mode_group_has_button(group,
+                                                              0x1000000);
+               ck_assert_int_eq(rc, 0);
+       }
+
+       for (i = 0; i < ngroups; i++) {
+               group = libinput_device_tablet_pad_get_mode_group(device, i);
+               rc = libinput_tablet_pad_mode_group_has_strip(group,
+                                                             nstrips);
+               ck_assert_int_eq(rc, 0);
+               rc = libinput_tablet_pad_mode_group_has_strip(group,
+                                                              nstrips + 1);
+               ck_assert_int_eq(rc, 0);
+               rc = libinput_tablet_pad_mode_group_has_strip(group,
+                                                              0x1000000);
+               ck_assert_int_eq(rc, 0);
+       }
+
+       for (i = 0; i < ngroups; i++) {
+               group = libinput_device_tablet_pad_get_mode_group(device, i);
+               rc = libinput_tablet_pad_mode_group_has_ring(group,
+                                                            nrings);
+               ck_assert_int_eq(rc, 0);
+               rc = libinput_tablet_pad_mode_group_has_ring(group,
+                                                            nrings + 1);
+               ck_assert_int_eq(rc, 0);
+               rc = libinput_tablet_pad_mode_group_has_ring(group,
+                                                            0x1000000);
+               ck_assert_int_eq(rc, 0);
+       }
+}
+END_TEST
+
+START_TEST(pad_mode_group_has_no_toggle)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_tablet_pad_mode_group* group;
+       int ngroups, nbuttons;
+       int i, b;
+
+       ngroups = libinput_device_tablet_pad_get_num_mode_groups(device);
+       ck_assert_int_ge(ngroups, 1);
+
+       /* Button must not be toggle buttons */
+       nbuttons = libinput_device_tablet_pad_get_num_buttons(device);
+       for (i = 0; i < ngroups; i++) {
+               group = libinput_device_tablet_pad_get_mode_group(device, i);
+               for (b = 0; b < nbuttons; b++) {
+                       ck_assert(!libinput_tablet_pad_mode_group_button_is_toggle(
+                                                                   group,
+                                                                   b));
+               }
+       }
+}
+END_TEST
+
+void
+litest_setup_tests_pad(void)
+{
+       litest_add("pad:cap", pad_cap, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:cap", pad_no_cap, LITEST_ANY, LITEST_TABLET_PAD);
+
+       litest_add("pad:button", pad_num_buttons, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:button", pad_button, LITEST_TABLET_PAD, LITEST_ANY);
+
+       litest_add("pad:ring", pad_has_ring, LITEST_RING, LITEST_ANY);
+       litest_add("pad:ring", pad_ring, LITEST_RING, LITEST_ANY);
+       litest_add("pad:ring", pad_ring_finger_up, LITEST_RING, LITEST_ANY);
+
+       litest_add("pad:strip", pad_has_strip, LITEST_STRIP, LITEST_ANY);
+       litest_add("pad:strip", pad_strip, LITEST_STRIP, LITEST_ANY);
+       litest_add("pad:strip", pad_strip_finger_up, LITEST_STRIP, LITEST_ANY);
+
+       litest_add_for_device("pad:left_handed", pad_left_handed_default, LITEST_WACOM_INTUOS5_PAD);
+       litest_add_for_device("pad:left_handed", pad_no_left_handed, LITEST_WACOM_INTUOS3_PAD);
+       litest_add_for_device("pad:left_handed", pad_left_handed_ring, LITEST_WACOM_INTUOS5_PAD);
+       /* None of the current strip tablets are left-handed */
+
+       litest_add("pad:modes", pad_mode_groups, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:modes", pad_mode_groups_userdata, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:modes", pad_mode_groups_ref, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:modes", pad_mode_group_mode, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:modes", pad_mode_group_has, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:modes", pad_mode_group_has_invalid, LITEST_TABLET_PAD, LITEST_ANY);
+       litest_add("pad:modes", pad_mode_group_has_no_toggle, LITEST_TABLET_PAD, LITEST_ANY);
+}
index 243edd793d3b648c066956d2067d53290f6aa527..0890d3e0cd0c4865f2d3190901562dd34b9535e6 100644 (file)
@@ -1,24 +1,24 @@
-
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
  *
- * THE 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.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -27,7 +27,8 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <libinput.h>
-#include <libudev.h>
+#include <stdio.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
 #include "litest.h"
@@ -48,7 +49,7 @@ static void close_restricted(int fd, void *data)
        close(fd);
 }
 
-const struct libinput_interface simple_interface = {
+static const struct libinput_interface simple_interface = {
        .open_restricted = open_restricted,
        .close_restricted = close_restricted,
 };
@@ -99,6 +100,64 @@ START_TEST(path_create_invalid)
 }
 END_TEST
 
+START_TEST(path_create_invalid_kerneldev)
+{
+       struct libinput *li;
+       struct libinput_device *device;
+       const char *path = "/dev/uinput";
+
+       open_func_count = 0;
+       close_func_count = 0;
+
+       li = libinput_path_create_context(&simple_interface, NULL);
+       ck_assert(li != NULL);
+       device = libinput_path_add_device(li, path);
+       ck_assert(device == NULL);
+
+       ck_assert_int_eq(open_func_count, 1);
+       ck_assert_int_eq(close_func_count, 1);
+
+       libinput_unref(li);
+       ck_assert_int_eq(close_func_count, 1);
+
+       open_func_count = 0;
+       close_func_count = 0;
+}
+END_TEST
+
+START_TEST(path_create_invalid_file)
+{
+       struct libinput *li;
+       struct libinput_device *device;
+       char path[] = "/tmp/litest_path_XXXXXX";
+       int fd;
+
+       umask(002);
+       fd = mkstemp(path);
+       ck_assert_int_ge(fd, 0);
+       close(fd);
+
+       open_func_count = 0;
+       close_func_count = 0;
+
+       li = libinput_path_create_context(&simple_interface, NULL);
+       unlink(path);
+
+       ck_assert(li != NULL);
+       device = libinput_path_add_device(li, path);
+       ck_assert(device == NULL);
+
+       ck_assert_int_eq(open_func_count, 0);
+       ck_assert_int_eq(close_func_count, 0);
+
+       libinput_unref(li);
+       ck_assert_int_eq(close_func_count, 0);
+
+       open_func_count = 0;
+       close_func_count = 0;
+}
+END_TEST
+
 START_TEST(path_create_destroy)
 {
        struct libinput *li;
@@ -332,7 +391,9 @@ START_TEST(path_add_invalid_path)
 
        li = litest_create_context();
 
+       litest_disable_log_handler(li);
        device = libinput_path_add_device(li, "/tmp/");
+       litest_restore_log_handler(li);
        ck_assert(device == NULL);
 
        libinput_dispatch(li);
@@ -876,11 +937,13 @@ START_TEST(path_seat_recycle)
 }
 END_TEST
 
-int
-main(int argc, char **argv)
+void
+litest_setup_tests_path(void)
 {
        litest_add_no_device("path:create", path_create_NULL);
        litest_add_no_device("path:create", path_create_invalid);
+       litest_add_no_device("path:create", path_create_invalid_file);
+       litest_add_no_device("path:create", path_create_invalid_kerneldev);
        litest_add_no_device("path:create", path_create_destroy);
        litest_add_no_device("path:create", path_set_user_data);
        litest_add_no_device("path:suspend", path_suspend);
@@ -889,15 +952,13 @@ main(int argc, char **argv)
        litest_add_no_device("path:suspend", path_add_device_suspend_resume);
        litest_add_no_device("path:suspend", path_add_device_suspend_resume_fail);
        litest_add_no_device("path:suspend", path_add_device_suspend_resume_remove_device);
-       litest_add_for_device("path:seat", path_added_seat, LITEST_SYNAPTICS_CLICKPAD);
-       litest_add_for_device("path:seat", path_seat_change, LITEST_SYNAPTICS_CLICKPAD);
+       litest_add_for_device("path:seat", path_added_seat, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("path:seat", path_seat_change, LITEST_SYNAPTICS_CLICKPAD_X220);
        litest_add("path:device events", path_added_device, LITEST_ANY, LITEST_ANY);
        litest_add("path:device events", path_device_sysname, LITEST_ANY, LITEST_ANY);
-       litest_add_for_device("path:device events", path_add_device, LITEST_SYNAPTICS_CLICKPAD);
+       litest_add_for_device("path:device events", path_add_device, LITEST_SYNAPTICS_CLICKPAD_X220);
        litest_add_no_device("path:device events", path_add_invalid_path);
-       litest_add_for_device("path:device events", path_remove_device, LITEST_SYNAPTICS_CLICKPAD);
-       litest_add_for_device("path:device events", path_double_remove_device, LITEST_SYNAPTICS_CLICKPAD);
+       litest_add_for_device("path:device events", path_remove_device, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("path:device events", path_double_remove_device, LITEST_SYNAPTICS_CLICKPAD_X220);
        litest_add_no_device("path:seat", path_seat_recycle);
-
-       return litest_run(argc, argv);
 }
index 24ea726666249637f5cb81580f59c003cd4cffbf..175cb3b19ce900e74a2382afbf90376b3f376e76 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
 #include "libinput-util.h"
 #include "litest.h"
 
-static struct libinput_event_pointer *
-get_accelerated_motion_event(struct libinput *li)
-{
-       struct libinput_event *event;
-       struct libinput_event_pointer *ptrev;
-
-       while (1) {
-               event = libinput_get_event(li);
-               ck_assert_notnull(event);
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-
-               ptrev = libinput_event_get_pointer_event(event);
-               ck_assert_notnull(ptrev);
-
-               if (fabs(libinput_event_pointer_get_dx(ptrev)) < DBL_MIN &&
-                   fabs(libinput_event_pointer_get_dy(ptrev)) < DBL_MIN) {
-                       libinput_event_destroy(event);
-                       continue;
-               }
-
-               return ptrev;
-       }
-
-       ck_abort_msg("No accelerated pointer motion event found");
-       return NULL;
-}
-
 static void
 test_relative_event(struct litest_device *dev, int dx, int dy)
 {
        struct libinput *li = dev->libinput;
        struct libinput_event_pointer *ptrev;
+       struct libinput_event *event;
        double ev_dx, ev_dy;
        double expected_dir;
        double expected_length;
        double actual_dir;
        double actual_length;
 
-       /* Send two deltas, as the first one may be eaten up by an
-        * acceleration filter. */
-       litest_event(dev, EV_REL, REL_X, dx);
-       litest_event(dev, EV_REL, REL_Y, dy);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
        litest_event(dev, EV_REL, REL_X, dx);
        litest_event(dev, EV_REL, REL_Y, dy);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
 
        libinput_dispatch(li);
 
-       ptrev = get_accelerated_motion_event(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_motion_event(event);
 
        expected_length = sqrt(4 * dx*dx + 4 * dy*dy);
        expected_dir = atan2(dx, dy);
@@ -95,21 +65,42 @@ test_relative_event(struct litest_device *dev, int dx, int dy)
        actual_dir = atan2(ev_dx, ev_dy);
 
        /* Check the length of the motion vector (tolerate 1.0 indifference). */
-       ck_assert(fabs(expected_length) >= actual_length);
+       litest_assert(fabs(expected_length) >= actual_length);
 
        /* Check the direction of the motion vector (tolerate 2π/4 radians
         * indifference). */
-       ck_assert(fabs(expected_dir - actual_dir) < M_PI_2);
+       litest_assert(fabs(expected_dir - actual_dir) < M_PI_2);
 
-       libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev));
+       libinput_event_destroy(event);
 
        litest_drain_events(dev->libinput);
 }
 
+static void
+disable_button_scrolling(struct litest_device *device)
+{
+       struct libinput_device *dev = device->libinput_device;
+       enum libinput_config_status status,
+                                   expected;
+
+       status = libinput_device_config_scroll_set_method(dev,
+                                       LIBINPUT_CONFIG_SCROLL_NO_SCROLL);
+
+       expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       litest_assert_int_eq(status, expected);
+}
+
 START_TEST(pointer_motion_relative)
 {
        struct litest_device *dev = litest_current_device();
 
+       /* send a single event, the first movement
+          is always decelerated by 0.3 */
+       litest_event(dev, EV_REL, REL_X, 1);
+       litest_event(dev, EV_REL, REL_Y, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(dev->libinput);
+
        litest_drain_events(dev->libinput);
 
        test_relative_event(dev, 1, 0);
@@ -124,6 +115,95 @@ START_TEST(pointer_motion_relative)
 }
 END_TEST
 
+START_TEST(pointer_motion_relative_zero)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       int i;
+
+       /* NOTE: this test does virtually nothing. The kernel should not
+        * allow 0/0 events to be passed to userspace. If it ever happens,
+        * let's hope this test fails if we do the wrong thing.
+        */
+       litest_drain_events(li);
+
+       for (i = 0; i < 5; i++) {
+               litest_event(dev, EV_REL, REL_X, 0);
+               litest_event(dev, EV_REL, REL_Y, 0);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+       litest_assert_empty_queue(li);
+
+       /* send a single event, the first movement
+          is always decelerated by 0.3 */
+       litest_event(dev, EV_REL, REL_X, 1);
+       litest_event(dev, EV_REL, REL_Y, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       libinput_event_destroy(libinput_get_event(li));
+       litest_assert_empty_queue(li);
+
+       for (i = 0; i < 5; i++) {
+               litest_event(dev, EV_REL, REL_X, 0);
+               litest_event(dev, EV_REL, REL_Y, 0);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(dev->libinput);
+       }
+       litest_assert_empty_queue(li);
+
+}
+END_TEST
+
+START_TEST(pointer_motion_relative_min_decel)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event_pointer *ptrev;
+       struct libinput_event *event;
+       double evx, evy;
+       int dx, dy;
+       int cardinal = _i; /* ranged test */
+       double len;
+
+       int deltas[8][2] = {
+               /* N, NE, E, ... */
+               { 0, 1 },
+               { 1, 1 },
+               { 1, 0 },
+               { 1, -1 },
+               { 0, -1 },
+               { -1, -1 },
+               { -1, 0 },
+               { -1, 1 },
+       };
+
+       litest_drain_events(dev->libinput);
+
+       dx = deltas[cardinal][0];
+       dy = deltas[cardinal][1];
+
+       litest_event(dev, EV_REL, REL_X, dx);
+       litest_event(dev, EV_REL, REL_Y, dy);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_motion_event(event);
+       evx = libinput_event_pointer_get_dx(ptrev);
+       evy = libinput_event_pointer_get_dy(ptrev);
+
+       ck_assert((evx == 0.0) == (dx == 0));
+       ck_assert((evy == 0.0) == (dy == 0));
+
+       len = hypot(evx, evy);
+       ck_assert(fabs(len) >= 0.3);
+
+       libinput_event_destroy(event);
+}
+END_TEST
+
 static void
 test_absolute_event(struct litest_device *dev, double x, double y)
 {
@@ -131,21 +211,22 @@ test_absolute_event(struct litest_device *dev, double x, double y)
        struct libinput_event *event;
        struct libinput_event_pointer *ptrev;
        double ex, ey;
+       enum libinput_event_type type = LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE;
 
        litest_touch_down(dev, 0, x, y);
        libinput_dispatch(li);
 
        event = libinput_get_event(li);
-       ck_assert_int_eq(libinput_event_get_type(event),
-                        LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+       litest_assert_notnull(event);
+       litest_assert_int_eq(libinput_event_get_type(event), type);
 
        ptrev = libinput_event_get_pointer_event(event);
-       ck_assert(ptrev != NULL);
+       litest_assert(ptrev != NULL);
 
        ex = libinput_event_pointer_get_absolute_x_transformed(ptrev, 100);
        ey = libinput_event_pointer_get_absolute_y_transformed(ptrev, 100);
-       ck_assert_int_eq(ex + 0.5, x);
-       ck_assert_int_eq(ey + 0.5, y);
+       litest_assert_int_eq((int)(ex + 0.5), (int)x);
+       litest_assert_int_eq((int)(ey + 0.5), (int)y);
 
        libinput_event_destroy(event);
 }
@@ -162,6 +243,60 @@ START_TEST(pointer_motion_absolute)
 }
 END_TEST
 
+START_TEST(pointer_absolute_initial_state)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *libinput1, *libinput2;
+       struct libinput_event *ev1, *ev2;
+       struct libinput_event_pointer *p1, *p2;
+       int axis = _i; /* looped test */
+
+       libinput1 = dev->libinput;
+       litest_touch_down(dev, 0, 40, 60);
+       litest_touch_up(dev, 0);
+
+       /* device is now on some x/y value */
+       litest_drain_events(libinput1);
+
+       libinput2 = litest_create_context();
+       libinput_path_add_device(libinput2,
+                                libevdev_uinput_get_devnode(dev->uinput));
+       litest_drain_events(libinput2);
+
+       if (axis == ABS_X)
+               litest_touch_down(dev, 0, 40, 70);
+       else
+               litest_touch_down(dev, 0, 70, 60);
+       litest_touch_up(dev, 0);
+
+       litest_wait_for_event(libinput1);
+       litest_wait_for_event(libinput2);
+
+       while (libinput_next_event_type(libinput1)) {
+               ev1 = libinput_get_event(libinput1);
+               ev2 = libinput_get_event(libinput2);
+
+               ck_assert_int_eq(libinput_event_get_type(ev1),
+                                LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+               ck_assert_int_eq(libinput_event_get_type(ev1),
+                                libinput_event_get_type(ev2));
+
+               p1 = libinput_event_get_pointer_event(ev1);
+               p2 = libinput_event_get_pointer_event(ev2);
+
+               ck_assert_int_eq(libinput_event_pointer_get_absolute_x(p1),
+                                libinput_event_pointer_get_absolute_x(p2));
+               ck_assert_int_eq(libinput_event_pointer_get_absolute_y(p1),
+                                libinput_event_pointer_get_absolute_y(p2));
+
+               libinput_event_destroy(ev1);
+               libinput_event_destroy(ev2);
+       }
+
+       libinput_unref(libinput2);
+}
+END_TEST
+
 static void
 test_unaccel_event(struct litest_device *dev, int dx, int dy)
 {
@@ -177,18 +312,13 @@ test_unaccel_event(struct litest_device *dev, int dx, int dy)
       libinput_dispatch(li);
 
       event = libinput_get_event(li);
-      ck_assert_notnull(event);
-      ck_assert_int_eq(libinput_event_get_type(event),
-                       LIBINPUT_EVENT_POINTER_MOTION);
-
-      ptrev = libinput_event_get_pointer_event(event);
-      ck_assert(ptrev != NULL);
+      ptrev = litest_is_motion_event(event);
 
       ev_dx = libinput_event_pointer_get_dx_unaccelerated(ptrev);
       ev_dy = libinput_event_pointer_get_dy_unaccelerated(ptrev);
 
-      ck_assert_int_eq(dx, ev_dx);
-      ck_assert_int_eq(dy, ev_dy);
+      litest_assert_int_eq(dx, ev_dx);
+      litest_assert_int_eq(dy, ev_dy);
 
       libinput_event_destroy(event);
 
@@ -230,6 +360,8 @@ START_TEST(pointer_button)
 {
        struct litest_device *dev = litest_current_device();
 
+       disable_button_scrolling(dev);
+
        litest_drain_events(dev->libinput);
 
        test_button_event(dev, BTN_LEFT, 1);
@@ -245,8 +377,7 @@ START_TEST(pointer_button)
        }
 
        /* Skip middle button test on trackpoints (used for scrolling) */
-       if (!libevdev_has_property(dev->evdev, INPUT_PROP_POINTING_STICK) &&
-           libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE)) {
+       if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE)) {
                test_button_event(dev, BTN_MIDDLE, 1);
                test_button_event(dev, BTN_MIDDLE, 0);
        }
@@ -342,6 +473,33 @@ START_TEST(pointer_button_auto_release)
 }
 END_TEST
 
+static inline int
+wheel_click_angle(struct litest_device *dev, int which)
+{
+       struct udev_device *d;
+       const char *prop = NULL;
+       const int default_angle = 15;
+       int angle = default_angle;
+
+       d = libinput_device_get_udev_device(dev->libinput_device);
+       litest_assert_ptr_notnull(d);
+
+       if (which == REL_HWHEEL)
+               prop = udev_device_get_property_value(d, "MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL");
+       if(!prop)
+               prop = udev_device_get_property_value(d, "MOUSE_WHEEL_CLICK_ANGLE");
+       if (!prop)
+               goto out;
+
+       angle = parse_mouse_wheel_click_angle_property(prop);
+       if (angle == 0)
+               angle = default_angle;
+
+out:
+       udev_device_unref(d);
+       return angle;
+}
+
 static void
 test_wheel_event(struct litest_device *dev, int which, int amount)
 {
@@ -350,11 +508,11 @@ test_wheel_event(struct litest_device *dev, int which, int amount)
        struct libinput_event_pointer *ptrev;
        enum libinput_pointer_axis axis;
 
-       /* the current evdev implementation scales the scroll wheel events
-          up by a factor 15 */
-       const int scroll_step = 15;
-       int expected = amount * scroll_step;
-       int discrete = amount;
+       int scroll_step, expected, discrete;;
+
+       scroll_step = wheel_click_angle(dev, which);
+       expected = amount * scroll_step;
+       discrete = amount;
 
        if (libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device)) {
                expected *= -1;
@@ -369,24 +527,18 @@ test_wheel_event(struct litest_device *dev, int which, int amount)
 
        libinput_dispatch(li);
 
-       event = libinput_get_event(li);
-       ck_assert(event != NULL);
-       ck_assert_int_eq(libinput_event_get_type(event),
-                         LIBINPUT_EVENT_POINTER_AXIS);
-
-       ptrev = libinput_event_get_pointer_event(event);
-       ck_assert(ptrev != NULL);
-
        axis = (which == REL_WHEEL) ?
                                LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL :
                                LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
+       event = libinput_get_event(li);
+       ptrev = litest_is_axis_event(event,
+                                    axis,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_WHEEL);
 
-       ck_assert_int_eq(libinput_event_pointer_get_axis_value(ptrev, axis),
+       litest_assert_int_eq(libinput_event_pointer_get_axis_value(ptrev, axis),
                         expected);
-       ck_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
-                        LIBINPUT_POINTER_AXIS_SOURCE_WHEEL);
-       ck_assert_int_eq(libinput_event_pointer_get_axis_value_discrete(ptrev, axis),
-                        discrete);
+       litest_assert_int_eq(libinput_event_pointer_get_axis_value_discrete(ptrev, axis),
+                            discrete);
        libinput_event_destroy(event);
 }
 
@@ -396,11 +548,17 @@ START_TEST(pointer_scroll_wheel)
 
        litest_drain_events(dev->libinput);
 
-       test_wheel_event(dev, REL_WHEEL, -1);
-       test_wheel_event(dev, REL_WHEEL, 1);
+       /* make sure we hit at least one of the below two conditions */
+       ck_assert(libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL) ||
+                 libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL));
+
+       if (libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL)) {
+               test_wheel_event(dev, REL_WHEEL, -1);
+               test_wheel_event(dev, REL_WHEEL, 1);
 
-       test_wheel_event(dev, REL_WHEEL, -5);
-       test_wheel_event(dev, REL_WHEEL, 6);
+               test_wheel_event(dev, REL_WHEEL, -5);
+               test_wheel_event(dev, REL_WHEEL, 6);
+       }
 
        if (libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL)) {
                test_wheel_event(dev, REL_HWHEEL, -1);
@@ -446,11 +604,17 @@ START_TEST(pointer_scroll_natural_wheel)
 
        libinput_device_config_scroll_set_natural_scroll_enabled(device, 1);
 
-       test_wheel_event(dev, REL_WHEEL, -1);
-       test_wheel_event(dev, REL_WHEEL, 1);
+       /* make sure we hit at least one of the below two conditions */
+       ck_assert(libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL) ||
+                 libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL));
+
+       if (libevdev_has_event_code(dev->evdev, EV_REL, REL_WHEEL)) {
+               test_wheel_event(dev, REL_WHEEL, -1);
+               test_wheel_event(dev, REL_WHEEL, 1);
 
-       test_wheel_event(dev, REL_WHEEL, -5);
-       test_wheel_event(dev, REL_WHEEL, 6);
+               test_wheel_event(dev, REL_WHEEL, -5);
+               test_wheel_event(dev, REL_WHEEL, 6);
+       }
 
        if (libevdev_has_event_code(dev->evdev, EV_REL, REL_HWHEEL)) {
                test_wheel_event(dev, REL_HWHEEL, -1);
@@ -732,6 +896,108 @@ START_TEST(pointer_scroll_button)
 }
 END_TEST
 
+START_TEST(pointer_scroll_button_no_event_before_timeout)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       int i;
+
+       disable_button_scrolling(device);
+
+       libinput_device_config_scroll_set_method(device->libinput_device,
+                                       LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
+       libinput_device_config_scroll_set_button(device->libinput_device,
+                                                BTN_LEFT);
+       litest_drain_events(li);
+
+       litest_button_click(device, BTN_LEFT, true);
+       litest_assert_empty_queue(li);
+
+       for (i = 0; i < 10; i++) {
+               litest_event(device, EV_REL, REL_Y, 1);
+               litest_event(device, EV_SYN, SYN_REPORT, 0);
+       }
+       litest_assert_empty_queue(li);
+
+       litest_timeout_buttonscroll();
+       libinput_dispatch(li);
+       litest_button_click(device, BTN_LEFT, false);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(pointer_scroll_button_middle_emulation)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
+       int i;
+
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                               LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       status = libinput_device_config_scroll_set_method(device,
+                                LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_scroll_set_button(device, BTN_MIDDLE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_drain_events(li);
+
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_RIGHT, 1);
+       libinput_dispatch(li);
+       litest_timeout_buttonscroll();
+       libinput_dispatch(li);
+
+       for (i = 0; i < 10; i++) {
+               litest_event(dev, EV_REL, REL_Y, -1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
+
+       libinput_dispatch(li);
+
+       litest_button_click(dev, BTN_LEFT, 0);
+       litest_button_click(dev, BTN_RIGHT, 0);
+       libinput_dispatch(li);
+
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -1);
+       litest_assert_empty_queue(li);
+
+       /* Restore default scroll behavior */
+       libinput_device_config_scroll_set_method(dev->libinput_device,
+               libinput_device_config_scroll_get_default_method(
+                       dev->libinput_device));
+       libinput_device_config_scroll_set_button(dev->libinput_device,
+               libinput_device_config_scroll_get_default_button(
+                       dev->libinput_device));
+}
+END_TEST
+
+START_TEST(pointer_scroll_nowheel_defaults)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_scroll_method method;
+       uint32_t button;
+
+       method = libinput_device_config_scroll_get_method(device);
+       ck_assert_int_eq(method, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
+
+       method = libinput_device_config_scroll_get_default_method(device);
+       ck_assert_int_eq(method, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
+
+       button = libinput_device_config_scroll_get_button(device);
+       ck_assert_int_eq(button, BTN_MIDDLE);
+       button = libinput_device_config_scroll_get_default_button(device);
+       ck_assert_int_eq(button, BTN_MIDDLE);
+}
+END_TEST
+
 START_TEST(pointer_accel_defaults)
 {
        struct litest_device *dev = litest_current_device();
@@ -740,15 +1006,18 @@ START_TEST(pointer_accel_defaults)
        double speed;
 
        ck_assert(libinput_device_config_accel_is_available(device));
-       ck_assert(libinput_device_config_accel_get_default_speed(device) == 0.0);
-       ck_assert(libinput_device_config_accel_get_speed(device) == 0.0);
+       ck_assert_double_eq(libinput_device_config_accel_get_default_speed(device),
+                           0.0);
+       ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                           0.0);
 
        for (speed = -2.0; speed < -1.0; speed += 0.2) {
                status = libinput_device_config_accel_set_speed(device,
                                                                speed);
                ck_assert_int_eq(status,
                                 LIBINPUT_CONFIG_STATUS_INVALID);
-               ck_assert(libinput_device_config_accel_get_speed(device) == 0.0);
+               ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                                   0.0);
        }
 
        for (speed = -1.0; speed <= 1.0; speed += 0.2) {
@@ -756,7 +1025,8 @@ START_TEST(pointer_accel_defaults)
                                                                speed);
                ck_assert_int_eq(status,
                                 LIBINPUT_CONFIG_STATUS_SUCCESS);
-               ck_assert(libinput_device_config_accel_get_speed(device) == speed);
+               ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                                   speed);
        }
 
        for (speed = 1.2; speed <= -2.0; speed += 0.2) {
@@ -764,7 +1034,8 @@ START_TEST(pointer_accel_defaults)
                                                                speed);
                ck_assert_int_eq(status,
                                 LIBINPUT_CONFIG_STATUS_INVALID);
-               ck_assert(libinput_device_config_accel_get_speed(device) == 1.0);
+               ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                                   1.0);
        }
 
 }
@@ -795,8 +1066,10 @@ START_TEST(pointer_accel_defaults_absolute)
        double speed;
 
        ck_assert(!libinput_device_config_accel_is_available(device));
-       ck_assert(libinput_device_config_accel_get_default_speed(device) == 0.0);
-       ck_assert(libinput_device_config_accel_get_speed(device) == 0.0);
+       ck_assert_double_eq(libinput_device_config_accel_get_default_speed(device),
+                           0.0);
+       ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                           0.0);
 
        for (speed = -2.0; speed <= 2.0; speed += 0.2) {
                status = libinput_device_config_accel_set_speed(device,
@@ -807,26 +1080,675 @@ START_TEST(pointer_accel_defaults_absolute)
                else
                        ck_assert_int_eq(status,
                                         LIBINPUT_CONFIG_STATUS_INVALID);
-               ck_assert(libinput_device_config_accel_get_speed(device) == 0.0);
+               ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                                   0.0);
        }
 }
 END_TEST
 
-int main (int argc, char **argv) {
+START_TEST(pointer_accel_defaults_absolute_relative)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       ck_assert(libinput_device_config_accel_is_available(device));
+       ck_assert_double_eq(libinput_device_config_accel_get_default_speed(device),
+                           0.0);
+       ck_assert_double_eq(libinput_device_config_accel_get_speed(device),
+                           0.0);
+}
+END_TEST
+
+START_TEST(pointer_accel_direction_change)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *pev;
+       int i;
+       double delta;
+
+       litest_drain_events(li);
+
+       for (i = 0; i < 10; i++) {
+               litest_event(dev, EV_REL, REL_X, -1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
+       litest_event(dev, EV_REL, REL_X, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       litest_wait_for_event_of_type(li,
+                                     LIBINPUT_EVENT_POINTER_MOTION,
+                                     -1);
+       event = libinput_get_event(li);
+       do {
+               pev = libinput_event_get_pointer_event(event);
+
+               delta = libinput_event_pointer_get_dx(pev);
+               ck_assert_double_le(delta, 0.0);
+               libinput_event_destroy(event);
+               event = libinput_get_event(li);
+       } while (libinput_next_event_type(li) != LIBINPUT_EVENT_NONE);
+
+       pev = libinput_event_get_pointer_event(event);
+       delta = libinput_event_pointer_get_dx(pev);
+       ck_assert_double_gt(delta, 0.0);
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(pointer_accel_profile_defaults)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       enum libinput_config_accel_profile profile;
+       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_ADAPTIVE);
+
+       profile = libinput_device_config_accel_get_profile(device);
+       ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
+
+       profiles = libinput_device_config_accel_get_profiles(device);
+       ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
+       ck_assert(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
+
+       status = libinput_device_config_accel_set_profile(device,
+                                                         LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       profile = libinput_device_config_accel_get_profile(device);
+       ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
+
+       profile = libinput_device_config_accel_get_default_profile(device);
+       ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
+
+       status = libinput_device_config_accel_set_profile(device,
+                                                         LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       profile = libinput_device_config_accel_get_profile(device);
+       ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
+}
+END_TEST
+
+START_TEST(pointer_accel_profile_defaults_noprofile)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       enum libinput_config_accel_profile profile;
+       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_NONE);
+
+       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(pointer_accel_profile_invalid)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+
+       ck_assert(libinput_device_config_accel_is_available(device));
+
+       status = libinput_device_config_accel_set_profile(device,
+                                          LIBINPUT_CONFIG_ACCEL_PROFILE_NONE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+
+       status = libinput_device_config_accel_set_profile(device,
+                                          LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE + 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+
+       status = libinput_device_config_accel_set_profile(device,
+                          LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE |LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(pointer_accel_profile_flat_motion_relative)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+
+       libinput_device_config_accel_set_profile(device,
+                                                LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
+       litest_drain_events(dev->libinput);
+
+       test_relative_event(dev, 1, 0);
+       test_relative_event(dev, 1, 1);
+       test_relative_event(dev, 1, -1);
+       test_relative_event(dev, 0, 1);
+
+       test_relative_event(dev, -1, 0);
+       test_relative_event(dev, -1, 1);
+       test_relative_event(dev, -1, -1);
+       test_relative_event(dev, 0, -1);
+}
+END_TEST
+
+START_TEST(middlebutton)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       enum libinput_config_status status;
+       unsigned int i;
+       const int btn[][4] = {
+               { BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT },
+               { BTN_LEFT, BTN_RIGHT, BTN_RIGHT, BTN_LEFT },
+               { BTN_RIGHT, BTN_LEFT, BTN_LEFT, BTN_RIGHT },
+               { BTN_RIGHT, BTN_LEFT, BTN_RIGHT, BTN_LEFT },
+       };
+
+       disable_button_scrolling(device);
+
+       status = libinput_device_config_middle_emulation_set_enabled(
+                                           device->libinput_device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       litest_drain_events(li);
+
+       for (i = 0; i < ARRAY_LENGTH(btn); i++) {
+               litest_button_click(device, btn[i][0], true);
+               litest_button_click(device, btn[i][1], true);
+
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_empty_queue(li);
+
+               litest_button_click(device, btn[i][2], false);
+               litest_button_click(device, btn[i][3], false);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+       }
+}
+END_TEST
+
+START_TEST(middlebutton_nostart_while_down)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       enum libinput_config_status status;
+       unsigned int i;
+       const int btn[][4] = {
+               { BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT },
+               { BTN_LEFT, BTN_RIGHT, BTN_RIGHT, BTN_LEFT },
+               { BTN_RIGHT, BTN_LEFT, BTN_LEFT, BTN_RIGHT },
+               { BTN_RIGHT, BTN_LEFT, BTN_RIGHT, BTN_LEFT },
+       };
+
+       if (!libinput_device_pointer_has_button(device->libinput_device,
+                                               BTN_MIDDLE))
+               return;
+
+       disable_button_scrolling(device);
+
+       status = libinput_device_config_middle_emulation_set_enabled(
+                                           device->libinput_device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       litest_button_click(device, BTN_MIDDLE, true);
+       litest_drain_events(li);
+
+       for (i = 0; i < ARRAY_LENGTH(btn); i++) {
+               litest_button_click(device, btn[i][0], true);
+               litest_assert_button_event(li,
+                                          btn[i][0],
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_button_click(device, btn[i][1], true);
+               litest_assert_button_event(li,
+                                          btn[i][1],
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+
+               litest_assert_empty_queue(li);
+
+               litest_button_click(device, btn[i][2], false);
+               litest_assert_button_event(li,
+                                          btn[i][2],
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_button_click(device, btn[i][3], false);
+               litest_assert_button_event(li,
+                                          btn[i][3],
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+       }
+
+       litest_button_click(device, BTN_MIDDLE, false);
+       litest_drain_events(li);
+}
+END_TEST
+
+START_TEST(middlebutton_timeout)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       enum libinput_config_status status;
+       unsigned int button;
+
+       disable_button_scrolling(device);
+
+       status = libinput_device_config_middle_emulation_set_enabled(
+                                           device->libinput_device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       for (button = BTN_LEFT; button <= BTN_RIGHT; button++) {
+               litest_drain_events(li);
+               litest_button_click(device, button, true);
+               litest_assert_empty_queue(li);
+               litest_timeout_middlebutton();
+
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+
+               litest_button_click(device, button, false);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+       }
+}
+END_TEST
+
+START_TEST(middlebutton_doubleclick)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       enum libinput_config_status status;
+       unsigned int i;
+       const int btn[][4] = {
+               { BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT },
+               { BTN_LEFT, BTN_RIGHT, BTN_RIGHT, BTN_LEFT },
+               { BTN_RIGHT, BTN_LEFT, BTN_LEFT, BTN_RIGHT },
+               { BTN_RIGHT, BTN_LEFT, BTN_RIGHT, BTN_LEFT },
+       };
+
+       disable_button_scrolling(device);
+
+       status = libinput_device_config_middle_emulation_set_enabled(
+                                   device->libinput_device,
+                                   LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       litest_drain_events(li);
+
+       for (i = 0; i < ARRAY_LENGTH(btn); i++) {
+               litest_button_click(device, btn[i][0], true);
+               litest_button_click(device, btn[i][1], true);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_empty_queue(li);
+
+               litest_button_click(device, btn[i][2], false);
+               litest_button_click(device, btn[i][2], true);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_button_click(device, btn[i][3], false);
+
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+       }
+}
+END_TEST
+
+START_TEST(middlebutton_middleclick)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       enum libinput_config_status status;
+       unsigned int button;
+
+       disable_button_scrolling(device);
+
+       if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE))
+               return;
+
+       status = libinput_device_config_middle_emulation_set_enabled(
+                                           device->libinput_device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       /* one button down, then middle -> release buttons */
+       for (button = BTN_LEFT; button <= BTN_RIGHT; button++) {
+               /* release button before middle */
+               litest_drain_events(li);
+               litest_button_click(device, button, true);
+               litest_button_click(device, BTN_MIDDLE, true);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_empty_queue(li);
+               litest_button_click(device, button, false);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_button_click(device, BTN_MIDDLE, false);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+
+               /* release middle before button */
+               litest_button_click(device, button, true);
+               litest_button_click(device, BTN_MIDDLE, true);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_empty_queue(li);
+               litest_button_click(device, BTN_MIDDLE, false);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_button_click(device, button, false);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+       }
+}
+END_TEST
+
+START_TEST(middlebutton_middleclick_during)
+{
+       struct litest_device *device = litest_current_device();
+       struct libinput *li = device->libinput;
+       enum libinput_config_status status;
+       unsigned int button;
+
+       disable_button_scrolling(device);
+
+       if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE))
+               return;
+
+       status = libinput_device_config_middle_emulation_set_enabled(
+                                           device->libinput_device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED)
+               return;
+
+       litest_drain_events(li);
+
+       /* trigger emulation, then real middle */
+       for (button = BTN_LEFT; button <= BTN_RIGHT; button++) {
+               litest_button_click(device, BTN_LEFT, true);
+               litest_button_click(device, BTN_RIGHT, true);
+
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+
+               litest_button_click(device, BTN_MIDDLE, true);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+
+               litest_assert_empty_queue(li);
+
+               /* middle still down, release left/right */
+               litest_button_click(device, button, false);
+               litest_assert_empty_queue(li);
+               litest_button_click(device, button, true);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_empty_queue(li);
+
+               /* release both */
+               litest_button_click(device, BTN_LEFT, false);
+               litest_button_click(device, BTN_RIGHT, false);
+               litest_assert_button_event(li,
+                                          button,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+
+               litest_button_click(device, BTN_MIDDLE, false);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+               litest_assert_empty_queue(li);
+       }
+}
+END_TEST
+
+START_TEST(middlebutton_default_enabled)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       int available;
+       enum libinput_config_middle_emulation_state deflt, state;
+
+       available = libinput_device_config_middle_emulation_is_available(device);
+       ck_assert(available);
+
+       if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE))
+               deflt = LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
+       else
+               deflt = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED;
+
+       state = libinput_device_config_middle_emulation_get_enabled(device);
+       ck_assert_int_eq(state, deflt);
+
+       state = libinput_device_config_middle_emulation_get_default_enabled(
+                                           device);
+       ck_assert_int_eq(state, deflt);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device, 3);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(middlebutton_default_clickpad)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       enum libinput_config_middle_emulation_state state;
+       int available;
+
+       available = libinput_device_config_middle_emulation_is_available(device);
+       ck_assert(available);
+
+       state = libinput_device_config_middle_emulation_get_enabled(device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       state = libinput_device_config_middle_emulation_get_default_enabled(
+                                           device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                           LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device, 3);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(middlebutton_default_touchpad)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_middle_emulation_state state;
+       int available;
+       const char *name = libinput_device_get_name(dev->libinput_device);
+
+       if (streq(name, "litest AlpsPS/2 ALPS GlidePoint") ||
+           streq(name, "litest AlpsPS/2 ALPS DualPoint TouchPad"))
+           return;
+
+       available = libinput_device_config_middle_emulation_is_available(device);
+       ck_assert(!available);
+
+       if (libevdev_has_event_code(dev->evdev, EV_KEY, BTN_MIDDLE))
+               return;
+
+       state = libinput_device_config_middle_emulation_get_enabled(
+                                           device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       state = libinput_device_config_middle_emulation_get_default_enabled(
+                                           device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+}
+END_TEST
+
+START_TEST(middlebutton_default_alps)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_middle_emulation_state state;
+       int available;
+
+       available = libinput_device_config_middle_emulation_is_available(device);
+       ck_assert(available);
+
+       state = libinput_device_config_middle_emulation_get_enabled(
+                                           device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       state = libinput_device_config_middle_emulation_get_default_enabled(
+                                           device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+}
+END_TEST
+
+START_TEST(middlebutton_default_disabled)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_middle_emulation_state state;
+       enum libinput_config_status status;
+       int available;
+
+       available = libinput_device_config_middle_emulation_is_available(device);
+       ck_assert(!available);
+       state = libinput_device_config_middle_emulation_get_enabled(device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       state = libinput_device_config_middle_emulation_get_default_enabled(
+                                                                   device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                    LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                                    LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+}
+END_TEST
+
+START_TEST(pointer_time_usec)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event_pointer *ptrev;
+       struct libinput_event *event;
+       uint64_t time_usec;
+
+       litest_drain_events(dev->libinput);
+
+       litest_event(dev, EV_REL, REL_X, 1);
+       litest_event(dev, EV_REL, REL_Y, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_wait_for_event(li);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_motion_event(event);
+
+       time_usec = libinput_event_pointer_get_time_usec(ptrev);
+       ck_assert_int_eq(libinput_event_pointer_get_time(ptrev),
+                        (uint32_t) (time_usec / 1000));
+
+       libinput_event_destroy(event);
+       litest_drain_events(dev->libinput);
+}
+END_TEST
+
+void
+litest_setup_tests_pointer(void)
+{
+       struct range axis_range = {ABS_X, ABS_Y + 1};
+       struct range compass = {0, 7}; /* cardinal directions */
 
        litest_add("pointer:motion", pointer_motion_relative, LITEST_RELATIVE, LITEST_ANY);
+       litest_add_for_device("pointer:motion", pointer_motion_relative_zero, LITEST_MOUSE);
+       litest_add_ranged("pointer:motion", pointer_motion_relative_min_decel, LITEST_RELATIVE, LITEST_ANY, &compass);
        litest_add("pointer:motion", pointer_motion_absolute, LITEST_ABSOLUTE, LITEST_ANY);
        litest_add("pointer:motion", pointer_motion_unaccel, LITEST_RELATIVE, LITEST_ANY);
        litest_add("pointer:button", pointer_button, LITEST_BUTTON, LITEST_CLICKPAD);
-       litest_add_no_device("pointer:button_auto_release", pointer_button_auto_release);
-       litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_ANY);
+       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_TABLET);
        litest_add("pointer:scroll", pointer_scroll_button, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
-       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_no_device("pointer:seat button count", pointer_seat_button_count);
+       litest_add("pointer:scroll", pointer_scroll_button_no_event_before_timeout, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY);
+       litest_add("pointer:scroll", pointer_scroll_button_middle_emulation, 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_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_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);
@@ -836,7 +1758,27 @@ int main (int argc, char **argv) {
 
        litest_add("pointer:accel", pointer_accel_defaults, LITEST_RELATIVE, LITEST_ANY);
        litest_add("pointer:accel", pointer_accel_invalid, LITEST_RELATIVE, LITEST_ANY);
-       litest_add("pointer:accel", pointer_accel_defaults_absolute, LITEST_ABSOLUTE, LITEST_ANY);
-
-       return litest_run(argc, argv);
+       litest_add("pointer:accel", pointer_accel_defaults_absolute, LITEST_ABSOLUTE, LITEST_RELATIVE);
+       litest_add("pointer:accel", pointer_accel_defaults_absolute_relative, LITEST_ABSOLUTE|LITEST_RELATIVE, LITEST_ANY);
+       litest_add("pointer:accel", pointer_accel_direction_change, LITEST_RELATIVE, LITEST_ANY);
+       litest_add("pointer:accel", pointer_accel_profile_defaults, LITEST_RELATIVE, LITEST_TOUCHPAD);
+       litest_add("pointer:accel", pointer_accel_profile_defaults_noprofile, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("pointer:accel", pointer_accel_profile_invalid, LITEST_RELATIVE, LITEST_ANY);
+       litest_add("pointer:accel", pointer_accel_profile_flat_motion_relative, LITEST_RELATIVE, LITEST_TOUCHPAD);
+
+       litest_add("pointer:middlebutton", middlebutton, LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_nostart_while_down, LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_timeout, LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_doubleclick, LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_middleclick, LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_middleclick_during, LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_default_enabled, LITEST_BUTTON, LITEST_TOUCHPAD|LITEST_POINTINGSTICK);
+       litest_add("pointer:middlebutton", middlebutton_default_clickpad, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("pointer:middlebutton", middlebutton_default_touchpad, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("pointer:middlebutton", middlebutton_default_disabled, LITEST_ANY, LITEST_BUTTON);
+       litest_add_for_device("pointer:middlebutton", middlebutton_default_alps, LITEST_ALPS_SEMI_MT);
+
+       litest_add_ranged("pointer:state", pointer_absolute_initial_state, LITEST_ABSOLUTE, LITEST_ANY, &axis_range);
+
+       litest_add("pointer:time", pointer_time_usec, LITEST_RELATIVE, LITEST_ANY);
 }
diff --git a/test/tablet.c b/test/tablet.c
new file mode 100644 (file)
index 0000000..212ef0c
--- /dev/null
@@ -0,0 +1,4191 @@
+/*
+ * 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
+
+static inline double
+rotate_event(struct litest_device *dev, int angle_degrees)
+{
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_tablet_tool *tev;
+       const struct input_absinfo *abs;
+       double a = (angle_degrees - 90 - 175)/180.0 * M_PI;
+       double val;
+       int x, y;
+       int tilt_center_x, tilt_center_y;
+
+       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;
+
+       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);
+
+       libinput_event_destroy(event);
+       litest_assert_empty_queue(li);
+
+       return val;
+}
+
+START_TEST(left_handed_mouse_rotation)
+{
+#if HAVE_LIBWACOM
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
+       int angle;
+       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;
+
+       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_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 = 185; angle < 540; angle += 5) {
+               int expected_angle = angle - 180;
+
+               val = rotate_event(dev, angle % 360);
+
+               /* 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, expected_angle + 5);
+
+               old_val = val;
+       }
+#endif
+}
+END_TEST
+
+START_TEST(left_handed_artpen_rotation)
+{
+#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;
+       const struct input_absinfo *abs;
+       enum libinput_config_status status;
+       double val;
+       double scale;
+       int angle;
+
+       if (!libevdev_has_event_code(dev->evdev,
+                                   EV_ABS,
+                                   ABS_Z))
+               return;
+
+       status = libinput_device_config_left_handed_set(dev->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       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 = 188; angle < 540; angle += 8) {
+               int a = angle * scale + abs->minimum;
+               int expected_angle = angle - 180;
+
+               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), (expected_angle + 90) % 360);
+
+               libinput_event_destroy(event);
+               litest_assert_empty_queue(li);
+
+       }
+#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(motion_outside_bounds)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_tablet_tool *tablet_event;
+       double val;
+
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+
+       litest_tablet_proximity_in(dev, 50, 50, axes);
+       litest_drain_events(li);
+
+       /* On the 24HD x/y of 0 is outside the limit */
+       litest_event(dev, EV_ABS, ABS_X, 0);
+       litest_event(dev, EV_ABS, ABS_Y, 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_AXIS);
+       val = libinput_event_tablet_tool_get_x(tablet_event);
+       ck_assert_double_lt(val, 0.0);
+       val = libinput_event_tablet_tool_get_y(tablet_event);
+       ck_assert_double_gt(val, 0.0);
+
+       val = libinput_event_tablet_tool_get_x_transformed(tablet_event, 100);
+       ck_assert_double_lt(val, 0.0);
+
+       libinput_event_destroy(event);
+
+       /* On the 24HD x/y of 0 is outside the limit */
+       litest_event(dev, EV_ABS, ABS_X, 1000);
+       litest_event(dev, EV_ABS, ABS_Y, 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_AXIS);
+       val = libinput_event_tablet_tool_get_x(tablet_event);
+       ck_assert_double_gt(val, 0.0);
+       val = libinput_event_tablet_tool_get_y(tablet_event);
+       ck_assert_double_lt(val, 0.0);
+
+       val = libinput_event_tablet_tool_get_y_transformed(tablet_event, 100);
+       ck_assert_double_lt(val, 0.0);
+
+       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));
+       libinput_event_destroy(event);
+
+       ck_assert(libinput_tablet_tool_unref(tool) == NULL);
+}
+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(li, LITEST_WACOM_INTUOS);
+               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_delayed_serial)
+{
+       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;
+       unsigned int serial;
+
+       litest_drain_events(li);
+
+       litest_event(dev, EV_ABS, ABS_X, 4500);
+       litest_event(dev, EV_ABS, ABS_Y, 2000);
+       litest_event(dev, EV_MSC, MSC_SERIAL, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_PEN, 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_PROXIMITY);
+       tool = libinput_event_tablet_tool_get_tool(tev);
+       serial = libinput_tablet_tool_get_serial(tool);
+       ck_assert_int_eq(serial, 0);
+       libinput_event_destroy(event);
+
+       for (int x = 4500; x < 8000; x += 1000) {
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, 2000);
+               litest_event(dev, EV_MSC, MSC_SERIAL, 0);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+       litest_drain_events(li);
+
+       /* Now send the serial */
+       litest_event(dev, EV_ABS, ABS_X, 4500);
+       litest_event(dev, EV_ABS, ABS_Y, 2000);
+       litest_event(dev, EV_MSC, MSC_SERIAL, 1234566);
+       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);
+       tool = libinput_event_tablet_tool_get_tool(tev);
+       serial = libinput_tablet_tool_get_serial(tool);
+       ck_assert_int_eq(serial, 0);
+       libinput_event_destroy(event);
+
+       for (int x = 4500; x < 8000; x += 500) {
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, 2000);
+               litest_event(dev, EV_MSC, MSC_SERIAL, 1234566);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+
+       event = libinput_get_event(li);
+       do {
+               tev = litest_is_tablet_event(event,
+                                            LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+               tool = libinput_event_tablet_tool_get_tool(tev);
+               serial = libinput_tablet_tool_get_serial(tool);
+               ck_assert_int_eq(serial, 0);
+               libinput_event_destroy(event);
+               event = libinput_get_event(li);
+       } while (event != NULL);
+
+       /* Quirk: tool out event is a serial of 0 */
+       litest_event(dev, EV_ABS, ABS_X, 4500);
+       litest_event(dev, EV_ABS, ABS_Y, 2000);
+       litest_event(dev, EV_MSC, MSC_SERIAL, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_PEN, 0);
+       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);
+       serial = libinput_tablet_tool_get_serial(tool);
+       ck_assert_int_eq(serial, 0);
+       libinput_event_destroy(event);
+}
+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 libinput_event_tablet_tool *tev;
+       struct libinput_tablet_tool *tool;
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { ABS_TILT_X, 0 },
+               { ABS_TILT_Y, 0 },
+               { -1, -1 }
+       };
+       const char *devnode;
+       unsigned int serial;
+
+       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_assert_empty_queue(li);
+
+       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_PROXIMITY);
+       tool = libinput_event_tablet_tool_get_tool(tev);
+       serial = libinput_tablet_tool_get_serial(tool);
+       libinput_event_destroy(event);
+
+       litest_tablet_motion(dev, 30, 40, axes);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       tev = litest_is_tablet_event(event,
+                                    LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+       tool = libinput_event_tablet_tool_get_tool(tev);
+       ck_assert_int_eq(serial,
+                        libinput_tablet_tool_get_serial(tool));
+       libinput_event_destroy(event);
+
+       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;
+       int angle;
+       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;
+
+       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) {
+               val = rotate_event(dev, angle);
+
+               /* 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;
+       }
+}
+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_slider)
+{
+       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;
+       double expected;
+       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);
+
+               expected = ((v - abs->minimum)/scale) * 2 - 1;
+               ck_assert_double_eq(val, expected);
+               ck_assert_double_ge(val, -1.0);
+               ck_assert_double_le(val, 1.0);
+               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 }
+       };
+       uint64_t time_usec;
+
+       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);
+       time_usec = libinput_event_tablet_tool_get_time_usec(tev);
+       ck_assert_int_eq(libinput_event_tablet_tool_get_time(tev),
+                        (uint32_t) (time_usec / 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, 0 },
+               { ABS_PRESSURE, 10 },
+               { -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);
+
+       event = libinput_get_event(li);
+       tablet_event = litest_is_tablet_event(event,
+                                             LIBINPUT_EVENT_TABLET_TOOL_TIP);
+       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);
+
+       event = libinput_get_event(li);
+       tablet_event = litest_is_tablet_event(event,
+                                             LIBINPUT_EVENT_TABLET_TOOL_TIP);
+       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(x > 99.0);
+       ck_assert(x < 100.0);
+       ck_assert(y > 49.0);
+       ck_assert(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_range)
+{
+       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, 10 },
+               { -1, -1 },
+       };
+       int pressure;
+       double p;
+
+       litest_tablet_proximity_in(dev, 5, 100, axes);
+       litest_drain_events(li);
+       libinput_dispatch(li);
+
+       for (pressure = 1; pressure <= 100; pressure += 10) {
+               litest_axis_set_value(axes, ABS_PRESSURE, pressure);
+               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);
+               p = libinput_event_tablet_tool_get_pressure(tev);
+               ck_assert_double_ge(p, 0.0);
+               ck_assert_double_le(p, 1.0);
+               libinput_event_destroy(event);
+       }
+}
+END_TEST
+
+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(tablet_distance_range)
+{
+       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, 0 },
+               { -1, -1 },
+       };
+       int distance;
+       double dist;
+
+       litest_tablet_proximity_in(dev, 5, 100, axes);
+       litest_drain_events(li);
+       libinput_dispatch(li);
+
+       for (distance = 0; distance <= 100; distance += 10) {
+               litest_axis_set_value(axes, ABS_DISTANCE, distance);
+               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);
+               dist = libinput_event_tablet_tool_get_distance(tev);
+               ck_assert_double_ge(dist, 0.0);
+               ck_assert_double_le(dist, 1.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, 10 },
+               { 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 [-64, 64] tilt range, so
+        * we expect 51 degrees ± rounding errors */
+       tx = libinput_event_tablet_tool_get_tilt_x(tev);
+       ck_assert_double_le(tx, -50);
+       ck_assert_double_ge(tx, -52);
+
+       ty = libinput_event_tablet_tool_get_tilt_y(tev);
+       ck_assert_double_ge(ty, -65);
+       ck_assert_double_lt(ty, -63);
+
+       libinput_event_destroy(event);
+
+       expected_tx = -64.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_ge(tx, expected_tx - 2);
+               ck_assert_double_le(tx, expected_tx + 2);
+
+               ty = libinput_event_tablet_tool_get_tilt_y(tev);
+               ck_assert_double_ge(ty, -65);
+               ck_assert_double_lt(ty, -63);
+
+               libinput_event_destroy(event);
+
+               expected_tx = tx + 6.04;
+       }
+
+       /* the last event must reach the max */
+       ck_assert_double_ge(tx, 63.0);
+       ck_assert_double_le(tx, 64.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, 10 },
+               { -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 [-64, 64] tilt range, so
+        * we expect 50 degrees ± rounding errors */
+       ty = libinput_event_tablet_tool_get_tilt_y(tev);
+       ck_assert_double_le(ty, -50);
+       ck_assert_double_ge(ty, -52);
+
+       tx = libinput_event_tablet_tool_get_tilt_x(tev);
+       ck_assert_double_ge(tx, -65);
+       ck_assert_double_lt(tx, -63);
+
+       libinput_event_destroy(event);
+
+       expected_ty = -64;
+
+       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_ge(ty, expected_ty - 2);
+               ck_assert_double_le(ty, expected_ty + 2);
+
+               tx = libinput_event_tablet_tool_get_tilt_x(tev);
+               ck_assert_double_ge(tx, -65);
+               ck_assert_double_lt(tx, -63);
+
+               libinput_event_destroy(event);
+
+               expected_ty = ty + 6;
+       }
+
+       /* the last event must reach the max */
+       ck_assert_double_ge(ty, 63.0);
+       ck_assert_double_le(tx, 64.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
+
+static void
+touch_arbitration(struct litest_device *dev,
+                 enum litest_device_type other,
+                 bool is_touchpad)
+{
+       struct litest_device *finger;
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+
+       finger = litest_add_device(li, other);
+       litest_drain_events(li);
+
+       litest_tablet_proximity_in(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 20, 40, axes);
+       litest_drain_events(li);
+
+       litest_touch_down(finger, 0, 30, 30);
+       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1);
+       litest_assert_empty_queue(li);
+
+       litest_tablet_motion(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 20, 40, axes);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+       litest_tablet_proximity_out(dev);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
+
+       /* finger still down */
+       litest_touch_move_to(finger, 0, 80, 80, 30, 30, 10, 1);
+       litest_touch_up(finger, 0);
+       litest_assert_empty_queue(li);
+
+       /* lift finger, expect expect events */
+       litest_touch_down(finger, 0, 30, 30);
+       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(finger, 0);
+       libinput_dispatch(li);
+
+       if (is_touchpad)
+               litest_assert_only_typed_events(li,
+                                               LIBINPUT_EVENT_POINTER_MOTION);
+       else
+               litest_assert_touch_sequence(li);
+
+       litest_delete_device(finger);
+}
+
+START_TEST(intuos_touch_arbitration)
+{
+       touch_arbitration(litest_current_device(), LITEST_WACOM_FINGER, true);
+}
+END_TEST
+
+START_TEST(cintiq_touch_arbitration)
+{
+       touch_arbitration(litest_current_device(),
+                         LITEST_WACOM_CINTIQ_13HDT_FINGER,
+                         false);
+}
+END_TEST
+
+static void
+touch_arbitration_stop_touch(struct litest_device *dev,
+                            enum litest_device_type other,
+                            bool is_touchpad)
+{
+       struct litest_device *finger;
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+
+       finger = litest_add_device(li, other);
+       litest_touch_down(finger, 0, 30, 30);
+       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1);
+
+       litest_tablet_proximity_in(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 20, 40, axes);
+       litest_drain_events(li);
+
+       litest_touch_move_to(finger, 0, 80, 80, 30, 30, 10, 1);
+       /* start another finger to make sure that one doesn't send events
+          either */
+       litest_touch_down(finger, 1, 30, 30);
+       litest_touch_move_to(finger, 1, 30, 30, 80, 80, 10, 1);
+       litest_assert_empty_queue(li);
+
+       litest_tablet_motion(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 20, 40, axes);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+       litest_tablet_proximity_out(dev);
+       litest_drain_events(li);
+
+       /* Finger needs to be lifted for events to happen*/
+       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1);
+       litest_assert_empty_queue(li);
+       litest_touch_move_to(finger, 1, 80, 80, 30, 30, 10, 1);
+       litest_assert_empty_queue(li);
+       litest_touch_up(finger, 0);
+       litest_touch_move_to(finger, 1, 30, 30, 80, 80, 10, 1);
+       litest_assert_empty_queue(li);
+       litest_touch_up(finger, 1);
+       litest_touch_down(finger, 0, 30, 30);
+       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(finger, 0);
+       libinput_dispatch(li);
+
+       if (is_touchpad)
+               litest_assert_only_typed_events(li,
+                                               LIBINPUT_EVENT_POINTER_MOTION);
+       else
+               litest_assert_touch_sequence(li);
+
+       litest_delete_device(finger);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+}
+
+START_TEST(intuos_touch_arbitration_stop_touch)
+{
+       touch_arbitration_stop_touch(litest_current_device(),
+                                    LITEST_WACOM_FINGER,
+                                    true);
+}
+END_TEST
+
+START_TEST(cintiq_touch_arbitration_stop_touch)
+{
+       touch_arbitration_stop_touch(litest_current_device(),
+                                    LITEST_WACOM_CINTIQ_13HDT_FINGER,
+                                    false);
+}
+END_TEST
+
+static void
+touch_arbitration_suspend_touch(struct litest_device *dev,
+                               enum litest_device_type other,
+                               bool is_touchpad)
+{
+       struct litest_device *tablet;
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+
+       tablet = litest_add_device(li, other);
+
+       /* we can't force a device suspend, but we can at least make sure
+          the device doesn't send events */
+       status = libinput_device_config_send_events_set_mode(
+                            dev->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_drain_events(li);
+
+       litest_tablet_proximity_in(tablet, 10, 10, axes);
+       litest_tablet_motion(tablet, 10, 10, axes);
+       litest_tablet_motion(tablet, 20, 40, axes);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 30);
+       litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       /* Remove tablet device to unpair, still disabled though */
+       litest_delete_device(tablet);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+
+       litest_touch_down(dev, 0, 30, 30);
+       litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       /* Touch device is still disabled */
+       litest_touch_down(dev, 0, 30, 30);
+       litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       status = libinput_device_config_send_events_set_mode(
+                            dev->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_touch_down(dev, 0, 30, 30);
+       litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       if (is_touchpad)
+               litest_assert_only_typed_events(li,
+                                               LIBINPUT_EVENT_POINTER_MOTION);
+       else
+               litest_assert_touch_sequence(li);
+}
+
+START_TEST(intuos_touch_arbitration_suspend_touch_device)
+{
+       touch_arbitration_suspend_touch(litest_current_device(),
+                                       LITEST_WACOM_INTUOS,
+                                       true);
+}
+END_TEST
+
+START_TEST(cintiq_touch_arbitration_suspend_touch_device)
+{
+       touch_arbitration_suspend_touch(litest_current_device(),
+                                       LITEST_WACOM_CINTIQ_13HDT_PEN,
+                                       false);
+}
+END_TEST
+
+static void
+touch_arbitration_remove_touch(struct litest_device *dev,
+                              enum litest_device_type other,
+                              bool is_touchpad)
+{
+       struct litest_device *finger;
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+
+       finger = litest_add_device(li, other);
+       litest_touch_down(finger, 0, 30, 30);
+       litest_touch_move_to(finger, 0, 30, 30, 80, 80, 10, 1);
+
+       litest_tablet_proximity_in(dev, 10, 10, axes);
+       litest_drain_events(li);
+
+       litest_delete_device(finger);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+       litest_assert_empty_queue(li);
+
+       litest_tablet_motion(dev, 10, 10, axes);
+       litest_tablet_motion(dev, 20, 40, axes);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+}
+
+START_TEST(intuos_touch_arbitration_remove_touch)
+{
+       touch_arbitration_remove_touch(litest_current_device(),
+                                      LITEST_WACOM_INTUOS,
+                                      true);
+}
+END_TEST
+
+START_TEST(cintiq_touch_arbitration_remove_touch)
+{
+       touch_arbitration_remove_touch(litest_current_device(),
+                                      LITEST_WACOM_CINTIQ_13HDT_FINGER,
+                                      false);
+}
+END_TEST
+
+static void
+touch_arbitration_remove_tablet(struct litest_device *dev,
+                               enum litest_device_type other,
+                               bool is_touchpad)
+{
+       struct litest_device *tablet;
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_DISTANCE, 10 },
+               { ABS_PRESSURE, 0 },
+               { -1, -1 }
+       };
+
+       tablet = litest_add_device(li, other);
+       libinput_dispatch(li);
+       litest_tablet_proximity_in(tablet, 10, 10, axes);
+       litest_tablet_motion(tablet, 10, 10, axes);
+       litest_tablet_motion(tablet, 20, 40, axes);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 30);
+       litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1);
+       litest_assert_empty_queue(li);
+
+       litest_delete_device(tablet);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+
+       /* Touch is still down, don't enable */
+       litest_touch_move_to(dev, 0, 80, 80, 30, 30, 10, 1);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 30, 30);
+       litest_touch_move_to(dev, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       if (is_touchpad)
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       else
+               litest_assert_touch_sequence(li);
+}
+
+START_TEST(intuos_touch_arbitration_remove_tablet)
+{
+       touch_arbitration_remove_tablet(litest_current_device(),
+                                       LITEST_WACOM_INTUOS,
+                                       true);
+}
+END_TEST
+
+START_TEST(cintiq_touch_arbitration_remove_tablet)
+{
+       touch_arbitration_remove_tablet(litest_current_device(),
+                                       LITEST_WACOM_CINTIQ_13HDT_PEN,
+                                       false);
+}
+END_TEST
+
+void
+litest_setup_tests_tablet(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_for_device("tablet:tool_serial", tool_delayed_serial, LITEST_WACOM_HID4800_PEN);
+       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_for_device("tablet:motion", motion_outside_bounds, LITEST_WACOM_CINTIQ_24HD);
+       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", left_handed_mouse_rotation, LITEST_WACOM_INTUOS);
+       litest_add_for_device("tablet:left_handed", left_handed_artpen_rotation, 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_slider, 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_range, LITEST_WACOM_INTUOS);
+       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_for_device("tablet:distance", tablet_distance_range, 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);
+
+       litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration, LITEST_WACOM_INTUOS);
+       litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_stop_touch, LITEST_WACOM_INTUOS);
+       litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_suspend_touch_device, LITEST_WACOM_FINGER);
+       litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_remove_touch, LITEST_WACOM_INTUOS);
+       litest_add_for_device("tablet:touch-arbitration", intuos_touch_arbitration_remove_tablet, LITEST_WACOM_FINGER);
+
+       litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration, LITEST_WACOM_CINTIQ_13HDT_PEN);
+       litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_stop_touch, LITEST_WACOM_CINTIQ_13HDT_PEN);
+       litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_suspend_touch_device, LITEST_WACOM_CINTIQ_13HDT_FINGER);
+       litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_remove_touch, LITEST_WACOM_CINTIQ_13HDT_PEN);
+       litest_add_for_device("tablet:touch-arbitration", cintiq_touch_arbitration_remove_tablet, LITEST_WACOM_CINTIQ_13HDT_FINGER);
+}
index 29890a41a6c32155d5abf3a3f54e99993d640455..ac2f29f48092aef9b68d81812eb2730542e73eea 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -182,6 +183,9 @@ START_TEST(touch_double_touch_down_up)
        dev = litest_current_device();
        libinput = dev->libinput;
 
+       /* note: this test is a false negative, libevdev will filter
+        * tracking IDs re-used in the same slot. */
+
        litest_touch_down(dev, 0, 0, 0);
        litest_touch_down(dev, 0, 0, 0);
        litest_touch_up(dev, 0);
@@ -241,9 +245,7 @@ START_TEST(touch_calibration_scale)
 
                litest_wait_for_event(li);
                ev = libinput_get_event(li);
-               ck_assert_int_eq(libinput_event_get_type(ev),
-                                LIBINPUT_EVENT_TOUCH_DOWN);
-               tev = libinput_event_get_touch_event(ev);
+               tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN);
 
                x = libinput_event_touch_get_x_transformed(tev, width);
                y = libinput_event_touch_get_y_transformed(tev, height);
@@ -312,9 +314,7 @@ START_TEST(touch_calibration_rotation)
                litest_touch_up(dev, 0);
                litest_wait_for_event(li);
                ev = libinput_get_event(li);
-               ck_assert_int_eq(libinput_event_get_type(ev),
-                                LIBINPUT_EVENT_TOUCH_DOWN);
-               tev = libinput_event_get_touch_event(ev);
+               tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN);
 
                x = libinput_event_touch_get_x_transformed(tev, width);
                y = libinput_event_touch_get_y_transformed(tev, height);
@@ -343,7 +343,6 @@ START_TEST(touch_calibration_rotation)
                }
 #undef almost_equal
 
-
                libinput_event_destroy(ev);
                litest_drain_events(li);
        }
@@ -379,9 +378,7 @@ START_TEST(touch_calibration_translation)
 
                litest_wait_for_event(li);
                ev = libinput_get_event(li);
-               ck_assert_int_eq(libinput_event_get_type(ev),
-                                LIBINPUT_EVENT_TOUCH_DOWN);
-               tev = libinput_event_get_touch_event(ev);
+               tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN);
 
                x = libinput_event_touch_get_x_transformed(tev, width);
                y = libinput_event_touch_get_y_transformed(tev, height);
@@ -440,6 +437,8 @@ START_TEST(fake_mt_exists)
         * have different capabilities */
        ck_assert(libinput_device_has_capability(device,
                                                 LIBINPUT_DEVICE_CAP_POINTER));
+
+       libinput_event_destroy(event);
 }
 END_TEST
 
@@ -461,13 +460,270 @@ START_TEST(fake_mt_no_touch_events)
        litest_touch_up(dev, 0);
        litest_touch_up(dev, 1);
 
-       litest_assert_empty_queue(li);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
+}
+END_TEST
+
+START_TEST(touch_protocol_a_init)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_device *device = dev->libinput_device;
+
+       ck_assert_int_ne(libinput_next_event_type(li),
+                        LIBINPUT_EVENT_NONE);
+
+       ck_assert(libinput_device_has_capability(device,
+                                                LIBINPUT_DEVICE_CAP_TOUCH));
+}
+END_TEST
+
+START_TEST(touch_protocol_a_touch)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_touch *tev;
+       double x, y, oldx, oldy;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 5, 95);
+
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_DOWN, -1);
+
+       ev = libinput_get_event(li);
+       tev = litest_is_touch_event(ev, LIBINPUT_EVENT_TOUCH_DOWN);
+
+       oldx = libinput_event_touch_get_x(tev);
+       oldy = libinput_event_touch_get_y(tev);
+
+       libinput_event_destroy(ev);
+
+       litest_touch_move_to(dev, 0, 10, 90, 90, 10, 20, 1);
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_MOTION, -1);
+
+       while ((ev = libinput_get_event(li))) {
+               if (libinput_event_get_type(ev) ==
+                   LIBINPUT_EVENT_TOUCH_FRAME) {
+                       libinput_event_destroy(ev);
+                       continue;
+               }
+               ck_assert_int_eq(libinput_event_get_type(ev),
+                                LIBINPUT_EVENT_TOUCH_MOTION);
+
+               tev = libinput_event_get_touch_event(ev);
+               x = libinput_event_touch_get_x(tev);
+               y = libinput_event_touch_get_y(tev);
+
+               ck_assert_int_gt(x, oldx);
+               ck_assert_int_lt(y, oldy);
+
+               oldx = x;
+               oldy = y;
+
+               libinput_event_destroy(ev);
+       }
+
+       litest_touch_up(dev, 0);
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_UP, -1);
 }
 END_TEST
 
-int
-main(int argc, char **argv)
+START_TEST(touch_protocol_a_2fg_touch)
 {
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_touch *tev;
+       int pos;
+
+       litest_drain_events(li);
+
+       litest_push_event_frame(dev);
+       litest_touch_down(dev, 0, 5, 95);
+       litest_touch_down(dev, 0, 95, 5);
+       litest_pop_event_frame(dev);
+
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_DOWN, -1);
+
+       ev = libinput_get_event(li);
+       libinput_event_destroy(ev);
+
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_DOWN, -1);
+
+       ev = libinput_get_event(li);
+       libinput_event_destroy(ev);
+
+       for (pos = 10; pos < 100; pos += 10) {
+               litest_push_event_frame(dev);
+               litest_touch_move_to(dev, 0, pos, 100 - pos, pos, 100 - pos, 1, 1);
+               litest_touch_move_to(dev, 0, 100 - pos, pos, 100 - pos, pos, 1, 1);
+               litest_pop_event_frame(dev);
+               litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_MOTION, -1);
+               ev = libinput_get_event(li);
+               tev = libinput_event_get_touch_event(ev);
+               ck_assert_int_eq(libinput_event_touch_get_slot(tev),
+                               0);
+               libinput_event_destroy(ev);
+
+               litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_MOTION, -1);
+               ev = libinput_get_event(li);
+               tev = libinput_event_get_touch_event(ev);
+               ck_assert_int_eq(libinput_event_touch_get_slot(tev),
+                               1);
+               libinput_event_destroy(ev);
+       }
+
+       litest_event(dev, EV_SYN, SYN_MT_REPORT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_UP, -1);
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TOUCH_UP, -1);
+}
+END_TEST
+
+START_TEST(touch_initial_state)
+{
+       struct litest_device *dev;
+       struct libinput *libinput1, *libinput2;
+       struct libinput_event *ev1 = NULL;
+       struct libinput_event *ev2 = NULL;
+       struct libinput_event_touch *t1, *t2;
+       struct libinput_device *device1, *device2;
+       int axis = _i; /* looped test */
+
+       dev = litest_current_device();
+       device1 = dev->libinput_device;
+       libinput_device_config_tap_set_enabled(device1,
+                                              LIBINPUT_CONFIG_TAP_DISABLED);
+
+       libinput1 = dev->libinput;
+       litest_touch_down(dev, 0, 40, 60);
+       litest_touch_up(dev, 0);
+
+       /* device is now on some x/y value */
+       litest_drain_events(libinput1);
+
+       libinput2 = litest_create_context();
+       device2 = libinput_path_add_device(libinput2,
+                                          libevdev_uinput_get_devnode(
+                                                              dev->uinput));
+       libinput_device_config_tap_set_enabled(device2,
+                                              LIBINPUT_CONFIG_TAP_DISABLED);
+       litest_drain_events(libinput2);
+
+       if (axis == ABS_X)
+               litest_touch_down(dev, 0, 40, 70);
+       else
+               litest_touch_down(dev, 0, 70, 60);
+       litest_touch_up(dev, 0);
+
+       litest_wait_for_event(libinput1);
+       litest_wait_for_event(libinput2);
+
+       while (libinput_next_event_type(libinput1)) {
+               ev1 = libinput_get_event(libinput1);
+               ev2 = libinput_get_event(libinput2);
+
+               t1 = litest_is_touch_event(ev1, 0);
+               t2 = litest_is_touch_event(ev2, 0);
+
+               ck_assert_int_eq(libinput_event_get_type(ev1),
+                                libinput_event_get_type(ev2));
+
+               if (libinput_event_get_type(ev1) == LIBINPUT_EVENT_TOUCH_UP ||
+                   libinput_event_get_type(ev1) == LIBINPUT_EVENT_TOUCH_FRAME)
+                       break;
+
+               ck_assert_int_eq(libinput_event_touch_get_x(t1),
+                                libinput_event_touch_get_x(t2));
+               ck_assert_int_eq(libinput_event_touch_get_y(t1),
+                                libinput_event_touch_get_y(t2));
+
+               libinput_event_destroy(ev1);
+               libinput_event_destroy(ev2);
+       }
+
+       libinput_event_destroy(ev1);
+       libinput_event_destroy(ev2);
+
+       libinput_unref(libinput2);
+}
+END_TEST
+
+START_TEST(touch_time_usec)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_touch *tev;
+       uint64_t time_usec;
+
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 10, 10);
+
+       litest_wait_for_event(li);
+
+       event = libinput_get_event(li);
+       tev = litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_DOWN);
+       time_usec = libinput_event_touch_get_time_usec(tev);
+       ck_assert_int_eq(libinput_event_touch_get_time(tev),
+                        (uint32_t) (time_usec / 1000));
+       libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(touch_fuzz)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int i;
+       int x = 700, y = 300;
+
+       litest_drain_events(dev->libinput);
+
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 30);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_DOWN);
+       libinput_event_destroy(event);
+       event = libinput_get_event(li);
+       litest_is_touch_event(event, LIBINPUT_EVENT_TOUCH_FRAME);
+       libinput_event_destroy(event);
+
+       litest_drain_events(li);
+
+       for (i = 0; i < 50; i++) {
+               if (i % 2) {
+                       x++;
+                       y--;
+               } else {
+                       x--;
+                       y++;
+               }
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+               litest_assert_empty_queue(li);
+       }
+}
+END_TEST
+
+void
+litest_setup_tests_touch(void)
+{
+       struct range axes = { ABS_X, ABS_Y + 1};
+
        litest_add("touch:frame", touch_frame_events, LITEST_TOUCH, LITEST_ANY);
        litest_add_no_device("touch:abs-transform", touch_abs_transform);
        litest_add_no_device("touch:many-slots", touch_many_slots);
@@ -484,5 +740,13 @@ main(int argc, char **argv)
        litest_add("touch:fake-mt", fake_mt_exists, LITEST_FAKE_MT, LITEST_ANY);
        litest_add("touch:fake-mt", fake_mt_no_touch_events, LITEST_FAKE_MT, LITEST_ANY);
 
-       return litest_run(argc, argv);
+       litest_add("touch:protocol a", touch_protocol_a_init, LITEST_PROTOCOL_A, LITEST_ANY);
+       litest_add("touch:protocol a", touch_protocol_a_touch, LITEST_PROTOCOL_A, LITEST_ANY);
+       litest_add("touch:protocol a", touch_protocol_a_2fg_touch, LITEST_PROTOCOL_A, LITEST_ANY);
+
+       litest_add_ranged("touch:state", touch_initial_state, LITEST_TOUCH, LITEST_PROTOCOL_A, &axes);
+
+       litest_add("touch:time", touch_time_usec, LITEST_TOUCH, LITEST_TOUCHPAD);
+
+       litest_add_for_device("touch:fuzz", touch_fuzz, LITEST_MULTITOUCH_FUZZ_SCREEN);
 }
diff --git a/test/touchpad-buttons.c b/test/touchpad-buttons.c
new file mode 100644 (file)
index 0000000..2beb637
--- /dev/null
@@ -0,0 +1,1865 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <check.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libinput.h>
+#include <unistd.h>
+
+#include "libinput-util.h"
+#include "litest.h"
+
+START_TEST(touchpad_click_defaults_clickfinger)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       uint32_t methods, method;
+       enum libinput_config_status status;
+
+       /* call this test for apple touchpads */
+
+       methods = libinput_device_config_click_get_methods(device);
+       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+
+       method = libinput_device_config_click_get_method(device);
+       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       method = libinput_device_config_click_get_default_method(device);
+       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+
+       status = libinput_device_config_click_set_method(device,
+                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_click_set_method(device,
+                                                        LIBINPUT_CONFIG_CLICK_METHOD_NONE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+}
+END_TEST
+
+START_TEST(touchpad_click_defaults_btnarea)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       uint32_t methods, method;
+       enum libinput_config_status status;
+
+       /* call this test for non-apple clickpads */
+
+       methods = libinput_device_config_click_get_methods(device);
+       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+
+       method = libinput_device_config_click_get_method(device);
+       ck_assert_int_eq(method,  LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       method = libinput_device_config_click_get_default_method(device);
+       ck_assert_int_eq(method,  LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+
+       status = libinput_device_config_click_set_method(device,
+                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_click_set_method(device,
+                                                        LIBINPUT_CONFIG_CLICK_METHOD_NONE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+}
+END_TEST
+
+START_TEST(touchpad_click_defaults_none)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       uint32_t methods, method;
+       enum libinput_config_status status;
+
+       /* call this test for non-clickpads */
+
+       methods = libinput_device_config_click_get_methods(device);
+       ck_assert_int_eq(methods, 0);
+
+       method = libinput_device_config_click_get_method(device);
+       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_NONE);
+       method = libinput_device_config_click_get_default_method(device);
+       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_NONE);
+
+       status = libinput_device_config_click_set_method(device,
+                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+       status = libinput_device_config_click_set_method(device,
+                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_clickfinger)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_clickfinger_no_touch)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_clickfinger)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_clickfinger)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_num_slots(dev->evdev) < 3)
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 60, 70);
+       litest_touch_down(dev, 2, 70, 70);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_clickfinger_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_num_slots(dev->evdev) >= 3 ||
+           !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP))
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 60, 70);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_4fg_clickfinger)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+
+       if (libevdev_get_num_slots(dev->evdev) < 4)
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 60, 70);
+       litest_touch_down(dev, 2, 70, 70);
+       litest_touch_down(dev, 3, 80, 70);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+       litest_touch_up(dev, 3);
+
+       libinput_dispatch(li);
+
+       litest_wait_for_event(li);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_MIDDLE,
+                              LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(event);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_MIDDLE,
+                              LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_event_destroy(event);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_4fg_clickfinger_btntool_2slots)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+
+       if (libevdev_get_num_slots(dev->evdev) >= 3 ||
+           !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_QUADTAP))
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 60, 70);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_wait_for_event(li);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_MIDDLE,
+                              LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(event);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_MIDDLE,
+                              LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_event_destroy(event);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_4fg_clickfinger_btntool_3slots)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+
+       if (libevdev_get_num_slots(dev->evdev) >= 4 ||
+           libevdev_get_num_slots(dev->evdev) < 3 ||
+           !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_TRIPLETAP))
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 60, 70);
+       litest_touch_down(dev, 2, 70, 70);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+
+       libinput_dispatch(li);
+
+       litest_wait_for_event(li);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_MIDDLE,
+                              LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(event);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_MIDDLE,
+                              LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_event_destroy(event);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_clickfinger_distance)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       double w, h;
+       bool small_touchpad = false;
+       unsigned int expected_button;
+
+       if (libinput_device_get_size(dev->libinput_device, &w, &h) == 0 &&
+           h < 50.0)
+               small_touchpad = true;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 50);
+       litest_touch_down(dev, 1, 10, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 50, 5);
+       litest_touch_down(dev, 1, 50, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       /* if the touchpad is small enough, we expect all fingers to count
+        * for clickfinger */
+       if (small_touchpad)
+               expected_button = BTN_RIGHT;
+       else
+               expected_button = BTN_LEFT;
+
+       litest_assert_button_event(li,
+                                  expected_button,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  expected_button,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_clickfinger_distance)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_num_slots(dev->evdev) < 3)
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 90);
+       litest_touch_down(dev, 1, 10, 15);
+       litest_touch_down(dev, 2, 10, 15);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 2);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_clickfinger_distance_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_num_slots(dev->evdev) > 2)
+               return;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 90);
+       litest_touch_down(dev, 1, 10, 15);
+       libinput_dispatch(li);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_clickfinger_bottom)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       /* this test is run for the T440s touchpad only, makes getting the
+        * mm correct easier */
+
+       libinput_device_config_click_set_method(dev->libinput_device,
+                                               LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       litest_drain_events(li);
+
+       /* one above, one below the magic line, vert spread ca 27mm */
+       litest_touch_down(dev, 0, 40, 60);
+       litest_touch_down(dev, 1, 60, 100);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+
+       /* both below the magic line */
+       litest_touch_down(dev, 0, 40, 100);
+       litest_touch_down(dev, 1, 60, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       /* one above, one below the magic line, vert spread 17mm */
+       litest_touch_down(dev, 0, 50, 75);
+       litest_touch_down(dev, 1, 55, 100);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_clickfinger_to_area_method)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_enable_buttonareas(dev);
+
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       /* use bottom right corner to catch accidental softbutton right */
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+}
+END_TEST
+
+START_TEST(touchpad_clickfinger_to_area_method_while_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_enable_buttonareas(dev);
+
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_enable_clickfinger(dev);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_drain_events(li);
+
+       /* use bottom right corner to catch accidental softbutton right */
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+}
+END_TEST
+
+START_TEST(touchpad_area_to_clickfinger_method)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       /* use bottom right corner to catch accidental softbutton right */
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_enable_buttonareas(dev);
+
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+}
+END_TEST
+
+START_TEST(touchpad_area_to_clickfinger_method_while_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       /* use bottom right corner to catch accidental softbutton right */
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_enable_buttonareas(dev);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_touch_down(dev, 0, 95, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+}
+END_TEST
+
+START_TEST(touchpad_clickfinger_3fg_tool_position)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+       litest_drain_events(li);
+
+       /* one in thumb area, one in normal area + TRIPLETAP. spread is wide
+        * but any 3fg touch+click counts as middle */
+       litest_touch_down(dev, 0, 5, 99);
+       litest_touch_down(dev, 1, 90, 15);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_clickfinger_4fg_tool_position)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 5, 99);
+       litest_touch_down(dev, 1, 90, 15);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(touchpad_btn_left)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(clickpad_btn_left)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_buttonareas(dev);
+
+       litest_drain_events(li);
+
+       /* A clickpad always needs a finger down to tell where the
+          click happens */
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+       ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+}
+END_TEST
+
+START_TEST(clickpad_click_n_drag)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       libinput_dispatch(li);
+       ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+
+       /* now put a second finger down */
+       litest_touch_down(dev, 1, 70, 70);
+       litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(clickpad_finger_pin)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libevdev *evdev = dev->evdev;
+       const struct input_absinfo *abs;
+       double w, h;
+       double dist;
+
+       abs = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
+       ck_assert_notnull(abs);
+       if (abs->resolution == 0)
+               return;
+
+       if (libinput_device_get_size(dev->libinput_device, &w, &h) != 0)
+               return;
+
+       dist = 100.0/max(w, h);
+
+       litest_drain_events(li);
+
+       /* make sure the movement generates pointer events when
+          not pinned */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 52, 52, 10, 1);
+       litest_touch_move_to(dev, 0, 52, 52, 48, 48, 10, 1);
+       litest_touch_move_to(dev, 0, 48, 48, 50, 50, 10, 1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_button_click(dev, BTN_LEFT, true);
+       litest_drain_events(li);
+
+       litest_touch_move_to(dev, 0, 50, 50, 50 + dist, 50 + dist, 10, 1);
+       litest_touch_move_to(dev, 0, 50 + dist, 50 + dist, 50, 50, 10, 1);
+       litest_touch_move_to(dev, 0, 50, 50, 50 - dist, 50 - dist, 10, 1);
+
+       litest_assert_empty_queue(li);
+
+       litest_button_click(dev, BTN_LEFT, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+
+       /* still pinned after release */
+       litest_touch_move_to(dev, 0, 50, 50, 50 + dist, 50 + dist, 10, 1);
+       litest_touch_move_to(dev, 0, 50 + dist, 50 + dist, 50, 50, 10, 1);
+       litest_touch_move_to(dev, 0, 50, 50, 50 - dist, 50 - dist, 10, 1);
+
+       litest_assert_empty_queue(li);
+
+       /* move to unpin */
+       litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_left)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 10, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_middle)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_right)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                           LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                           LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_left_tap_n_drag)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       /* Tap in left button area, then finger down, button click
+               -> expect left button press/release and left button press
+          Release button, finger up
+               -> expect right button release
+        */
+       litest_touch_down(dev, 0, 20, 90);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 20, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                           LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                           LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                           LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                           LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_right_tap_n_drag)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       /* Tap in right button area, then finger down, button click
+               -> expect left button press/release and right button press
+          Release button, finger up
+               -> expect right button release
+        */
+       litest_touch_down(dev, 0, 90, 90);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 90, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_left_1st_fg_move)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       double x = 0, y = 0;
+       int nevents = 0;
+
+       litest_drain_events(li);
+
+       /* One finger down in the left button area, button press
+               -> expect a button event
+          Move finger up out of the area, wait for timeout
+          Move finger around diagonally down left
+               -> expect motion events down left
+          Release finger
+               -> expect a button event */
+
+       /* finger down, press in left button */
+       litest_touch_down(dev, 0, 20, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       /* move out of the area, then wait for softbutton timer */
+       litest_touch_move_to(dev, 0, 20, 90, 90, 20, 10, 0);
+       libinput_dispatch(li);
+       litest_timeout_softbuttons();
+       libinput_dispatch(li);
+       litest_drain_events(li);
+
+       /* move down left, expect motion */
+       litest_touch_move_to(dev, 0, 90, 20, 20, 90, 10, 0);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert(event != NULL);
+       while (event) {
+               struct libinput_event_pointer *p;
+
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_MOTION);
+               p = libinput_event_get_pointer_event(event);
+
+               /* we moved up/right, now down/left so the pointer accel
+                  code may lag behind with the dx/dy vectors. Hence, add up
+                  the x/y movements and expect that on average we moved
+                  left and down */
+               x += libinput_event_pointer_get_dx(p);
+               y += libinput_event_pointer_get_dy(p);
+               nevents++;
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+               event = libinput_get_event(li);
+       }
+
+       ck_assert(x/nevents < 0);
+       ck_assert(y/nevents > 0);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_left_2nd_fg_move)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+
+       litest_drain_events(li);
+
+       /* One finger down in the left button area, button press
+               -> expect a button event
+          Put a second finger down in the area, move it right, release
+               -> expect motion events right
+          Put a second finger down in the area, move it down, release
+               -> expect motion events down
+          Release second finger, release first finger
+               -> expect a button event */
+       litest_touch_down(dev, 0, 20, 90);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 1, 20, 20);
+       litest_touch_move_to(dev, 1, 20, 20, 80, 20, 10, 0);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert(event != NULL);
+       while (event) {
+               struct libinput_event_pointer *p;
+               double x, y;
+
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_MOTION);
+               p = libinput_event_get_pointer_event(event);
+
+               x = libinput_event_pointer_get_dx(p);
+               y = libinput_event_pointer_get_dy(p);
+
+               /* Ignore events only containing an unaccelerated motion
+                * vector. */
+               if (x != 0 || y != 0) {
+                       ck_assert(x > 0);
+                       ck_assert(y == 0);
+               }
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+               event = libinput_get_event(li);
+       }
+       litest_touch_up(dev, 1);
+
+       /* second finger down */
+       litest_touch_down(dev, 1, 20, 20);
+       litest_touch_move_to(dev, 1, 20, 20, 20, 80, 10, 0);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert(event != NULL);
+       while (event) {
+               struct libinput_event_pointer *p;
+               double x, y;
+
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_MOTION);
+               p = libinput_event_get_pointer_event(event);
+
+               x = libinput_event_pointer_get_dx(p);
+               y = libinput_event_pointer_get_dy(p);
+
+               ck_assert(x == 0);
+               ck_assert(y > 0);
+
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+               event = libinput_get_event(li);
+       }
+
+       litest_touch_up(dev, 1);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_left_to_right)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       /* One finger down in left software button area,
+          move to right button area immediately, click
+               -> expect right button event
+       */
+
+       litest_touch_down(dev, 0, 20, 90);
+       litest_touch_move_to(dev, 0, 20, 90, 90, 90, 10, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_softbutton_right_to_left)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       /* One finger down in right software button area,
+          move to left button area immediately, click
+               -> expect left button event
+       */
+
+       litest_touch_down(dev, 0, 90, 90);
+       litest_touch_move_to(dev, 0, 90, 90, 20, 90, 10, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_topsoftbuttons_left)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 10, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_topsoftbuttons_right)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_topsoftbuttons_middle)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_topsoftbuttons_move_out_ignore)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       /* Finger down in top button area, wait past enter timeout
+          Move into main area, wait past leave timeout
+          Click
+            -> expect no events
+        */
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 5);
+       libinput_dispatch(li);
+       litest_timeout_softbuttons();
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       litest_touch_move_to(dev, 0, 50, 5, 80, 90, 20, 0);
+       libinput_dispatch(li);
+       litest_timeout_softbuttons();
+       libinput_dispatch(li);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_topsoftbuttons_clickfinger)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 90, 5);
+       litest_touch_down(dev, 1, 80, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+}
+END_TEST
+
+START_TEST(clickpad_topsoftbuttons_clickfinger_dev_disabled)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct litest_device *trackpoint = litest_add_device(li,
+                                                            LITEST_TRACKPOINT);
+
+       libinput_device_config_send_events_set_mode(dev->libinput_device,
+                                                   LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       litest_enable_clickfinger(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 90, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 90, 5);
+       litest_touch_down(dev, 1, 10, 5);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(clickpad_middleemulation_config_delayed)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
+       int enabled;
+
+       enabled = libinput_device_config_middle_emulation_get_enabled(device);
+       ck_assert(!enabled);
+
+       litest_touch_down(dev, 0, 30, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       /* actual config is delayed, but status is immediate */
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                               LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       enabled = libinput_device_config_middle_emulation_get_enabled(device);
+       ck_assert(enabled);
+
+       status = libinput_device_config_middle_emulation_set_enabled(device,
+                               LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       enabled = libinput_device_config_middle_emulation_get_enabled(device);
+       ck_assert(!enabled);
+}
+END_TEST
+
+START_TEST(clickpad_middleemulation_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_buttonareas(dev);
+       litest_enable_middleemu(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 95);
+       litest_touch_down(dev, 1, 80, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_middleemulation_click_middle_left)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_buttonareas(dev);
+       litest_enable_middleemu(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 49, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_middleemulation_click_middle_right)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_buttonareas(dev);
+       litest_enable_middleemu(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 51, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_middleemulation_click_enable_while_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_buttonareas(dev);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 49, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_enable_middleemu(dev);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 49, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+}
+END_TEST
+
+START_TEST(clickpad_middleemulation_click_disable_while_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_buttonareas(dev);
+       litest_enable_middleemu(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 95);
+       litest_touch_down(dev, 1, 70, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_disable_middleemu(dev);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 49, 95);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+}
+END_TEST
+
+void
+litest_setup_tests_touchpad_buttons(void)
+{
+       litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger_no_touch, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_3fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_3fg_clickfinger_btntool, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_4fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_4fg_clickfinger_btntool_2slots, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_4fg_clickfinger_btntool_3slots, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger_distance, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_3fg_clickfinger_distance, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_3fg_clickfinger_distance_btntool, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add_for_device("touchpad:clickfinger", touchpad_2fg_clickfinger_bottom, LITEST_SYNAPTICS_TOPBUTTONPAD);
+       litest_add("touchpad:clickfinger", touchpad_clickfinger_to_area_method, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger",
+                  touchpad_clickfinger_to_area_method_while_down, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger", touchpad_area_to_clickfinger_method, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:clickfinger",
+                  touchpad_area_to_clickfinger_method_while_down, LITEST_CLICKPAD, LITEST_ANY);
+       /* run those two for the T440 one only so we don't have to worry
+        * about small touchpads messing with thumb detection expectations */
+       litest_add_for_device("touchpad:clickfinger", touchpad_clickfinger_3fg_tool_position, LITEST_SYNAPTICS_TOPBUTTONPAD);
+       litest_add_for_device("touchpad:clickfinger", touchpad_clickfinger_4fg_tool_position, LITEST_SYNAPTICS_TOPBUTTONPAD);
+
+       litest_add("touchpad:click", touchpad_click_defaults_clickfinger, LITEST_APPLE_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:click", touchpad_click_defaults_btnarea, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:click", touchpad_click_defaults_none, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+
+       litest_add("touchpad:click", touchpad_btn_left, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("touchpad:click", clickpad_btn_left, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:click", clickpad_click_n_drag, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:click", clickpad_finger_pin, LITEST_CLICKPAD, LITEST_ANY);
+
+       litest_add("touchpad:softbutton", clickpad_softbutton_left, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_middle, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_right, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_left_tap_n_drag, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_right_tap_n_drag, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_left_1st_fg_move, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_left_2nd_fg_move, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_left_to_right, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+       litest_add("touchpad:softbutton", clickpad_softbutton_right_to_left, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
+
+       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_left, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_right, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_middle, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_move_out_ignore, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_clickfinger, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_clickfinger_dev_disabled, LITEST_TOPBUTTONPAD, LITEST_ANY);
+
+       litest_add("touchpad:middleemulation", clickpad_middleemulation_config_delayed, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:middleemulation", clickpad_middleemulation_click, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:middleemulation", clickpad_middleemulation_click_middle_left, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:middleemulation", clickpad_middleemulation_click_middle_right, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:middleemulation", clickpad_middleemulation_click_enable_while_down, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:middleemulation", clickpad_middleemulation_click_disable_while_down, LITEST_CLICKPAD, LITEST_ANY);
+}
diff --git a/test/touchpad-tap.c b/test/touchpad-tap.c
new file mode 100644 (file)
index 0000000..d648e79
--- /dev/null
@@ -0,0 +1,2209 @@
+/*
+ * Copyright © 2014-2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <check.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libinput.h>
+#include <unistd.h>
+
+#include "libinput-util.h"
+#include "litest.h"
+
+START_TEST(touchpad_1fg_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert(event == NULL);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_doubletap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime, curtime;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_timeout_tap();
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       oldtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_le(oldtime, curtime);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_lt(oldtime, curtime);
+       oldtime = curtime;
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_le(oldtime, curtime);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       litest_timeout_tap();
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+       litest_timeout_tap();
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap_n_drag_move)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4);
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps < range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_gt(curtime, oldtime);
+
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap_n_drag_2fg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i,
+           ntaps;
+
+       if (libevdev_has_property(dev->evdev, INPUT_PROP_SEMI_MT))
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       msleep(10);
+       litest_touch_down(dev, 1, 70, 50);
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps < range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_gt(curtime, oldtime);
+
+       litest_touch_move_to(dev, 1, 70, 50, 90, 50, 10, 4);
+
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+       litest_timeout_tap();
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap_n_drag_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(li);
+       litest_button_click(dev, BTN_LEFT, true);
+       litest_button_click(dev, BTN_LEFT, false);
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_up(dev, 0);
+       litest_timeout_tap();
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap_n_drag_timeout)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(li);
+
+       litest_timeout_tap();
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps < range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_gt(curtime, oldtime);
+
+       litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4);
+
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap_n_drag_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(li);
+
+       litest_timeout_tap();
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps < range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_gt(curtime, oldtime);
+
+       litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4);
+
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 70, 50);
+       litest_touch_up(dev, 0);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_multitap_n_drag_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(li);
+
+       litest_timeout_tap();
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps < range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       curtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+       ck_assert_int_gt(curtime, oldtime);
+
+       litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4);
+
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 70, 50);
+       litest_button_click(dev, BTN_LEFT, true);
+       litest_button_click(dev, BTN_LEFT, false);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       /* the physical click */
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev __attribute__((unused));
+
+       litest_enable_tap(dev->libinput_device);
+       litest_disable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20, 2);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       libinput_dispatch(li);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+
+       /* don't use helper functions here, we expect the event be available
+        * immediately, not after a timeout that the helper functions may
+        * trigger.
+        */
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert_notnull(event);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_event_destroy(event);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag_draglock)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20, 2);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       libinput_dispatch(li);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       /* lift finger, set down again, should continue dragging */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20, 2);
+       litest_touch_up(dev, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_timeout_tap();
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag_draglock_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20, 2);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       libinput_dispatch(li);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       /* lift finger, set down again, should continue dragging */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20, 2);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag_draglock_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 20, 2);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       libinput_dispatch(li);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_button_click(dev, BTN_LEFT, true);
+       litest_button_click(dev, BTN_LEFT, false);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       /* the physical click */
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag_draglock_timeout)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_assert_empty_queue(li);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_timeout_tapndrag();
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_n_drag)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_disable_drag_lock(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 70);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 30, 70);
+       litest_touch_down(dev, 1, 80, 70);
+       litest_touch_move_to(dev, 0, 30, 70, 30, 30, 5, 40);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_n_drag_3fg_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) > 2)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 70);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 30, 70);
+       litest_touch_down(dev, 1, 80, 90);
+       litest_touch_move_to(dev, 0, 30, 70, 30, 30, 5, 40);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       /* Putting down a third finger should end the drag */
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       /* Releasing the fingers should not cause any events */
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_n_drag_3fg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 2)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 30, 70);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 30, 70);
+       litest_touch_down(dev, 1, 80, 90);
+       litest_touch_move_to(dev, 0, 30, 70, 30, 30, 5, 40);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       /* Putting down a third finger should end the drag */
+       litest_touch_down(dev, 2, 50, 50);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       /* Releasing the fingers should not cause any events */
+       litest_touch_up(dev, 2);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       enum libinput_config_tap_button_map map = _i; /* ranged test */
+       unsigned int button;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_set_tap_map(dev->libinput_device, map);
+
+       switch (map) {
+       case LIBINPUT_CONFIG_TAP_MAP_LRM:
+               button = BTN_RIGHT;
+               break;
+       case LIBINPUT_CONFIG_TAP_MAP_LMR:
+               button = BTN_MIDDLE;
+               break;
+       default:
+               litest_abort_msg("Invalid map range %d", map);
+       }
+
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_inverted)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       enum libinput_config_tap_button_map map = _i; /* ranged test */
+       unsigned int button;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_set_tap_map(dev->libinput_device, map);
+
+       switch (map) {
+       case LIBINPUT_CONFIG_TAP_MAP_LRM:
+               button = BTN_RIGHT;
+               break;
+       case LIBINPUT_CONFIG_TAP_MAP_LMR:
+               button = BTN_MIDDLE;
+               break;
+       default:
+               litest_abort_msg("Invalid map range %d", map);
+       }
+
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_n_hold_first)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       litest_touch_up(dev, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+       litest_timeout_tap();
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_n_hold_second)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+       litest_timeout_tap();
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_quickrelease)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* Finger down, finger up -> tap button press
+        * Physical button click -> no button press/release
+        * Tap timeout -> tap button release */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* two fingers down, left button click, fingers up
+          -> one left button, one right button event pair */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_2fg_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* two fingers down, button click, fingers up
+          -> only one button left event pair */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_2fg_tap_click_apple)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* two fingers down, button click, fingers up
+          -> only one button right event pair
+          (apple have clickfinger enabled by default) */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_no_2fg_tap_after_move)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_drain_events(dev->libinput);
+
+       /* one finger down, move past threshold,
+          second finger down, first finger up
+          -> no event
+        */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10, 0);
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 1, 70, 50);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_no_2fg_tap_after_timeout)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_drain_events(dev->libinput);
+
+       /* one finger down, wait past tap timeout,
+          second finger down, first finger up
+          -> no event
+        */
+       litest_touch_down(dev, 0, 50, 50);
+       libinput_dispatch(dev->libinput);
+       litest_timeout_tap();
+       libinput_dispatch(dev->libinput);
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 1, 70, 50);
+       litest_touch_up(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_no_first_fg_tap_after_move)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* one finger down, second finger down,
+          second finger moves beyond threshold,
+          first finger up
+          -> no event
+        */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       libinput_dispatch(dev->libinput);
+       litest_touch_move_to(dev, 1, 70, 50, 90, 90, 10, 0);
+       libinput_dispatch(dev->libinput);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(dev->libinput);
+
+       while ((event = libinput_get_event(li))) {
+               ck_assert_int_ne(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_BUTTON);
+               libinput_event_destroy(event);
+       }
+}
+END_TEST
+
+START_TEST(touchpad_1fg_double_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* one finger down, up, down, button click, finger up
+          -> two button left event pairs */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_1fg_tap_n_drag_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* one finger down, up, down, move, button click, finger up
+          -> two button left event pairs, motion allowed */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 50, 10, 0);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       enum libinput_config_tap_button_map map = _i; /* ranged test */
+       unsigned int button;
+       int i;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 2)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_set_tap_map(dev->libinput_device, map);
+
+       switch (map) {
+       case LIBINPUT_CONFIG_TAP_MAP_LRM:
+               button = BTN_MIDDLE;
+               break;
+       case LIBINPUT_CONFIG_TAP_MAP_LMR:
+               button = BTN_RIGHT;
+               break;
+       default:
+               litest_abort_msg("Invalid map range %d", map);
+       }
+
+       for (i = 0; i < 3; i++) {
+               litest_drain_events(li);
+
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_down(dev, 1, 70, 50);
+               litest_touch_down(dev, 2, 80, 50);
+
+               litest_touch_up(dev, (i + 2) % 3);
+               litest_touch_up(dev, (i + 1) % 3);
+               litest_touch_up(dev, (i + 0) % 3);
+
+               libinput_dispatch(li);
+
+               litest_assert_button_event(li, button,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_timeout_tap();
+               litest_assert_button_event(li, button,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+
+               libinput_dispatch(li);
+               event = libinput_get_event(li);
+               ck_assert(event == NULL);
+       }
+}
+END_TEST
+
+START_TEST(touchpad_3fg_tap_quickrelease)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 2)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_touch_down(dev, 2, 80, 50);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 2);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, BTN_MIDDLE,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_tap_btntool)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       enum libinput_config_tap_button_map map = _i; /* ranged test */
+       unsigned int button;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) > 2)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_set_tap_map(dev->libinput_device, map);
+
+       switch (map) {
+       case LIBINPUT_CONFIG_TAP_MAP_LRM:
+               button = BTN_MIDDLE;
+               break;
+       case LIBINPUT_CONFIG_TAP_MAP_LMR:
+               button = BTN_RIGHT;
+               break;
+       default:
+               litest_abort_msg("Invalid map range %d", map);
+       }
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 1);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert(event == NULL);
+}
+END_TEST
+
+START_TEST(touchpad_3fg_tap_btntool_inverted)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       enum libinput_config_tap_button_map map = _i; /* ranged test */
+       unsigned int button;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) > 2)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_set_tap_map(dev->libinput_device, map);
+
+       switch (map) {
+       case LIBINPUT_CONFIG_TAP_MAP_LRM:
+               button = BTN_MIDDLE;
+               break;
+       case LIBINPUT_CONFIG_TAP_MAP_LMR:
+               button = BTN_RIGHT;
+               break;
+       default:
+               litest_abort_msg("invalid map range %d", map);
+       }
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, button,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ck_assert(event == NULL);
+}
+END_TEST
+
+START_TEST(touchpad_4fg_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int i;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 3)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       for (i = 0; i < 4; i++) {
+               litest_drain_events(li);
+
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_down(dev, 1, 70, 50);
+               litest_touch_down(dev, 2, 80, 50);
+               litest_touch_down(dev, 3, 90, 50);
+
+               litest_touch_up(dev, (i + 3) % 4);
+               litest_touch_up(dev, (i + 2) % 4);
+               litest_touch_up(dev, (i + 1) % 4);
+               litest_touch_up(dev, (i + 0) % 4);
+
+               libinput_dispatch(li);
+               litest_assert_empty_queue(li);
+               litest_timeout_tap();
+               litest_assert_empty_queue(li);
+               event = libinput_get_event(li);
+               ck_assert(event == NULL);
+       }
+}
+END_TEST
+
+START_TEST(touchpad_4fg_tap_quickrelease)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 3)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
+       litest_touch_down(dev, 2, 80, 50);
+       litest_touch_down(dev, 3, 90, 50);
+
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 2);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 3);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUADTAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+       litest_timeout_tap();
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_5fg_tap)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int i;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 4)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       for (i = 0; i < 5; i++) {
+               litest_drain_events(li);
+
+               litest_touch_down(dev, 0, 20, 50);
+               litest_touch_down(dev, 1, 30, 50);
+               litest_touch_down(dev, 2, 40, 50);
+               litest_touch_down(dev, 3, 50, 50);
+               litest_touch_down(dev, 4, 60, 50);
+
+               litest_touch_up(dev, (i + 4) % 5);
+               litest_touch_up(dev, (i + 3) % 5);
+               litest_touch_up(dev, (i + 2) % 5);
+               litest_touch_up(dev, (i + 1) % 5);
+               litest_touch_up(dev, (i + 0) % 5);
+
+               libinput_dispatch(li);
+               litest_assert_empty_queue(li);
+               litest_timeout_tap();
+               litest_assert_empty_queue(li);
+               event = libinput_get_event(li);
+               ck_assert(event == NULL);
+       }
+}
+END_TEST
+
+START_TEST(touchpad_5fg_tap_quickrelease)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       if (libevdev_get_abs_maximum(dev->evdev,
+                                    ABS_MT_SLOT) <= 4)
+               return;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 20, 50);
+       litest_touch_down(dev, 1, 30, 50);
+       litest_touch_down(dev, 2, 40, 50);
+       litest_touch_down(dev, 3, 70, 50);
+       litest_touch_down(dev, 4, 90, 50);
+
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 2);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 3);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 4);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_QUINTTAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+       litest_timeout_tap();
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(clickpad_1fg_tap_click)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_drain_events(dev->libinput);
+
+       /* finger down, button click, finger up
+          -> only one button left event pair */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li, BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_tap_is_available)
+{
+       struct litest_device *dev = litest_current_device();
+
+       ck_assert_int_ge(libinput_device_config_tap_get_finger_count(dev->libinput_device), 1);
+}
+END_TEST
+
+START_TEST(touchpad_tap_is_not_available)
+{
+       struct litest_device *dev = litest_current_device();
+
+       ck_assert_int_eq(libinput_device_config_tap_get_finger_count(dev->libinput_device), 0);
+       ck_assert_int_eq(libinput_device_config_tap_get_enabled(dev->libinput_device),
+                        LIBINPUT_CONFIG_TAP_DISABLED);
+       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device,
+                                                               LIBINPUT_CONFIG_TAP_ENABLED),
+                        LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device,
+                                                               LIBINPUT_CONFIG_TAP_DISABLED),
+                        LIBINPUT_CONFIG_STATUS_SUCCESS);
+}
+END_TEST
+
+START_TEST(touchpad_tap_default_disabled)
+{
+       struct litest_device *dev = litest_current_device();
+
+       /* this test is only run on specific devices */
+
+       ck_assert_int_eq(libinput_device_config_tap_get_default_enabled(dev->libinput_device),
+                        LIBINPUT_CONFIG_TAP_DISABLED);
+}
+END_TEST
+
+START_TEST(touchpad_tap_default_enabled)
+{
+       struct litest_device *dev = litest_current_device();
+
+       /* this test is only run on specific devices */
+
+       ck_assert_int_eq(libinput_device_config_tap_get_default_enabled(dev->libinput_device),
+                        LIBINPUT_CONFIG_TAP_ENABLED);
+}
+END_TEST
+
+START_TEST(touchpad_tap_invalid)
+{
+       struct litest_device *dev = litest_current_device();
+
+       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device, 2),
+                        LIBINPUT_CONFIG_STATUS_INVALID);
+       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device, -1),
+                        LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(touchpad_tap_default_map)
+{
+       struct litest_device *dev = litest_current_device();
+       enum libinput_config_tap_button_map map;
+
+       map = libinput_device_config_tap_get_button_map(dev->libinput_device);
+       ck_assert_int_eq(map, LIBINPUT_CONFIG_TAP_MAP_LRM);
+
+       map = libinput_device_config_tap_get_default_button_map(dev->libinput_device);
+       ck_assert_int_eq(map, LIBINPUT_CONFIG_TAP_MAP_LRM);
+}
+END_TEST
+
+START_TEST(touchpad_tap_set_map)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_tap_button_map map;
+       enum libinput_config_status status;
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LRM;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       map = libinput_device_config_tap_get_button_map(dev->libinput_device);
+       ck_assert_int_eq(map, LIBINPUT_CONFIG_TAP_MAP_LRM);
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LMR;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       map = libinput_device_config_tap_get_button_map(dev->libinput_device);
+       ck_assert_int_eq(map, LIBINPUT_CONFIG_TAP_MAP_LMR);
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LRM - 1;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LMR + 1;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(touchpad_tap_set_map_no_tapping)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_tap_button_map map;
+       enum libinput_config_status status;
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LRM;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LMR;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LRM - 1;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+
+       map = LIBINPUT_CONFIG_TAP_MAP_LMR + 1;
+       status = libinput_device_config_tap_set_button_map(device, map);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(touchpad_tap_map_delayed)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       enum libinput_config_tap_button_map map;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_set_tap_map(dev->libinput_device,
+                          LIBINPUT_CONFIG_TAP_MAP_LRM);
+       litest_drain_events(dev->libinput);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 70);
+       libinput_dispatch(li);
+
+       litest_set_tap_map(dev->libinput_device,
+                          LIBINPUT_CONFIG_TAP_MAP_LMR);
+       map = libinput_device_config_tap_get_button_map(dev->libinput_device);
+       ck_assert_int_eq(map, LIBINPUT_CONFIG_TAP_MAP_LMR);
+
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_timeout_tap();
+       litest_assert_button_event(li, BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_drag_default_disabled)
+{
+       struct litest_device *dev = litest_current_device();
+
+       /* this test is only run on specific devices */
+
+       ck_assert_int_eq(libinput_device_config_tap_get_default_drag_enabled(dev->libinput_device),
+                        LIBINPUT_CONFIG_DRAG_DISABLED);
+}
+END_TEST
+
+START_TEST(touchpad_drag_default_enabled)
+{
+       struct litest_device *dev = litest_current_device();
+
+       /* this test is only run on specific devices */
+
+       ck_assert_int_eq(libinput_device_config_tap_get_default_drag_enabled(dev->libinput_device),
+                        LIBINPUT_CONFIG_DRAG_ENABLED);
+}
+END_TEST
+
+START_TEST(touchpad_drag_config_invalid)
+{
+       struct litest_device *dev = litest_current_device();
+
+       ck_assert_int_eq(libinput_device_config_tap_set_drag_enabled(dev->libinput_device, 2),
+                        LIBINPUT_CONFIG_STATUS_INVALID);
+       ck_assert_int_eq(libinput_device_config_tap_set_drag_enabled(dev->libinput_device, -1),
+                        LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(touchpad_drag_config_enabledisable)
+{
+       struct litest_device *dev = litest_current_device();
+       enum libinput_config_drag_state state;
+
+       litest_enable_tap(dev->libinput_device);
+
+       litest_disable_tap_drag(dev->libinput_device);
+       state = libinput_device_config_tap_get_drag_enabled(dev->libinput_device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DRAG_DISABLED);
+
+       litest_enable_tap_drag(dev->libinput_device);
+       state = libinput_device_config_tap_get_drag_enabled(dev->libinput_device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DRAG_ENABLED);
+
+       /* same thing with tapping disabled */
+       litest_enable_tap(dev->libinput_device);
+
+       litest_disable_tap_drag(dev->libinput_device);
+       state = libinput_device_config_tap_get_drag_enabled(dev->libinput_device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DRAG_DISABLED);
+
+       litest_enable_tap_drag(dev->libinput_device);
+       state = libinput_device_config_tap_get_drag_enabled(dev->libinput_device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DRAG_ENABLED);
+}
+END_TEST
+
+START_TEST(touchpad_drag_disabled)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_disable_tap_drag(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10, 0);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+
+}
+END_TEST
+
+START_TEST(touchpad_drag_disabled_immediate)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *ev;
+       struct libinput_event_pointer *ptrev;
+       uint64_t press_time, release_time;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_disable_tap_drag(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 50);
+       msleep(10); /* to force a time difference */
+       libinput_dispatch(li);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+
+       ev = libinput_get_event(li);
+       ptrev = litest_is_button_event(ev,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       press_time = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(ev);
+
+       ev = libinput_get_event(li);
+       ptrev = litest_is_button_event(ev,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       release_time = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(ev);
+
+       ck_assert_int_gt(release_time, press_time);
+}
+END_TEST
+
+START_TEST(touchpad_drag_disabled_multitap_no_drag)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint32_t oldtime = 0,
+                curtime;
+       int range = _i, /* looped test */
+           ntaps;
+
+       litest_enable_tap(dev->libinput_device);
+       litest_disable_tap_drag(dev->libinput_device);
+
+       litest_drain_events(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               litest_touch_down(dev, 0, 50, 50);
+               litest_touch_up(dev, 0);
+               libinput_dispatch(li);
+               msleep(10);
+       }
+
+       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 70, 50, 10, 4);
+       libinput_dispatch(li);
+
+       for (ntaps = 0; ntaps <= range; ntaps++) {
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_PRESSED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_gt(curtime, oldtime);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_button_event(event,
+                                              BTN_LEFT,
+                                              LIBINPUT_BUTTON_STATE_RELEASED);
+               curtime = libinput_event_pointer_get_time(ptrev);
+               libinput_event_destroy(event);
+               ck_assert_int_ge(curtime, oldtime);
+               oldtime = curtime;
+       }
+
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_MOTION);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_drag_lock_default_disabled)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+
+       ck_assert_int_eq(libinput_device_config_tap_get_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+       ck_assert_int_eq(libinput_device_config_tap_get_default_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 3);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(touchpad_drag_lock_default_unavailable)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+
+       ck_assert_int_eq(libinput_device_config_tap_get_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+       ck_assert_int_eq(libinput_device_config_tap_get_default_drag_lock_enabled(device),
+                        LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 LIBINPUT_CONFIG_DRAG_LOCK_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       status = libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                 3);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+void
+litest_setup_tests_touchpad_tap(void)
+{
+       struct range multitap_range = {3, 8};
+       struct range tap_map_range = { LIBINPUT_CONFIG_TAP_MAP_LRM,
+                                      LIBINPUT_CONFIG_TAP_MAP_LMR + 1 };
+
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap-1fg:1fg", touchpad_1fg_doubletap, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap_n_drag_timeout, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap_n_drag_tap, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap_n_drag_move, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap_n_drag_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &multitap_range);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap_n_drag_click, LITEST_CLICKPAD, LITEST_ANY, &multitap_range);
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap_n_drag_draglock, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap_n_drag_draglock_tap, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap_n_drag_draglock_timeout, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_n_drag_3fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_n_drag_3fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add_ranged("tap-2fg:2fg", touchpad_2fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT, &tap_map_range);
+       litest_add_ranged("tap-2fg:2fg", touchpad_2fg_tap_inverted, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &tap_map_range);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_n_hold_first, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_n_hold_second, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("tap-2fg:2fg", touchpad_1fg_tap_click, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD);
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_click, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_SINGLE_TOUCH|LITEST_CLICKPAD);
+
+       litest_add("tap-2fg:2fg", touchpad_2fg_tap_click_apple, LITEST_APPLE_CLICKPAD, LITEST_ANY);
+       litest_add("tap-2fg:2fg", touchpad_no_2fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("tap-2fg:2fg", touchpad_no_2fg_tap_after_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("tap-2fg:2fg", touchpad_no_first_fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("tap-2fg:2fg", touchpad_no_first_fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add_ranged("tap-3fg:3fg", touchpad_3fg_tap_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &tap_map_range);
+       litest_add_ranged("tap-3fg:3fg", touchpad_3fg_tap_btntool_inverted, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &tap_map_range);
+       litest_add_ranged("tap-3fg:3fg", touchpad_3fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH, &tap_map_range);
+       litest_add("tap-3fg:3fg", touchpad_3fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("tap-4fg:4fg", touchpad_4fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("tap-4fg:4fg", touchpad_4fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("tap-5fg:5fg", touchpad_5fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("tap-5fg:5fg", touchpad_5fg_tap_quickrelease, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+
+       /* Real buttons don't interfere with tapping, so don't run those for
+          pads with buttons */
+       litest_add("tap-1fg:1fg", touchpad_1fg_double_tap_click, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap_n_drag_click, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add_ranged("tap-multitap:1fg", touchpad_1fg_multitap_n_drag_tap_click, LITEST_CLICKPAD, LITEST_ANY, &multitap_range);
+       litest_add("tap-1fg:1fg", touchpad_1fg_tap_n_drag_draglock_tap_click, LITEST_CLICKPAD, LITEST_ANY);
+
+       litest_add("tap:config", touchpad_tap_default_disabled, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_ANY);
+       litest_add("tap:config", touchpad_tap_default_enabled, LITEST_TOUCHPAD, LITEST_BUTTON);
+       litest_add("tap:config", touchpad_tap_invalid, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:config", touchpad_tap_is_available, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:config", touchpad_tap_is_not_available, LITEST_ANY, LITEST_TOUCHPAD);
+
+       litest_add("tap:config", touchpad_tap_default_map, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:config", touchpad_tap_set_map, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:config", touchpad_tap_set_map_no_tapping, LITEST_ANY, LITEST_TOUCHPAD);
+       litest_add("tap:config", touchpad_tap_map_delayed, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+
+       litest_add("tap-1fg:1fg", clickpad_1fg_tap_click, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("tap-2fg:2fg", clickpad_2fg_tap_click, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
+
+       litest_add("tap:draglock", touchpad_drag_lock_default_disabled, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:draglock", touchpad_drag_lock_default_unavailable, LITEST_ANY, LITEST_TOUCHPAD);
+
+       litest_add("tap:drag", touchpad_drag_default_disabled, LITEST_ANY, LITEST_TOUCHPAD);
+       litest_add("tap:drag", touchpad_drag_default_enabled, LITEST_TOUCHPAD, LITEST_BUTTON);
+       litest_add("tap:drag", touchpad_drag_config_invalid, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:drag", touchpad_drag_config_enabledisable, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:drag", touchpad_drag_disabled, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("tap:drag", touchpad_drag_disabled_immediate, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add_ranged("tap-multitap:drag", touchpad_drag_disabled_multitap_no_drag, LITEST_TOUCHPAD, LITEST_ANY, &multitap_range);
+}
index 9c34b506de6318be46912b91850bc7d82721b28c..0169bd74ef43f427e1098f5f00728c802137ee74 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -38,10 +39,12 @@ START_TEST(touchpad_1fg_motion)
        struct libinput_event *event;
        struct libinput_event_pointer *ptrev;
 
+       litest_disable_tap(dev->libinput_device);
+
        litest_drain_events(li);
 
        litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5, 0);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 50, 20, 0);
        litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
@@ -68,12 +71,15 @@ START_TEST(touchpad_2fg_no_motion)
        struct libinput *li = dev->libinput;
        struct libinput_event *event;
 
+       libinput_device_config_tap_set_enabled(dev->libinput_device,
+                                              LIBINPUT_CONFIG_TAP_DISABLED);
+
        litest_drain_events(li);
 
        litest_touch_down(dev, 0, 20, 20);
        litest_touch_down(dev, 1, 70, 20);
-       litest_touch_move_to(dev, 0, 20, 20, 80, 80, 5, 0);
-       litest_touch_move_to(dev, 1, 70, 20, 80, 50, 5, 0);
+       litest_touch_move_to(dev, 0, 20, 20, 80, 80, 20, 0);
+       litest_touch_move_to(dev, 1, 70, 20, 80, 50, 20, 0);
        litest_touch_up(dev, 1);
        litest_touch_up(dev, 0);
 
@@ -89,3243 +95,4558 @@ START_TEST(touchpad_2fg_no_motion)
 }
 END_TEST
 
-START_TEST(touchpad_1fg_tap)
+static void
+test_2fg_scroll(struct litest_device *dev, double dx, double dy, int want_sleep)
 {
-       struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       litest_touch_down(dev, 0, 49, 50);
+       litest_touch_down(dev, 1, 51, 50);
 
-       litest_drain_events(li);
+       litest_touch_move_two_touches(dev, 49, 50, 51, 50, dx, dy, 10, 0);
 
-       litest_touch_down(dev, 0, 50, 50);
+       /* Avoid a small scroll being seen as a tap */
+       if (want_sleep) {
+               libinput_dispatch(li);
+               litest_timeout_tap();
+               libinput_dispatch(li);
+       }
+
+       litest_touch_up(dev, 1);
        litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
+}
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_timeout_tap();
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+START_TEST(touchpad_2fg_scroll)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
 
-       libinput_dispatch(li);
-       event = libinput_get_event(li);
-       ck_assert(event == NULL);
+       if (!litest_has_2fg_scroll(dev))
+               return;
+
+       litest_enable_2fg_scroll(dev);
+       litest_drain_events(li);
+
+       test_2fg_scroll(dev, 0.1, 40, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 10);
+       test_2fg_scroll(dev, 0.1, -40, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -10);
+       test_2fg_scroll(dev, 40, 0.1, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 10);
+       test_2fg_scroll(dev, -40, 0.1, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -10);
+
+       /* 2fg scroll smaller than the threshold should not generate events */
+       test_2fg_scroll(dev, 0.1, 0.1, 1);
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_1fg_tap_n_drag)
+START_TEST(touchpad_2fg_scroll_diagonal)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       int i;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       if (!litest_has_2fg_scroll(dev))
+               return;
 
+       litest_enable_2fg_scroll(dev);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5, 40);
-       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 45, 30);
+       litest_touch_down(dev, 1, 55, 30);
 
+       litest_touch_move_two_touches(dev, 45, 30, 55, 30, 10, 10, 10, 0);
        libinput_dispatch(li);
+       litest_wait_for_event_of_type(li,
+                                     LIBINPUT_EVENT_POINTER_AXIS,
+                                     -1);
+       litest_drain_events(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       /* get rid of any touch history still adding x deltas sideways */
+       for (i = 0; i < 5; i++)
+               litest_touch_move(dev, 0, 55, 41 + i);
+       litest_drain_events(li);
 
-       libinput_dispatch(li);
+       for (i = 6; i < 10; i++) {
+               litest_touch_move(dev, 0, 55, 41 + i);
+               libinput_dispatch(li);
 
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+               event = libinput_get_event(li);
+               ptrev = litest_is_axis_event(event,
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                               LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+               ck_assert(!libinput_event_pointer_has_axis(ptrev,
+                               LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL));
+               libinput_event_destroy(event);
+       }
 
-       /* lift finger, set down again, should continue dragging */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 80, 80, 5, 40);
+       litest_touch_up(dev, 1);
        litest_touch_up(dev, 0);
-
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
-
-       litest_timeout_tap();
-
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-
-       litest_assert_empty_queue(li);
+       libinput_dispatch(li);
 }
 END_TEST
 
-START_TEST(touchpad_1fg_tap_n_drag_timeout)
+START_TEST(touchpad_2fg_scroll_slow_distance)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       double width, height;
+       double y_move = 100;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       if (!litest_has_2fg_scroll(dev))
+               return;
 
+       /* We want to move > 5 mm. */
+       ck_assert_int_eq(libinput_device_get_size(dev->libinput_device,
+                                                 &width,
+                                                 &height), 0);
+       y_move = 100.0/height * 7;
+
+       litest_enable_2fg_scroll(dev);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 0, 49, 50);
+       litest_touch_down(dev, 1, 51, 50);
+       litest_touch_move_two_touches(dev, 49, 50, 51, 50, 0, y_move, 100, 10);
+       litest_touch_up(dev, 1);
        litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 50, 50);
        libinput_dispatch(li);
-       litest_timeout_tap();
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       event = libinput_get_event(li);
+       ck_assert_notnull(event);
 
-       litest_assert_empty_queue(li);
-       litest_touch_up(dev, 0);
+       /* last event is value 0, tested elsewhere */
+       while (libinput_next_event_type(li) != LIBINPUT_EVENT_NONE) {
+               double axisval;
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_AXIS);
+               ptrev = libinput_event_get_pointer_event(event);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+               axisval = libinput_event_pointer_get_axis_value(ptrev,
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               ck_assert(axisval > 0.0);
+
+               /* this is to verify we test the right thing, if the value
+                  is greater than scroll.threshold we triggered the wrong
+                  condition */
+               ck_assert(axisval < 5.0);
+
+               libinput_event_destroy(event);
+               event = libinput_get_event(li);
+       }
 
        litest_assert_empty_queue(li);
+       libinput_event_destroy(event);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_tap_n_drag)
+START_TEST(touchpad_2fg_scroll_source)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       if (!litest_has_2fg_scroll(dev))
+               return;
 
+       litest_enable_2fg_scroll(dev);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 30, 70);
-       litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 30, 70);
-       litest_touch_down(dev, 1, 80, 70);
-       litest_touch_move_to(dev, 0, 30, 70, 30, 30, 5, 40);
-       libinput_dispatch(li);
+       test_2fg_scroll(dev, 0, 30, 0);
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       while ((event = libinput_get_event(li))) {
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_AXIS);
+               ptrev = libinput_event_get_pointer_event(event);
+               ck_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
+                                LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+               libinput_event_destroy(event);
+       }
+}
+END_TEST
 
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+START_TEST(touchpad_2fg_scroll_semi_mt)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
 
-       litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
+       if (!litest_has_2fg_scroll(dev))
+               return;
 
-       /* This will wait for the DRAGGING_WAIT timeout */
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_enable_2fg_scroll(dev);
+       litest_drain_events(li);
 
-       litest_assert_empty_queue(li);
+       litest_touch_down(dev, 0, 20, 20);
+       litest_touch_down(dev, 1, 30, 20);
+       libinput_dispatch(li);
+       litest_touch_move_two_touches(dev,
+                                     20, 20,
+                                     30, 20,
+                                     30, 40,
+                                     10, 1);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_tap_n_drag_3fg_btntool)
+START_TEST(touchpad_2fg_scroll_return_to_motion)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       if (!litest_has_2fg_scroll(dev))
+               return;
 
+       litest_enable_2fg_scroll(dev);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 30, 70);
-       litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 30, 70);
-       litest_touch_down(dev, 1, 80, 90);
-       litest_touch_move_to(dev, 0, 30, 70, 30, 30, 5, 40);
-       libinput_dispatch(li);
+       /* start with motion */
+       litest_touch_down(dev, 0, 70, 70);
+       litest_touch_move_to(dev, 0, 70, 70, 49, 50, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       /* 2fg scroll */
+       litest_touch_down(dev, 1, 51, 50);
+       litest_touch_move_two_touches(dev, 49, 50, 51, 50, 0, 20, 5, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
+       litest_timeout_finger_switch();
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
+       litest_touch_move_to(dev, 0, 49, 70, 49, 50, 10, 0);
        litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       /* Putting down a third finger should end the drag */
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       /* back to 2fg scroll, lifting the other finger */
+       litest_touch_down(dev, 1, 51, 50);
+       litest_touch_move_two_touches(dev, 49, 50, 51, 50, 0, 20, 5, 0);
+       litest_touch_up(dev, 0);
        libinput_dispatch(li);
+       litest_timeout_finger_switch();
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       /* move with second finger */
+       litest_touch_move_to(dev, 1, 51, 70, 51, 50, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       /* Releasing the fingers should not cause any events */
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
        litest_touch_up(dev, 1);
-       litest_touch_up(dev, 0);
-
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_tap)
+START_TEST(touchpad_scroll_natural_defaults)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
 
-       litest_drain_events(dev->libinput);
-
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 70);
-       litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
+       ck_assert_int_ge(libinput_device_config_scroll_has_natural_scroll(dev->libinput_device), 1);
+       ck_assert_int_eq(libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device), 0);
+       ck_assert_int_eq(libinput_device_config_scroll_get_default_natural_scroll_enabled(dev->libinput_device), 0);
+}
+END_TEST
 
-       libinput_dispatch(li);
+START_TEST(touchpad_scroll_natural_enable_config)
+{
+       struct litest_device *dev = litest_current_device();
+       enum libinput_config_status status;
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_timeout_tap();
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       status = libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       ck_assert_int_eq(libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device), 1);
 
-       litest_assert_empty_queue(li);
+       status = libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 0);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       ck_assert_int_eq(libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device), 0);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_tap_inverted)
+START_TEST(touchpad_scroll_natural_2fg)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
-
-       litest_drain_events(dev->libinput);
+       if (!litest_has_2fg_scroll(dev))
+               return;
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 70);
-       litest_touch_up(dev, 1);
-       litest_touch_up(dev, 0);
+       litest_enable_2fg_scroll(dev);
+       litest_drain_events(li);
 
-       libinput_dispatch(li);
+       libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 1);
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_timeout_tap();
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       test_2fg_scroll(dev, 0.1, 40, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -10);
+       test_2fg_scroll(dev, 0.1, -40, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 10);
+       test_2fg_scroll(dev, 40, 0.1, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -10);
+       test_2fg_scroll(dev, -40, 0.1, 0);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 10);
 
-       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_1fg_tap_click)
+START_TEST(touchpad_scroll_natural_edge)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       litest_enable_edge_scroll(dev);
+       litest_drain_events(li);
 
-       litest_drain_events(dev->libinput);
+       libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 1);
 
-       /* Finger down, finger up -> tap button press
-        * Physical button click -> no button press/release
-        * Tap timeout -> tap button release */
-       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
        litest_touch_up(dev, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       libinput_dispatch(li);
-       litest_timeout_tap();
 
        libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -4);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 99, 80);
+       litest_touch_move_to(dev, 0, 99, 80, 99, 20, 10, 0);
+       litest_touch_up(dev, 0);
 
+       libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
        litest_assert_empty_queue(li);
+
 }
 END_TEST
 
-START_TEST(touchpad_2fg_tap_click)
+START_TEST(touchpad_edge_scroll_vert)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
+       litest_touch_up(dev, 0);
 
-       litest_drain_events(dev->libinput);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       /* two fingers down, left button click, fingers up
-          -> one left button, one right button event pair */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 1);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
        litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 99, 80);
+       litest_touch_move_to(dev, 0, 99, 80, 99, 20, 10, 0);
+       litest_touch_up(dev, 0);
 
+       libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -4);
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_2fg_tap_click)
+static int
+touchpad_has_horiz_edge_scroll_size(struct litest_device *dev)
+{
+       double width, height;
+       int rc;
+
+       rc = libinput_device_get_size(dev->libinput_device, &width, &height);
+
+       return rc == 0 && height >= 50;
+}
+
+START_TEST(touchpad_edge_scroll_horiz)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
+       litest_touch_up(dev, 0);
 
-       litest_drain_events(dev->libinput);
+       if (!touchpad_has_horiz_edge_scroll_size(dev))
+               return;
 
-       /* two fingers down, button click, fingers up
-          -> only one button left event pair */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 1);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
+
+       litest_touch_down(dev, 0, 20, 99);
+       litest_touch_move_to(dev, 0, 20, 99, 70, 99, 10, 0);
        litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 4);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 70, 99);
+       litest_touch_move_to(dev, 0, 70, 99, 20, 99, 10, 0);
+       litest_touch_up(dev, 0);
 
+       libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -4);
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_tap_click_apple)
+START_TEST(touchpad_edge_scroll_horiz_clickpad)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
-
-       litest_drain_events(dev->libinput);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       /* two fingers down, button click, fingers up
-          -> only one button right event pair
-          (apple have clickfinger enabled by default) */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 1);
+       litest_touch_down(dev, 0, 20, 99);
+       litest_touch_move_to(dev, 0, 20, 99, 70, 99, 10, 0);
        litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 4);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 70, 99);
+       litest_touch_move_to(dev, 0, 70, 99, 20, 99, 10, 0);
+       litest_touch_up(dev, 0);
 
+       libinput_dispatch(li);
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -4);
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_no_2fg_tap_after_move)
+START_TEST(touchpad_edge_scroll_no_horiz)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       litest_drain_events(dev->libinput);
+       if (touchpad_has_horiz_edge_scroll_size(dev))
+               return;
 
-       /* one finger down, move past threshold,
-          second finger down, first finger up
-          -> no event
-        */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 90, 90, 10, 0);
-       litest_drain_events(dev->libinput);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       litest_touch_down(dev, 1, 70, 50);
+       litest_touch_down(dev, 0, 20, 99);
+       litest_touch_move_to(dev, 0, 20, 99, 70, 99, 10, 0);
        litest_touch_up(dev, 0);
 
-       litest_assert_empty_queue(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_down(dev, 0, 70, 99);
+       litest_touch_move_to(dev, 0, 70, 99, 20, 99, 10, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 }
 END_TEST
 
-START_TEST(touchpad_no_2fg_tap_after_timeout)
+START_TEST(touchpad_scroll_defaults)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-
-       litest_drain_events(dev->libinput);
-
-       /* one finger down, wait past tap timeout,
-          second finger down, first finger up
-          -> no event
-        */
-       litest_touch_down(dev, 0, 50, 50);
-       libinput_dispatch(dev->libinput);
-       litest_timeout_tap();
-       libinput_dispatch(dev->libinput);
-       litest_drain_events(dev->libinput);
+       struct libinput_device *device = dev->libinput_device;
+       struct libevdev *evdev = dev->evdev;
+       enum libinput_config_scroll_method method, expected;
+       enum libinput_config_status status;
 
-       litest_touch_down(dev, 1, 70, 50);
-       litest_touch_up(dev, 0);
+       method = libinput_device_config_scroll_get_methods(device);
+       ck_assert(method & LIBINPUT_CONFIG_SCROLL_EDGE);
+       if (libevdev_get_num_slots(evdev) > 1)
+               ck_assert(method & LIBINPUT_CONFIG_SCROLL_2FG);
+       else
+               ck_assert((method & LIBINPUT_CONFIG_SCROLL_2FG) == 0);
+
+       if (libevdev_get_num_slots(evdev) > 1)
+               expected = LIBINPUT_CONFIG_SCROLL_2FG;
+       else
+               expected = LIBINPUT_CONFIG_SCROLL_EDGE;
+
+       method = libinput_device_config_scroll_get_method(device);
+       ck_assert_int_eq(method, expected);
+       method = libinput_device_config_scroll_get_default_method(device);
+       ck_assert_int_eq(method, expected);
+
+       status = libinput_device_config_scroll_set_method(device,
+                                         LIBINPUT_CONFIG_SCROLL_EDGE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_scroll_set_method(device,
+                                         LIBINPUT_CONFIG_SCROLL_2FG);
 
-       litest_assert_empty_queue(li);
+       if (libevdev_get_num_slots(evdev) > 1)
+               ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       else
+               ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 }
 END_TEST
 
-START_TEST(touchpad_no_first_fg_tap_after_move)
+START_TEST(touchpad_edge_scroll_timeout)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
        struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       double width = 0, height = 0;
+       int nevents = 0;
+       double mm; /* one mm in percent of the device */
 
-       litest_drain_events(dev->libinput);
+       ck_assert_int_eq(libinput_device_get_size(dev->libinput_device,
+                                                 &width,
+                                                 &height), 0);
+       mm = 100.0/height;
 
-       /* one finger down, second finger down,
-          second finger moves beyond threshold,
-          first finger up
-          -> no event
-        */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
-       libinput_dispatch(dev->libinput);
-       litest_touch_move_to(dev, 1, 70, 50, 90, 90, 10, 0);
-       libinput_dispatch(dev->libinput);
+       /* timeout-based scrolling is disabled when software buttons are
+        * active, so switch to clickfinger. Not all test devices support
+        * that, hence the extra check. */
+       if (libinput_device_config_click_get_methods(dev->libinput_device) &
+           LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER)
+               litest_enable_clickfinger(dev);
+
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
+
+       /* move 0.5mm, enough to load up the motion history, but less than
+        * the scroll threshold of 2mm */
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 20 + mm/2, 8, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_edgescroll();
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+
+       /* now move slowly up to the 2mm scroll threshold. we expect events */
+       litest_touch_move_to(dev, 0, 99, 20 + mm/2, 99, 20 + mm * 2, 20, 0);
        litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
-       libinput_dispatch(dev->libinput);
+       libinput_dispatch(li);
+
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
 
        while ((event = libinput_get_event(li))) {
-               ck_assert_int_ne(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_BUTTON);
+               double value;
+
+               ptrev = litest_is_axis_event(event,
+                                            LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                            0);
+               value = libinput_event_pointer_get_axis_value(ptrev,
+                                                             LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               ck_assert_double_lt(value, 5.0);
                libinput_event_destroy(event);
+               nevents++;
        }
+
+       /* we sent 20 events but allow for some to be swallowed by rounding
+        * errors, the hysteresis, etc. */
+       ck_assert_int_ge(nevents, 10);
+
+       litest_assert_empty_queue(li);
+       libinput_event_destroy(event);
 }
 END_TEST
 
-START_TEST(touchpad_1fg_double_tap_click)
+START_TEST(touchpad_edge_scroll_no_motion)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
-
-       litest_drain_events(dev->libinput);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       /* one finger down, up, down, button click, finger up
-          -> two button left event pairs */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 50, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 10);
+       litest_touch_move_to(dev, 0, 99, 10, 99, 70, 12, 0);
+       /* moving outside -> no motion event */
+       litest_touch_move_to(dev, 0, 99, 70, 20, 80, 12, 0);
+       /* moving down outside edge once scrolling had started -> scroll */
+       litest_touch_move_to(dev, 0, 20, 80, 40, 99, 12, 0);
        litest_touch_up(dev, 0);
-
        libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-
+       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 4);
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_1fg_tap_n_drag_click)
+START_TEST(touchpad_edge_scroll_no_edge_after_motion)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
-
-       litest_drain_events(dev->libinput);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       /* one finger down, up, down, move, button click, finger up
-          -> two button left event pairs, motion allowed */
-       litest_touch_down(dev, 0, 50, 50);
+       /* moving into the edge zone must not trigger scroll events */
+       litest_touch_down(dev, 0, 20, 20);
+       litest_touch_move_to(dev, 0, 20, 20, 99, 20, 12, 0);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 12, 0);
        litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 80, 50, 10, 0);
-
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_dispatch(li);
 
        litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       litest_assert_empty_queue(li);
+}
+END_TEST
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+START_TEST(touchpad_edge_scroll_source)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
        litest_touch_up(dev, 0);
 
-       libinput_dispatch(li);
-
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
 
-       litest_assert_empty_queue(li);
+       while ((event = libinput_get_event(li))) {
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_AXIS);
+               ptrev = libinput_event_get_pointer_event(event);
+               ck_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
+                                LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+               libinput_event_destroy(event);
+       }
 }
 END_TEST
 
-START_TEST(touchpad_3fg_tap)
+START_TEST(touchpad_edge_scroll_no_2fg)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       int i;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       litest_drain_events(li);
+       litest_enable_edge_scroll(dev);
 
-       for (i = 0; i < 3; i++) {
-               litest_drain_events(li);
+       litest_touch_down(dev, 0, 49, 50);
+       litest_touch_down(dev, 1, 51, 50);
+       litest_touch_move_two_touches(dev, 49, 50, 51, 50, 20, 30, 5, 0);
+       libinput_dispatch(li);
+       litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
+       libinput_dispatch(li);
 
-               litest_touch_down(dev, 0, 50, 50);
-               litest_touch_down(dev, 1, 70, 50);
-               litest_touch_down(dev, 2, 80, 50);
+       litest_assert_empty_queue(li);
+}
+END_TEST
 
-               litest_touch_up(dev, (i + 2) % 3);
-               litest_touch_up(dev, (i + 1) % 3);
-               litest_touch_up(dev, (i + 0) % 3);
+START_TEST(touchpad_edge_scroll_into_buttonareas)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
 
-               libinput_dispatch(li);
+       litest_enable_buttonareas(dev);
+       litest_enable_edge_scroll(dev);
+       litest_drain_events(li);
 
-               litest_assert_button_event(li, BTN_MIDDLE,
-                                          LIBINPUT_BUTTON_STATE_PRESSED);
-               litest_timeout_tap();
-               litest_assert_button_event(li, BTN_MIDDLE,
-                                          LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 99, 40);
+       litest_touch_move_to(dev, 0, 99, 40, 99, 95, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+       /* in the button zone now, make sure we still get events */
+       litest_touch_move_to(dev, 0, 99, 95, 99, 100, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-               libinput_dispatch(li);
-               event = libinput_get_event(li);
-               ck_assert(event == NULL);
-       }
+       /* and out of the zone again */
+       litest_touch_move_to(dev, 0, 99, 100, 99, 70, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+
+       /* still out of the zone */
+       litest_touch_move_to(dev, 0, 99, 70, 99, 50, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 }
 END_TEST
 
-START_TEST(touchpad_3fg_tap_btntool)
+START_TEST(touchpad_edge_scroll_within_buttonareas)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device, 1);
+       if (!touchpad_has_horiz_edge_scroll_size(dev))
+               return;
 
+       litest_enable_buttonareas(dev);
+       litest_enable_edge_scroll(dev);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 1);
-       litest_touch_up(dev, 0);
+       litest_touch_down(dev, 0, 20, 99);
 
-       libinput_dispatch(li);
+       /* within left button */
+       litest_touch_move_to(dev, 0, 20, 99, 40, 99, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-       litest_assert_button_event(li, BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_timeout_tap();
-       litest_assert_button_event(li, BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       /* over to right button */
+       litest_touch_move_to(dev, 0, 40, 99, 60, 99, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-       libinput_dispatch(li);
-       event = libinput_get_event(li);
-       ck_assert(event == NULL);
+       /* within right button */
+       litest_touch_move_to(dev, 0, 60, 99, 80, 99, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 }
 END_TEST
 
-START_TEST(touchpad_3fg_tap_btntool_inverted)
+START_TEST(touchpad_edge_scroll_buttonareas_click_stops_scroll)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
        struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       double val;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device, 1);
+       if (!touchpad_has_horiz_edge_scroll_size(dev))
+               return;
 
+       litest_enable_buttonareas(dev);
+       litest_enable_edge_scroll(dev);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
+       litest_touch_down(dev, 0, 20, 95);
+       litest_touch_move_to(dev, 0, 20, 95, 70, 95, 10, 5);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
+       litest_button_click(dev, BTN_LEFT, true);
        libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_timeout_tap();
-       litest_assert_button_event(li, BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       event = libinput_get_event(li);
+       ptrev = litest_is_axis_event(event,
+                                    LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+       val = libinput_event_pointer_get_axis_value(ptrev,
+                                   LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
+       ck_assert(val == 0.0);
+       libinput_event_destroy(event);
 
-       libinput_dispatch(li);
        event = libinput_get_event(li);
-       ck_assert(event == NULL);
-}
-END_TEST
+       ptrev = litest_is_button_event(event,
+                                      BTN_RIGHT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
 
-START_TEST(touchpad_click_defaults_clickfinger)
-{
-       struct litest_device *dev = litest_current_device();
-       struct libinput_device *device = dev->libinput_device;
-       uint32_t methods, method;
-       enum libinput_config_status status;
+       libinput_event_destroy(event);
 
-       /* call this test for apple touchpads */
+       /* within button areas -> no movement */
+       litest_touch_move_to(dev, 0, 70, 95, 90, 95, 10, 0);
+       litest_assert_empty_queue(li);
 
-       methods = libinput_device_config_click_get_methods(device);
-       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       litest_button_click(dev, BTN_LEFT, false);
 
-       method = libinput_device_config_click_get_method(device);
-       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       method = libinput_device_config_click_get_default_method(device);
-       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
 
-       status = libinput_device_config_click_set_method(device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-       status = libinput_device_config_click_set_method(device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_NONE);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_touch_up(dev, 0);
 }
 END_TEST
 
-START_TEST(touchpad_click_defaults_btnarea)
+START_TEST(touchpad_edge_scroll_clickfinger_click_stops_scroll)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput_device *device = dev->libinput_device;
-       uint32_t methods, method;
-       enum libinput_config_status status;
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       double val;
+
+       if (!touchpad_has_horiz_edge_scroll_size(dev))
+               return;
 
-       /* call this test for non-apple clickpads */
+       litest_enable_clickfinger(dev);
+       litest_enable_edge_scroll(dev);
+       litest_drain_events(li);
 
-       methods = libinput_device_config_click_get_methods(device);
-       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert(methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       litest_touch_down(dev, 0, 20, 95);
+       litest_touch_move_to(dev, 0, 20, 95, 70, 95, 10, 5);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-       method = libinput_device_config_click_get_method(device);
-       ck_assert_int_eq(method,  LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       method = libinput_device_config_click_get_default_method(device);
-       ck_assert_int_eq(method,  LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
+       litest_button_click(dev, BTN_LEFT, true);
+       libinput_dispatch(li);
 
-       status = libinput_device_config_click_set_method(device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-       status = libinput_device_config_click_set_method(device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_NONE);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       event = libinput_get_event(li);
+       ptrev = litest_is_axis_event(event,
+                                    LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
+                                    LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+       val = libinput_event_pointer_get_axis_value(ptrev,
+                                   LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
+       ck_assert(val == 0.0);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+
+       libinput_event_destroy(event);
+
+       /* clickfinger releases pointer -> expect movement */
+       litest_touch_move_to(dev, 0, 70, 95, 90, 95, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       litest_assert_empty_queue(li);
+
+       litest_button_click(dev, BTN_LEFT, false);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+
+       litest_touch_up(dev, 0);
 }
 END_TEST
 
-START_TEST(touchpad_click_defaults_none)
+START_TEST(touchpad_edge_scroll_into_area)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput_device *device = dev->libinput_device;
-       uint32_t methods, method;
-       enum libinput_config_status status;
+       struct libinput *li = dev->libinput;
 
-       /* call this test for non-clickpads */
+       litest_enable_edge_scroll(dev);
+       litest_drain_events(li);
 
-       methods = libinput_device_config_click_get_methods(device);
-       ck_assert_int_eq(methods, 0);
+       /* move into area, move vertically, move back to edge */
 
-       method = libinput_device_config_click_get_method(device);
-       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_NONE);
-       method = libinput_device_config_click_get_default_method(device);
-       ck_assert_int_eq(method, LIBINPUT_CONFIG_CLICK_METHOD_NONE);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 50, 15, 2);
+       litest_touch_move_to(dev, 0, 99, 50, 20, 50, 15, 2);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_AXIS);
+       litest_touch_move_to(dev, 0, 20, 50, 20, 20, 15, 2);
+       litest_touch_move_to(dev, 0, 20, 20, 99, 20, 15, 2);
+       litest_assert_empty_queue(li);
 
-       status = libinput_device_config_click_set_method(device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
-       status = libinput_device_config_click_set_method(device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+       litest_touch_move_to(dev, 0, 99, 20, 99, 50, 15, 2);
+       litest_assert_only_typed_events(li,
+                                       LIBINPUT_EVENT_POINTER_AXIS);
 }
 END_TEST
 
+static int
+touchpad_has_palm_detect_size(struct litest_device *dev)
+{
+       double width, height;
+       unsigned int vendor;
+       int rc;
+
+       vendor = libinput_device_get_id_vendor(dev->libinput_device);
+       if (vendor == VENDOR_ID_WACOM)
+               return 0;
+       if (vendor == VENDOR_ID_APPLE)
+               return 1;
+
+       rc = libinput_device_get_size(dev->libinput_device, &width, &height);
+
+       return rc == 0 && width >= 70;
+}
 
-START_TEST(touchpad_1fg_clickfinger)
+START_TEST(touchpad_palm_detect_at_edge)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
+
+       litest_enable_2fg_scroll(dev);
+
+       litest_disable_tap(dev->libinput_device);
 
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 50);
+       litest_touch_move_to(dev, 0, 99, 50, 99, 70, 5, 0);
        litest_touch_up(dev, 0);
 
-       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 5, 50);
+       litest_touch_move_to(dev, 0, 5, 50, 5, 70, 5, 0);
+       litest_touch_up(dev, 0);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_clickfinger)
+START_TEST(touchpad_no_palm_detect_at_edge_for_edge_scrolling)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!touchpad_has_palm_detect_size(dev))
+               return;
+
+       litest_enable_edge_scroll(dev);
 
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 70);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 50);
+       litest_touch_move_to(dev, 0, 99, 50, 99, 70, 5, 0);
        litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
-
-       libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 }
 END_TEST
 
-START_TEST(touchpad_clickfinger_to_area_method)
+START_TEST(touchpad_palm_detect_at_bottom_corners)
 {
        struct litest_device *dev = litest_current_device();
-       enum libinput_config_status status;
        struct libinput *li = dev->libinput;
 
-       litest_drain_events(li);
-
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_enable_2fg_scroll(dev);
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_disable_tap(dev->libinput_device);
 
+       /* Run for non-clickpads only: make sure the bottom corners trigger
+          palm detection too */
        litest_drain_events(li);
 
-       /* use bottom right corner to catch accidental softbutton right */
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 95);
+       litest_touch_move_to(dev, 0, 99, 95, 99, 99, 10, 0);
        litest_touch_up(dev, 0);
-       libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
 
+       litest_touch_down(dev, 0, 5, 95);
+       litest_touch_move_to(dev, 0, 5, 95, 5, 99, 5, 0);
+       litest_touch_up(dev, 0);
 }
 END_TEST
 
-START_TEST(touchpad_clickfinger_to_area_method_while_down)
+START_TEST(touchpad_palm_detect_at_top_corners)
 {
        struct litest_device *dev = litest_current_device();
-       enum libinput_config_status status;
        struct libinput *li = dev->libinput;
 
-       litest_drain_events(li);
-
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       libinput_dispatch(li);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_enable_2fg_scroll(dev);
 
+       litest_disable_tap(dev->libinput_device);
 
+       /* Run for non-clickpads only: make sure the bottom corners trigger
+          palm detection too */
        litest_drain_events(li);
 
-       /* use bottom right corner to catch accidental softbutton right */
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 5);
+       litest_touch_move_to(dev, 0, 99, 5, 99, 9, 10, 0);
        litest_touch_up(dev, 0);
-       libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
 
+       litest_touch_down(dev, 0, 5, 5);
+       litest_touch_move_to(dev, 0, 5, 5, 5, 9, 5, 0);
+       litest_touch_up(dev, 0);
 }
 END_TEST
 
-START_TEST(touchpad_area_to_clickfinger_method)
+START_TEST(touchpad_palm_detect_palm_stays_palm)
 {
        struct litest_device *dev = litest_current_device();
-       enum libinput_config_status status;
        struct libinput *li = dev->libinput;
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-
-       litest_drain_events(li);
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
 
-       /* use bottom right corner to catch accidental softbutton right */
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
+       litest_enable_2fg_scroll(dev);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_disable_tap(dev->libinput_device);
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 20);
+       litest_touch_move_to(dev, 0, 99, 20, 75, 99, 10, 0);
        litest_touch_up(dev, 0);
-       libinput_dispatch(li);
-
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_area_to_clickfinger_method_while_down)
+START_TEST(touchpad_palm_detect_palm_becomes_pointer)
 {
        struct litest_device *dev = litest_current_device();
-       enum libinput_config_status status;
        struct libinput *li = dev->libinput;
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
 
-       litest_drain_events(li);
+       litest_enable_2fg_scroll(dev);
 
-       /* use bottom right corner to catch accidental softbutton right */
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_disable_tap(dev->libinput_device);
 
-       status = libinput_device_config_click_set_method(dev->libinput_device,
-                                                        LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_drain_events(li);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 99, 50);
+       litest_touch_move_to(dev, 0, 99, 50, 0, 70, 20, 0);
        litest_touch_up(dev, 0);
-       libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
        libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_btn_left)
+START_TEST(touchpad_palm_detect_no_palm_moving_into_edges)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
+       if (!touchpad_has_palm_detect_size(dev))
+               return;
+
+       litest_disable_tap(dev->libinput_device);
+
+       /* moving non-palm into the edge does not label it as palm */
        litest_drain_events(li);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 99, 50, 10, 0);
+
+       litest_drain_events(li);
 
+       litest_touch_move_to(dev, 0, 99, 50, 99, 90, 10, 0);
        libinput_dispatch(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_1fg_tap_click)
+START_TEST(touchpad_palm_detect_tap_hardbuttons)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       if (!touchpad_has_palm_detect_size(dev))
+               return;
 
-       litest_drain_events(dev->libinput);
+       litest_enable_tap(dev->libinput_device);
 
-       /* finger down, button click, finger up
-          -> only one button left event pair */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 95, 5);
        litest_touch_up(dev, 0);
-       libinput_dispatch(li);
-       litest_timeout_tap();
+       litest_assert_empty_queue(li);
 
-       libinput_dispatch(li);
+       litest_touch_down(dev, 0, 5, 5);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 5, 99);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
+       litest_touch_down(dev, 0, 95, 99);
+       litest_touch_up(dev, 0);
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_btn_left)
+START_TEST(touchpad_palm_detect_tap_softbuttons)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       litest_drain_events(li);
-
-       /* A clickpad always needs a finger down to tell where the
-          click happens */
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       if (!touchpad_has_palm_detect_size(dev))
+               return;
 
-       libinput_dispatch(li);
-       ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_buttonareas(dev);
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 95, 5);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 5, 5);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 5, 99);
+       litest_touch_up(dev, 0);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
+
+       litest_touch_down(dev, 0, 95, 99);
+       litest_touch_up(dev, 0);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_click_n_drag)
+START_TEST(touchpad_palm_detect_tap_clickfinger)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
-       litest_drain_events(li);
-
-       litest_touch_down(dev, 0, 50, 50);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       if (!touchpad_has_palm_detect_size(dev))
+               return;
 
-       libinput_dispatch(li);
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
 
-       libinput_dispatch(li);
-       ck_assert_int_eq(libinput_next_event_type(li), LIBINPUT_EVENT_NONE);
+       litest_drain_events(li);
 
-       /* now put a second finger down */
-       litest_touch_down(dev, 1, 70, 70);
-       litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5, 0);
-       litest_touch_up(dev, 1);
+       litest_touch_down(dev, 0, 95, 5);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       litest_touch_down(dev, 0, 5, 5);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 5, 99);
        litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li, BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 95, 99);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_left)
+START_TEST(touchpad_no_palm_detect_2fg_scroll)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
 
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
+
+       litest_enable_2fg_scroll(dev);
+
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 10, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       /* first finger is palm, second finger isn't so we trigger 2fg
+        * scrolling */
+       litest_touch_down(dev, 0, 99, 50);
+       litest_touch_move_to(dev, 0, 99, 50, 99, 40, 10, 0);
+       litest_touch_move_to(dev, 0, 99, 40, 99, 50, 10, 0);
+       litest_assert_empty_queue(li);
+       litest_touch_down(dev, 1, 50, 50);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_touch_move_two_touches(dev, 99, 50, 50, 50, 0, -20, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+}
+END_TEST
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
+START_TEST(touchpad_palm_detect_both_edges)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       if (!touchpad_has_palm_detect_size(dev) ||
+           !litest_has_2fg_scroll(dev))
+               return;
 
-       libinput_dispatch(li);
+       litest_enable_2fg_scroll(dev);
+
+       litest_drain_events(li);
+
+       /* two fingers moving up/down in the left/right palm zone must not
+        * generate events */
+       litest_touch_down(dev, 0, 99, 50);
+       litest_touch_move_to(dev, 0, 99, 50, 99, 40, 10, 0);
+       litest_touch_move_to(dev, 0, 99, 40, 99, 50, 10, 0);
+       litest_assert_empty_queue(li);
+       litest_touch_down(dev, 1, 1, 50);
+       litest_touch_move_to(dev, 1, 1, 50, 1, 40, 10, 0);
+       litest_touch_move_to(dev, 1, 1, 40, 1, 50, 10, 0);
+       litest_assert_empty_queue(li);
 
+       litest_touch_move_two_touches(dev, 99, 50, 1, 50, 0, -20, 10, 0);
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_right)
+START_TEST(touchpad_left_handed)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
-       litest_drain_events(li);
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_drain_events(li);
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_LEFT, 0);
 
        litest_assert_button_event(li,
                                   BTN_RIGHT,
-                           LIBINPUT_BUTTON_STATE_PRESSED);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
        litest_assert_button_event(li,
                                   BTN_RIGHT,
-                           LIBINPUT_BUTTON_STATE_RELEASED);
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
 
-       libinput_dispatch(li);
+       litest_button_click(dev, BTN_RIGHT, 1);
+       litest_button_click(dev, BTN_RIGHT, 0);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
 
-       litest_assert_empty_queue(li);
+       if (libevdev_has_event_code(dev->evdev,
+                                   EV_KEY,
+                                   BTN_MIDDLE)) {
+               litest_button_click(dev, BTN_MIDDLE, 1);
+               litest_button_click(dev, BTN_MIDDLE, 0);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_PRESSED);
+               litest_assert_button_event(li,
+                                          BTN_MIDDLE,
+                                          LIBINPUT_BUTTON_STATE_RELEASED);
+       }
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_left_tap_n_drag)
+START_TEST(touchpad_left_handed_clickpad)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
        litest_drain_events(li);
-
-       /* Tap in left button area, then finger down, button click
-               -> expect left button press/release and left button press
-          Release button, finger up
-               -> expect right button release
-        */
-       litest_touch_down(dev, 0, 20, 90);
+       litest_touch_down(dev, 0, 10, 90);
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_LEFT, 0);
        litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 20, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
 
        litest_assert_button_event(li,
-                                  BTN_LEFT,
-                           LIBINPUT_BUTTON_STATE_PRESSED);
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_drain_events(li);
+       litest_touch_down(dev, 0, 90, 90);
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_LEFT, 0);
+       litest_touch_up(dev, 0);
+
        litest_assert_button_event(li,
                                   BTN_LEFT,
-                           LIBINPUT_BUTTON_STATE_RELEASED);
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
        litest_assert_button_event(li,
                                   BTN_LEFT,
-                           LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_drain_events(li);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_LEFT, 0);
        litest_touch_up(dev, 0);
 
        litest_assert_button_event(li,
                                   BTN_LEFT,
-                           LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_empty_queue(li);
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_right_tap_n_drag)
+START_TEST(touchpad_left_handed_clickfinger)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
        litest_drain_events(li);
-
-       /* Tap in right button area, then finger down, button click
-               -> expect left button press/release and right button press
-          Release button, finger up
-               -> expect right button release
-        */
-       litest_touch_down(dev, 0, 90, 90);
+       litest_touch_down(dev, 0, 10, 90);
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_LEFT, 0);
        litest_touch_up(dev, 0);
-       litest_touch_down(dev, 0, 90, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
 
+       /* Clickfinger is unaffected by left-handed setting */
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_PRESSED);
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_drain_events(li);
+       litest_touch_down(dev, 0, 10, 90);
+       litest_touch_down(dev, 1, 30, 90);
+       litest_button_click(dev, BTN_LEFT, 1);
+       litest_button_click(dev, BTN_LEFT, 0);
        litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
 
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
        litest_assert_button_event(li,
                                   BTN_RIGHT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_left_1st_fg_move)
+START_TEST(touchpad_left_handed_tapping)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       double x = 0, y = 0;
-       int nevents = 0;
-
-       litest_drain_events(li);
-
-       /* One finger down in the left button area, button press
-               -> expect a button event
-          Move finger up out of the area, wait for timeout
-          Move finger around diagonally down left
-               -> expect motion events down left
-          Release finger
-               -> expect a button event */
+       enum libinput_config_status status;
 
-       /* finger down, press in left button */
-       litest_touch_down(dev, 0, 20, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_enable_tap(dev->libinput_device);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       /* move out of the area, then wait for softbutton timer */
-       litest_touch_move_to(dev, 0, 20, 90, 90, 20, 10, 0);
-       libinput_dispatch(li);
-       litest_timeout_softbuttons();
-       libinput_dispatch(li);
        litest_drain_events(li);
 
-       /* move down left, expect motion */
-       litest_touch_move_to(dev, 0, 90, 20, 20, 90, 10, 0);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
-       event = libinput_get_event(li);
-       ck_assert(event != NULL);
-       while (event) {
-               struct libinput_event_pointer *p;
-
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-               p = libinput_event_get_pointer_event(event);
-
-               /* we moved up/right, now down/left so the pointer accel
-                  code may lag behind with the dx/dy vectors. Hence, add up
-                  the x/y movements and expect that on average we moved
-                  left and down */
-               x += libinput_event_pointer_get_dx(p);
-               y += libinput_event_pointer_get_dy(p);
-               nevents++;
-
-               libinput_event_destroy(event);
-               libinput_dispatch(li);
-               event = libinput_get_event(li);
-       }
-
-       ck_assert(x/nevents < 0);
-       ck_assert(y/nevents > 0);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
+       litest_timeout_tap();
+       libinput_dispatch(li);
 
+       /* Tapping is unaffected by left-handed setting */
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
-
-       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_left_2nd_fg_move)
+START_TEST(touchpad_left_handed_tapping_2fg)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
+       enum libinput_config_status status;
 
-       litest_drain_events(li);
+       litest_enable_tap(dev->libinput_device);
 
-       /* One finger down in the left button area, button press
-               -> expect a button event
-          Put a second finger down in the area, move it right, release
-               -> expect motion events right
-          Put a second finger down in the area, move it down, release
-               -> expect motion events down
-          Release second finger, release first finger
-               -> expect a button event */
-       litest_touch_down(dev, 0, 20, 90);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
-
-       litest_touch_down(dev, 1, 20, 20);
-       litest_touch_move_to(dev, 1, 20, 20, 80, 20, 10, 0);
-
-       libinput_dispatch(li);
-       event = libinput_get_event(li);
-       ck_assert(event != NULL);
-       while (event) {
-               struct libinput_event_pointer *p;
-               double x, y;
-
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-               p = libinput_event_get_pointer_event(event);
-
-               x = libinput_event_pointer_get_dx(p);
-               y = libinput_event_pointer_get_dy(p);
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-               /* Ignore events only containing an unaccelerated motion
-                * vector. */
-               if (x != 0 || y != 0) {
-                       ck_assert(x > 0);
-                       ck_assert(y == 0);
-               }
+       litest_drain_events(li);
 
-               libinput_event_destroy(event);
-               libinput_dispatch(li);
-               event = libinput_get_event(li);
-       }
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_down(dev, 1, 70, 50);
        litest_touch_up(dev, 1);
-
-       /* second finger down */
-       litest_touch_down(dev, 1, 20, 20);
-       litest_touch_move_to(dev, 1, 20, 20, 20, 80, 10, 0);
+       litest_touch_up(dev, 0);
 
        libinput_dispatch(li);
-       event = libinput_get_event(li);
-       ck_assert(event != NULL);
-       while (event) {
-               struct libinput_event_pointer *p;
-               double x, y;
-
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-               p = libinput_event_get_pointer_event(event);
-
-               x = libinput_event_pointer_get_dx(p);
-               y = libinput_event_pointer_get_dy(p);
-
-               ck_assert(x == 0);
-               ck_assert(y > 0);
-
-               libinput_event_destroy(event);
-               libinput_dispatch(li);
-               event = libinput_get_event(li);
-       }
-
-       litest_touch_up(dev, 1);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
+       litest_timeout_tap();
+       libinput_dispatch(li);
 
+       /* Tapping is unaffected by left-handed setting */
        litest_assert_button_event(li,
-                                  BTN_LEFT,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
-
-       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_left_to_right)
+START_TEST(touchpad_left_handed_delayed)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
        litest_drain_events(li);
+       litest_button_click(dev, BTN_LEFT, 1);
+       libinput_dispatch(li);
 
-       /* One finger down in left software button area,
-          move to right button area immediately, click
-               -> expect right button event
-       */
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_touch_down(dev, 0, 20, 90);
-       litest_touch_move_to(dev, 0, 20, 90, 90, 90, 10, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_button_click(dev, BTN_LEFT, 0);
 
        litest_assert_button_event(li,
-                                  BTN_RIGHT,
+                                  BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
+       /* left-handed takes effect now */
+       litest_button_click(dev, BTN_RIGHT, 1);
+       libinput_dispatch(li);
+       litest_timeout_middlebutton();
+       libinput_dispatch(li);
+       litest_button_click(dev, BTN_LEFT, 1);
+       libinput_dispatch(li);
+
+       status = libinput_device_config_left_handed_set(d, 0);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
+       litest_button_click(dev, BTN_RIGHT, 0);
+       litest_button_click(dev, BTN_LEFT, 0);
+
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_RIGHT,
+                                  LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_assert_button_event(li,
+                                  BTN_LEFT,
+                                  LIBINPUT_BUTTON_STATE_RELEASED);
        litest_assert_button_event(li,
                                   BTN_RIGHT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
-
-       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_softbutton_right_to_left)
+START_TEST(touchpad_left_handed_clickpad_delayed)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
        litest_drain_events(li);
+       litest_touch_down(dev, 0, 10, 90);
+       litest_button_click(dev, BTN_LEFT, 1);
+       libinput_dispatch(li);
 
-       /* One finger down in right software button area,
-          move to left button area immediately, click
-               -> expect left button event
-       */
+       status = libinput_device_config_left_handed_set(d, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_touch_down(dev, 0, 90, 90);
-       litest_touch_move_to(dev, 0, 90, 90, 20, 90, 10, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_button_click(dev, BTN_LEFT, 0);
+       litest_touch_up(dev, 0);
 
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
 
-       litest_assert_empty_queue(li);
-}
-END_TEST
-
-START_TEST(clickpad_topsoftbuttons_left)
-{
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-
+       /* left-handed takes effect now */
        litest_drain_events(li);
+       litest_touch_down(dev, 0, 90, 90);
+       litest_button_click(dev, BTN_LEFT, 1);
+       libinput_dispatch(li);
 
-       litest_touch_down(dev, 0, 10, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       status = libinput_device_config_left_handed_set(d, 0);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_button_click(dev, BTN_LEFT, 0);
+       litest_touch_up(dev, 0);
 
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-
        litest_assert_button_event(li,
                                   BTN_LEFT,
                                   LIBINPUT_BUTTON_STATE_RELEASED);
-
-       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_topsoftbuttons_right)
+static void
+hover_continue(struct litest_device *dev, unsigned int slot,
+              int x, int y)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-
-       litest_drain_events(li);
-
-       litest_touch_down(dev, 0, 90, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, slot);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+       litest_event(dev, EV_ABS, ABS_X, x);
+       litest_event(dev, EV_ABS, ABS_Y, y);
+       litest_event(dev, EV_ABS, ABS_PRESSURE, 10);
+       litest_event(dev, EV_ABS, ABS_TOOL_WIDTH, 6);
+       /* WARNING: no SYN_REPORT! */
+}
 
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+static void
+hover_start(struct litest_device *dev, unsigned int slot,
+           int x, int y)
+{
+       static unsigned int tracking_id;
 
-       litest_assert_empty_queue(li);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, slot);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, ++tracking_id);
+       hover_continue(dev, slot, x, y);
+       /* WARNING: no SYN_REPORT! */
 }
-END_TEST
 
-START_TEST(clickpad_topsoftbuttons_middle)
+START_TEST(touchpad_semi_mt_hover_noevent)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
+       int i;
+       int x = 2400,
+           y = 2400;
 
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       hover_start(dev, 0, x, y);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
 
-       litest_assert_button_event(li,
-                                  BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_empty_queue(li);
+       for (i = 0; i < 10; i++) {
+               x += 200;
+               y -= 200;
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
 
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-
-       litest_assert_button_event(li,
-                                  BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
 
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(clickpad_topsoftbuttons_move_out_ignore)
+START_TEST(touchpad_semi_mt_hover_down)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-
-       /* Finger down in top button area, wait past enter timeout
-          Move into main area, wait past leave timeout
-          Click
-            -> expect no events
-        */
+       struct libinput_event *event;
+       int i;
+       int x = 2400,
+           y = 2400;
 
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 5);
-       libinput_dispatch(li);
-       litest_timeout_softbuttons();
-       libinput_dispatch(li);
-       litest_assert_empty_queue(li);
-
-       litest_touch_move_to(dev, 0, 50, 5, 80, 90, 20, 0);
-       libinput_dispatch(li);
-       litest_timeout_softbuttons();
-       libinput_dispatch(li);
-
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       hover_start(dev, 0, x, y);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
 
-       litest_touch_up(dev, 0);
+       for (i = 0; i < 10; i++) {
+               x += 200;
+               y -= 200;
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
 
        litest_assert_empty_queue(li);
-}
-END_TEST
 
-START_TEST(clickpad_topsoftbuttons_clickfinger)
-{
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-
-       libinput_device_config_click_set_method(dev->libinput_device,
-                                               LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       litest_drain_events(li);
-
-       litest_touch_down(dev, 0, 90, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
+       litest_event(dev, EV_ABS, ABS_X, x + 100);
+       litest_event(dev, EV_ABS, ABS_Y, y + 100);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_dispatch(li);
+       for (i = 0; i < 10; i++) {
+               x -= 200;
+               y += 200;
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
+
+       libinput_dispatch(li);
+
+       ck_assert_int_ne(libinput_next_event_type(li),
+                        LIBINPUT_EVENT_NONE);
+       while ((event = libinput_get_event(li)) != NULL) {
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_MOTION);
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+
+       /* go back to hover */
+       hover_continue(dev, 0, x, y);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       for (i = 0; i < 10; i++) {
+               x += 200;
+               y -= 200;
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
+
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_semi_mt_hover_down_hover_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int i, j;
+       int x = 1400,
+           y = 1400;
+
+       litest_drain_events(li);
+
+       /* hover */
+       hover_start(dev, 0, x, y);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_assert_empty_queue(li);
+
+       for (i = 0; i < 3; i++) {
+               /* touch */
+               litest_event(dev, EV_ABS, ABS_X, x + 100);
+               litest_event(dev, EV_ABS, ABS_Y, y + 100);
+               litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+
+               for (j = 0; j < 5; j++) {
+                       x += 200;
+                       y += 200;
+                       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+                       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+                       litest_event(dev, EV_ABS, ABS_X, x);
+                       litest_event(dev, EV_ABS, ABS_Y, y);
+                       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               }
+
+               libinput_dispatch(li);
+
+               ck_assert_int_ne(libinput_next_event_type(li),
+                                LIBINPUT_EVENT_NONE);
+               while ((event = libinput_get_event(li)) != NULL) {
+                       ck_assert_int_eq(libinput_event_get_type(event),
+                                        LIBINPUT_EVENT_POINTER_MOTION);
+                       libinput_event_destroy(event);
+                       libinput_dispatch(li);
+               }
+
+               /* go back to hover */
+               hover_continue(dev, 0, x, y);
+               litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+               for (j = 0; j < 5; j++) {
+                       x -= 200;
+                       y -= 200;
+                       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+                       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+                       litest_event(dev, EV_ABS, ABS_X, x);
+                       litest_event(dev, EV_ABS, ABS_Y, y);
+                       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               }
+
+               litest_assert_empty_queue(li);
+       }
+
+       /* touch */
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+
+       /* start a new touch to be sure */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+       litest_touch_up(dev, 0);
+
+       libinput_dispatch(li);
+       ck_assert_int_ne(libinput_next_event_type(li),
+                        LIBINPUT_EVENT_NONE);
+       while ((event = libinput_get_event(li)) != NULL) {
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_MOTION);
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+}
+END_TEST
+
+START_TEST(touchpad_semi_mt_hover_down_up)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       int i;
+       int x = 1400,
+           y = 1400;
+
+       litest_drain_events(li);
+
+       /* hover two fingers, then touch */
+       hover_start(dev, 0, x, y);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_assert_empty_queue(li);
+
+       hover_start(dev, 1, x, y);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+
+       /* hover first finger, end second in same frame */
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       /* now move the finger */
+       for (i = 0; i < 10; i++) {
+               litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               x -= 100;
+               y -= 100;
+       }
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+}
+END_TEST
+
+START_TEST(touchpad_semi_mt_hover_2fg_noevent)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       int i;
+       int x = 2400,
+           y = 2400;
+
+       litest_drain_events(li);
+
+       hover_start(dev, 0, x, y);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       hover_start(dev, 1, x + 500, y + 500);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       for (i = 0; i < 10; i++) {
+               x += 200;
+               y -= 200;
+               litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x + 500);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y + 500);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
+
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_semi_mt_hover_2fg_1fg_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       int i;
+       int x = 2400,
+           y = 2400;
+
+       litest_drain_events(li);
+
+       /* two slots active, but BTN_TOOL_FINGER only */
+       hover_start(dev, 0, x, y);
+       hover_start(dev, 1, x + 500, y + 500);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       for (i = 0; i < 10; i++) {
+               x += 200;
+               y -= 200;
+               litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
+               litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x + 500);
+               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y + 500);
+               litest_event(dev, EV_ABS, ABS_X, x);
+               litest_event(dev, EV_ABS, ABS_Y, y);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       }
+
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       libinput_dispatch(li);
+
+       ck_assert_int_ne(libinput_next_event_type(li),
+                        LIBINPUT_EVENT_NONE);
+       while ((event = libinput_get_event(li)) != NULL) {
+               ck_assert_int_eq(libinput_event_get_type(event),
+                                LIBINPUT_EVENT_POINTER_MOTION);
+               libinput_event_destroy(event);
+               libinput_dispatch(li);
+       }
+}
+END_TEST
+
+START_TEST(touchpad_semi_mt_hover_2fg_up)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_touch_down(dev, 0, 70, 50);
+       litest_touch_down(dev, 1, 50, 50);
+
+       litest_push_event_frame(dev);
+       litest_touch_move(dev, 0, 72, 50);
+       litest_touch_move(dev, 1, 52, 50);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_pop_event_frame(dev);
+
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+       litest_drain_events(li);
+}
+END_TEST
+
+START_TEST(touchpad_hover_noevent)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       litest_hover_start(dev, 0, 50, 50);
+       litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+       litest_hover_end(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_hover_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       /* hover the finger */
+       litest_hover_start(dev, 0, 50, 50);
+
+       litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+
+       litest_assert_empty_queue(li);
+
+       /* touch the finger on the sensor */
+       litest_touch_move_to(dev, 0, 70, 70, 50, 50, 10, 10);
+
+       libinput_dispatch(li);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       /* go back to hover */
+       litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+       litest_hover_end(dev, 0);
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_hover_down_hover_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       int i;
+
+       litest_drain_events(li);
+
+       litest_hover_start(dev, 0, 50, 50);
+
+       for (i = 0; i < 3; i++) {
+
+               /* hover the finger */
+               litest_hover_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+
+               litest_assert_empty_queue(li);
+
+               /* touch the finger */
+               litest_touch_move_to(dev, 0, 70, 70, 50, 50, 10, 10);
+
+               libinput_dispatch(li);
+
+               litest_assert_only_typed_events(li,
+                                               LIBINPUT_EVENT_POINTER_MOTION);
+       }
+
+       litest_hover_end(dev, 0);
+
+       /* start a new touch to be sure */
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+       litest_touch_up(dev, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+}
+END_TEST
+
+START_TEST(touchpad_hover_down_up)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       /* hover two fingers, and a touch */
+       litest_push_event_frame(dev);
+       litest_hover_start(dev, 0, 50, 50);
+       litest_hover_start(dev, 1, 50, 50);
+       litest_touch_down(dev, 2, 50, 50);
+       litest_pop_event_frame(dev);;
+
+       litest_assert_empty_queue(li);
+
+       /* hover first finger, end second and third in same frame */
+       litest_push_event_frame(dev);
+       litest_hover_move(dev, 0, 55, 55);
+       litest_hover_end(dev, 1);
+       litest_touch_up(dev, 2);
+       litest_pop_event_frame(dev);;
+
+       litest_assert_empty_queue(li);
+
+       /* now move the finger */
+       litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+
+       litest_touch_up(dev, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+}
+END_TEST
+
+START_TEST(touchpad_hover_2fg_noevent)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+
+       litest_drain_events(li);
+
+       /* hover two fingers */
+       litest_push_event_frame(dev);
+       litest_hover_start(dev, 0, 25, 25);
+       litest_hover_start(dev, 1, 50, 50);
+       litest_pop_event_frame(dev);;
+
+       litest_hover_move_two_touches(dev, 25, 25, 50, 50, 50, 50, 10, 0);
+
+       litest_push_event_frame(dev);
+       litest_hover_end(dev, 0);
+       litest_hover_end(dev, 1);
+       litest_pop_event_frame(dev);;
+
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_hover_2fg_1fg_down)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       int i;
+
+       litest_drain_events(li);
+
+       /* hover two fingers */
+       litest_push_event_frame(dev);
+       litest_hover_start(dev, 0, 25, 25);
+       litest_touch_down(dev, 1, 50, 50);
+       litest_pop_event_frame(dev);;
+
+       for (i = 0; i < 10; i++) {
+               litest_push_event_frame(dev);
+               litest_hover_move(dev, 0, 25 + 5 * i, 25 + 5 * i);
+               litest_touch_move(dev, 1, 50 + 5 * i, 50 - 5 * i);
+               litest_pop_event_frame(dev);;
+       }
+
+       litest_push_event_frame(dev);
+       litest_hover_end(dev, 0);
+       litest_touch_up(dev, 1);
+       litest_pop_event_frame(dev);;
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+}
+END_TEST
+
+static void
+assert_btnevent_from_device(struct litest_device *device,
+                           unsigned int button,
+                           enum libinput_button_state state)
+{
+       struct libinput *li = device->libinput;
+       struct libinput_event *e;
+
+       libinput_dispatch(li);
+       e = libinput_get_event(li);
+       litest_is_button_event(e, button, state);
+
+       litest_assert_ptr_eq(libinput_event_get_device(e), device->libinput_device);
+       libinput_event_destroy(e);
+}
+
+START_TEST(touchpad_trackpoint_buttons)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+
+       const struct buttons {
+               unsigned int device_value;
+               unsigned int real_value;
+       } buttons[] = {
+               { BTN_0, BTN_LEFT },
+               { BTN_1, BTN_RIGHT },
+               { BTN_2, BTN_MIDDLE },
+       };
+       const struct buttons *b;
+
+       trackpoint = litest_add_device(li,
+                                      LITEST_TRACKPOINT);
+       libinput_device_config_scroll_set_method(trackpoint->libinput_device,
+                                        LIBINPUT_CONFIG_SCROLL_NO_SCROLL);
+
+       litest_drain_events(li);
+
+       ARRAY_FOR_EACH(buttons, b) {
+               litest_button_click(touchpad, b->device_value, true);
+               assert_btnevent_from_device(trackpoint,
+                                           b->real_value,
+                                           LIBINPUT_BUTTON_STATE_PRESSED);
+
+               litest_button_click(touchpad, b->device_value, false);
+
+               assert_btnevent_from_device(trackpoint,
+                                           b->real_value,
+                                           LIBINPUT_BUTTON_STATE_RELEASED);
+       }
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(touchpad_trackpoint_mb_scroll)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+
+       trackpoint = litest_add_device(li,
+                                      LITEST_TRACKPOINT);
+
+       litest_drain_events(li);
+       litest_button_click(touchpad, BTN_2, true); /* middle */
+       libinput_dispatch(li);
+       litest_timeout_buttonscroll();
+       libinput_dispatch(li);
+       litest_event(trackpoint, EV_REL, REL_Y, -2);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -2);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -2);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_event(trackpoint, EV_REL, REL_Y, -2);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       litest_button_click(touchpad, BTN_2, false);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(touchpad_trackpoint_mb_click)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+       enum libinput_config_status status;
+
+       trackpoint = litest_add_device(li,
+                                      LITEST_TRACKPOINT);
+       status = libinput_device_config_scroll_set_method(
+                                 trackpoint->libinput_device,
+                                 LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_drain_events(li);
+       litest_button_click(touchpad, BTN_2, true); /* middle */
+       litest_button_click(touchpad, BTN_2, false);
+
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_MIDDLE,
+                                   LIBINPUT_BUTTON_STATE_PRESSED);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_MIDDLE,
+                                   LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(touchpad_trackpoint_buttons_softbuttons)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+
+       trackpoint = litest_add_device(li,
+                                      LITEST_TRACKPOINT);
+
+       litest_drain_events(li);
+
+       litest_touch_down(touchpad, 0, 95, 90);
+       litest_button_click(touchpad, BTN_LEFT, true);
+       litest_button_click(touchpad, BTN_1, true);
+       litest_button_click(touchpad, BTN_LEFT, false);
+       litest_touch_up(touchpad, 0);
+       litest_button_click(touchpad, BTN_1, false);
+
+       assert_btnevent_from_device(touchpad,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_PRESSED);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_PRESSED);
+       assert_btnevent_from_device(touchpad,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_RELEASED);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_touch_down(touchpad, 0, 95, 90);
+       litest_button_click(touchpad, BTN_LEFT, true);
+       litest_button_click(touchpad, BTN_1, true);
+       litest_button_click(touchpad, BTN_1, false);
+       litest_button_click(touchpad, BTN_LEFT, false);
+       litest_touch_up(touchpad, 0);
+
+       assert_btnevent_from_device(touchpad,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_PRESSED);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_PRESSED);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_RELEASED);
+       assert_btnevent_from_device(touchpad,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_RELEASED);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(touchpad_trackpoint_buttons_2fg_scroll)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+       struct libinput_event *e;
+       struct libinput_event_pointer *pev;
+       double val;
+
+       trackpoint = litest_add_device(li,
+                                      LITEST_TRACKPOINT);
+
+       litest_drain_events(li);
+
+       litest_touch_down(touchpad, 0, 49, 70);
+       litest_touch_down(touchpad, 1, 51, 70);
+       litest_touch_move_two_touches(touchpad, 49, 70, 51, 70, 0, -40, 10, 0);
+
+       libinput_dispatch(li);
+       litest_wait_for_event(li);
+
+       /* Make sure we get scroll events but _not_ the scroll release */
+       while ((e = libinput_get_event(li))) {
+               ck_assert_int_eq(libinput_event_get_type(e),
+                                LIBINPUT_EVENT_POINTER_AXIS);
+               pev = libinput_event_get_pointer_event(e);
+               val = libinput_event_pointer_get_axis_value(pev,
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               ck_assert(val != 0.0);
+               libinput_event_destroy(e);
+       }
+
+       litest_button_click(touchpad, BTN_1, true);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_PRESSED);
+
+       litest_touch_move_to(touchpad, 0, 40, 30, 40, 70, 10, 0);
+       litest_touch_move_to(touchpad, 1, 60, 30, 60, 70, 10, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+
+       while ((e = libinput_get_event(li))) {
+               ck_assert_int_eq(libinput_event_get_type(e),
+                                LIBINPUT_EVENT_POINTER_AXIS);
+               pev = libinput_event_get_pointer_event(e);
+               val = libinput_event_pointer_get_axis_value(pev,
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               ck_assert(val != 0.0);
+               libinput_event_destroy(e);
+       }
+
+       litest_button_click(touchpad, BTN_1, false);
+       assert_btnevent_from_device(trackpoint,
+                                   BTN_RIGHT,
+                                   LIBINPUT_BUTTON_STATE_RELEASED);
+
+       /* the movement lags behind the touch movement, so the first couple
+          events can be downwards even though we started scrolling up. do a
+          short scroll up, drain those events, then we can use
+          litest_assert_scroll() which tests for the trailing 0/0 scroll
+          for us.
+          */
+       litest_touch_move_to(touchpad, 0, 40, 70, 40, 60, 10, 0);
+       litest_touch_move_to(touchpad, 1, 60, 70, 60, 60, 10, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+       litest_touch_move_to(touchpad, 0, 40, 60, 40, 30, 10, 0);
+       litest_touch_move_to(touchpad, 1, 60, 60, 60, 30, 10, 0);
+
+       litest_touch_up(touchpad, 0);
+       litest_touch_up(touchpad, 1);
+
+       libinput_dispatch(li);
+
+       litest_assert_scroll(li,
+                            LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                            -1);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(touchpad_trackpoint_no_trackpoint)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct libinput *li = touchpad->libinput;
+
+       litest_drain_events(li);
+       litest_button_click(touchpad, BTN_0, true); /* left */
+       litest_button_click(touchpad, BTN_0, false);
+       litest_assert_empty_queue(li);
+
+       litest_button_click(touchpad, BTN_1, true); /* right */
+       litest_button_click(touchpad, BTN_1, false);
+       litest_assert_empty_queue(li);
+
+       litest_button_click(touchpad, BTN_2, true); /* middle */
+       litest_button_click(touchpad, BTN_2, false);
+       litest_assert_empty_queue(li);
+}
+END_TEST
+
+START_TEST(touchpad_initial_state)
+{
+       struct litest_device *dev;
+       struct libinput *libinput1, *libinput2;
+       struct libinput_event *ev1, *ev2;
+       struct libinput_event_pointer *p1, *p2;
+       int axis = _i; /* looped test */
+       int x = 40, y = 60;
+
+       dev = litest_current_device();
+       libinput1 = dev->libinput;
+
+       litest_disable_tap(dev->libinput_device);
+
+       litest_touch_down(dev, 0, x, y);
+       litest_touch_up(dev, 0);
+
+       /* device is now on some x/y value */
+       litest_drain_events(libinput1);
+
+       libinput2 = litest_create_context();
+       libinput_path_add_device(libinput2,
+                                libevdev_uinput_get_devnode(dev->uinput));
+       litest_drain_events(libinput2);
+
+       if (axis == ABS_X)
+               x = 30;
+       else
+               y = 30;
+       litest_touch_down(dev, 0, x, y);
+       litest_touch_move_to(dev, 0, x, y, 80, 80, 10, 1);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(libinput1);
+       libinput_dispatch(libinput2);
+
+       litest_wait_for_event(libinput1);
+       litest_wait_for_event(libinput2);
+
+       while (libinput_next_event_type(libinput1)) {
+               ev1 = libinput_get_event(libinput1);
+               ev2 = libinput_get_event(libinput2);
+
+               p1 = litest_is_motion_event(ev1);
+               p2 = litest_is_motion_event(ev2);
+
+               ck_assert_int_eq(libinput_event_get_type(ev1),
+                                libinput_event_get_type(ev2));
+
+               ck_assert_int_eq(libinput_event_pointer_get_dx(p1),
+                                libinput_event_pointer_get_dx(p2));
+               ck_assert_int_eq(libinput_event_pointer_get_dy(p1),
+                                libinput_event_pointer_get_dy(p2));
+               libinput_event_destroy(ev1);
+               libinput_event_destroy(ev2);
+       }
+
+       libinput_unref(libinput2);
+}
+END_TEST
+
+static inline bool
+has_disable_while_typing(struct litest_device *device)
+{
+       return libinput_device_config_dwt_is_available(device->libinput_device);
+}
+
+static inline struct litest_device *
+dwt_init_paired_keyboard(struct libinput *li,
+                        struct litest_device *touchpad)
+{
+       enum litest_device_type which = LITEST_KEYBOARD;
+
+       if (libevdev_get_id_vendor(touchpad->evdev) == VENDOR_ID_APPLE)
+               which = LITEST_APPLE_KEYBOARD;
+
+       return litest_add_device(li, which);
+}
+
+START_TEST(touchpad_dwt)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* within timeout - no events */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_dwt_short();
+       libinput_dispatch(li);
+
+       /* after timeout  - motion events*/
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_update_keyboard)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard, *yubikey;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       litest_disable_tap(touchpad->libinput_device);
+
+       /* Yubikey is initialized first */
+       yubikey = litest_add_device(li, LITEST_YUBIKEY);
+       litest_drain_events(li);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* within timeout - no events */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_dwt_short();
+       libinput_dispatch(li);
+
+       /* after timeout  - motion events*/
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+       litest_delete_device(yubikey);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_update_keyboard_with_state)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard, *yubikey;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       litest_disable_tap(touchpad->libinput_device);
+
+       /* Yubikey is initialized first */
+       yubikey = litest_add_device(li, LITEST_YUBIKEY);
+       litest_drain_events(li);
+
+       litest_keyboard_key(yubikey, KEY_A, true);
+       litest_keyboard_key(yubikey, KEY_A, false);
+       litest_keyboard_key(yubikey, KEY_A, true);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
+
+       litest_keyboard_key(yubikey, KEY_A, false);
+       litest_keyboard_key(yubikey, KEY_A, true);
+       litest_drain_events(li);
+
+       /* yubikey still has A down */
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_drain_events(li);
+
+       /* expected repairing, dwt should be disabled */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       /* release remaining key */
+       litest_keyboard_key(yubikey, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+       litest_delete_device(yubikey);
+}
+END_TEST
+START_TEST(touchpad_dwt_enable_touch)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* finger down after last key event, but
+          we're still within timeout - no events */
+       msleep(10);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_dwt_short();
+       libinput_dispatch(li);
+
+       /* same touch after timeout  - motion events*/
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_touch_hold)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       msleep(1); /* make sure touch starts after key press */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* touch still down - no events */
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_touch_move_to(touchpad, 0, 70, 50, 30, 50, 5, 1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* touch still down - no events */
+       litest_timeout_dwt_short();
+       libinput_dispatch(li);
+       litest_touch_move_to(touchpad, 0, 30, 50, 50, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_key_hold)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_key_hold_timeout)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       litest_timeout_dwt_long();
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
 
        litest_assert_empty_queue(li);
 
-       litest_touch_down(dev, 0, 90, 5);
-       litest_touch_down(dev, 1, 10, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       /* key is up, but still within timeout */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       /* expire timeout */
+       litest_timeout_dwt_long();
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(clickpad_topsoftbuttons_clickfinger_dev_disabled)
+START_TEST(touchpad_dwt_key_hold_timeout_existing_touch_cornercase)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-       struct litest_device *trackpoint = litest_add_device(li,
-                                                            LITEST_TRACKPOINT);
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       libinput_device_config_click_set_method(dev->libinput_device,
-                                               LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
-       libinput_device_config_send_events_set_mode(dev->libinput_device,
-                                                   LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       /* Note: this tests for the current behavior of a cornercase, and
+        * the behaviour is essentially a bug. If this test fails it may be
+        * because the buggy behavior was fixed.
+        */
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 90, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       litest_timeout_dwt_long();
+       libinput_dispatch(li);
 
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       /* Touch starting after re-issuing the dwt timeout */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
 
        litest_assert_empty_queue(li);
 
-       litest_touch_down(dev, 0, 90, 5);
-       litest_touch_down(dev, 1, 10, 5);
-       litest_event(dev, EV_KEY, BTN_LEFT, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_event(dev, EV_KEY, BTN_LEFT, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       /* key is up, but still within timeout */
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 5, 1);
+       litest_assert_empty_queue(li);
 
-       litest_assert_button_event(li,
-                                  BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_MIDDLE,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       /* Expire dwt timeout. Because the touch started after re-issuing
+        * the last timeout, it looks like the touch started after the last
+        * key press. Such touches are enabled for pointer motion by
+        * libinput when dwt expires.
+        * This is buggy behavior and not what a user would typically
+        * expect. But it's hard to trigger in real life too.
+        */
+       litest_timeout_dwt_long();
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       /* If the below check for motion event fails because no events are
+        * in the pipe, the buggy behavior was fixed and this test case
+        * can be removed */
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_delete_device(trackpoint);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
+START_TEST(touchpad_dwt_key_hold_timeout_existing_touch)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-static void
-test_2fg_scroll(struct litest_device *dev, double dx, double dy, int want_sleep)
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       litest_keyboard_key(keyboard, KEY_A, true);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       libinput_dispatch(li);
+       litest_timeout_dwt_long();
+       libinput_dispatch(li);
+
+       litest_assert_empty_queue(li);
+
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+       /* key is up, but still within timeout */
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 5, 1);
+       litest_assert_empty_queue(li);
+
+       /* expire timeout, but touch started before release */
+       litest_timeout_dwt_long();
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
+
+       litest_delete_device(keyboard);
+}
+END_TEST
+
+START_TEST(touchpad_dwt_type)
 {
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       int i;
 
-       litest_touch_down(dev, 0, 47, 50);
-       litest_touch_down(dev, 1, 53, 50);
+       if (!has_disable_while_typing(touchpad))
+               return;
 
-       litest_touch_move_to(dev, 0, 47, 50, 47 + dx, 50 + dy, 5, 0);
-       litest_touch_move_to(dev, 1, 53, 50, 53 + dx, 50 + dy, 5, 0);
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
 
-       /* Avoid a small scroll being seen as a tap */
-       if (want_sleep) {
-               libinput_dispatch(li);
-               litest_timeout_tap();
+       for (i = 0; i < 5; i++) {
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
                libinput_dispatch(li);
        }
 
-       litest_touch_up(dev, 1);
-       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
 
+       litest_timeout_dwt_long();
        libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
 }
+END_TEST
 
-START_TEST(touchpad_2fg_scroll)
+START_TEST(touchpad_dwt_type_short_timeout)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       int i;
 
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       test_2fg_scroll(dev, 0.1, 40, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 10);
-       test_2fg_scroll(dev, 0.1, -40, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -10);
-       test_2fg_scroll(dev, 40, 0.1, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 10);
-       test_2fg_scroll(dev, -40, 0.1, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -10);
+       for (i = 0; i < 5; i++) {
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
+               libinput_dispatch(li);
+       }
 
-       /* 2fg scroll smaller than the threshold should not generate events */
-       test_2fg_scroll(dev, 0.1, 0.1, 200);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_dwt_short();
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
        litest_assert_empty_queue(li);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_scroll_slow_distance)
+START_TEST(touchpad_dwt_modifier_no_dwt)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       struct libinput_event_pointer *ptrev;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       unsigned int modifiers[] = {
+               KEY_LEFTCTRL,
+               KEY_RIGHTCTRL,
+               KEY_LEFTALT,
+               KEY_RIGHTALT,
+               KEY_LEFTSHIFT,
+               KEY_RIGHTSHIFT,
+               KEY_FN,
+               KEY_CAPSLOCK,
+               KEY_TAB,
+               KEY_COMPOSE,
+               KEY_RIGHTMETA,
+               KEY_LEFTMETA,
+       };
+       unsigned int *key;
 
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 20, 30);
-       litest_touch_down(dev, 1, 40, 30);
-       litest_touch_move_to(dev, 0, 20, 30, 20, 50, 60, 10);
-       litest_touch_move_to(dev, 1, 40, 30, 40, 50, 60, 10);
-       litest_touch_up(dev, 1);
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
+       ARRAY_FOR_EACH(modifiers, key) {
+               litest_keyboard_key(keyboard, *key, true);
+               litest_keyboard_key(keyboard, *key, false);
+               libinput_dispatch(li);
 
-       event = libinput_get_event(li);
-       ck_assert_notnull(event);
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       /* last event is value 0, tested elsewhere */
-       while (libinput_next_event_type(li) != LIBINPUT_EVENT_NONE) {
-               double axisval;
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               ptrev = libinput_event_get_pointer_event(event);
+               litest_touch_down(touchpad, 0, 50, 50);
+               litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+               litest_touch_up(touchpad, 0);
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       }
 
-               axisval = libinput_event_pointer_get_axis_value(ptrev,
-                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
-               ck_assert(axisval > 0.0);
+       litest_delete_device(keyboard);
+}
+END_TEST
 
-               /* this is to verify we test the right thing, if the value
-                  is greater than scroll.threshold we triggered the wrong
-                  condition */
-               ck_assert(axisval < 5.0);
+START_TEST(touchpad_dwt_modifier_combo_no_dwt)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       unsigned int modifiers[] = {
+               KEY_LEFTCTRL,
+               KEY_RIGHTCTRL,
+               KEY_LEFTALT,
+               KEY_RIGHTALT,
+               KEY_LEFTSHIFT,
+               KEY_RIGHTSHIFT,
+               KEY_FN,
+               KEY_CAPSLOCK,
+               KEY_TAB,
+               KEY_COMPOSE,
+               KEY_RIGHTMETA,
+               KEY_LEFTMETA,
+       };
+       unsigned int *key;
 
-               libinput_event_destroy(event);
-               event = libinput_get_event(li);
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       ARRAY_FOR_EACH(modifiers, key) {
+               litest_keyboard_key(keyboard, *key, true);
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
+               litest_keyboard_key(keyboard, KEY_B, true);
+               litest_keyboard_key(keyboard, KEY_B, false);
+               litest_keyboard_key(keyboard, *key, false);
+               libinput_dispatch(li);
+
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+               litest_touch_down(touchpad, 0, 50, 50);
+               litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+               litest_touch_up(touchpad, 0);
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
        }
 
-       litest_assert_empty_queue(li);
-       libinput_event_destroy(event);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_scroll_source)
+START_TEST(touchpad_dwt_modifier_combo_dwt_after)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       struct libinput_event_pointer *ptrev;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       unsigned int modifiers[] = {
+               KEY_LEFTCTRL,
+               KEY_RIGHTCTRL,
+               KEY_LEFTALT,
+               KEY_RIGHTALT,
+               KEY_LEFTSHIFT,
+               KEY_RIGHTSHIFT,
+               KEY_FN,
+               KEY_CAPSLOCK,
+               KEY_TAB,
+               KEY_COMPOSE,
+               KEY_RIGHTMETA,
+               KEY_LEFTMETA,
+       };
+       unsigned int *key;
 
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       test_2fg_scroll(dev, 0, 20, 0);
-       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
+       ARRAY_FOR_EACH(modifiers, key) {
+               litest_keyboard_key(keyboard, *key, true);
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
+               litest_keyboard_key(keyboard, *key, false);
+               libinput_dispatch(li);
 
-       while ((event = libinput_get_event(li))) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               ptrev = libinput_event_get_pointer_event(event);
-               ck_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
-                                LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
-               libinput_event_destroy(event);
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
+               libinput_dispatch(li);
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+               litest_touch_down(touchpad, 0, 50, 50);
+               litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+               litest_touch_up(touchpad, 0);
+               litest_assert_empty_queue(li);
+
+               litest_timeout_dwt_long();
+               libinput_dispatch(li);
        }
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_2fg_scroll_return_to_motion)
+START_TEST(touchpad_dwt_modifier_combo_dwt_remains)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       unsigned int modifiers[] = {
+               KEY_LEFTCTRL,
+               KEY_RIGHTCTRL,
+               KEY_LEFTALT,
+               KEY_RIGHTALT,
+               KEY_LEFTSHIFT,
+               KEY_RIGHTSHIFT,
+               KEY_FN,
+               KEY_CAPSLOCK,
+               KEY_TAB,
+               KEY_COMPOSE,
+               KEY_RIGHTMETA,
+               KEY_LEFTMETA,
+       };
+       unsigned int *key;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
 
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       /* start with motion */
-       litest_touch_down(dev, 0, 70, 70);
-       litest_touch_move_to(dev, 0, 70, 70, 47, 50, 10, 0);
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       ARRAY_FOR_EACH(modifiers, key) {
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
+               libinput_dispatch(li);
 
-       /* 2fg scroll */
-       litest_touch_down(dev, 1, 53, 50);
-       litest_touch_move_to(dev, 0, 47, 50, 47, 70, 5, 0);
-       litest_touch_move_to(dev, 1, 53, 50, 53, 70, 5, 0);
-       litest_touch_up(dev, 1);
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+               /* this can't really be tested directly. The above key
+                * should enable dwt, the next key continues and extends the
+                * timeout as usual (despite the modifier combo). but
+                * testing for timeout differences is fickle, so all we can
+                * test though is that dwt is still on after the modifier
+                * combo and does not get disabled immediately.
+                */
+               litest_keyboard_key(keyboard, *key, true);
+               litest_keyboard_key(keyboard, KEY_A, true);
+               litest_keyboard_key(keyboard, KEY_A, false);
+               litest_keyboard_key(keyboard, *key, false);
+               libinput_dispatch(li);
 
-       litest_touch_move_to(dev, 0, 47, 70, 47, 50, 10, 0);
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       /* back to 2fg scroll, lifting the other finger */
-       litest_touch_down(dev, 1, 50, 50);
-       litest_touch_move_to(dev, 0, 47, 50, 47, 70, 5, 0);
-       litest_touch_move_to(dev, 1, 53, 50, 53, 70, 5, 0);
-       litest_touch_up(dev, 0);
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+               litest_touch_down(touchpad, 0, 50, 50);
+               litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+               litest_touch_up(touchpad, 0);
+               litest_assert_empty_queue(li);
 
-       /* move with second finger */
-       litest_touch_move_to(dev, 1, 53, 70, 53, 50, 10, 0);
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+               litest_timeout_dwt_long();
+               libinput_dispatch(li);
+       }
 
-       litest_touch_up(dev, 1);
-       litest_assert_empty_queue(li);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_scroll_natural_defaults)
+START_TEST(touchpad_dwt_fkeys_no_dwt)
 {
-       struct litest_device *dev = litest_current_device();
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       unsigned int key;
 
-       ck_assert_int_ge(libinput_device_config_scroll_has_natural_scroll(dev->libinput_device), 1);
-       ck_assert_int_eq(libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device), 0);
-       ck_assert_int_eq(libinput_device_config_scroll_get_default_natural_scroll_enabled(dev->libinput_device), 0);
-}
-END_TEST
+       if (!has_disable_while_typing(touchpad))
+               return;
 
-START_TEST(touchpad_scroll_natural_enable_config)
-{
-       struct litest_device *dev = litest_current_device();
-       enum libinput_config_status status;
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
+
+       for (key = KEY_F1; key < KEY_CNT; key++) {
+               if (!libinput_device_keyboard_has_key(keyboard->libinput_device,
+                                                     key))
+                       continue;
+
+               litest_keyboard_key(keyboard, key, true);
+               litest_keyboard_key(keyboard, key, false);
+               libinput_dispatch(li);
+
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       status = libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-       ck_assert_int_eq(libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device), 1);
+               litest_touch_down(touchpad, 0, 50, 50);
+               litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
+               litest_touch_up(touchpad, 0);
+               litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       }
 
-       status = libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 0);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-       ck_assert_int_eq(libinput_device_config_scroll_get_natural_scroll_enabled(dev->libinput_device), 0);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_scroll_natural)
+START_TEST(touchpad_dwt_tap)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
 
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_enable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       libinput_device_config_scroll_set_natural_scroll_enabled(dev->libinput_device, 1);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       libinput_dispatch(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_up(touchpad, 0);
 
-       test_2fg_scroll(dev, 0.1, 40, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -10);
-       test_2fg_scroll(dev, 0.1, -40, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 10);
-       test_2fg_scroll(dev, 40, 0.1, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -10);
-       test_2fg_scroll(dev, -40, 0.1, 0);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 10);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
+       litest_timeout_dwt_short();
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_edge_scroll)
+START_TEST(touchpad_dwt_tap_drag)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-
-       litest_drain_events(li);
-
-       litest_touch_down(dev, 0, 99, 20);
-       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
-       litest_touch_up(dev, 0);
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       libinput_dispatch(li);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 10);
-       litest_assert_empty_queue(li);
+       if (!has_disable_while_typing(touchpad))
+               return;
 
-       litest_touch_down(dev, 0, 99, 80);
-       litest_touch_move_to(dev, 0, 99, 80, 99, 20, 10, 0);
-       litest_touch_up(dev, 0);
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_enable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
 
+       litest_keyboard_key(keyboard, KEY_A, true);
        libinput_dispatch(li);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -10);
-       litest_assert_empty_queue(li);
+       msleep(1); /* make sure touch starts after key press */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_up(touchpad, 0);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 5, 1);
 
-       litest_touch_down(dev, 0, 20, 99);
-       litest_touch_move_to(dev, 0, 20, 99, 70, 99, 10, 0);
-       litest_touch_up(dev, 0);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
+       litest_timeout_dwt_short();
        libinput_dispatch(li);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, 10);
-       litest_assert_empty_queue(li);
-
-       litest_touch_down(dev, 0, 70, 99);
-       litest_touch_move_to(dev, 0, 70, 99, 20, 99, 10, 0);
-       litest_touch_up(dev, 0);
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 50, 5, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       libinput_dispatch(li);
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, -10);
-       litest_assert_empty_queue(li);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_edge_scroll_slow_distance)
+START_TEST(touchpad_dwt_click)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       struct libinput_event_pointer *ptrev;
-
-       litest_drain_events(li);
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       litest_touch_down(dev, 0, 99, 20);
-       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 60, 10);
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
+       if (!has_disable_while_typing(touchpad))
+               return;
 
-       event = libinput_get_event(li);
-       ck_assert_notnull(event);
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
+       litest_drain_events(li);
 
-       while (libinput_next_event_type(li) != LIBINPUT_EVENT_NONE) {
-               double axisval;
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               ptrev = libinput_event_get_pointer_event(event);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-               axisval = libinput_event_pointer_get_axis_value(ptrev,
-                                LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
-               ck_assert(axisval > 0.0);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_button_click(touchpad, BTN_LEFT, true);
+       litest_button_click(touchpad, BTN_LEFT, false);
+       libinput_dispatch(li);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
 
-               /* this is to verify we test the right thing, if the value
-                  is greater than scroll.threshold we triggered the wrong
-                  condition */
-               ck_assert(axisval < 5.0);
+       litest_keyboard_key(keyboard, KEY_A, false);
 
-               libinput_event_destroy(event);
-               event = libinput_get_event(li);
-       }
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       litest_assert_empty_queue(li);
-       libinput_event_destroy(event);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_edge_scroll_no_motion)
+START_TEST(touchpad_dwt_edge_scroll)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       litest_enable_edge_scroll(touchpad);
 
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 20);
-       litest_touch_move_to(dev, 0, 99, 20, 99, 60, 10, 0);
-       /* moving outside -> no motion event */
-       litest_touch_move_to(dev, 0, 99, 60, 20, 80, 10, 0);
-       /* moving down outside edge once scrolling had started -> scroll */
-       litest_touch_move_to(dev, 0, 20, 80, 40, 99, 10, 0);
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, 5);
+       litest_touch_down(touchpad, 0, 99, 20);
+       libinput_dispatch(li);
+       litest_timeout_edgescroll();
+       libinput_dispatch(li);
        litest_assert_empty_queue(li);
-}
-END_TEST
-
-START_TEST(touchpad_edge_scroll_no_edge_after_motion)
-{
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
 
-       litest_drain_events(li);
+       /* edge scroll timeout is 300ms atm, make sure we don't accidentally
+          exit the DWT timeout */
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       /* moving into the edge zone must not trigger scroll events */
-       litest_touch_down(dev, 0, 20, 20);
-       litest_touch_move_to(dev, 0, 20, 20, 99, 20, 10, 0);
-       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
-       litest_touch_up(dev, 0);
+       litest_touch_move_to(touchpad, 0, 99, 20, 99, 80, 60, 10);
        libinput_dispatch(li);
+       litest_assert_empty_queue(li);
 
-       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+       litest_touch_move_to(touchpad, 0, 99, 80, 99, 20, 60, 10);
+       litest_touch_up(touchpad, 0);
+       libinput_dispatch(li);
        litest_assert_empty_queue(li);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_edge_scroll_source)
+START_TEST(touchpad_dwt_edge_scroll_interrupt)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       struct libinput_event_pointer *ptrev;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
+       struct libinput_event_pointer *stop_event;
 
+       if (!has_disable_while_typing(touchpad))
+               return;
+
+       litest_enable_edge_scroll(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 20);
-       litest_touch_move_to(dev, 0, 99, 20, 99, 80, 10, 0);
-       litest_touch_up(dev, 0);
+       litest_touch_down(touchpad, 0, 99, 20);
+       libinput_dispatch(li);
+       litest_timeout_edgescroll();
+       litest_touch_move_to(touchpad, 0, 99, 20, 99, 30, 10, 10);
+       libinput_dispatch(li);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-       litest_wait_for_event_of_type(li, LIBINPUT_EVENT_POINTER_AXIS, -1);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
 
-       while ((event = libinput_get_event(li))) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               ptrev = libinput_event_get_pointer_event(event);
-               ck_assert_int_eq(libinput_event_pointer_get_axis_source(ptrev),
-                                LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
-               libinput_event_destroy(event);
-       }
+       /* scroll stop event */
+       litest_wait_for_event(li);
+       stop_event = litest_is_axis_event(libinput_get_event(li),
+                                         LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+                                         LIBINPUT_POINTER_AXIS_SOURCE_FINGER);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(stop_event));
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_timeout_dwt_long();
+
+       /* Known bad behavior: a touch starting to edge-scroll before dwt
+        * kicks in will stop to scroll but be recognized as normal
+        * pointer-moving touch once the timeout expires. We'll fix that
+        * when we need to.
+        */
+       litest_touch_move_to(touchpad, 0, 99, 30, 99, 80, 10, 5);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_tap_is_available)
+START_TEST(touchpad_dwt_config_default_on)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       enum libinput_config_dwt_state state;
 
-       ck_assert_int_ge(libinput_device_config_tap_get_finger_count(dev->libinput_device), 1);
-       ck_assert_int_eq(libinput_device_config_tap_get_enabled(dev->libinput_device),
-                        LIBINPUT_CONFIG_TAP_DISABLED);
-}
-END_TEST
+       if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_WACOM ||
+           libevdev_get_id_bustype(dev->evdev) == BUS_BLUETOOTH) {
+               ck_assert(!libinput_device_config_dwt_is_available(device));
+               return;
+       }
 
-START_TEST(touchpad_tap_is_not_available)
-{
-       struct litest_device *dev = litest_current_device();
+       ck_assert(libinput_device_config_dwt_is_available(device));
+       state = libinput_device_config_dwt_get_enabled(device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DWT_ENABLED);
+       state = libinput_device_config_dwt_get_default_enabled(device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DWT_ENABLED);
+
+       status = libinput_device_config_dwt_set_enabled(device,
+                                       LIBINPUT_CONFIG_DWT_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_dwt_set_enabled(device,
+                                       LIBINPUT_CONFIG_DWT_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       ck_assert_int_eq(libinput_device_config_tap_get_finger_count(dev->libinput_device), 0);
-       ck_assert_int_eq(libinput_device_config_tap_get_enabled(dev->libinput_device),
-                        LIBINPUT_CONFIG_TAP_DISABLED);
-       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                                               LIBINPUT_CONFIG_TAP_ENABLED),
-                        LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+       status = libinput_device_config_dwt_set_enabled(device, 3);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
 }
 END_TEST
 
-START_TEST(touchpad_tap_default)
+START_TEST(touchpad_dwt_config_default_off)
 {
        struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       enum libinput_config_dwt_state state;
 
-       ck_assert_int_eq(libinput_device_config_tap_get_default_enabled(dev->libinput_device),
-                        LIBINPUT_CONFIG_TAP_DISABLED);
-}
-END_TEST
+       ck_assert(!libinput_device_config_dwt_is_available(device));
+       state = libinput_device_config_dwt_get_enabled(device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DWT_DISABLED);
+       state = libinput_device_config_dwt_get_default_enabled(device);
+       ck_assert_int_eq(state, LIBINPUT_CONFIG_DWT_DISABLED);
 
-START_TEST(touchpad_tap_invalid)
-{
-       struct litest_device *dev = litest_current_device();
+       status = libinput_device_config_dwt_set_enabled(device,
+                                       LIBINPUT_CONFIG_DWT_ENABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+       status = libinput_device_config_dwt_set_enabled(device,
+                                       LIBINPUT_CONFIG_DWT_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device, 2),
-                        LIBINPUT_CONFIG_STATUS_INVALID);
-       ck_assert_int_eq(libinput_device_config_tap_set_enabled(dev->libinput_device, -1),
-                        LIBINPUT_CONFIG_STATUS_INVALID);
+       status = libinput_device_config_dwt_set_enabled(device, 3);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
 }
 END_TEST
 
-static int
-touchpad_has_palm_detect_size(struct litest_device *dev)
+static inline void
+disable_dwt(struct litest_device *dev)
 {
-       double width, height;
-       int rc;
-
-       if (libinput_device_get_id_vendor(dev->libinput_device) == 0x5ac) /* Apple */
-               return 1;
-
-       rc = libinput_device_get_size(dev->libinput_device, &width, &height);
+       enum libinput_config_status status,
+                                   expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_dwt_set_enabled(dev->libinput_device,
+                                               LIBINPUT_CONFIG_DWT_DISABLED);
+       litest_assert_int_eq(status, expected);
+}
 
-       return rc == 0 && width >= 80;
+static inline void
+enable_dwt(struct litest_device *dev)
+{
+       enum libinput_config_status status,
+                                   expected = LIBINPUT_CONFIG_STATUS_SUCCESS;
+       status = libinput_device_config_dwt_set_enabled(dev->libinput_device,
+                                               LIBINPUT_CONFIG_DWT_ENABLED);
+       litest_assert_int_eq(status, expected);
 }
 
-START_TEST(touchpad_palm_detect_at_edge)
+START_TEST(touchpad_dwt_disabled)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       if (!touchpad_has_palm_detect_size(dev))
+       if (!has_disable_while_typing(touchpad))
                return;
 
+       disable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 50);
-       litest_touch_move_to(dev, 0, 99, 50, 99, 70, 5, 0);
-       litest_touch_up(dev, 0);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       litest_assert_empty_queue(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
 
-       litest_touch_down(dev, 0, 5, 50);
-       litest_touch_move_to(dev, 0, 5, 50, 5, 70, 5, 0);
-       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_palm_detect_at_bottom_corners)
+START_TEST(touchpad_dwt_disable_during_touch)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       if (!touchpad_has_palm_detect_size(dev))
+       if (!has_disable_while_typing(touchpad))
                return;
 
-       /* Run for non-clickpads only: make sure the bottom corners trigger
-          palm detection too */
+       enable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 95);
-       litest_touch_move_to(dev, 0, 99, 95, 99, 99, 10, 0);
-       litest_touch_up(dev, 0);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
        litest_assert_empty_queue(li);
 
-       litest_touch_down(dev, 0, 5, 95);
-       litest_touch_move_to(dev, 0, 5, 95, 5, 99, 5, 0);
-       litest_touch_up(dev, 0);
+       disable_dwt(touchpad);
+
+       /* touch already down -> keeps being ignored */
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 70, 10, 1);
+       litest_touch_up(touchpad, 0);
+
+       litest_assert_empty_queue(li);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_palm_detect_at_top_corners)
+START_TEST(touchpad_dwt_disable_before_touch)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       if (!touchpad_has_palm_detect_size(dev))
+       if (!has_disable_while_typing(touchpad))
                return;
 
-       /* Run for non-clickpads only: make sure the bottom corners trigger
-          palm detection too */
+       enable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 5);
-       litest_touch_move_to(dev, 0, 99, 5, 99, 9, 10, 0);
-       litest_touch_up(dev, 0);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
+       disable_dwt(touchpad);
+       libinput_dispatch(li);
+
+       /* touch down during timeout -> still discarded */
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
        litest_assert_empty_queue(li);
 
-       litest_touch_down(dev, 0, 5, 5);
-       litest_touch_move_to(dev, 0, 5, 5, 5, 9, 5, 0);
-       litest_touch_up(dev, 0);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_palm_detect_palm_stays_palm)
+START_TEST(touchpad_dwt_disable_during_key_release)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       if (!touchpad_has_palm_detect_size(dev))
+       if (!has_disable_while_typing(touchpad))
                return;
 
+       enable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 20);
-       litest_touch_move_to(dev, 0, 99, 20, 75, 99, 5, 0);
-       litest_touch_up(dev, 0);
-       litest_assert_empty_queue(li);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       disable_dwt(touchpad);
+       libinput_dispatch(li);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       /* touch down during timeout, wait, should generate events */
+       litest_touch_down(touchpad, 0, 50, 50);
+       libinput_dispatch(li);
+       litest_timeout_dwt_long();
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_palm_detect_palm_becomes_pointer)
+START_TEST(touchpad_dwt_disable_during_key_hold)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       if (!touchpad_has_palm_detect_size(dev))
+       if (!has_disable_while_typing(touchpad))
                return;
 
+       enable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 99, 50);
-       litest_touch_move_to(dev, 0, 99, 50, 0, 70, 5, 0);
-       litest_touch_up(dev, 0);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
+       disable_dwt(touchpad);
        libinput_dispatch(li);
 
+       /* touch down during timeout, wait, should generate events */
+       litest_touch_down(touchpad, 0, 50, 50);
+       libinput_dispatch(li);
+       litest_timeout_dwt_long();
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
        litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_assert_empty_queue(li);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_palm_detect_no_palm_moving_into_edges)
+START_TEST(touchpad_dwt_enable_during_touch)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput *li = dev->libinput;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       if (!touchpad_has_palm_detect_size(dev))
+       if (!has_disable_while_typing(touchpad))
                return;
 
-       /* moving non-palm into the edge does not label it as palm */
+       disable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 99, 50, 5, 0);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       litest_drain_events(li);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_touch_move_to(dev, 0, 99, 50, 99, 90, 5, 0);
-       libinput_dispatch(li);
+       enable_dwt(touchpad);
 
+       /* touch already down -> still sends events */
+       litest_touch_move_to(touchpad, 0, 70, 50, 50, 70, 10, 1);
+       litest_touch_up(touchpad, 0);
        litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_touch_up(dev, 0);
-       libinput_dispatch(li);
-       litest_assert_empty_queue(li);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_left_handed)
+START_TEST(touchpad_dwt_enable_before_touch)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
-       struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!has_disable_while_typing(touchpad))
+               return;
 
+       disable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
+       litest_disable_tap(touchpad->libinput_device);
        litest_drain_events(li);
-       litest_button_click(dev, BTN_LEFT, 1);
-       litest_button_click(dev, BTN_LEFT, 0);
 
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       litest_button_click(dev, BTN_RIGHT, 1);
-       litest_button_click(dev, BTN_RIGHT, 0);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       enable_dwt(touchpad);
+       libinput_dispatch(li);
 
-       if (libevdev_has_event_code(dev->evdev,
-                                   EV_KEY,
-                                   BTN_MIDDLE)) {
-               litest_button_click(dev, BTN_MIDDLE, 1);
-               litest_button_click(dev, BTN_MIDDLE, 0);
-               litest_assert_button_event(li,
-                                          BTN_MIDDLE,
-                                          LIBINPUT_BUTTON_STATE_PRESSED);
-               litest_assert_button_event(li,
-                                          BTN_MIDDLE,
-                                          LIBINPUT_BUTTON_STATE_RELEASED);
-       }
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_left_handed_clickpad)
+START_TEST(touchpad_dwt_enable_during_tap)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
-       struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!has_disable_while_typing(touchpad))
+               return;
 
+       litest_enable_tap(touchpad->libinput_device);
+       disable_dwt(touchpad);
+
+       keyboard = dwt_init_paired_keyboard(li, touchpad);
        litest_drain_events(li);
-       litest_touch_down(dev, 0, 10, 90);
-       litest_button_click(dev, BTN_LEFT, 1);
-       litest_button_click(dev, BTN_LEFT, 0);
-       litest_touch_up(dev, 0);
 
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
-       litest_drain_events(li);
-       litest_touch_down(dev, 0, 90, 90);
-       litest_button_click(dev, BTN_LEFT, 1);
-       litest_button_click(dev, BTN_LEFT, 0);
-       litest_touch_up(dev, 0);
+       litest_touch_down(touchpad, 0, 50, 50);
+       libinput_dispatch(li);
+       enable_dwt(touchpad);
+       libinput_dispatch(li);
+       litest_touch_up(touchpad, 0);
+       libinput_dispatch(li);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_timeout_tap();
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
 
-       litest_drain_events(li);
-       litest_touch_down(dev, 0, 50, 50);
-       litest_button_click(dev, BTN_LEFT, 1);
-       litest_button_click(dev, BTN_LEFT, 0);
-       litest_touch_up(dev, 0);
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_delete_device(keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_left_handed_clickfinger)
+START_TEST(touchpad_dwt_apple)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
-       struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *keyboard, *apple_keyboard;
+       struct libinput *li = touchpad->libinput;
 
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       ck_assert(has_disable_while_typing(touchpad));
 
+       /* Only the apple keyboard can trigger DWT */
+       keyboard = litest_add_device(li, LITEST_KEYBOARD);
        litest_drain_events(li);
-       litest_touch_down(dev, 0, 10, 90);
-       litest_button_click(dev, BTN_LEFT, 1);
-       litest_button_click(dev, BTN_LEFT, 0);
-       litest_touch_up(dev, 0);
 
-       /* Clickfinger is unaffected by left-handed setting */
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_keyboard_key(keyboard, KEY_A, true);
+       litest_keyboard_key(keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
 
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       apple_keyboard = litest_add_device(li, LITEST_APPLE_KEYBOARD);
        litest_drain_events(li);
-       litest_touch_down(dev, 0, 10, 90);
-       litest_touch_down(dev, 1, 30, 90);
-       litest_button_click(dev, BTN_LEFT, 1);
-       litest_button_click(dev, BTN_LEFT, 0);
-       litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
 
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_keyboard_key(apple_keyboard, KEY_A, true);
+       litest_keyboard_key(apple_keyboard, KEY_A, false);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_KEYBOARD_KEY);
+
+       litest_touch_down(touchpad, 0, 50, 50);
+       litest_touch_move_to(touchpad, 0, 50, 50, 70, 50, 10, 1);
+       litest_touch_up(touchpad, 0);
+       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+
+       litest_delete_device(keyboard);
+       litest_delete_device(apple_keyboard);
 }
 END_TEST
 
-START_TEST(touchpad_left_handed_tapping)
+static int
+has_thumb_detect(struct litest_device *dev)
 {
-       struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
-       struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
-
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
-
-       litest_drain_events(li);
+       double w, h;
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_up(dev, 0);
+       if (!libevdev_has_event_code(dev->evdev, EV_ABS, ABS_MT_PRESSURE))
+               return 0;
 
-       libinput_dispatch(li);
-       litest_timeout_tap();
-       libinput_dispatch(li);
+       if (libinput_device_get_size(dev->libinput_device, &w, &h) != 0)
+               return 0;
 
-       /* Tapping is unaffected by left-handed setting */
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       return h >= 50.0;
 }
-END_TEST
 
-START_TEST(touchpad_left_handed_tapping_2fg)
+START_TEST(touchpad_thumb_begin_no_motion)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-       libinput_device_config_tap_set_enabled(dev->libinput_device,
-                                              LIBINPUT_CONFIG_TAP_ENABLED);
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!has_thumb_detect(dev))
+               return;
+
+       litest_disable_tap(dev->libinput_device);
 
        litest_drain_events(li);
 
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_down(dev, 1, 70, 50);
+       litest_touch_down_extended(dev, 0, 50, 99, axes);
+       litest_touch_move_to(dev, 0, 50, 99, 80, 99, 10, 0);
        litest_touch_up(dev, 0);
-       litest_touch_up(dev, 1);
-
-       libinput_dispatch(li);
-       litest_timeout_tap();
-       libinput_dispatch(li);
 
-       /* Tapping is unaffected by left-handed setting */
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_left_handed_delayed)
+START_TEST(touchpad_thumb_update_no_motion)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
-
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-       litest_drain_events(li);
-       litest_button_click(dev, BTN_LEFT, 1);
-       libinput_dispatch(li);
+       litest_disable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
 
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!has_thumb_detect(dev))
+               return;
 
-       litest_button_click(dev, BTN_LEFT, 0);
+       litest_drain_events(li);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 59, 99);
+       litest_touch_move_extended(dev, 0, 59, 99, axes);
+       litest_touch_move_to(dev, 0, 60, 99, 80, 99, 10, 0);
+       litest_touch_up(dev, 0);
 
-       /* left-handed takes effect now */
-       litest_button_click(dev, BTN_RIGHT, 1);
-       litest_button_click(dev, BTN_LEFT, 1);
-       libinput_dispatch(li);
+       litest_assert_empty_queue(li);
+}
+END_TEST
 
-       status = libinput_device_config_left_handed_set(d, 0);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+START_TEST(touchpad_thumb_moving)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-       litest_button_click(dev, BTN_RIGHT, 0);
-       litest_button_click(dev, BTN_LEFT, 0);
+       litest_disable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_assert_button_event(li,
-                                  BTN_RIGHT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       if (!has_thumb_detect(dev))
+               return;
+
+       litest_drain_events(li);
+
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_move_to(dev, 0, 50, 99, 60, 99, 10, 0);
+       litest_touch_move_extended(dev, 0, 65, 99, axes);
+       litest_touch_move_to(dev, 0, 65, 99, 80, 99, 10, 0);
+       litest_touch_up(dev, 0);
+
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 }
 END_TEST
 
-START_TEST(touchpad_left_handed_clickpad_delayed)
+START_TEST(touchpad_thumb_clickfinger)
 {
        struct litest_device *dev = litest_current_device();
-       struct libinput_device *d = dev->libinput_device;
        struct libinput *li = dev->libinput;
-       enum libinput_config_status status;
-
-       litest_drain_events(li);
-       litest_touch_down(dev, 0, 10, 90);
-       litest_button_click(dev, BTN_LEFT, 1);
-       libinput_dispatch(li);
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-       status = libinput_device_config_left_handed_set(d, 1);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       if (!has_thumb_detect(dev))
+               return;
 
-       litest_button_click(dev, BTN_LEFT, 0);
-       litest_touch_up(dev, 0);
+       litest_disable_tap(dev->libinput_device);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_device_config_click_set_method(dev->libinput_device,
+                                               LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER);
 
-       /* left-handed takes effect now */
        litest_drain_events(li);
-       litest_touch_down(dev, 0, 90, 90);
-       litest_button_click(dev, BTN_LEFT, 1);
+
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_down(dev, 1, 60, 99);
+       litest_touch_move_extended(dev, 0, 55, 99, axes);
+       litest_button_click(dev, BTN_LEFT, true);
+
        libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev));
 
-       status = libinput_device_config_left_handed_set(d, 0);
-       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       litest_assert_empty_queue(li);
 
-       litest_button_click(dev, BTN_LEFT, 0);
+       litest_button_click(dev, BTN_LEFT, false);
        litest_touch_up(dev, 0);
+       litest_touch_up(dev, 1);
 
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_PRESSED);
-       litest_assert_button_event(li,
-                                  BTN_LEFT,
-                                  LIBINPUT_BUTTON_STATE_RELEASED);
-}
-END_TEST
+       litest_drain_events(li);
 
-static void
-hover_continue(struct litest_device *dev, unsigned int slot,
-              int x, int y)
-{
-       litest_event(dev, EV_ABS, ABS_MT_SLOT, slot);
-       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-       litest_event(dev, EV_ABS, ABS_X, x);
-       litest_event(dev, EV_ABS, ABS_Y, y);
-       litest_event(dev, EV_ABS, ABS_PRESSURE, 10);
-       litest_event(dev, EV_ABS, ABS_TOOL_WIDTH, 6);
-       /* WARNING: no SYN_REPORT! */
-}
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_down(dev, 1, 60, 99);
+       litest_touch_move_extended(dev, 1, 65, 99, axes);
+       litest_button_click(dev, BTN_LEFT, true);
 
-static void
-hover_start(struct litest_device *dev, unsigned int slot,
-           int x, int y)
-{
-       static unsigned int tracking_id;
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev));
 
-       litest_event(dev, EV_ABS, ABS_MT_SLOT, slot);
-       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, ++tracking_id);
-       hover_continue(dev, slot, x, y);
-       /* WARNING: no SYN_REPORT! */
+       litest_assert_empty_queue(li);
 }
+END_TEST
 
-START_TEST(touchpad_hover_noevent)
+START_TEST(touchpad_thumb_btnarea)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       int i;
-       int x = 2400,
-           y = 2400;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
+
+       if (!has_thumb_detect(dev))
+               return;
+
+       litest_disable_tap(dev->libinput_device);
+
+       libinput_device_config_click_set_method(dev->libinput_device,
+                                               LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS);
 
        litest_drain_events(li);
 
-       hover_start(dev, 0, x, y);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_down(dev, 0, 90, 99);
+       litest_touch_move_extended(dev, 0, 95, 99, axes);
+       litest_button_click(dev, BTN_LEFT, true);
 
-       for (i = 0; i < 10; i++) {
-               x += 200;
-               y -= 200;
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       }
+       /* button areas work as usual with a thumb */
 
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_RIGHT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev));
 
        litest_assert_empty_queue(li);
 }
 END_TEST
 
-START_TEST(touchpad_hover_down)
+START_TEST(touchpad_thumb_edgescroll)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       int i;
-       int x = 2400,
-           y = 2400;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-       litest_drain_events(li);
+       if (!has_thumb_detect(dev))
+               return;
 
-       hover_start(dev, 0, x, y);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_enable_edge_scroll(dev);
+       litest_disable_tap(dev->libinput_device);
 
-       for (i = 0; i < 10; i++) {
-               x += 200;
-               y -= 200;
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       }
+       litest_drain_events(li);
 
-       litest_assert_empty_queue(li);
+       litest_touch_down(dev, 0, 99, 30);
+       litest_touch_move_to(dev, 0, 99, 30, 99, 50, 10, 0);
+       litest_drain_events(li);
 
-       litest_event(dev, EV_ABS, ABS_X, x + 100);
-       litest_event(dev, EV_ABS, ABS_Y, y + 100);
-       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_touch_move_extended(dev, 0, 99, 55, axes);
        libinput_dispatch(li);
-       for (i = 0; i < 10; i++) {
-               x -= 200;
-               y += 200;
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       }
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
 
-       libinput_dispatch(li);
+       litest_touch_move_to(dev, 0, 99, 55, 99, 70, 10, 0);
 
-       ck_assert_int_ne(libinput_next_event_type(li),
-                        LIBINPUT_EVENT_NONE);
-       while ((event = libinput_get_event(li)) != NULL) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-               libinput_event_destroy(event);
-               libinput_dispatch(li);
-       }
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_AXIS);
+}
+END_TEST
 
-       /* go back to hover */
-       hover_continue(dev, 0, x, y);
-       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+START_TEST(touchpad_thumb_tap_begin)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-       for (i = 0; i < 10; i++) {
-               x += 200;
-               y -= 200;
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       }
+       if (!has_thumb_detect(dev))
+               return;
 
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
+       litest_drain_events(li);
+
+       /* touch down is a thumb */
+       litest_touch_down_extended(dev, 0, 50, 99, axes);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
 
        litest_assert_empty_queue(li);
+
+       /* make sure normal tap still works */
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
 }
 END_TEST
 
-START_TEST(touchpad_hover_down_hover_down)
+START_TEST(touchpad_thumb_tap_touch)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       struct libinput_event *event;
-       int i, j;
-       int x = 1400,
-           y = 1400;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
+
+       if (!has_thumb_detect(dev))
+               return;
 
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
        litest_drain_events(li);
 
-       /* hover */
-       hover_start(dev, 0, x, y);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       /* event after touch down is thumb */
+       litest_touch_down(dev, 0, 50, 80);
+       litest_touch_move_extended(dev, 0, 51, 99, axes);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
        litest_assert_empty_queue(li);
 
-       for (i = 0; i < 3; i++) {
-               /* touch */
-               litest_event(dev, EV_ABS, ABS_X, x + 100);
-               litest_event(dev, EV_ABS, ABS_Y, y + 100);
-               litest_event(dev, EV_KEY, BTN_TOUCH, 1);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-               libinput_dispatch(li);
+       /* make sure normal tap still works */
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+}
+END_TEST
 
-               for (j = 0; j < 5; j++) {
-                       x += 200;
-                       y += 200;
-                       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-                       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-                       litest_event(dev, EV_ABS, ABS_X, x);
-                       litest_event(dev, EV_ABS, ABS_Y, y);
-                       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-               }
+START_TEST(touchpad_thumb_tap_hold)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
 
-               libinput_dispatch(li);
+       if (!has_thumb_detect(dev))
+               return;
 
-               ck_assert_int_ne(libinput_next_event_type(li),
-                                LIBINPUT_EVENT_NONE);
-               while ((event = libinput_get_event(li)) != NULL) {
-                       ck_assert_int_eq(libinput_event_get_type(event),
-                                        LIBINPUT_EVENT_POINTER_MOTION);
-                       libinput_event_destroy(event);
-                       libinput_dispatch(li);
-               }
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
+       litest_drain_events(li);
 
-               /* go back to hover */
-               hover_continue(dev, 0, x, y);
-               litest_event(dev, EV_KEY, BTN_TOUCH, 0);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       /* event in state HOLD is thumb */
+       litest_touch_down(dev, 0, 50, 99);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       libinput_dispatch(li);
+       litest_touch_move_extended(dev, 0, 51, 99, axes);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       /* make sure normal tap still works */
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
+}
+END_TEST
+
+START_TEST(touchpad_thumb_tap_hold_2ndfg)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
+
+       if (!has_thumb_detect(dev))
+               return;
 
-               for (j = 0; j < 5; j++) {
-                       x += 200;
-                       y += 200;
-                       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-                       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-                       litest_event(dev, EV_ABS, ABS_X, x);
-                       litest_event(dev, EV_ABS, ABS_Y, y);
-                       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-               }
+       litest_enable_tap(dev->libinput_device);
+       litest_enable_clickfinger(dev);
+       litest_drain_events(li);
 
-               litest_assert_empty_queue(li);
-       }
+       /* event in state HOLD is thumb */
+       litest_touch_down(dev, 0, 50, 99);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       libinput_dispatch(li);
+       litest_touch_move_extended(dev, 0, 51, 99, axes);
 
-       /* touch */
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       litest_assert_empty_queue(li);
 
+       /* one finger is a thumb, now get second finger down */
+       litest_touch_down(dev, 1, 60, 50);
        litest_assert_empty_queue(li);
 
-       /* start a new touch to be sure */
-       litest_touch_down(dev, 0, 50, 50);
-       litest_touch_move_to(dev, 0, 50, 50, 70, 70, 10, 10);
+       /* release thumb */
        litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
+       /* timeout -> into HOLD, no event on release */
        libinput_dispatch(li);
-       ck_assert_int_ne(libinput_next_event_type(li),
-                        LIBINPUT_EVENT_NONE);
-       while ((event = libinput_get_event(li)) != NULL) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-               libinput_event_destroy(event);
-               libinput_dispatch(li);
-       }
+       litest_timeout_tap();
+       libinput_dispatch(li);
+       litest_touch_up(dev, 1);
+       litest_assert_empty_queue(li);
+
+       /* make sure normal tap still works */
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_up(dev, 0);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
 }
 END_TEST
 
-START_TEST(touchpad_hover_down_up)
+START_TEST(touchpad_thumb_tap_hold_2ndfg_tap)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       int i;
-       int x = 1400,
-           y = 1400;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       struct axis_replacement axes[] = {
+               { ABS_MT_PRESSURE, 75 },
+               { -1, 0 }
+       };
+
+       if (!has_thumb_detect(dev))
+               return;
 
+       litest_enable_tap(dev->libinput_device);
        litest_drain_events(li);
 
-       /* hover two fingers, then touch */
-       hover_start(dev, 0, x, y);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       litest_assert_empty_queue(li);
+       /* event in state HOLD is thumb */
+       litest_touch_down(dev, 0, 50, 99);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       libinput_dispatch(li);
+       litest_touch_move_extended(dev, 0, 51, 99, axes);
 
-       hover_start(dev, 1, x, y);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
        litest_assert_empty_queue(li);
 
-       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
-       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-
+       /* one finger is a thumb, now get second finger down */
+       litest_touch_down(dev, 1, 60, 50);
        litest_assert_empty_queue(li);
 
-       /* hover first finger, end second in same frame */
-       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
-       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
-       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
-       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-
+       /* release thumb */
+       litest_touch_up(dev, 0);
        litest_assert_empty_queue(li);
 
-       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       /* release second finger, within timeout, ergo event */
+       litest_touch_up(dev, 1);
        libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev));
 
-       /* now move the finger */
-       for (i = 0; i < 10; i++) {
-               litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-               x -= 100;
-               y -= 100;
-       }
-
-       litest_assert_only_typed_events(li,
-                                       LIBINPUT_EVENT_POINTER_MOTION);
+       libinput_dispatch(li);
+       litest_timeout_tap();
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_LEFT,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       libinput_event_destroy(libinput_event_pointer_get_base_event(ptrev));
 
-       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
-       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
-       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       /* make sure normal tap still works */
+       litest_touch_down(dev, 0, 50, 99);
+       litest_touch_up(dev, 0);
        libinput_dispatch(li);
+       litest_timeout_tap();
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_BUTTON);
 }
 END_TEST
 
-START_TEST(touchpad_hover_2fg_noevent)
+START_TEST(touchpad_tool_tripletap_touch_count)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
-       int i;
-       int x = 2400,
-           y = 2400;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
 
+       /* Synaptics touchpads sometimes end one touch point while
+        * simultaneously setting BTN_TOOL_TRIPLETAP.
+        * https://bugs.freedesktop.org/show_bug.cgi?id=91352
+        */
        litest_drain_events(li);
+       litest_enable_clickfinger(dev);
 
-       hover_start(dev, 0, x, y);
+       /* touch 1 down */
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 1);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 1200);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 3200);
+       litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 78);
+       litest_event(dev, EV_ABS, ABS_X, 1200);
+       litest_event(dev, EV_ABS, ABS_Y, 3200);
+       litest_event(dev, EV_ABS, ABS_PRESSURE, 78);
        litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       msleep(2);
 
-       hover_start(dev, 1, x + 500, y + 500);
+       /* touch 2 down */
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 1);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 2200);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 3200);
+       litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 73);
        litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
        litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       msleep(2);
 
-       for (i = 0; i < 10; i++) {
-               x += 200;
-               y -= 200;
-               litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x + 500);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y + 500);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       }
-
+       /* touch 3 down, coordinate jump + ends slot 1 */
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 4000);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 4000);
+       litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 78);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_X, 4000);
+       litest_event(dev, EV_ABS, ABS_Y, 4000);
+       litest_event(dev, EV_ABS, ABS_PRESSURE, 78);
        litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 1);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       msleep(2);
 
-       litest_assert_empty_queue(li);
+       /* slot 2 reactivated:
+        * Note, slot is activated close enough that we don't accidentally
+        * trigger the clickfinger distance check, remains to be seen if
+        * that is true for real-world interaction.
+        */
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 4000);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 4000);
+       litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 78);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, 3);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_X, 3500);
+       litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, 3500);
+       litest_event(dev, EV_ABS, ABS_MT_PRESSURE, 73);
+       litest_event(dev, EV_ABS, ABS_X, 4000);
+       litest_event(dev, EV_ABS, ABS_Y, 4000);
+       litest_event(dev, EV_ABS, ABS_PRESSURE, 78);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       msleep(2);
 
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       /* now a click should trigger middle click */
+       litest_event(dev, EV_KEY, BTN_LEFT, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_event(dev, EV_KEY, BTN_LEFT, 0);
        litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
 
-       litest_assert_empty_queue(li);
+       litest_wait_for_event(li);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_MIDDLE,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       libinput_event_destroy(event);
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_MIDDLE,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       /* silence gcc set-but-not-used warning, litest_is_button_event
+        * checks what we care about */
+       event = libinput_event_pointer_get_base_event(ptrev);
+       libinput_event_destroy(event);
+
+       /* release everything */
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
+       litest_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);
+       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOOL_TRIPLETAP, 0);
+       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
 }
 END_TEST
 
-START_TEST(touchpad_hover_2fg_1fg_down)
+START_TEST(touchpad_slot_swap)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
        struct libinput_event *event;
-       int i;
-       int x = 2400,
-           y = 2400;
+       struct libinput_event_pointer *ptrev;
+       int first, second;
 
+       /* Synaptics touchpads sometimes end the wrong touchpoint on finger
+        * up, causing the remaining slot to continue with the other slot's
+        * coordinates.
+        * https://bugs.freedesktop.org/show_bug.cgi?id=91352
+        */
        litest_drain_events(li);
 
-       /* two slots active, but BTN_TOOL_FINGER only */
-       hover_start(dev, 0, x, y);
-       hover_start(dev, 1, x + 500, y + 500);
-       litest_event(dev, EV_KEY, BTN_TOUCH, 1);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 1);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
-
-       for (i = 0; i < 10; i++) {
-               x += 200;
-               y -= 200;
-               litest_event(dev, EV_ABS, ABS_MT_SLOT, 0);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y);
-               litest_event(dev, EV_ABS, ABS_MT_SLOT, 1);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_X, x + 500);
-               litest_event(dev, EV_ABS, ABS_MT_POSITION_Y, y + 500);
-               litest_event(dev, EV_ABS, ABS_X, x);
-               litest_event(dev, EV_ABS, ABS_Y, y);
-               litest_event(dev, EV_SYN, SYN_REPORT, 0);
-       }
+       for (first = 0; first <= 1; first++) {
+               second = 1 - first;
 
-       litest_event(dev, EV_KEY, BTN_TOUCH, 0);
-       litest_event(dev, EV_KEY, BTN_TOOL_FINGER, 0);
-       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               litest_touch_down(dev, 0, 50, 50);
+               libinput_dispatch(li);
+               litest_touch_down(dev, 1, 70, 70);
+               libinput_dispatch(li);
 
-       libinput_dispatch(li);
+               litest_touch_move_to(dev, first, 50, 50, 50, 30, 10, 1);
+               litest_drain_events(li);
 
-       ck_assert_int_ne(libinput_next_event_type(li),
-                        LIBINPUT_EVENT_NONE);
-       while ((event = libinput_get_event(li)) != NULL) {
-               ck_assert_int_eq(libinput_event_get_type(event),
-                                LIBINPUT_EVENT_POINTER_MOTION);
-               libinput_event_destroy(event);
+               /* release touch 0, continue other slot with 0's coords */
+               litest_push_event_frame(dev);
+               litest_touch_up(dev, first);
+               litest_touch_move(dev, second, 50, 30.1);
+               litest_pop_event_frame(dev);
+               libinput_dispatch(li);
+               litest_touch_move_to(dev, second, 50, 30, 50, 11, 10, 1);
                libinput_dispatch(li);
+               event = libinput_get_event(li);
+               do {
+                       ptrev = litest_is_motion_event(event);
+                       ck_assert_double_eq(libinput_event_pointer_get_dx(ptrev), 0.0);
+                       ck_assert_double_lt(libinput_event_pointer_get_dy(ptrev), 1.0);
+
+                       libinput_event_destroy(event);
+                       event = libinput_get_event(li);
+               } while (event);
+               litest_assert_empty_queue(li);
+
+               litest_touch_up(dev, second);
        }
 }
 END_TEST
 
-static void
-assert_btnevent_from_device(struct litest_device *device,
-                           unsigned int button,
-                           enum libinput_button_state state)
+START_TEST(touchpad_time_usec)
 {
-       struct libinput *li = device->libinput;
-       struct libinput_event *e;
-       struct libinput_event_pointer *pev;
-
-       libinput_dispatch(li);
-       e = libinput_get_event(li);
-       ck_assert_notnull(e);
-       ck_assert_int_eq(libinput_event_get_type(e),
-                        LIBINPUT_EVENT_POINTER_BUTTON);
-       pev = libinput_event_get_pointer_event(e);
-
-       ck_assert_ptr_eq(libinput_event_get_device(e), device->libinput_device);
-       ck_assert_int_eq(libinput_event_pointer_get_button(pev),
-                        button);
-       ck_assert_int_eq(libinput_event_pointer_get_button_state(pev),
-                        state);
-       libinput_event_destroy(e);
-}
-
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
 
-START_TEST(touchpad_trackpoint_buttons)
-{
-       struct litest_device *touchpad = litest_current_device();
-       struct litest_device *trackpoint;
-       struct libinput *li = touchpad->libinput;
+       litest_disable_tap(dev->libinput_device);
 
-       const struct buttons {
-               unsigned int device_value;
-               unsigned int real_value;
-       } buttons[] = {
-               { BTN_0, BTN_LEFT },
-               { BTN_1, BTN_RIGHT },
-               { BTN_2, BTN_MIDDLE },
-       };
-       const struct buttons *b;
+       litest_drain_events(li);
 
-       trackpoint = litest_add_device(li,
-                                      LITEST_TRACKPOINT);
-       libinput_device_config_scroll_set_method(trackpoint->libinput_device,
-                                        LIBINPUT_CONFIG_SCROLL_NO_SCROLL);
+       litest_touch_down(dev, 0, 50, 50);
+       litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5, 0);
+       litest_touch_up(dev, 0);
 
-       litest_drain_events(li);
+       libinput_dispatch(li);
 
-       ARRAY_FOR_EACH(buttons, b) {
-               litest_button_click(touchpad, b->device_value, true);
-               assert_btnevent_from_device(trackpoint,
-                                           b->real_value,
-                                           LIBINPUT_BUTTON_STATE_PRESSED);
+       event = libinput_get_event(li);
+       ck_assert_notnull(event);
 
-               litest_button_click(touchpad, b->device_value, false);
+       while (event) {
+               uint64_t utime;
 
-               assert_btnevent_from_device(trackpoint,
-                                           b->real_value,
-                                           LIBINPUT_BUTTON_STATE_RELEASED);
-       }
+               ptrev = litest_is_motion_event(event);
+               utime = libinput_event_pointer_get_time_usec(ptrev);
 
-       litest_delete_device(trackpoint);
+               ck_assert_int_eq(libinput_event_pointer_get_time(ptrev),
+                                (uint32_t) (utime / 1000));
+               libinput_event_destroy(event);
+               event = libinput_get_event(li);
+       }
 }
 END_TEST
 
-START_TEST(touchpad_trackpoint_mb_scroll)
+START_TEST(touchpad_jump_finger_motion)
 {
-       struct litest_device *touchpad = litest_current_device();
-       struct litest_device *trackpoint;
-       struct libinput *li = touchpad->libinput;
-
-       trackpoint = litest_add_device(li,
-                                      LITEST_TRACKPOINT);
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
 
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
        litest_drain_events(li);
-       litest_button_click(touchpad, BTN_2, true); /* middle */
-       libinput_dispatch(li);
-       litest_timeout_buttonscroll();
+
+       litest_disable_log_handler(li);
+       litest_touch_move_to(dev, 0, 90, 30, 20, 80, 1, 0);
+       litest_assert_empty_queue(li);
+       litest_restore_log_handler(li);
+
+       litest_touch_move_to(dev, 0, 20, 80, 21, 81, 10, 0);
+       litest_touch_up(dev, 0);
+
+       /* expect lots of little events, no big jump */
        libinput_dispatch(li);
-       litest_event(trackpoint, EV_REL, REL_Y, -2);
-       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
-       litest_event(trackpoint, EV_REL, REL_Y, -2);
-       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
-       litest_event(trackpoint, EV_REL, REL_Y, -2);
-       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
-       litest_event(trackpoint, EV_REL, REL_Y, -2);
-       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
-       litest_button_click(touchpad, BTN_2, false);
+       event = libinput_get_event(li);
+       do {
+               double dx, dy;
 
-       litest_assert_only_typed_events(li,
-                                       LIBINPUT_EVENT_POINTER_AXIS);
+               ptrev = litest_is_motion_event(event);
+               dx = libinput_event_pointer_get_dx(ptrev);
+               dy = libinput_event_pointer_get_dy(ptrev);
+               ck_assert_int_lt(abs(dx), 20);
+               ck_assert_int_lt(abs(dy), 20);
 
-       litest_delete_device(trackpoint);
+               libinput_event_destroy(event);
+               event = libinput_get_event(li);
+       } while (event != NULL);
 }
 END_TEST
 
-START_TEST(touchpad_trackpoint_mb_click)
+START_TEST(touchpad_disabled_on_mouse)
 {
-       struct litest_device *touchpad = litest_current_device();
-       struct litest_device *trackpoint;
-       struct libinput *li = touchpad->libinput;
+       struct litest_device *dev = litest_current_device();
+       struct litest_device *mouse;
+       struct libinput *li = dev->libinput;
        enum libinput_config_status status;
 
-       trackpoint = litest_add_device(li,
-                                      LITEST_TRACKPOINT);
-       status = libinput_device_config_scroll_set_method(
-                                 trackpoint->libinput_device,
-                                 LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
+       litest_drain_events(li);
+
+       status = libinput_device_config_send_events_set_mode(
+                            dev->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE);
        ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_drain_events(li);
-       litest_button_click(touchpad, BTN_2, true); /* middle */
-       litest_button_click(touchpad, BTN_2, false);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_MIDDLE,
-                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_MIDDLE,
-                                   LIBINPUT_BUTTON_STATE_RELEASED);
-       litest_delete_device(trackpoint);
+       mouse = litest_add_device(li, LITEST_MOUSE);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_ADDED);
+
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
+
+       litest_delete_device(mouse);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 }
 END_TEST
 
-START_TEST(touchpad_trackpoint_buttons_softbuttons)
+START_TEST(touchpad_disabled_on_mouse_suspend_mouse)
 {
-       struct litest_device *touchpad = litest_current_device();
-       struct litest_device *trackpoint;
-       struct libinput *li = touchpad->libinput;
-
-       trackpoint = litest_add_device(li,
-                                      LITEST_TRACKPOINT);
+       struct litest_device *dev = litest_current_device();
+       struct litest_device *mouse;
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
        litest_drain_events(li);
 
-       litest_touch_down(touchpad, 0, 95, 90);
-       litest_button_click(touchpad, BTN_LEFT, true);
-       litest_button_click(touchpad, BTN_1, true);
-       litest_button_click(touchpad, BTN_LEFT, false);
-       litest_touch_up(touchpad, 0);
-       litest_button_click(touchpad, BTN_1, false);
+       status = libinput_device_config_send_events_set_mode(
+                            dev->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       assert_btnevent_from_device(touchpad,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       assert_btnevent_from_device(touchpad,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_RELEASED);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_touch_down(touchpad, 0, 95, 90);
-       litest_button_click(touchpad, BTN_LEFT, true);
-       litest_button_click(touchpad, BTN_1, true);
-       litest_button_click(touchpad, BTN_1, false);
-       litest_button_click(touchpad, BTN_LEFT, false);
-       litest_touch_up(touchpad, 0);
+       mouse = litest_add_device(li, LITEST_MOUSE);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_ADDED);
 
-       assert_btnevent_from_device(touchpad,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_PRESSED);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_RELEASED);
-       assert_btnevent_from_device(touchpad,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_RELEASED);
+       /* Disable external mouse -> expect touchpad events */
+       status = libinput_device_config_send_events_set_mode(
+                            mouse->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_delete_device(trackpoint);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(mouse);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 }
 END_TEST
 
-START_TEST(touchpad_trackpoint_buttons_2fg_scroll)
+START_TEST(touchpad_disabled_double_mouse)
 {
-       struct litest_device *touchpad = litest_current_device();
-       struct litest_device *trackpoint;
-       struct libinput *li = touchpad->libinput;
-       struct libinput_event *e;
-       struct libinput_event_pointer *pev;
-       double val;
-
-       trackpoint = litest_add_device(li,
-                                      LITEST_TRACKPOINT);
+       struct litest_device *dev = litest_current_device();
+       struct litest_device *mouse1, *mouse2;
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
        litest_drain_events(li);
 
-       litest_touch_down(touchpad, 0, 40, 70);
-       litest_touch_down(touchpad, 1, 60, 70);
-       litest_touch_move_to(touchpad, 0, 40, 70, 40, 30, 10, 0);
-       litest_touch_move_to(touchpad, 1, 60, 70, 60, 30, 10, 0);
+       status = libinput_device_config_send_events_set_mode(
+                            dev->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       libinput_dispatch(li);
-       litest_wait_for_event(li);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       /* Make sure we get scroll events but _not_ the scroll release */
-       while ((e = libinput_get_event(li))) {
-               ck_assert_int_eq(libinput_event_get_type(e),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               pev = libinput_event_get_pointer_event(e);
-               val = libinput_event_pointer_get_axis_value(pev,
-                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
-               ck_assert(val != 0.0);
-               libinput_event_destroy(e);
-       }
+       mouse1 = litest_add_device(li, LITEST_MOUSE);
+       mouse2 = litest_add_device(li, LITEST_MOUSE);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_ADDED);
 
-       litest_button_click(touchpad, BTN_1, true);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_PRESSED);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
-       litest_touch_move_to(touchpad, 0, 40, 30, 40, 70, 10, 0);
-       litest_touch_move_to(touchpad, 1, 60, 30, 60, 70, 10, 0);
+       litest_delete_device(mouse1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
 
-       litest_assert_only_typed_events(li,
-                                       LIBINPUT_EVENT_POINTER_AXIS);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_empty_queue(li);
 
-       while ((e = libinput_get_event(li))) {
-               ck_assert_int_eq(libinput_event_get_type(e),
-                                LIBINPUT_EVENT_POINTER_AXIS);
-               pev = libinput_event_get_pointer_event(e);
-               val = libinput_event_pointer_get_axis_value(pev,
-                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
-               ck_assert(val != 0.0);
-               libinput_event_destroy(e);
-       }
+       litest_delete_device(mouse2);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
 
-       litest_button_click(touchpad, BTN_1, false);
-       assert_btnevent_from_device(trackpoint,
-                                   BTN_RIGHT,
-                                   LIBINPUT_BUTTON_STATE_RELEASED);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+}
+END_TEST
 
-       /* the movement lags behind the touch movement, so the first couple
-          events can be downwards even though we started scrolling up. do a
-          short scroll up, drain those events, then we can use
-          litest_assert_scroll() which tests for the trailing 0/0 scroll
-          for us.
-          */
-       litest_touch_move_to(touchpad, 0, 40, 70, 40, 60, 10, 0);
-       litest_touch_move_to(touchpad, 1, 60, 70, 60, 60, 10, 0);
-       litest_assert_only_typed_events(li,
-                                       LIBINPUT_EVENT_POINTER_AXIS);
-       litest_touch_move_to(touchpad, 0, 40, 60, 40, 30, 10, 0);
-       litest_touch_move_to(touchpad, 1, 60, 60, 60, 30, 10, 0);
+START_TEST(touchpad_disabled_double_mouse_one_suspended)
+{
+       struct litest_device *dev = litest_current_device();
+       struct litest_device *mouse1, *mouse2;
+       struct libinput *li = dev->libinput;
+       enum libinput_config_status status;
 
-       litest_touch_up(touchpad, 0);
-       litest_touch_up(touchpad, 1);
+       litest_drain_events(li);
 
-       libinput_dispatch(li);
+       status = libinput_device_config_send_events_set_mode(
+                            dev->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_assert_scroll(li,
-                            LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
-                            -1);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 
-       litest_delete_device(trackpoint);
-}
-END_TEST
+       mouse1 = litest_add_device(li, LITEST_MOUSE);
+       mouse2 = litest_add_device(li, LITEST_MOUSE);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_ADDED);
 
-START_TEST(touchpad_trackpoint_no_trackpoint)
-{
-       struct litest_device *touchpad = litest_current_device();
-       struct libinput *li = touchpad->libinput;
+       /* Disable one external mouse -> don't expect touchpad events */
+       status = libinput_device_config_send_events_set_mode(
+                            mouse1->libinput_device,
+                            LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
 
-       litest_drain_events(li);
-       litest_button_click(touchpad, BTN_0, true); /* left */
-       litest_button_click(touchpad, BTN_0, false);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
        litest_assert_empty_queue(li);
 
-       litest_button_click(touchpad, BTN_1, true); /* right */
-       litest_button_click(touchpad, BTN_1, false);
-       litest_assert_empty_queue(li);
+       litest_delete_device(mouse1);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
 
-       litest_button_click(touchpad, BTN_2, true); /* middle */
-       litest_button_click(touchpad, BTN_2, false);
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
        litest_assert_empty_queue(li);
+
+       litest_delete_device(mouse2);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
+
+       litest_touch_down(dev, 0, 20, 30);
+       litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10, 0);
+       litest_touch_up(dev, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
 }
 END_TEST
 
-int main(int argc, char **argv) {
+void
+litest_setup_tests_touchpad(void)
+{
+       struct range axis_range = {ABS_X, ABS_Y + 1};
 
        litest_add("touchpad:motion", touchpad_1fg_motion, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:motion", touchpad_2fg_no_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
 
-       litest_add("touchpad:tap", touchpad_1fg_tap, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_1fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_1fg_tap_n_drag_timeout, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_2fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:tap", touchpad_2fg_tap_n_drag_3fg_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:tap", touchpad_2fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:tap", touchpad_2fg_tap_inverted, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:tap", touchpad_1fg_tap_click, LITEST_TOUCHPAD, LITEST_CLICKPAD);
-       litest_add("touchpad:tap", touchpad_2fg_tap_click, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_CLICKPAD);
-
-       litest_add("touchpad:tap", touchpad_2fg_tap_click_apple, LITEST_APPLE_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_no_2fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:tap", touchpad_no_2fg_tap_after_timeout, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:tap", touchpad_no_first_fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:tap", touchpad_no_first_fg_tap_after_move, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       /* apple is the only one with real 3-finger support */
-       litest_add("touchpad:tap", touchpad_3fg_tap_btntool, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:tap", touchpad_3fg_tap_btntool_inverted, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:tap", touchpad_3fg_tap, LITEST_APPLE_CLICKPAD, LITEST_ANY);
-
-       /* Real buttons don't interfere with tapping, so don't run those for
-          pads with buttons */
-       litest_add("touchpad:tap", touchpad_1fg_double_tap_click, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_1fg_tap_n_drag_click, LITEST_CLICKPAD, LITEST_ANY);
-
-       litest_add("touchpad:tap", touchpad_tap_default, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_tap_invalid, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_tap_is_available, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:tap", touchpad_tap_is_not_available, LITEST_ANY, LITEST_TOUCHPAD);
-
-       litest_add("touchpad:tap", clickpad_1fg_tap_click, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:tap", clickpad_2fg_tap_click, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH|LITEST_APPLE_CLICKPAD);
-
-       litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:clickfinger", touchpad_clickfinger_to_area_method, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:clickfinger",
-                  touchpad_clickfinger_to_area_method_while_down, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:clickfinger", touchpad_area_to_clickfinger_method, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:clickfinger",
-                  touchpad_area_to_clickfinger_method_while_down, LITEST_CLICKPAD, LITEST_ANY);
-
-       litest_add("touchpad:click", touchpad_click_defaults_clickfinger, LITEST_APPLE_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:click", touchpad_click_defaults_btnarea, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:click", touchpad_click_defaults_none, LITEST_TOUCHPAD, LITEST_CLICKPAD);
-
-       litest_add("touchpad:click", touchpad_btn_left, LITEST_TOUCHPAD, LITEST_CLICKPAD);
-       litest_add("touchpad:click", clickpad_btn_left, LITEST_CLICKPAD, LITEST_ANY);
-       litest_add("touchpad:click", clickpad_click_n_drag, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
-
-       litest_add("touchpad:softbutton", clickpad_softbutton_left, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_right, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_left_tap_n_drag, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_right_tap_n_drag, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_left_1st_fg_move, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_left_2nd_fg_move, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_left_to_right, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-       litest_add("touchpad:softbutton", clickpad_softbutton_right_to_left, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
-
-       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_left, LITEST_TOPBUTTONPAD, LITEST_ANY);
-       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_right, LITEST_TOPBUTTONPAD, LITEST_ANY);
-       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_middle, LITEST_TOPBUTTONPAD, LITEST_ANY);
-       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_move_out_ignore, LITEST_TOPBUTTONPAD, LITEST_ANY);
-       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_clickfinger, LITEST_TOPBUTTONPAD, LITEST_ANY);
-       litest_add("touchpad:topsoftbuttons", clickpad_topsoftbuttons_clickfinger_dev_disabled, LITEST_TOPBUTTONPAD, LITEST_ANY);
-
-       litest_add("touchpad:scroll", touchpad_2fg_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:scroll", touchpad_2fg_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
+       litest_add("touchpad:scroll", touchpad_2fg_scroll_diagonal, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH|LITEST_SEMI_MT);
        litest_add("touchpad:scroll", touchpad_2fg_scroll_slow_distance, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
        litest_add("touchpad:scroll", touchpad_2fg_scroll_return_to_motion, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
        litest_add("touchpad:scroll", touchpad_2fg_scroll_source, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:scroll", touchpad_2fg_scroll_semi_mt, LITEST_SEMI_MT, LITEST_SINGLE_TOUCH);
        litest_add("touchpad:scroll", touchpad_scroll_natural_defaults, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:scroll", touchpad_scroll_natural_enable_config, LITEST_TOUCHPAD, LITEST_ANY);
-       litest_add("touchpad:scroll", touchpad_scroll_natural, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:scroll", touchpad_edge_scroll, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY);
-       litest_add("touchpad:scroll", touchpad_edge_scroll_no_motion, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY);
-       litest_add("touchpad:scroll", touchpad_edge_scroll_no_edge_after_motion, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY);
-       litest_add("touchpad:scroll", touchpad_edge_scroll_slow_distance, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY);
-       litest_add("touchpad:scroll", touchpad_edge_scroll_source, LITEST_TOUCHPAD|LITEST_SINGLE_TOUCH, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_scroll_natural_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:scroll", touchpad_scroll_natural_edge, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:scroll", touchpad_scroll_defaults, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_vert, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_horiz, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_horiz_clickpad, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_no_horiz, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_no_motion, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_no_edge_after_motion, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_timeout, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_source, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_no_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_into_buttonareas, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_within_buttonareas, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_buttonareas_click_stops_scroll, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_clickfinger_click_stops_scroll, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:scroll", touchpad_edge_scroll_into_area, LITEST_TOUCHPAD, LITEST_ANY);
 
        litest_add("touchpad:palm", touchpad_palm_detect_at_edge, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:palm", touchpad_palm_detect_at_bottom_corners, LITEST_TOUCHPAD, LITEST_CLICKPAD);
@@ -3333,23 +4654,37 @@ int main(int argc, char **argv) {
        litest_add("touchpad:palm", touchpad_palm_detect_palm_becomes_pointer, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:palm", touchpad_palm_detect_palm_stays_palm, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:palm", touchpad_palm_detect_no_palm_moving_into_edges, LITEST_TOUCHPAD, LITEST_ANY);
-
-       litest_add("touchpad:left-handed", touchpad_left_handed, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:palm", touchpad_palm_detect_tap_hardbuttons, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:palm", touchpad_palm_detect_tap_softbuttons, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:palm", touchpad_palm_detect_tap_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:palm", touchpad_no_palm_detect_at_edge_for_edge_scrolling, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:palm", touchpad_no_palm_detect_2fg_scroll, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:palm", touchpad_palm_detect_both_edges, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
+
+       litest_add("touchpad:left-handed", touchpad_left_handed, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD);
        litest_add("touchpad:left-handed", touchpad_left_handed_clickpad, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
        litest_add("touchpad:left-handed", touchpad_left_handed_clickfinger, LITEST_APPLE_CLICKPAD, LITEST_ANY);
        litest_add("touchpad:left-handed", touchpad_left_handed_tapping, LITEST_TOUCHPAD, LITEST_ANY);
        litest_add("touchpad:left-handed", touchpad_left_handed_tapping_2fg, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH);
-       litest_add("touchpad:left-handed", touchpad_left_handed_delayed, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:left-handed", touchpad_left_handed_delayed, LITEST_TOUCHPAD|LITEST_BUTTON, LITEST_CLICKPAD);
        litest_add("touchpad:left-handed", touchpad_left_handed_clickpad_delayed, LITEST_CLICKPAD, LITEST_APPLE_CLICKPAD);
 
-       /* Hover tests aren't generic, they only work on this device and
-        * ignore the semi-mt capability (it doesn't matter for the tests */
-       litest_add_for_device("touchpad:hover", touchpad_hover_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT);
-       litest_add_for_device("touchpad:hover", touchpad_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT);
-       litest_add_for_device("touchpad:hover", touchpad_hover_down_up, LITEST_SYNAPTICS_HOVER_SEMI_MT);
-       litest_add_for_device("touchpad:hover", touchpad_hover_down_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT);
-       litest_add_for_device("touchpad:hover", touchpad_hover_2fg_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT);
-       litest_add_for_device("touchpad:hover", touchpad_hover_2fg_1fg_down, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       /* Semi-MT hover tests aren't generic, they only work on this device and
+        * ignore the semi-mt capability (it doesn't matter for the tests) */
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_down_up, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_down_hover_down, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_2fg_noevent, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_2fg_1fg_down, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+       litest_add_for_device("touchpad:semi-mt-hover", touchpad_semi_mt_hover_2fg_up, LITEST_SYNAPTICS_HOVER_SEMI_MT);
+
+       litest_add("touchpad:hover", touchpad_hover_noevent, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY);
+       litest_add("touchpad:hover", touchpad_hover_down, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY);
+       litest_add("touchpad:hover", touchpad_hover_down_up, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY);
+       litest_add("touchpad:hover", touchpad_hover_down_hover_down, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY);
+       litest_add("touchpad:hover", touchpad_hover_2fg_noevent, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY);
+       litest_add("touchpad:hover", touchpad_hover_2fg_1fg_down, LITEST_TOUCHPAD|LITEST_HOVER, LITEST_ANY);
 
        litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_buttons, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
        litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_mb_scroll, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
@@ -3358,5 +4693,62 @@ int main(int argc, char **argv) {
        litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_buttons_2fg_scroll, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
        litest_add_for_device("touchpad:trackpoint", touchpad_trackpoint_no_trackpoint, LITEST_SYNAPTICS_TRACKPOINT_BUTTONS);
 
-       return litest_run(argc, argv);
+       litest_add_ranged("touchpad:state", touchpad_initial_state, LITEST_TOUCHPAD, LITEST_ANY, &axis_range);
+
+       litest_add("touchpad:dwt", touchpad_dwt, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add_for_device("touchpad:dwt", touchpad_dwt_update_keyboard, LITEST_SYNAPTICS_I2C);
+       litest_add_for_device("touchpad:dwt", touchpad_dwt_update_keyboard_with_state, LITEST_SYNAPTICS_I2C);
+       litest_add("touchpad:dwt", touchpad_dwt_enable_touch, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_touch_hold, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_key_hold, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_key_hold_timeout, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_key_hold_timeout_existing_touch, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_key_hold_timeout_existing_touch_cornercase, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_type, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_type_short_timeout, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_modifier_no_dwt, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_modifier_combo_no_dwt, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_modifier_combo_dwt_after, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_modifier_combo_dwt_remains, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_fkeys_no_dwt, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_tap, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_tap_drag, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_click, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_edge_scroll, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:dwt", touchpad_dwt_edge_scroll_interrupt, LITEST_TOUCHPAD, LITEST_CLICKPAD);
+       litest_add("touchpad:dwt", touchpad_dwt_config_default_on, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_config_default_off, LITEST_ANY, LITEST_TOUCHPAD);
+       litest_add("touchpad:dwt", touchpad_dwt_disabled, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_disable_during_touch, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_disable_before_touch, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_disable_during_key_release, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_disable_during_key_hold, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_enable_during_touch, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_enable_before_touch, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add("touchpad:dwt", touchpad_dwt_enable_during_tap, LITEST_TOUCHPAD, LITEST_ANY);
+       litest_add_for_device("touchpad:dwt", touchpad_dwt_apple, LITEST_BCM5974);
+
+       litest_add("touchpad:thumb", touchpad_thumb_begin_no_motion, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_update_no_motion, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_moving, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_clickfinger, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_btnarea, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_edgescroll, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_tap_begin, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_tap_touch, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_tap_hold, LITEST_CLICKPAD, LITEST_ANY);
+       litest_add("touchpad:thumb", touchpad_thumb_tap_hold_2ndfg, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
+       litest_add("touchpad:thumb", touchpad_thumb_tap_hold_2ndfg_tap, LITEST_CLICKPAD, LITEST_SINGLE_TOUCH);
+
+       litest_add_for_device("touchpad:bugs", touchpad_tool_tripletap_touch_count, LITEST_SYNAPTICS_TOPBUTTONPAD);
+       litest_add_for_device("touchpad:bugs", touchpad_slot_swap, LITEST_SYNAPTICS_TOPBUTTONPAD);
+
+       litest_add("touchpad:time", touchpad_time_usec, LITEST_TOUCHPAD, LITEST_ANY);
+
+       litest_add_for_device("touchpad:jumps", touchpad_jump_finger_motion, LITEST_SYNAPTICS_CLICKPAD_X220);
+
+       litest_add_for_device("touchpad:sendevents", touchpad_disabled_on_mouse, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("touchpad:sendevents", touchpad_disabled_on_mouse_suspend_mouse, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("touchpad:sendevents", touchpad_disabled_double_mouse, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("touchpad:sendevents", touchpad_disabled_double_mouse_one_suspended, LITEST_SYNAPTICS_CLICKPAD_X220);
 }
diff --git a/test/trackball.c b/test/trackball.c
new file mode 100644 (file)
index 0000000..28639ad
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * Copyright © 2016 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <check.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libinput.h>
+#include <unistd.h>
+
+#include "libinput-util.h"
+#include "litest.h"
+
+START_TEST(trackball_rotation_config_defaults)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       int angle;
+
+       ck_assert(libinput_device_config_rotation_is_available(device));
+
+       angle = libinput_device_config_rotation_get_angle(device);
+       ck_assert_int_eq(angle, 0);
+       angle = libinput_device_config_rotation_get_default_angle(device);
+       ck_assert_int_eq(angle, 0);
+}
+END_TEST
+
+START_TEST(trackball_rotation_config_invalid_range)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+
+       status = libinput_device_config_rotation_set_angle(device, 360);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+       status = libinput_device_config_rotation_set_angle(device, 361);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+       status = libinput_device_config_rotation_set_angle(device, -1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+}
+END_TEST
+
+START_TEST(trackball_rotation_config_no_rotation)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       int angle;
+
+       ck_assert(!libinput_device_config_rotation_is_available(device));
+
+       angle = libinput_device_config_rotation_get_angle(device);
+       ck_assert_int_eq(angle, 0);
+       angle = libinput_device_config_rotation_get_default_angle(device);
+       ck_assert_int_eq(angle, 0);
+
+       /* 0 always succeeds */
+       status = libinput_device_config_rotation_set_angle(device, 0);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       for (angle = 1; angle < 360; angle++) {
+               if (angle % 90 == 0)
+                       continue;
+               status = libinput_device_config_rotation_set_angle(device,
+                                                                  angle);
+               ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+       }
+}
+END_TEST
+
+START_TEST(trackball_rotation_config_right_angle)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       int angle;
+
+       ck_assert(libinput_device_config_rotation_is_available(device));
+
+       for (angle = 0; angle < 360; angle += 90) {
+               status = libinput_device_config_rotation_set_angle(device,
+                                                                  angle);
+               ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       }
+}
+END_TEST
+
+START_TEST(trackball_rotation_config_odd_angle)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput_device *device = dev->libinput_device;
+       enum libinput_config_status status;
+       int angle;
+
+       ck_assert(libinput_device_config_rotation_is_available(device));
+
+       for (angle = 0; angle < 360; angle++) {
+               if (angle % 90 == 0)
+                       continue;
+               status = libinput_device_config_rotation_set_angle(device,
+                                                                  angle);
+               ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_INVALID);
+       }
+}
+END_TEST
+
+START_TEST(trackball_rotation_x)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       int angle;
+       double dx, dy;
+
+       litest_drain_events(li);
+
+       for (angle = 0; angle < 360; angle++) {
+               libinput_device_config_rotation_set_angle(device, angle);
+
+               litest_event(dev, EV_REL, REL_X, 1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_motion_event(event);
+
+               /* Test unaccelerated because pointer accel may mangle the
+                  other coords */
+               dx = libinput_event_pointer_get_dx_unaccelerated(ptrev);
+               dy = libinput_event_pointer_get_dy_unaccelerated(ptrev);
+
+               switch (angle) {
+               case 0:
+                       ck_assert_double_eq(dx, 1.0);
+                       ck_assert_double_eq(dy, 0.0);
+                       break;
+               case 90:
+                       ck_assert_double_eq(dx, 0.0);
+                       ck_assert_double_eq(dy, 1.0);
+                       break;
+               case 180:
+                       ck_assert_double_eq(dx, -1.0);
+                       ck_assert_double_eq(dy, 0.0);
+                       break;
+               case 270:
+                       ck_assert_double_eq(dx, 0.0);
+                       ck_assert_double_eq(dy, -1.0);
+                       break;
+               }
+               libinput_event_destroy(event);
+       }
+}
+END_TEST
+
+START_TEST(trackball_rotation_y)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       int angle;
+       double dx, dy;
+
+       litest_drain_events(li);
+
+       for (angle = 0; angle < 360; angle++) {
+               libinput_device_config_rotation_set_angle(device, angle);
+
+               litest_event(dev, EV_REL, REL_Y, 1);
+               litest_event(dev, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+
+               event = libinput_get_event(li);
+               ptrev = litest_is_motion_event(event);
+
+               /* Test unaccelerated because pointer accel may mangle the
+                  other coords */
+               dx = libinput_event_pointer_get_dx_unaccelerated(ptrev);
+               dy = libinput_event_pointer_get_dy_unaccelerated(ptrev);
+
+               switch (angle) {
+               case 0:
+                       ck_assert_double_eq(dx, 0.0);
+                       ck_assert_double_eq(dy, 1.0);
+                       break;
+               case 90:
+                       ck_assert_double_eq(dx, -1.0);
+                       ck_assert_double_eq(dy, 0.0);
+                       break;
+               case 180:
+                       ck_assert_double_eq(dx, 0.0);
+                       ck_assert_double_eq(dy, -1.0);
+                       break;
+               case 270:
+                       ck_assert_double_eq(dx, 1.0);
+                       ck_assert_double_eq(dy, 0.0);
+                       break;
+               }
+               libinput_event_destroy(event);
+       }
+}
+END_TEST
+
+START_TEST(trackball_rotation_accel)
+{
+       struct litest_device *dev = litest_current_device();
+       struct libinput *li = dev->libinput;
+       struct libinput_device *device = dev->libinput_device;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       double dx, dy;
+
+       litest_drain_events(li);
+
+       /* Pointer accel mangles the coordinates, so we only test one angle
+        * and rely on the unaccelerated tests above to warn us when
+        * something's off */
+       libinput_device_config_rotation_set_angle(device, 90);
+
+       litest_event(dev, EV_REL, REL_Y, 1);
+       litest_event(dev, EV_REL, REL_X, 1);
+       litest_event(dev, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_motion_event(event);
+
+       dx = libinput_event_pointer_get_dx(ptrev);
+       dy = libinput_event_pointer_get_dy(ptrev);
+
+       ck_assert_double_lt(dx, 0.0);
+       ck_assert_double_gt(dy, 0.0);
+       libinput_event_destroy(event);
+}
+END_TEST
+
+void
+litest_setup_tests_trackball(void)
+{
+       litest_add("trackball:rotation", trackball_rotation_config_defaults, LITEST_TRACKBALL, LITEST_ANY);
+       litest_add("trackball:rotation", trackball_rotation_config_invalid_range, LITEST_TRACKBALL, LITEST_ANY);
+       litest_add("trackball:rotation", trackball_rotation_config_no_rotation, LITEST_ANY, LITEST_TRACKBALL);
+       litest_add("trackball:rotation", trackball_rotation_config_right_angle, LITEST_TRACKBALL, LITEST_ANY);
+       litest_add("trackball:rotation", trackball_rotation_config_odd_angle, LITEST_TRACKBALL, LITEST_ANY);
+       litest_add("trackball:rotation", trackball_rotation_x, LITEST_TRACKBALL, LITEST_ANY);
+       litest_add("trackball:rotation", trackball_rotation_y, LITEST_TRACKBALL, LITEST_ANY);
+       litest_add("trackball:rotation", trackball_rotation_accel, LITEST_TRACKBALL, LITEST_ANY);
+}
index 2708bad2b7e3440ee5cdffef9f96fe0bae4a9614..e9ba02716597bee26f9f0732913d469d6ee9b04b 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -35,15 +36,34 @@ START_TEST(trackpoint_middlebutton)
 {
        struct litest_device *dev = litest_current_device();
        struct libinput *li = dev->libinput;
+       struct libinput_event *event;
+       struct libinput_event_pointer *ptrev;
+       uint64_t ptime, rtime;
 
        litest_drain_events(li);
 
        /* A quick middle button click should get reported normally */
        litest_button_click(dev, BTN_MIDDLE, 1);
+       msleep(2);
        litest_button_click(dev, BTN_MIDDLE, 0);
 
-       litest_assert_button_event(li, BTN_MIDDLE, 1);
-       litest_assert_button_event(li, BTN_MIDDLE, 0);
+       litest_wait_for_event(li);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_MIDDLE,
+                                      LIBINPUT_BUTTON_STATE_PRESSED);
+       ptime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+
+       event = libinput_get_event(li);
+       ptrev = litest_is_button_event(event,
+                                      BTN_MIDDLE,
+                                      LIBINPUT_BUTTON_STATE_RELEASED);
+       rtime = libinput_event_pointer_get_time(ptrev);
+       libinput_event_destroy(event);
+
+       ck_assert_int_lt(ptime, rtime);
 
        litest_assert_empty_queue(li);
 }
@@ -130,12 +150,242 @@ START_TEST(trackpoint_scroll_source)
 }
 END_TEST
 
-int main(int argc, char **argv) {
+START_TEST(trackpoint_topsoftbuttons_left_handed_trackpoint)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+       enum libinput_config_status status;
+       struct libinput_event *event;
+       struct libinput_device *device;
+
+       trackpoint = litest_add_device(li, LITEST_TRACKPOINT);
+       litest_drain_events(li);
+       /* touchpad right-handed, trackpoint left-handed */
+       status = libinput_device_config_left_handed_set(
+                                       trackpoint->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_touch_down(touchpad, 0, 5, 5);
+       libinput_dispatch(li);
+       litest_button_click(touchpad, BTN_LEFT, true);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_RIGHT,
+                              LIBINPUT_BUTTON_STATE_PRESSED);
+       device = libinput_event_get_device(event);
+       ck_assert(device == trackpoint->libinput_device);
+       libinput_event_destroy(event);
+
+       litest_button_click(touchpad, BTN_LEFT, false);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_RIGHT,
+                              LIBINPUT_BUTTON_STATE_RELEASED);
+       device = libinput_event_get_device(event);
+       ck_assert(device == trackpoint->libinput_device);
+       libinput_event_destroy(event);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(trackpoint_topsoftbuttons_left_handed_touchpad)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+       enum libinput_config_status status;
+       struct libinput_event *event;
+       struct libinput_device *device;
+
+       trackpoint = litest_add_device(li, LITEST_TRACKPOINT);
+       litest_drain_events(li);
+       /* touchpad left-handed, trackpoint right-handed */
+       status = libinput_device_config_left_handed_set(
+                                       touchpad->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_touch_down(touchpad, 0, 5, 5);
+       libinput_dispatch(li);
+       litest_button_click(touchpad, BTN_LEFT, true);
+       libinput_dispatch(li);
+
+       event = libinput_get_event(li);
+       litest_is_button_event(event, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
+       device = libinput_event_get_device(event);
+       ck_assert(device == trackpoint->libinput_device);
+       libinput_event_destroy(event);
+
+       litest_button_click(touchpad, BTN_LEFT, false);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_LEFT,
+                              LIBINPUT_BUTTON_STATE_RELEASED);
+       device = libinput_event_get_device(event);
+       ck_assert(device == trackpoint->libinput_device);
+       libinput_event_destroy(event);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(trackpoint_topsoftbuttons_left_handed_both)
+{
+       struct litest_device *touchpad = litest_current_device();
+       struct litest_device *trackpoint;
+       struct libinput *li = touchpad->libinput;
+       enum libinput_config_status status;
+       struct libinput_event *event;
+       struct libinput_device *device;
+
+       trackpoint = litest_add_device(li, LITEST_TRACKPOINT);
+       litest_drain_events(li);
+       /* touchpad left-handed, trackpoint left-handed */
+       status = libinput_device_config_left_handed_set(
+                                       touchpad->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+       status = libinput_device_config_left_handed_set(
+                                       trackpoint->libinput_device, 1);
+       ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+       litest_touch_down(touchpad, 0, 5, 5);
+       libinput_dispatch(li);
+       litest_button_click(touchpad, BTN_LEFT, true);
+       libinput_dispatch(li);
 
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_RIGHT,
+                              LIBINPUT_BUTTON_STATE_PRESSED);
+       device = libinput_event_get_device(event);
+       ck_assert(device == trackpoint->libinput_device);
+       libinput_event_destroy(event);
+
+       litest_button_click(touchpad, BTN_LEFT, false);
+       libinput_dispatch(li);
+       event = libinput_get_event(li);
+       litest_is_button_event(event,
+                              BTN_RIGHT,
+                              LIBINPUT_BUTTON_STATE_RELEASED);
+       device = libinput_event_get_device(event);
+       ck_assert(device == trackpoint->libinput_device);
+       libinput_event_destroy(event);
+
+       litest_delete_device(trackpoint);
+}
+END_TEST
+
+START_TEST(trackpoint_palmdetect)
+{
+       struct litest_device *trackpoint = litest_current_device();
+       struct litest_device *touchpad;
+       struct libinput *li = trackpoint->libinput;
+       int i;
+
+       touchpad = litest_add_device(li, LITEST_SYNAPTICS_I2C);
+       litest_drain_events(li);
+
+       for (i = 0; i < 10; i++) {
+               litest_event(trackpoint, EV_REL, REL_X, 1);
+               litest_event(trackpoint, EV_REL, REL_Y, 1);
+               litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+       litest_drain_events(li);
+
+       litest_touch_down(touchpad, 0, 30, 30);
+       litest_touch_move_to(touchpad, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_trackpoint();
+       libinput_dispatch(li);
+
+       litest_touch_down(touchpad, 0, 30, 30);
+       litest_touch_move_to(touchpad, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(touchpad);
+}
+END_TEST
+
+START_TEST(trackpoint_palmdetect_resume_touch)
+{
+       struct litest_device *trackpoint = litest_current_device();
+       struct litest_device *touchpad;
+       struct libinput *li = trackpoint->libinput;
+       int i;
+
+       touchpad = litest_add_device(li, LITEST_SYNAPTICS_I2C);
+       litest_drain_events(li);
+
+       for (i = 0; i < 10; i++) {
+               litest_event(trackpoint, EV_REL, REL_X, 1);
+               litest_event(trackpoint, EV_REL, REL_Y, 1);
+               litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+               libinput_dispatch(li);
+       }
+       litest_drain_events(li);
+
+       litest_touch_down(touchpad, 0, 30, 30);
+       litest_touch_move_to(touchpad, 0, 30, 30, 80, 80, 10, 1);
+       litest_assert_empty_queue(li);
+
+       litest_timeout_trackpoint();
+       libinput_dispatch(li);
+
+       /* touch started after last tp event, expect resume */
+       litest_touch_move_to(touchpad, 0, 80, 80, 30, 30, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(touchpad);
+}
+END_TEST
+
+START_TEST(trackpoint_palmdetect_require_min_events)
+{
+       struct litest_device *trackpoint = litest_current_device();
+       struct litest_device *touchpad;
+       struct libinput *li = trackpoint->libinput;
+
+       touchpad = litest_add_device(li, LITEST_SYNAPTICS_I2C);
+       litest_drain_events(li);
+
+       /* A single event does not trigger palm detection */
+       litest_event(trackpoint, EV_REL, REL_X, 1);
+       litest_event(trackpoint, EV_REL, REL_Y, 1);
+       litest_event(trackpoint, EV_SYN, SYN_REPORT, 0);
+       libinput_dispatch(li);
+       litest_drain_events(li);
+
+       litest_touch_down(touchpad, 0, 30, 30);
+       litest_touch_move_to(touchpad, 0, 30, 30, 80, 80, 10, 1);
+       litest_touch_up(touchpad, 0);
+       litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
+
+       litest_delete_device(touchpad);
+}
+END_TEST
+
+void
+litest_setup_tests_trackpoint(void)
+{
        litest_add("trackpoint:middlebutton", trackpoint_middlebutton, LITEST_POINTINGSTICK, LITEST_ANY);
        litest_add("trackpoint:middlebutton", trackpoint_middlebutton_noscroll, LITEST_POINTINGSTICK, LITEST_ANY);
        litest_add("trackpoint:scroll", trackpoint_scroll, LITEST_POINTINGSTICK, LITEST_ANY);
        litest_add("trackpoint:scroll", trackpoint_scroll_source, LITEST_POINTINGSTICK, LITEST_ANY);
+       litest_add("trackpoint:left-handed", trackpoint_topsoftbuttons_left_handed_trackpoint, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("trackpoint:left-handed", trackpoint_topsoftbuttons_left_handed_touchpad, LITEST_TOPBUTTONPAD, LITEST_ANY);
+       litest_add("trackpoint:left-handed", trackpoint_topsoftbuttons_left_handed_both, LITEST_TOPBUTTONPAD, LITEST_ANY);
 
-       return litest_run(argc, argv);
+       litest_add("trackpoint:palmdetect", trackpoint_palmdetect, LITEST_POINTINGSTICK, LITEST_ANY);
+       litest_add("trackpoint:palmdetect", trackpoint_palmdetect_resume_touch, LITEST_POINTINGSTICK, LITEST_ANY);
+       litest_add("trackpoint:palmdetect", trackpoint_palmdetect_require_min_events, LITEST_POINTINGSTICK, LITEST_ANY);
 }
index c351bed57e8413a0ed93073d1d67dd5bbe5b1b32..ba6a10d9b3180ef21f4f74209f06200408c7fcea 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * Copyright © 2013 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #include <config.h>
@@ -26,6 +27,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <libinput.h>
+#include <libinput-util.h>
 #include <libudev.h>
 #include <unistd.h>
 
@@ -42,7 +44,7 @@ static void close_restricted(int fd, void *data)
        close(fd);
 }
 
-const struct libinput_interface simple_interface = {
+static const struct libinput_interface simple_interface = {
        .open_restricted = open_restricted,
        .close_restricted = close_restricted,
 };
@@ -50,7 +52,6 @@ const struct libinput_interface simple_interface = {
 START_TEST(udev_create_NULL)
 {
        struct libinput *li;
-       const struct libinput_interface interface;
        struct udev *udev;
 
        udev = udev_new();
@@ -58,13 +59,13 @@ START_TEST(udev_create_NULL)
        li = libinput_udev_create_context(NULL, NULL, NULL);
        ck_assert(li == NULL);
 
-       li = libinput_udev_create_context(&interface, NULL, NULL);
+       li = libinput_udev_create_context(&simple_interface, NULL, NULL);
        ck_assert(li == NULL);
 
        li = libinput_udev_create_context(NULL, NULL, udev);
        ck_assert(li == NULL);
 
-       li = libinput_udev_create_context(&interface, NULL, udev);
+       li = libinput_udev_create_context(&simple_interface, NULL, udev);
        ck_assert(li != NULL);
        ck_assert_int_eq(libinput_udev_assign_seat(li, NULL), -1);
 
@@ -184,7 +185,7 @@ START_TEST(udev_added_seat_default)
                ck_assert(seat != NULL);
 
                seat_name = libinput_seat_get_logical_name(seat);
-               default_seat_found = !strcmp(seat_name, "default");
+               default_seat_found = streq(seat_name, "default");
                libinput_event_destroy(event);
        }
 
@@ -411,8 +412,11 @@ START_TEST(udev_device_sysname)
        libinput_dispatch(li);
 
        while ((ev = libinput_get_event(li))) {
-               if (libinput_event_get_type(ev) != LIBINPUT_EVENT_DEVICE_ADDED)
+               if (libinput_event_get_type(ev) !=
+                   LIBINPUT_EVENT_DEVICE_ADDED) {
+                       libinput_event_destroy(ev);
                        continue;
+               }
 
                device = libinput_event_get_device(ev);
                sysname = libinput_device_get_sysname(device);
@@ -502,8 +506,8 @@ START_TEST(udev_seat_recycle)
 }
 END_TEST
 
-int
-main(int argc, char **argv)
+void
+litest_setup_tests_udev(void)
 {
        litest_add_no_device("udev:create", udev_create_NULL);
        litest_add_no_device("udev:create", udev_create_seat0);
@@ -513,11 +517,9 @@ main(int argc, char **argv)
        litest_add_no_device("udev:seat", udev_added_seat_default);
        litest_add_no_device("udev:seat", udev_change_seat);
 
-       litest_add_for_device("udev:suspend", udev_double_suspend, LITEST_SYNAPTICS_CLICKPAD);
-       litest_add_for_device("udev:suspend", udev_double_resume, LITEST_SYNAPTICS_CLICKPAD);
-       litest_add_for_device("udev:suspend", udev_suspend_resume, LITEST_SYNAPTICS_CLICKPAD);
-       litest_add_for_device("udev:device events", udev_device_sysname, LITEST_SYNAPTICS_CLICKPAD);
-       litest_add_for_device("udev:seat", udev_seat_recycle, LITEST_SYNAPTICS_CLICKPAD);
-
-       return litest_run(argc, argv);
+       litest_add_for_device("udev:suspend", udev_double_suspend, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("udev:suspend", udev_double_resume, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("udev:suspend", udev_suspend_resume, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("udev:device events", udev_device_sysname, LITEST_SYNAPTICS_CLICKPAD_X220);
+       litest_add_for_device("udev:seat", udev_seat_recycle, LITEST_SYNAPTICS_CLICKPAD_X220);
 }
index 3ba7f292b0ca11a4c4802e650078acccef4a02bb..b7f43499c3a0ee960a582ea3bdc381416a4160a5 100644 (file)
@@ -7,3 +7,35 @@
    fun:litest_run
    fun:main
 }
+{
+   mtdev:conditional_jumps_uninitialized_value
+   Memcheck:Cond
+   ...
+   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)
+   fun:__msync_nocancel
+   ...
+   fun:litest_backtrace
+}
index cf348a6fc64c0729ad64bcaa83d81027257a00a2..cb9342986e779aa7c0bd7b8bce490591cdfe7397 100644 (file)
@@ -1,2 +1,5 @@
 event-debug
 event-gui
+ptraccel-debug
+libinput-list-devices
+libinput-debug-events
index cebcd729547e57e9411292e7b1700cb1a76ad179..5fe169cd8d0d2b86fc26655985b3a94d67eea052 100644 (file)
@@ -1,18 +1,35 @@
-noinst_PROGRAMS = event-debug
+noinst_PROGRAMS = event-debug ptraccel-debug
+bin_PROGRAMS = libinput-list-devices libinput-debug-events
 noinst_LTLIBRARIES = libshared.la
 
 AM_CPPFLAGS = -I$(top_srcdir)/include \
-              -I$(top_srcdir)/src
-
+              -I$(top_srcdir)/src \
+              -I$(top_builddir)/src  # for libinput-version.h
 
 libshared_la_SOURCES = \
                       shared.c \
                       shared.h
+libshared_la_CFLAGS = $(LIBEVDEV_CFLAGS)
+libshared_la_LIBADD = $(LIBEVDEV_LIBS)
 
 event_debug_SOURCES = event-debug.c
-event_debug_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS)
+event_debug_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS) $(LIBEVDEV_LIBS)
 event_debug_LDFLAGS = -no-install
-event_debug_CFLAGS = $(LIBUDEV_CFLAGS)
+event_debug_CFLAGS = $(LIBUDEV_CFLAGS) $(LIBEVDEV_CFLAGS)
+
+ptraccel_debug_SOURCES = ptraccel-debug.c
+ptraccel_debug_LDADD = ../src/libfilter.la ../src/libinput.la
+ptraccel_debug_LDFLAGS = -no-install
+
+libinput_list_devices_SOURCES = libinput-list-devices.c
+libinput_list_devices_LDADD = ../src/libinput.la libshared.la $(LIBUDEV_LIBS)
+libinput_list_devices_CFLAGS = $(LIBUDEV_CFLAGS)
+dist_man1_MANS = libinput-list-devices.man
+
+libinput_debug_events_SOURCES = $(event_debug_SOURCES)
+libinput_debug_events_LDADD = $(event_debug_LDADD)
+libinput_debug_events_CFLAGS = $(event_debug_CFLAGS)
+dist_man1_MANS += libinput-debug-events.man
 
 if BUILD_EVENTGUI
 noinst_PROGRAMS += event-gui
@@ -22,3 +39,5 @@ event_gui_LDADD = ../src/libinput.la libshared.la $(CAIRO_LIBS) $(GTK_LIBS) $(LI
 event_gui_CFLAGS = $(CAIRO_CFLAGS) $(GTK_CFLAGS) $(LIBUDEV_CFLAGS)
 event_gui_LDFLAGS = -no-install
 endif
+
+EXTRA_DIST = make-ptraccel-graphs.sh
index 38a6e8238a053d17a30ea9b74e4881eab061e254..5cd591567d396e219147750ad62622261edeeefa 100644 (file)
@@ -1,27 +1,29 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #define _GNU_SOURCE
 #include <errno.h>
+#include <inttypes.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <stdio.h>
 #include <sys/ioctl.h>
 
 #include <libinput.h>
+#include <libevdev/libevdev.h>
 
 #include "shared.h"
 
 uint32_t start_time;
 static const uint32_t screen_width = 100;
 static const uint32_t screen_height = 100;
-struct tools_options options;
+struct tools_context context;
 static unsigned int stop = 0;
 
-static int
-open_restricted(const char *path, int flags, void *user_data)
-{
-       int fd = open(path, flags);
-       return fd < 0 ? -errno : fd;
-}
-
-static void
-close_restricted(int fd, void *user_data)
-{
-       close(fd);
-}
-
-static const struct libinput_interface interface = {
-       .open_restricted = open_restricted,
-       .close_restricted = close_restricted,
-};
-
 static void
 print_event_header(struct libinput_event *ev)
 {
+       /* use for pointer value only, do not dereference */
+       static void *last_device = NULL;
        struct libinput_device *dev = libinput_event_get_device(ev);
        const char *type = NULL;
+       char prefix;
 
        switch(libinput_event_get_type(ev)) {
        case LIBINPUT_EVENT_NONE:
@@ -106,9 +94,55 @@ print_event_header(struct libinput_event *ev)
        case LIBINPUT_EVENT_TOUCH_FRAME:
                type = "TOUCH_FRAME";
                break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+               type = "GESTURE_SWIPE_BEGIN";
+               break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+               type = "GESTURE_SWIPE_UPDATE";
+               break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+               type = "GESTURE_SWIPE_END";
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+               type = "GESTURE_PINCH_BEGIN";
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+               type = "GESTURE_PINCH_UPDATE";
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_END:
+               type = "GESTURE_PINCH_END";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
+               type = "TABLET_TOOL_AXIS";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
+               type = "TABLET_TOOL_PROXIMITY";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_TIP:
+               type = "TABLET_TOOL_TIP";
+               break;
+       case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
+               type = "TABLET_TOOL_BUTTON";
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+               type = "TABLET_PAD_BUTTON";
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_RING:
+               type = "TABLET_PAD_RING";
+               break;
+       case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+               type = "TABLET_PAD_STRIP";
+               break;
        }
 
-       printf("%-7s    %s      ", libinput_device_get_sysname(dev), type);
+       prefix = (last_device != dev) ? '-' : ' ';
+
+       printf("%c%-7s  %-16s ",
+              prefix,
+              libinput_device_get_sysname(dev),
+              type);
+
+       last_device = dev;
 }
 
 static void
@@ -124,7 +158,7 @@ print_device_notify(struct libinput_event *ev)
        struct libinput_seat *seat = libinput_device_get_seat(dev);
        struct libinput_device_group *group;
        double w, h;
-       uint32_t scroll_methods;
+       uint32_t scroll_methods, click_methods;
        static int next_group_id = 0;
        intptr_t group_id;
 
@@ -151,17 +185,31 @@ print_device_notify(struct libinput_event *ev)
        if (libinput_device_has_capability(dev,
                                           LIBINPUT_DEVICE_CAP_TOUCH))
                printf("t");
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_GESTURE))
+               printf("g");
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_TABLET_TOOL))
+               printf("T");
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_TABLET_PAD))
+               printf("P");
 
        if (libinput_device_get_size(dev, &w, &h) == 0)
                printf("\tsize %.2f/%.2fmm", w, h);
 
-       if (libinput_device_config_tap_get_finger_count((dev)))
+       if (libinput_device_config_tap_get_finger_count(dev)) {
            printf(" tap");
-       if (libinput_device_config_left_handed_is_available((dev)))
+           if (libinput_device_config_tap_get_drag_lock_enabled(dev))
+                   printf("(dl on)");
+           else
+                   printf("(dl off)");
+       }
+       if (libinput_device_config_left_handed_is_available(dev))
            printf(" left");
-       if (libinput_device_config_scroll_has_natural_scroll((dev)))
+       if (libinput_device_config_scroll_has_natural_scroll(dev))
            printf(" scroll-nat");
-       if (libinput_device_config_calibration_has_matrix((dev)))
+       if (libinput_device_config_calibration_has_matrix(dev))
            printf(" calib");
 
        scroll_methods = libinput_device_config_scroll_get_methods(dev);
@@ -175,6 +223,39 @@ print_device_notify(struct libinput_event *ev)
                        printf("-button");
        }
 
+       click_methods = libinput_device_config_click_get_methods(dev);
+       if (click_methods != LIBINPUT_CONFIG_CLICK_METHOD_NONE) {
+               printf(" click");
+               if (click_methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS)
+                       printf("-buttonareas");
+               if (click_methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER)
+                       printf("-clickfinger");
+       }
+
+       if (libinput_device_config_dwt_is_available(dev)) {
+               if (libinput_device_config_dwt_get_enabled(dev) ==
+                   LIBINPUT_CONFIG_DWT_ENABLED)
+                       printf(" dwt-on");
+               else
+                       printf(" dwt-off)");
+       }
+
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_TABLET_PAD)) {
+               int nbuttons, nstrips, nrings, ngroups;
+
+               nbuttons = libinput_device_tablet_pad_get_num_buttons(dev);
+               nstrips = libinput_device_tablet_pad_get_num_strips(dev);
+               nrings = libinput_device_tablet_pad_get_num_rings(dev);
+               ngroups = libinput_device_tablet_pad_get_num_mode_groups(dev);
+
+               printf(" buttons:%d strips:%d rings:%d mode groups:%d",
+                      nbuttons,
+                      nstrips,
+                      nrings,
+                      ngroups);
+       }
+
        printf("\n");
 
 }
@@ -184,11 +265,17 @@ print_key_event(struct libinput_event *ev)
 {
        struct libinput_event_keyboard *k = libinput_event_get_keyboard_event(ev);
        enum libinput_key_state state;
+       uint32_t key;
+       const char *keyname;
 
        print_event_time(libinput_event_keyboard_get_time(k));
        state = libinput_event_keyboard_get_key_state(k);
-       printf("%d %s\n",
-              libinput_event_keyboard_get_key(k),
+
+       key = libinput_event_keyboard_get_key(k);
+       keyname = libevdev_event_code_get_name(EV_KEY, key);
+       printf("%s (%d) %s\n",
+              keyname ? keyname : "???",
+              key,
               state == LIBINPUT_KEY_STATE_PRESSED ? "pressed" : "released");
 }
 
@@ -218,36 +305,150 @@ 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;
+       const char *buttonname;
+       int button;
 
        print_event_time(libinput_event_pointer_get_time(p));
 
+       button = libinput_event_pointer_get_button(p);
+       buttonname = libevdev_event_code_get_name(EV_KEY, button);
+
        state = libinput_event_pointer_get_button_state(p);
-       printf("%3d %s, seat count: %u\n",
-              libinput_event_pointer_get_button(p),
+       printf("%s (%d) %s, seat count: %u\n",
+              buttonname ? buttonname : "???",
+              button,
               state == LIBINPUT_BUTTON_STATE_PRESSED ? "pressed" : "released",
               libinput_event_pointer_get_seat_button_count(p));
 }
 
 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;
+       const char *buttonname;
+       int button;
+
+       print_event_time(libinput_event_tablet_tool_get_time(p));
+
+       button = libinput_event_tablet_tool_get_button(p);
+       buttonname = libevdev_event_code_get_name(EV_KEY, button);
+
+       state = libinput_event_tablet_tool_get_button_state(p);
+       printf("%3d (%s) %s, seat count: %u\n",
+              button,
+              buttonname ? buttonname : "???",
+              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;
+       const char *have_vert = "",
+                  *have_horiz = "";
 
        if (libinput_event_pointer_has_axis(p,
-                                   LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL))
+                               LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
                v = libinput_event_pointer_get_axis_value(p,
                              LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+               have_vert = "*";
+       }
        if (libinput_event_pointer_has_axis(p,
-                                   LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL))
+                               LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) {
                h = libinput_event_pointer_get_axis_value(p,
                              LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
+               have_horiz = "*";
+       }
        print_event_time(libinput_event_pointer_get_time(p));
-       printf("vert %.2f horiz %.2f\n", v, h);
+       printf("vert %.2f%s horiz %.2f%s\n", v, have_vert, h, have_horiz);
+}
+
+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
@@ -259,6 +460,96 @@ print_touch_event_without_coords(struct libinput_event *ev)
        printf("\n");
 }
 
+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)
 {
@@ -277,6 +568,127 @@ print_touch_event_with_coords(struct libinput_event *ev)
               xmm, ymm);
 }
 
+static void
+print_gesture_event_without_coords(struct libinput_event *ev)
+{
+       struct libinput_event_gesture *t = libinput_event_get_gesture_event(ev);
+       int finger_count = libinput_event_gesture_get_finger_count(t);
+       int cancelled = 0;
+       enum libinput_event_type type;
+
+       type = libinput_event_get_type(ev);
+
+       if (type == LIBINPUT_EVENT_GESTURE_SWIPE_END ||
+           type == LIBINPUT_EVENT_GESTURE_PINCH_END)
+           cancelled = libinput_event_gesture_get_cancelled(t);
+
+       print_event_time(libinput_event_gesture_get_time(t));
+       printf("%d%s\n", finger_count, cancelled ? " cancelled" : "");
+}
+
+static void
+print_gesture_event_with_coords(struct libinput_event *ev)
+{
+       struct libinput_event_gesture *t = libinput_event_get_gesture_event(ev);
+       double dx = libinput_event_gesture_get_dx(t);
+       double dy = libinput_event_gesture_get_dy(t);
+       double dx_unaccel = libinput_event_gesture_get_dx_unaccelerated(t);
+       double dy_unaccel = libinput_event_gesture_get_dy_unaccelerated(t);
+
+       print_event_time(libinput_event_gesture_get_time(t));
+
+       printf("%d %5.2f/%5.2f (%5.2f/%5.2f unaccelerated)",
+              libinput_event_gesture_get_finger_count(t),
+              dx, dy, dx_unaccel, dy_unaccel);
+
+       if (libinput_event_get_type(ev) ==
+           LIBINPUT_EVENT_GESTURE_PINCH_UPDATE) {
+               double scale = libinput_event_gesture_get_scale(t);
+               double angle = libinput_event_gesture_get_angle_delta(t);
+
+               printf(" %5.2f @ %5.2f\n", scale, angle);
+       } else {
+               printf("\n");
+       }
+}
+
+static void
+print_tablet_pad_button_event(struct libinput_event *ev)
+{
+       struct libinput_event_tablet_pad *p = libinput_event_get_tablet_pad_event(ev);
+       struct libinput_tablet_pad_mode_group *group;
+       enum libinput_button_state state;
+       unsigned int button, mode;
+
+       print_event_time(libinput_event_tablet_pad_get_time(p));
+
+       button = libinput_event_tablet_pad_get_button_number(p),
+       state = libinput_event_tablet_pad_get_button_state(p);
+       mode = libinput_event_tablet_pad_get_mode(p);
+       printf("%3d %s (mode %d)",
+              button,
+              state == LIBINPUT_BUTTON_STATE_PRESSED ? "pressed" : "released",
+              mode);
+
+       group = libinput_event_tablet_pad_get_mode_group(p);
+       if (libinput_tablet_pad_mode_group_button_is_toggle(group, button))
+               printf(" <mode toggle>");
+
+       printf("\n");
+}
+
+static void
+print_tablet_pad_ring_event(struct libinput_event *ev)
+{
+       struct libinput_event_tablet_pad *p = libinput_event_get_tablet_pad_event(ev);
+       const char *source = "<invalid>";
+       unsigned int mode;
+
+       print_event_time(libinput_event_tablet_pad_get_time(p));
+
+       switch (libinput_event_tablet_pad_get_ring_source(p)) {
+       case LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER:
+               source = "finger";
+               break;
+       case LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN:
+               source = "unknown";
+               break;
+       }
+
+       mode = libinput_event_tablet_pad_get_mode(p);
+       printf("ring %d position %.2f (source %s) (mode %d)\n",
+              libinput_event_tablet_pad_get_ring_number(p),
+              libinput_event_tablet_pad_get_ring_position(p),
+              source,
+              mode);
+}
+
+static void
+print_tablet_pad_strip_event(struct libinput_event *ev)
+{
+       struct libinput_event_tablet_pad *p = libinput_event_get_tablet_pad_event(ev);
+       const char *source = "<invalid>";
+       unsigned int mode;
+
+       print_event_time(libinput_event_tablet_pad_get_time(p));
+
+       switch (libinput_event_tablet_pad_get_strip_source(p)) {
+       case LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER:
+               source = "finger";
+               break;
+       case LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN:
+               source = "unknown";
+               break;
+       }
+
+       mode = libinput_event_tablet_pad_get_mode(p);
+       printf("strip %d position %.2f (source %s) (mode %d)\n",
+              libinput_event_tablet_pad_get_strip_number(p),
+              libinput_event_tablet_pad_get_strip_position(p),
+              source,
+              mode);
+}
+
 static int
 handle_and_print_events(struct libinput *li)
 {
@@ -294,7 +706,7 @@ handle_and_print_events(struct libinput *li)
                case LIBINPUT_EVENT_DEVICE_REMOVED:
                        print_device_notify(ev);
                        tools_device_apply_config(libinput_event_get_device(ev),
-                                                 &options);
+                                                 &context.options);
                        break;
                case LIBINPUT_EVENT_KEYBOARD_KEY:
                        print_key_event(ev);
@@ -306,10 +718,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);
@@ -326,6 +738,45 @@ handle_and_print_events(struct libinput *li)
                case LIBINPUT_EVENT_TOUCH_FRAME:
                        print_touch_event_without_coords(ev);
                        break;
+               case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+                       print_gesture_event_without_coords(ev);
+                       break;
+               case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+                       print_gesture_event_with_coords(ev);
+                       break;
+               case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+                       print_gesture_event_without_coords(ev);
+                       break;
+               case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+                       print_gesture_event_without_coords(ev);
+                       break;
+               case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+                       print_gesture_event_with_coords(ev);
+                       break;
+               case LIBINPUT_EVENT_GESTURE_PINCH_END:
+                       print_gesture_event_without_coords(ev);
+                       break;
+               case LIBINPUT_EVENT_TABLET_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;
+               case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+                       print_tablet_pad_button_event(ev);
+                       break;
+               case LIBINPUT_EVENT_TABLET_PAD_RING:
+                       print_tablet_pad_ring_event(ev);
+                       break;
+               case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+                       print_tablet_pad_strip_event(ev);
+                       break;
                }
 
                libinput_event_destroy(ev);
@@ -376,18 +827,18 @@ main(int argc, char **argv)
        struct libinput *li;
        struct timespec tp;
 
-       tools_init_options(&options);
+       clock_gettime(CLOCK_MONOTONIC, &tp);
+       start_time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
+
+       tools_init_context(&context);
 
-       if (tools_parse_args(argc, argv, &options))
+       if (tools_parse_args(argc, argv, &context))
                return 1;
 
-       li = tools_open_backend(&options, NULL, &interface);
+       li = tools_open_backend(&context);
        if (!li)
                return 1;
 
-       clock_gettime(CLOCK_MONOTONIC, &tp);
-       start_time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
-
        mainloop(li);
 
        libinput_unref(li);
index 85c5ab1355f0fd37e7bc5dff6d8d48d40dcc48e5..b67ca45236897a702b444cfeff55bd082e239cfd 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 #define _GNU_SOURCE
 #include <config.h>
 
 #define clip(val_, min_, max_) min((max_), max((min_), (val_)))
 
-struct tools_options options;
+struct tools_context context;
 
 struct touch {
        int active;
        int x, y;
 };
 
+struct point {
+       double x, y;
+};
+
 struct window {
        GtkWidget *win;
        GtkWidget *area;
@@ -62,8 +67,8 @@ struct window {
        int absx, absy;
 
        /* scroll bar positions */
-       int vx, vy;
-       int hx, hy;
+       double vx, vy;
+       double hx, hy;
 
        /* touch positions */
        struct touch touches[32];
@@ -71,6 +76,35 @@ struct window {
        /* l/m/r mouse buttons */
        int l, m, r;
 
+       /* touchpad swipe */
+       struct {
+               int nfingers;
+               double x, y;
+       } swipe;
+
+       struct {
+               int nfingers;
+               double scale;
+               double angle;
+               double x, y;
+       } pinch;
+
+       struct {
+               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];
 };
 
@@ -98,27 +132,53 @@ msg(const char *fmt, ...)
        va_end(args);
 }
 
-static gboolean
-draw(GtkWidget *widget, cairo_t *cr, gpointer data)
+static inline void
+draw_gestures(struct window *w, cairo_t *cr)
 {
-       struct window *w = data;
-       struct touch *t;
+       int i;
+       int offset;
 
-       cairo_set_source_rgb(cr, 1, 1, 1);
-       cairo_rectangle(cr, 0, 0, w->width, w->height);
-       cairo_fill(cr);
+       /* swipe */
+       cairo_save(cr);
+       cairo_translate(cr, w->swipe.x, w->swipe.y);
+       for (i = 0; i < w->swipe.nfingers; i++) {
+               cairo_set_source_rgb(cr, .8, .8, .4);
+               cairo_arc(cr, (i - 2) * 40, 0, 20, 0, 2 * M_PI);
+               cairo_fill(cr);
+       }
 
-       /* draw pointer sprite */
-       cairo_set_source_rgb(cr, 0, 0, 0);
+       for (i = 0; i < 4; i++) { /* 4 fg max */
+               cairo_set_source_rgb(cr, 0, 0, 0);
+               cairo_arc(cr, (i - 2) * 40, 0, 20, 0, 2 * M_PI);
+               cairo_stroke(cr);
+       }
+       cairo_restore(cr);
+
+       /* pinch */
        cairo_save(cr);
-       cairo_move_to(cr, w->x, w->y);
-       cairo_rel_line_to(cr, 10, 15);
-       cairo_rel_line_to(cr, -10, 0);
-       cairo_rel_line_to(cr, 0, -15);
-       cairo_fill(cr);
+       offset = w->pinch.scale * 100;
+       cairo_translate(cr, w->pinch.x, w->pinch.y);
+       cairo_rotate(cr, w->pinch.angle * M_PI/180.0);
+       if (w->pinch.nfingers > 0) {
+               cairo_set_source_rgb(cr, .4, .4, .8);
+               cairo_arc(cr, offset, -offset, 20, 0, 2 * M_PI);
+               cairo_arc(cr, -offset, offset, 20, 0, 2 * M_PI);
+               cairo_fill(cr);
+       }
+
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       cairo_arc(cr, offset, -offset, 20, 0, 2 * M_PI);
+       cairo_stroke(cr);
+       cairo_arc(cr, -offset, offset, 20, 0, 2 * M_PI);
+       cairo_stroke(cr);
+
        cairo_restore(cr);
 
-       /* draw scroll bars */
+}
+
+static inline void
+draw_scrollbars(struct window *w, cairo_t *cr)
+{
        cairo_set_source_rgb(cr, .4, .8, 0);
 
        cairo_save(cr);
@@ -126,8 +186,13 @@ draw(GtkWidget *widget, cairo_t *cr, gpointer data)
        cairo_rectangle(cr, w->hx - 20, w->hy - 10, 40, 20);
        cairo_fill(cr);
        cairo_restore(cr);
+}
+
+static inline void
+draw_touchpoints(struct window *w, cairo_t *cr)
+{
+       struct touch *t;
 
-       /* touch points */
        cairo_set_source_rgb(cr, .8, .2, .2);
 
        ARRAY_FOR_EACH(w->touches, t) {
@@ -136,16 +201,22 @@ draw(GtkWidget *widget, cairo_t *cr, gpointer data)
                cairo_fill(cr);
                cairo_restore(cr);
        }
+}
 
-       /* abs position */
+static inline void
+draw_abs_pointer(struct window *w, cairo_t *cr)
+{
        cairo_set_source_rgb(cr, .2, .4, .8);
 
        cairo_save(cr);
        cairo_arc(cr, w->absx, w->absy, 10, 0, 2 * M_PI);
        cairo_fill(cr);
        cairo_restore(cr);
+}
 
-       /* lmr buttons */
+static inline void
+draw_buttons(struct window *w, cairo_t *cr)
+{
        cairo_save(cr);
        if (w->l || w->m || w->r) {
                cairo_set_source_rgb(cr, .2, .8, .8);
@@ -164,6 +235,144 @@ draw(GtkWidget *widget, cairo_t *cr, gpointer data)
        cairo_rectangle(cr, w->width/2 + 30, w->height - 200, 70, 30);
        cairo_stroke(cr);
        cairo_restore(cr);
+}
+
+static inline void
+draw_tablet(struct window *w, cairo_t *cr)
+{
+       double x, y;
+       int first, last, mask;
+       int i;
+
+       /* 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/30.0, 1.0 + w->tool.tilt_y/30.0);
+       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);
+
+}
+
+static inline void
+draw_pointer(struct window *w, cairo_t *cr)
+{
+       /* draw pointer sprite */
+       cairo_set_source_rgb(cr, 0, 0, 0);
+       cairo_save(cr);
+       cairo_move_to(cr, w->x, w->y);
+       cairo_rel_line_to(cr, 10, 15);
+       cairo_rel_line_to(cr, -10, 0);
+       cairo_rel_line_to(cr, 0, -15);
+       cairo_fill(cr);
+       cairo_restore(cr);
+}
+
+static inline void
+draw_background(struct window *w, cairo_t *cr)
+{
+       int x1, x2, y1, y2, x3, y3, x4, y4;
+       int cols;
+
+       /* 10px and 5px grids */
+       cairo_save(cr);
+       cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
+       x1 = w->width/2 - 200;
+       y1 = w->height/2 - 200;
+       x2 = w->width/2 + 200;
+       y2 = w->height/2 - 200;
+       for (cols = 1; cols < 10; cols++) {
+               cairo_move_to(cr, x1 + 10 * cols, y1);
+               cairo_rel_line_to(cr, 0, 100);
+               cairo_move_to(cr, x1, y1 + 10 * cols);
+               cairo_rel_line_to(cr, 100, 0);
+
+               cairo_move_to(cr, x2 + 5 * cols, y2);
+               cairo_rel_line_to(cr, 0, 50);
+               cairo_move_to(cr, x2, y2 + 5 * cols);
+               cairo_rel_line_to(cr, 50, 0);
+       }
+
+       /* 3px horiz/vert bar codes */
+       x3 = w->width/2 - 200;
+       y3 = w->height/2 + 200;
+       x4 = w->width/2 + 200;
+       y4 = w->height/2 + 100;
+       for (cols = 0; cols < 50; cols++) {
+               cairo_move_to(cr, x3 + 3 * cols, y3);
+               cairo_rel_line_to(cr, 0, 20);
+
+               cairo_move_to(cr, x4, y4 + 3 * cols);
+               cairo_rel_line_to(cr, 20, 0);
+       }
+       cairo_stroke(cr);
+}
+
+static gboolean
+draw(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+       struct window *w = data;
+
+       cairo_set_source_rgb(cr, 1, 1, 1);
+       cairo_rectangle(cr, 0, 0, w->width, w->height);
+       cairo_fill(cr);
+
+       draw_background(w, cr);
+
+       draw_gestures(w, cr);
+       draw_scrollbars(w, cr);
+       draw_touchpoints(w, cr);
+       draw_abs_pointer(w, cr);
+       draw_buttons(w, cr);
+       draw_tablet(w, cr);
+       draw_pointer(w, cr);
 
        return TRUE;
 }
@@ -172,6 +381,8 @@ static void
 map_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
 {
        struct window *w = data;
+       GdkDisplay *display;
+       GdkWindow *window;
 
        gtk_window_get_size(GTK_WINDOW(widget), &w->width, &w->height);
 
@@ -183,10 +394,21 @@ map_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
        w->hx = w->width/2;
        w->hy = w->height/2;
 
+       w->swipe.x = w->width/2;
+       w->swipe.y = w->height/2;
+
+       w->pinch.scale = 1.0;
+       w->pinch.x = w->width/2;
+       w->pinch.y = w->height/2;
+
        g_signal_connect(G_OBJECT(w->area), "draw", G_CALLBACK(draw), w);
 
+       window = gdk_event_get_window(event);
+       display = gdk_window_get_display(window);
+
        gdk_window_set_cursor(gtk_widget_get_window(w->win),
-                             gdk_cursor_new(GDK_BLANK_CURSOR));
+                             gdk_cursor_new_for_display(display,
+                                                        GDK_BLANK_CURSOR));
 }
 
 static void
@@ -257,6 +479,7 @@ change_ptraccel(struct window *w, double amount)
 static void
 handle_event_device_notify(struct libinput_event *ev)
 {
+       struct tools_context *context;
        struct libinput_device *dev = libinput_event_get_device(ev);
        struct libinput *li;
        struct window *w;
@@ -273,17 +496,12 @@ handle_event_device_notify(struct libinput_event *ev)
            libinput_device_get_name(dev),
            type);
 
-       if (libinput_device_config_tap_get_finger_count(dev) > 0) {
-               enum libinput_config_status status;
-               status = libinput_device_config_tap_set_enabled(dev,
-                                                               LIBINPUT_CONFIG_TAP_ENABLED);
-               if (status != LIBINPUT_CONFIG_STATUS_SUCCESS)
-                       error("%s: Failed to enable tapping\n",
-                             libinput_device_get_sysname(dev));
-       }
-
        li = libinput_event_get_context(ev);
-       w = libinput_get_user_data(li);
+       context = libinput_get_user_data(li);
+       w = context->user_data;
+
+       tools_device_apply_config(libinput_event_get_device(ev),
+                                 &context->options);
 
        if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED) {
                for (i = 0; i < ARRAY_LENGTH(w->devices); i++) {
@@ -363,7 +581,7 @@ handle_event_axis(struct libinput_event *ev, struct window *w)
                        LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
                value = libinput_event_pointer_get_axis_value(p,
                                LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
-               w->vy += (int)value;
+               w->vy += value;
                w->vy = clip(w->vy, 0, w->height);
        }
 
@@ -371,7 +589,7 @@ handle_event_axis(struct libinput_event *ev, struct window *w)
                        LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) {
                value = libinput_event_pointer_get_axis_value(p,
                                LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
-               w->hx += (int)value;
+               w->hx += value;
                w->hx = clip(w->hx, 0, w->width);
        }
 }
@@ -425,11 +643,148 @@ handle_event_button(struct libinput_event *ev, struct window *w)
 
 }
 
+static void
+handle_event_swipe(struct libinput_event *ev, struct window *w)
+{
+       struct libinput_event_gesture *g = libinput_event_get_gesture_event(ev);
+       int nfingers;
+       double dx, dy;
+
+       nfingers = libinput_event_gesture_get_finger_count(g);
+
+       switch (libinput_event_get_type(ev)) {
+       case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+               w->swipe.nfingers = nfingers;
+               w->swipe.x = w->width/2;
+               w->swipe.y = w->height/2;
+               break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+               dx = libinput_event_gesture_get_dx(g);
+               dy = libinput_event_gesture_get_dy(g);
+               w->swipe.x += dx;
+               w->swipe.y += dy;
+               break;
+       case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+               w->swipe.nfingers = 0;
+               w->swipe.x = w->width/2;
+               w->swipe.y = w->height/2;
+               break;
+       default:
+               abort();
+       }
+}
+
+static void
+handle_event_pinch(struct libinput_event *ev, struct window *w)
+{
+       struct libinput_event_gesture *g = libinput_event_get_gesture_event(ev);
+       int nfingers;
+       double dx, dy;
+
+       nfingers = libinput_event_gesture_get_finger_count(g);
+
+       switch (libinput_event_get_type(ev)) {
+       case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+               w->pinch.nfingers = nfingers;
+               w->pinch.x = w->width/2;
+               w->pinch.y = w->height/2;
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+               dx = libinput_event_gesture_get_dx(g);
+               dy = libinput_event_gesture_get_dy(g);
+               w->pinch.x += dx;
+               w->pinch.y += dy;
+               w->pinch.scale = libinput_event_gesture_get_scale(g);
+               w->pinch.angle += libinput_event_gesture_get_angle_delta(g);
+               break;
+       case LIBINPUT_EVENT_GESTURE_PINCH_END:
+               w->pinch.nfingers = 0;
+               w->pinch.x = w->width/2;
+               w->pinch.y = w->height/2;
+               w->pinch.angle = 0.0;
+               w->pinch.scale = 1.0;
+               break;
+       default:
+               abort();
+       }
+}
+
+static 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)
 {
        struct libinput *li = data;
-       struct window *w = libinput_get_user_data(li);
+       struct tools_context *context = libinput_get_user_data(li);
+       struct window *w = context->user_data;
        struct libinput_event *ev;
 
        libinput_dispatch(li);
@@ -469,6 +824,26 @@ handle_event_libinput(GIOChannel *source, GIOCondition condition, gpointer data)
                                return FALSE;
                        }
                        break;
+               case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
+               case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
+               case LIBINPUT_EVENT_GESTURE_SWIPE_END:
+                       handle_event_swipe(ev, w);
+                       break;
+               case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
+               case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
+               case LIBINPUT_EVENT_GESTURE_PINCH_END:
+                       handle_event_pinch(ev, w);
+                       break;
+               case LIBINPUT_EVENT_TABLET_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;
+               case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+               case LIBINPUT_EVENT_TABLET_PAD_RING:
+               case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+                       break;
                }
 
                libinput_event_destroy(ev);
@@ -488,24 +863,6 @@ sockets_init(struct libinput *li)
        g_io_add_watch(c, G_IO_IN, handle_event_libinput, li);
 }
 
-static int
-open_restricted(const char *path, int flags, void *user_data)
-{
-       int fd = open(path, flags);
-       return fd < 0 ? -errno : fd;
-}
-
-static void
-close_restricted(int fd, void *user_data)
-{
-       close(fd);
-}
-
-static const struct libinput_interface interface = {
-       .open_restricted = open_restricted,
-       .close_restricted = close_restricted,
-};
-
 int
 main(int argc, char *argv[])
 {
@@ -515,21 +872,23 @@ main(int argc, char *argv[])
 
        gtk_init(&argc, &argv);
 
-       tools_init_options(&options);
+       tools_init_context(&context);
 
-       if (tools_parse_args(argc, argv, &options) != 0)
+       if (tools_parse_args(argc, argv, &context) != 0)
                return 1;
 
        udev = udev_new();
        if (!udev)
                error("Failed to initialize udev\n");
 
-       li = tools_open_backend(&options, &w, &interface);
+       context.user_data = &w;
+       li = tools_open_backend(&context);
        if (!li)
                return 1;
 
        window_init(&w);
        sockets_init(li);
+       handle_event_libinput(NULL, 0, li);
 
        gtk_main();
 
diff --git a/tools/libinput-debug-events.man b/tools/libinput-debug-events.man
new file mode 100644 (file)
index 0000000..bb39399
--- /dev/null
@@ -0,0 +1,31 @@
+.TH LIBINPUT-DEBUG-EVENTS "1"
+.SH NAME
+libinput-debug-events \- debug helper for libinput
+.SH SYNOPSIS
+.B libinput-debug-events [--help]
+.SH DESCRIPTION
+.PP
+The
+.I libinput-debug-events
+tool creates a libinput context and prints all events from these devices.
+.PP
+This is a debugging tool only, its output may change at any time. Do not
+rely on the output.
+.PP
+This tool usually needs to be run as root to have access to the
+/dev/input/eventX nodes.
+.SH OPTIONS
+.TP 8
+.B --help
+Print help
+.PP
+For all other options, see the output from --help. Options may be added or
+removed at any time.
+.SH NOTES
+.PP
+Events shown by this tool may not correspond to the events seen by a
+different user of libinput. This tool initializes a separate context.
+.PP
+Events shown by this tool include key codes in plain text. Anything you type
+while this tool is running will show up in the output, including your
+passwords.
diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c
new file mode 100644 (file)
index 0000000..2869dd7
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <libudev.h>
+
+#include <libinput.h>
+#include <libinput-util.h>
+#include <libinput-version.h>
+
+#include "shared.h"
+
+static const char *
+tap_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_tap_get_finger_count(device))
+               return "n/a";
+
+       if (libinput_device_config_tap_get_default_enabled(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static const char *
+drag_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_tap_get_finger_count(device))
+               return "n/a";
+
+       if (libinput_device_config_tap_get_default_drag_enabled(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static const char *
+draglock_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_tap_get_finger_count(device))
+               return "n/a";
+
+       if (libinput_device_config_tap_get_default_drag_lock_enabled(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static const char*
+left_handed_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_left_handed_is_available(device))
+               return "n/a";
+
+       if (libinput_device_config_left_handed_get_default(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static const char *
+nat_scroll_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_scroll_has_natural_scroll(device))
+               return "n/a";
+
+       if (libinput_device_config_scroll_get_default_natural_scroll_enabled(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static const char *
+middle_emulation_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_middle_emulation_is_available(device))
+               return "n/a";
+
+       if (libinput_device_config_middle_emulation_get_default_enabled(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static char *
+calibration_default(struct libinput_device *device)
+{
+       char *str;
+       float calibration[6];
+
+       if (!libinput_device_config_calibration_has_matrix(device)) {
+               xasprintf(&str, "n/a");
+               return str;
+       }
+
+       if (libinput_device_config_calibration_get_default_matrix(device,
+                                                 calibration) == 0) {
+               xasprintf(&str, "identity matrix");
+               return str;
+       }
+
+       xasprintf(&str,
+                "%.2f %.2f %.2f %.2f %.2f %.2f",
+                calibration[0],
+                calibration[1],
+                calibration[2],
+                calibration[3],
+                calibration[4],
+                calibration[5]);
+       return str;
+}
+
+static char *
+scroll_defaults(struct libinput_device *device)
+{
+       uint32_t scroll_methods;
+       char *str;
+       enum libinput_config_scroll_method method;
+
+       scroll_methods = libinput_device_config_scroll_get_methods(device);
+       if (scroll_methods == LIBINPUT_CONFIG_SCROLL_NO_SCROLL) {
+               xasprintf(&str, "none");
+               return str;
+       }
+
+       method = libinput_device_config_scroll_get_default_method(device);
+
+       xasprintf(&str,
+                "%s%s%s%s%s%s",
+                (method == LIBINPUT_CONFIG_SCROLL_2FG) ? "*" : "",
+                (scroll_methods & LIBINPUT_CONFIG_SCROLL_2FG) ? "two-finger " : "",
+                (method == LIBINPUT_CONFIG_SCROLL_EDGE) ? "*" : "",
+                (scroll_methods & LIBINPUT_CONFIG_SCROLL_EDGE) ? "edge " : "",
+                (method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) ? "*" : "",
+                (scroll_methods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) ? "button" : "");
+       return str;
+}
+
+static char*
+click_defaults(struct libinput_device *device)
+{
+       uint32_t click_methods;
+       char *str;
+       enum libinput_config_click_method method;
+
+       click_methods = libinput_device_config_click_get_methods(device);
+       if (click_methods == LIBINPUT_CONFIG_CLICK_METHOD_NONE) {
+               xasprintf(&str, "none");
+               return str;
+       }
+
+       method = libinput_device_config_click_get_default_method(device);
+       xasprintf(&str,
+                "%s%s%s%s",
+                (method == LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? "*" : "",
+                (click_methods & LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) ? "button-areas " : "",
+                (method == LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) ? "*" : "",
+                (click_methods & LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) ? "clickfinger " : "");
+       return str;
+}
+
+static char*
+accel_profiles(struct libinput_device *device)
+{
+       uint32_t profiles;
+       char *str;
+       enum libinput_config_accel_profile profile;
+
+       if (!libinput_device_config_accel_is_available(device)) {
+               xasprintf(&str, "n/a");
+               return str;
+       }
+
+       profiles = libinput_device_config_accel_get_profiles(device);
+       if (profiles == LIBINPUT_CONFIG_ACCEL_PROFILE_NONE) {
+               xasprintf(&str, "none");
+               return str;
+       }
+
+       profile = libinput_device_config_accel_get_default_profile(device);
+       xasprintf(&str,
+                 "%s%s %s%s",
+                 (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? "*" : "",
+                 (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT) ? "flat" : "",
+                 (profile == LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "*" : "",
+                 (profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE) ? "adaptive" : "");
+
+       return str;
+}
+
+static const char *
+dwt_default(struct libinput_device *device)
+{
+       if (!libinput_device_config_dwt_is_available(device))
+               return "n/a";
+
+       if (libinput_device_config_dwt_get_default_enabled(device))
+               return "enabled";
+       else
+               return "disabled";
+}
+
+static char *
+rotation_default(struct libinput_device *device)
+{
+       char *str;
+       double angle;
+
+       if (!libinput_device_config_rotation_is_available(device)) {
+               xasprintf(&str, "n/a");
+               return str;
+       }
+
+       angle = libinput_device_config_rotation_get_angle(device);
+       xasprintf(&str, "%.1f", angle);
+       return str;
+}
+
+static void
+print_pad_info(struct libinput_device *device)
+{
+       int nbuttons, nrings, nstrips;
+
+       nbuttons = libinput_device_tablet_pad_get_num_buttons(device);
+       nrings = libinput_device_tablet_pad_get_num_rings(device);
+       nstrips = libinput_device_tablet_pad_get_num_strips(device);
+
+       printf("Pad:\n");
+       printf("        Rings:   %d\n", nrings);
+       printf("        Strips:  %d\n", nstrips);
+       printf("        Buttons: %d\n", nbuttons);
+}
+
+static void
+print_device_notify(struct libinput_event *ev)
+{
+       struct libinput_device *dev = libinput_event_get_device(ev);
+       struct libinput_seat *seat = libinput_device_get_seat(dev);
+       struct libinput_device_group *group;
+       double w, h;
+       static int next_group_id = 0;
+       intptr_t group_id;
+       const char *devnode;
+       char *str;
+
+       group = libinput_device_get_device_group(dev);
+       group_id = (intptr_t)libinput_device_group_get_user_data(group);
+       if (!group_id) {
+               group_id = ++next_group_id;
+               libinput_device_group_set_user_data(group, (void*)group_id);
+       }
+
+       devnode = udev_device_get_devnode(
+                                 libinput_device_get_udev_device(dev));
+
+       printf("Device:           %s\n"
+              "Kernel:           %s\n"
+              "Group:            %d\n"
+              "Seat:             %s, %s\n",
+              libinput_device_get_name(dev),
+              devnode,
+              (int)group_id,
+              libinput_seat_get_physical_name(seat),
+              libinput_seat_get_logical_name(seat));
+
+       if (libinput_device_get_size(dev, &w, &h) == 0)
+               printf("Size:             %.2fx%.2fmm\n", w, h);
+       printf("Capabilities:     ");
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_KEYBOARD))
+               printf("keyboard ");
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_POINTER))
+               printf("pointer ");
+       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 ");
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_TABLET_PAD))
+               printf("tablet-pad");
+       printf("\n");
+
+       printf("Tap-to-click:     %s\n", tap_default(dev));
+       printf("Tap-and-drag:     %s\n",  drag_default(dev));
+       printf("Tap drag lock:    %s\n", draglock_default(dev));
+       printf("Left-handed:      %s\n", left_handed_default(dev));
+       printf("Nat.scrolling:    %s\n", nat_scroll_default(dev));
+       printf("Middle emulation: %s\n", middle_emulation_default(dev));
+       str = calibration_default(dev);
+       printf("Calibration:      %s\n", str);
+       free(str);
+
+       str = scroll_defaults(dev);
+       printf("Scroll methods:   %s\n", str);
+       free(str);
+
+       str = click_defaults(dev);
+       printf("Click methods:    %s\n", str);
+       free(str);
+
+       printf("Disable-w-typing: %s\n", dwt_default(dev));
+
+       str = accel_profiles(dev);
+       printf("Accel profiles:   %s\n", str);
+       free(str);
+
+       str = rotation_default(dev);
+       printf("Rotation:         %s\n", str);
+       free(str);
+
+       if (libinput_device_has_capability(dev,
+                                          LIBINPUT_DEVICE_CAP_TABLET_PAD))
+               print_pad_info(dev);
+
+       printf("\n");
+}
+
+static inline void
+usage(void)
+{
+       printf("Usage: %s [--help|--version]\n"
+              "\n"
+              "This tool creates a libinput context on the default seat \"seat0\"\n"
+              "and lists all devices recognized by libinput and the configuration options.\n"
+              "Where multiple options are possible, the default is prefixed with \"*\".\n"
+              "\n"
+              "Options:\n"
+              "--help ...... show this help\n"
+              "--version ... show version information\n"
+              "\n"
+              "This tool requires access to the /dev/input/eventX nodes.\n",
+              program_invocation_short_name);
+}
+
+int
+main(int argc, char **argv)
+{
+       struct libinput *li;
+       struct tools_context context;
+       struct libinput_event *ev;
+
+       if (argc > 1) {
+               if (streq(argv[1], "--help")) {
+                       usage();
+                       return 0;
+               } else if (streq(argv[1], "--version")) {
+                       printf("%s\n", LIBINPUT_VERSION);
+                       return 0;
+               } else {
+                       usage();
+                       return 1;
+               }
+       }
+
+       tools_init_context(&context);
+
+       li = tools_open_backend(&context);
+       if (!li)
+               return 1;
+
+       libinput_dispatch(li);
+       while ((ev = libinput_get_event(li))) {
+
+               if (libinput_event_get_type(ev) == LIBINPUT_EVENT_DEVICE_ADDED)
+                       print_device_notify(ev);
+
+               libinput_event_destroy(ev);
+               libinput_dispatch(li);
+       }
+
+       libinput_unref(li);
+
+       return 0;
+}
diff --git a/tools/libinput-list-devices.man b/tools/libinput-list-devices.man
new file mode 100644 (file)
index 0000000..cd89283
--- /dev/null
@@ -0,0 +1,37 @@
+.TH LIBINPUT-LIST-DEVICES "1"
+.SH NAME
+libinput-list-devices \- list local devices as recognized by libinput
+.SH SYNOPSIS
+.B libinput-list-devices [--help]
+.SH DESCRIPTION
+.PP
+The
+.I libinput-list-devices
+tool creates a libinput context on the default seat "seat0" and lists all
+devices regonized by libinput. Each device shows available configurations
+the respective default configuration setting.
+.PP
+For configuration options that allow multiple different settings (e.g.
+scrolling), all available settings are listed. The default setting is
+prefixed by an asterisk (*).
+.PP
+This tool usually needs to be run as root to have access to the
+/dev/input/eventX nodes.
+.SH OPTIONS
+.TP 8
+.B --help
+Print help
+.SH NOTES
+.PP
+Some specific feature may still be available on a device even when
+no configuration is exposed, a lack of a configuration option does not
+necessarily mean that this feature does not work.
+.PP
+A device may be recognized by libinput but not handled by the X.Org libinput
+driver or the Wayland compositor.
+.PP
+An xorg.conf(5) configuration entry or Wayland compositor setting may have
+changed configurations on a device. The
+.I libinput-list-devices
+tool only shows the device's default configuration, not the current
+configuration.
diff --git a/tools/make-ptraccel-graphs.sh b/tools/make-ptraccel-graphs.sh
new file mode 100755 (executable)
index 0000000..901baf9
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+tool=`dirname $0`/ptraccel-debug
+gnuplot=/usr/bin/gnuplot
+
+outfile="ptraccel-linear"
+for speed in -1 -0.75 -0.5 -0.25 0 0.5 1; do
+       $tool --mode=accel --dpi=1000 --filter=linear --speed=$speed > $outfile-$speed.gnuplot
+done
+$gnuplot <<EOF
+set terminal svg enhanced background rgb 'white'
+set output "$outfile.svg"
+set xlabel "speed in units/us"
+set ylabel "accel factor"
+set style data lines
+set yrange [0:3]
+set xrange [0:0.003]
+plot "$outfile--1.gnuplot" using 1:2 title "-1.0", \
+       "$outfile--0.75.gnuplot" using 1:2 title "-0.75", \
+       "$outfile--0.5.gnuplot" using 1:2 title "-0.5", \
+       "$outfile--0.25.gnuplot" using 1:2 title "-0.25", \
+       "$outfile-0.gnuplot" using 1:2 title "0.0", \
+       "$outfile-0.5.gnuplot" using 1:2 title "0.5", \
+       "$outfile-1.gnuplot" using 1:2 title "1.0"
+EOF
+
+outfile="ptraccel-low-dpi"
+for dpi in 200 400 800 1000; do
+       $tool --mode=accel --dpi=$dpi --filter=low-dpi > $outfile-$dpi.gnuplot
+done
+
+$gnuplot <<EOF
+set terminal svg enhanced background rgb 'white'
+set output "$outfile.svg"
+set xlabel "speed in units/us"
+set ylabel "accel factor"
+set style data lines
+set yrange [0:5]
+set xrange [0:0.003]
+plot "$outfile-200.gnuplot" using 1:2 title "200dpi", \
+     "$outfile-400.gnuplot" using 1:2 title "400dpi", \
+     "$outfile-800.gnuplot" using 1:2 title "800dpi", \
+     "$outfile-1000.gnuplot" using 1:2 title "1000dpi"
+EOF
+
+outfile="ptraccel-touchpad"
+$tool --mode=accel --dpi=1000 --filter=linear > $outfile-mouse.gnuplot
+$tool --mode=accel --dpi=1000 --filter=touchpad > $outfile-touchpad.gnuplot
+$gnuplot <<EOF
+set terminal svg enhanced background rgb 'white'
+set output "$outfile.svg"
+set xlabel "speed in units/us"
+set ylabel "accel factor"
+set style data lines
+set yrange [0:3]
+set xrange [0:0.003]
+plot "$outfile-mouse.gnuplot" using 1:2 title "linear (mouse)", \
+     "$outfile-touchpad.gnuplot" using 1:2 title "touchpad"
+EOF
+
+outfile="ptraccel-trackpoint"
+$tool --mode=accel --dpi=1000 --filter=linear > $outfile-mouse.gnuplot
+for constaccel in 1 2 3; do
+       dpi=$((1000/$constaccel))
+       $tool --mode=accel --dpi=$dpi --filter=trackpoint > $outfile-trackpoint-$constaccel.gnuplot
+done
+$gnuplot <<EOF
+set terminal svg enhanced background rgb 'white'
+set output "$outfile.svg"
+set xlabel "speed in units/us"
+set ylabel "accel factor"
+set style data lines
+set yrange [0:5]
+set xrange [0:0.003]
+plot "$outfile-mouse.gnuplot" using 1:2 title "linear (mouse)", \
+     "$outfile-trackpoint-1.gnuplot" using 1:2 title "const accel 1", \
+     "$outfile-trackpoint-2.gnuplot" using 1:2 title "const accel 2", \
+     "$outfile-trackpoint-3.gnuplot" using 1:2 title "const accel 3"
+EOF
diff --git a/tools/ptraccel-debug.c b/tools/ptraccel-debug.c
new file mode 100644 (file)
index 0000000..aa887fe
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <filter.h>
+#include <libinput-util.h>
+
+static void
+print_ptraccel_deltas(struct motion_filter *filter, double step)
+{
+       struct normalized_coords motion;
+       uint64_t time = 0;
+       double i;
+
+       printf("# gnuplot:\n");
+       printf("# set xlabel dx unaccelerated\n");
+       printf("# set ylabel dx accelerated\n");
+       printf("# set style data lines\n");
+       printf("# plot \"gnuplot.data\" using 1:2 title \"step %.2f\"\n", step);
+       printf("#\n");
+
+       /* Accel flattens out after 15 and becomes linear */
+       for (i = 0.0; i < 15.0; i += step) {
+               motion.x = i;
+               motion.y = 0;
+               time += us(12500); /* pretend 80Hz data */
+
+               motion = filter_dispatch(filter, &motion, NULL, time);
+
+               printf("%.2f    %.3f\n", i, motion.x);
+       }
+}
+
+static void
+print_ptraccel_movement(struct motion_filter *filter,
+                       int nevents,
+                       double max_dx,
+                       double step)
+{
+       struct normalized_coords motion;
+       uint64_t time = 0;
+       double dx;
+       int i;
+
+       printf("# gnuplot:\n");
+       printf("# set xlabel \"event number\"\n");
+       printf("# set ylabel \"delta motion\"\n");
+       printf("# set style data lines\n");
+       printf("# plot \"gnuplot.data\" using 1:2 title \"dx out\", \\\n");
+       printf("#      \"gnuplot.data\" using 1:3 title \"dx in\"\n");
+       printf("#\n");
+
+       if (nevents == 0) {
+               if (step > 1.0)
+                       nevents = max_dx;
+               else
+                       nevents = 1.0 * max_dx/step + 0.5;
+
+               /* Print more events than needed so we see the curve
+                * flattening out */
+               nevents *= 1.5;
+       }
+
+       dx = 0;
+
+       for (i = 0; i < nevents; i++) {
+               motion.x = dx;
+               motion.y = 0;
+               time += us(12500); /* pretend 80Hz data */
+
+               motion = filter_dispatch(filter, &motion, NULL, time);
+
+               printf("%d      %.3f    %.3f\n", i, motion.x, dx);
+
+               if (dx < max_dx)
+                       dx += step;
+       }
+}
+
+static void
+print_ptraccel_sequence(struct motion_filter *filter,
+                       int nevents,
+                       double *deltas)
+{
+       struct normalized_coords motion;
+       uint64_t time = 0;
+       double *dx;
+       int i;
+
+       printf("# gnuplot:\n");
+       printf("# set xlabel \"event number\"\n");
+       printf("# set ylabel \"delta motion\"\n");
+       printf("# set style data lines\n");
+       printf("# plot \"gnuplot.data\" using 1:2 title \"dx out\", \\\n");
+       printf("#      \"gnuplot.data\" using 1:3 title \"dx in\"\n");
+       printf("#\n");
+
+       dx = deltas;
+
+       for (i = 0; i < nevents; i++, dx++) {
+               motion.x = *dx;
+               motion.y = 0;
+               time += us(12500); /* pretend 80Hz data */
+
+               motion = filter_dispatch(filter, &motion, NULL, time);
+
+               printf("%d      %.3f    %.3f\n", i, motion.x, *dx);
+       }
+}
+
+static void
+print_accel_func(struct motion_filter *filter, accel_profile_func_t profile)
+{
+       double vel;
+
+       printf("# gnuplot:\n");
+       printf("# set xlabel \"speed\"\n");
+       printf("# set ylabel \"raw accel factor\"\n");
+       printf("# set style data lines\n");
+       printf("# plot \"gnuplot.data\" using 1:2\n");
+       for (vel = 0.0; vel < 0.003; vel += 0.0000001) {
+               double result = profile(filter, NULL, vel, 0 /* time */);
+               printf("%.8f\t%.4f\n", vel, result);
+       }
+}
+
+static void
+usage(void)
+{
+       printf("Usage: %s [options] [dx1] [dx2] [...] > gnuplot.data\n", program_invocation_short_name);
+       printf("\n"
+              "Options:\n"
+              "--mode=<motion|accel|delta|sequence> \n"
+              "        motion   ... print motion to accelerated motion (default)\n"
+              "        delta    ... print delta to accelerated delta\n"
+              "        accel    ... print accel factor\n"
+              "        sequence ... print motion for custom delta sequence\n"
+              "--maxdx=<double>  ... in motion mode only. Stop increasing dx at maxdx\n"
+              "--steps=<double>  ... in motion and delta modes only. Increase dx by step each round\n"
+              "--speed=<double>  ... accel speed [-1, 1], default 0\n"
+              "--dpi=<int>     ... device resolution in DPI (default: 1000)\n"
+              "--filter=<linear|low-dpi|touchpad|x230|trackpoint> \n"
+              "        linear    ... the default motion filter\n"
+              "        low-dpi   ... low-dpi filter, use --dpi with this argument\n"
+              "        touchpad  ... the touchpad motion filter\n"
+              "        x230      ... custom filter for the Lenovo x230 touchpad\n"
+              "        trackpoint... trackpoint motion filter\n"
+              "\n"
+              "If extra arguments are present and mode is not given, mode defaults to 'sequence'\n"
+              "and the arguments are interpreted as sequence of delta x coordinates\n"
+              "\n"
+              "If stdin is a pipe, mode defaults to 'sequence' and the pipe is read \n"
+              "for delta coordinates\n"
+              "\n"
+              "Output best viewed with gnuplot. See output for gnuplot commands\n");
+}
+
+int
+main(int argc, char **argv)
+{
+       struct motion_filter *filter;
+       double step = 0.1,
+              max_dx = 10;
+       int nevents = 0;
+       bool print_accel = false,
+            print_motion = true,
+            print_delta = false,
+            print_sequence = false;
+       double custom_deltas[1024];
+       double speed = 0.0;
+       int dpi = 1000;
+       const char *filter_type = "linear";
+       accel_profile_func_t profile = NULL;
+
+       enum {
+               OPT_MODE = 1,
+               OPT_NEVENTS,
+               OPT_MAXDX,
+               OPT_STEP,
+               OPT_SPEED,
+               OPT_DPI,
+               OPT_FILTER,
+       };
+
+       while (1) {
+               int c;
+               int option_index = 0;
+               static struct option long_options[] = {
+                       {"mode", 1, 0, OPT_MODE },
+                       {"nevents", 1, 0, OPT_NEVENTS },
+                       {"maxdx", 1, 0, OPT_MAXDX },
+                       {"step", 1, 0, OPT_STEP },
+                       {"speed", 1, 0, OPT_SPEED },
+                       {"dpi", 1, 0, OPT_DPI },
+                       {"filter", 1, 0, OPT_FILTER},
+                       {0, 0, 0, 0}
+               };
+
+               c = getopt_long(argc, argv, "",
+                               long_options, &option_index);
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case OPT_MODE:
+                       if (streq(optarg, "accel"))
+                               print_accel = true;
+                       else if (streq(optarg, "motion"))
+                               print_motion = true;
+                       else if (streq(optarg, "delta"))
+                               print_delta = true;
+                       else if (streq(optarg, "sequence"))
+                               print_sequence = true;
+                       else {
+                               usage();
+                               return 1;
+                       }
+                       break;
+               case OPT_NEVENTS:
+                       nevents = atoi(optarg);
+                       if (nevents == 0) {
+                               usage();
+                               return 1;
+                       }
+                       break;
+               case OPT_MAXDX:
+                       max_dx = strtod(optarg, NULL);
+                       if (max_dx == 0.0) {
+                               usage();
+                               return 1;
+                       }
+                       break;
+               case OPT_STEP:
+                       step = strtod(optarg, NULL);
+                       if (step == 0.0) {
+                               usage();
+                               return 1;
+                       }
+                       break;
+               case OPT_SPEED:
+                       speed = strtod(optarg, NULL);
+                       break;
+               case OPT_DPI:
+                       dpi = strtod(optarg, NULL);
+                       break;
+               case OPT_FILTER:
+                       filter_type = optarg;
+                       break;
+               default:
+                       usage();
+                       exit(1);
+                       break;
+               }
+       }
+
+       if (streq(filter_type, "linear")) {
+               filter = create_pointer_accelerator_filter_linear(dpi);
+               profile = pointer_accel_profile_linear;
+       } else if (streq(filter_type, "low-dpi")) {
+               filter = create_pointer_accelerator_filter_linear_low_dpi(dpi);
+               profile = pointer_accel_profile_linear_low_dpi;
+       } else if (streq(filter_type, "touchpad")) {
+               filter = create_pointer_accelerator_filter_touchpad(dpi);
+               profile = touchpad_accel_profile_linear;
+       } else if (streq(filter_type, "x230")) {
+               filter = create_pointer_accelerator_filter_lenovo_x230(dpi);
+               profile = touchpad_lenovo_x230_accel_profile;
+       } else if (streq(filter_type, "trackpoint")) {
+               filter = create_pointer_accelerator_filter_trackpoint(dpi);
+               profile = trackpoint_accel_profile;
+       } else {
+               fprintf(stderr, "Invalid filter type %s\n", filter_type);
+               return 1;
+       }
+
+       assert(filter != NULL);
+       filter_set_speed(filter, speed);
+
+       if (!isatty(STDIN_FILENO)) {
+               char buf[12];
+               print_sequence = true;
+               print_motion = false;
+               nevents = 0;
+               memset(custom_deltas, 0, sizeof(custom_deltas));
+
+               while(fgets(buf, sizeof(buf), stdin) && nevents < 1024) {
+                       custom_deltas[nevents++] = strtod(buf, NULL);
+               }
+       } else if (optind < argc) {
+               print_sequence = true;
+               print_motion = false;
+               nevents = 0;
+               memset(custom_deltas, 0, sizeof(custom_deltas));
+               while (optind < argc)
+                       custom_deltas[nevents++] = strtod(argv[optind++], NULL);
+       }
+
+       if (print_accel)
+               print_accel_func(filter, profile);
+       else if (print_delta)
+               print_ptraccel_deltas(filter, step);
+       else if (print_motion)
+               print_ptraccel_movement(filter, nevents, max_dx, step);
+       else if (print_sequence)
+               print_ptraccel_sequence(filter, nevents, custom_deltas);
+
+       filter_destroy(filter);
+
+       return 0;
+}
index aa4b74126b167b6fd149a77d0b53371df35c032d..d88edc3dbaf31303a2b37887b461e6cb868f56c5 100755 (executable)
@@ -8,4 +8,11 @@ make
 
 [ -e doc/html ] || (echo "HTML documentation failed to build" && exit 1)
 
-rsync --delete -avz doc/html/ freedesktop.org:/srv/wayland.freedesktop.org/www/libinput/doc/latest
+path=latest
+
+if [ -n "$1" ]; then
+       echo "Pushing to directory '$1'"
+       path="$1"
+fi
+
+rsync --delete -avz doc/html/ freedesktop.org:/srv/wayland.freedesktop.org/www/libinput/doc/$path
index 2cff52c445a5acf938c19b303bf394128d3fa61f..95655ba7eb367b83be9a34f0b9b4d50538de4f51 100644 (file)
@@ -1,49 +1,68 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #define _GNU_SOURCE
 #include <config.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <libudev.h>
 
+#include <libevdev/libevdev.h>
+#include <libinput-util.h>
+
 #include "shared.h"
 
 enum options {
        OPT_DEVICE,
        OPT_UDEV,
+       OPT_GRAB,
        OPT_HELP,
        OPT_VERBOSE,
        OPT_TAP_ENABLE,
        OPT_TAP_DISABLE,
+       OPT_TAP_MAP,
+       OPT_DRAG_ENABLE,
+       OPT_DRAG_DISABLE,
+       OPT_DRAG_LOCK_ENABLE,
+       OPT_DRAG_LOCK_DISABLE,
        OPT_NATURAL_SCROLL_ENABLE,
        OPT_NATURAL_SCROLL_DISABLE,
        OPT_LEFT_HANDED_ENABLE,
        OPT_LEFT_HANDED_DISABLE,
+       OPT_MIDDLEBUTTON_ENABLE,
+       OPT_MIDDLEBUTTON_DISABLE,
+       OPT_DWT_ENABLE,
+       OPT_DWT_DISABLE,
        OPT_CLICK_METHOD,
+       OPT_SCROLL_METHOD,
+       OPT_SCROLL_BUTTON,
+       OPT_SPEED,
+       OPT_PROFILE,
 };
 
 static void
@@ -66,51 +85,94 @@ tools_usage()
               "Features:\n"
               "--enable-tap\n"
               "--disable-tap.... enable/disable tapping\n"
+              "--enable-drag\n"
+              "--disable-drag.... enable/disable tap-n-drag\n"
+              "--enable-drag-lock\n"
+              "--disable-drag-lock.... enable/disable tapping drag lock\n"
               "--enable-natural-scrolling\n"
               "--disable-natural-scrolling.... enable/disable natural scrolling\n"
               "--enable-left-handed\n"
               "--disable-left-handed.... enable/disable left-handed button configuration\n"
+              "--enable-middlebutton\n"
+              "--disable-middlebutton.... enable/disable middle button emulation\n"
+              "--enable-dwt\n"
+              "--disable-dwt..... enable/disable disable-while-typing\n"
               "--set-click-method=[none|clickfinger|buttonareas] .... set the desired click method\n"
+              "--set-scroll-method=[none|twofinger|edge|button] ... set the desired scroll method\n"
+              "--set-scroll-button=BTN_MIDDLE ... set the button to the given button code\n"
+              "--set-profile=[adaptive|flat].... set pointer acceleration profile\n"
+              "--set-speed=<value>.... set pointer acceleration speed\n"
+              "--set-tap-map=[lrm|lmr] ... set button mapping for tapping\n"
               "\n"
               "These options apply to all applicable devices, if a feature\n"
               "is not explicitly specified it is left at each device's default.\n"
               "\n"
               "Other options:\n"
+              "--grab .......... Exclusively grab all openend devices\n"
               "--verbose ....... Print debugging output.\n"
               "--help .......... Print this help.\n",
                program_invocation_short_name);
 }
 
 void
-tools_init_options(struct tools_options *options)
+tools_init_context(struct tools_context *context)
 {
+       struct tools_options *options = &context->options;
+
+       context->user_data = NULL;
+
        memset(options, 0, sizeof(*options));
        options->tapping = -1;
+       options->tap_map = -1;
+       options->drag = -1;
+       options->drag_lock = -1;
        options->natural_scroll = -1;
        options->left_handed = -1;
+       options->middlebutton = -1;
+       options->dwt = -1;
        options->click_method = -1;
+       options->scroll_method = -1;
+       options->scroll_button = -1;
        options->backend = BACKEND_UDEV;
        options->seat = "seat0";
+       options->speed = 0.0;
+       options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
 }
 
 int
-tools_parse_args(int argc, char **argv, struct tools_options *options)
+tools_parse_args(int argc, char **argv, struct tools_context *context)
 {
+       struct tools_options *options = &context->options;
+
        while (1) {
                int c;
                int option_index = 0;
                static struct option opts[] = {
                        { "device", 1, 0, OPT_DEVICE },
                        { "udev", 0, 0, OPT_UDEV },
+                       { "grab", 0, 0, OPT_GRAB },
                        { "help", 0, 0, OPT_HELP },
                        { "verbose", 0, 0, OPT_VERBOSE },
                        { "enable-tap", 0, 0, OPT_TAP_ENABLE },
                        { "disable-tap", 0, 0, OPT_TAP_DISABLE },
+                       { "enable-drag", 0, 0, OPT_DRAG_ENABLE },
+                       { "disable-drag", 0, 0, OPT_DRAG_DISABLE },
+                       { "enable-drag-lock", 0, 0, OPT_DRAG_LOCK_ENABLE },
+                       { "disable-drag-lock", 0, 0, OPT_DRAG_LOCK_DISABLE },
                        { "enable-natural-scrolling", 0, 0, OPT_NATURAL_SCROLL_ENABLE },
                        { "disable-natural-scrolling", 0, 0, OPT_NATURAL_SCROLL_DISABLE },
                        { "enable-left-handed", 0, 0, OPT_LEFT_HANDED_ENABLE },
                        { "disable-left-handed", 0, 0, OPT_LEFT_HANDED_DISABLE },
+                       { "enable-middlebutton", 0, 0, OPT_MIDDLEBUTTON_ENABLE },
+                       { "disable-middlebutton", 0, 0, OPT_MIDDLEBUTTON_DISABLE },
+                       { "enable-dwt", 0, 0, OPT_DWT_ENABLE },
+                       { "disable-dwt", 0, 0, OPT_DWT_DISABLE },
                        { "set-click-method", 1, 0, OPT_CLICK_METHOD },
+                       { "set-scroll-method", 1, 0, OPT_SCROLL_METHOD },
+                       { "set-scroll-button", 1, 0, OPT_SCROLL_BUTTON },
+                       { "set-profile", 1, 0, OPT_PROFILE },
+                       { "set-tap-map", 1, 0, OPT_TAP_MAP },
+                       { "speed", 1, 0, OPT_SPEED },
                        { 0, 0, 0, 0}
                };
 
@@ -119,66 +181,165 @@ tools_parse_args(int argc, char **argv, struct tools_options *options)
                        break;
 
                switch(c) {
-                       case 'h': /* --help */
-                       case OPT_HELP:
+               case 'h':
+               case OPT_HELP:
+                       tools_usage();
+                       exit(0);
+               case OPT_DEVICE:
+                       options->backend = BACKEND_DEVICE;
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       options->device = optarg;
+                       break;
+               case OPT_UDEV:
+                       options->backend = BACKEND_UDEV;
+                       if (optarg)
+                               options->seat = optarg;
+                       break;
+               case OPT_GRAB:
+                       options->grab = 1;
+                       break;
+               case OPT_VERBOSE:
+                       options->verbose = 1;
+                       break;
+               case OPT_TAP_ENABLE:
+                       options->tapping = 1;
+                       break;
+               case OPT_TAP_DISABLE:
+                       options->tapping = 0;
+                       break;
+               case OPT_TAP_MAP:
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       if (streq(optarg, "lrm")) {
+                               options->tap_map = LIBINPUT_CONFIG_TAP_MAP_LRM;
+                       } else if (streq(optarg, "lmr")) {
+                               options->tap_map = LIBINPUT_CONFIG_TAP_MAP_LMR;
+                       } else {
+                               tools_usage();
+                               return 1;
+                       }
+                       break;
+               case OPT_DRAG_ENABLE:
+                       options->drag = 1;
+                       break;
+               case OPT_DRAG_DISABLE:
+                       options->drag = 0;
+                       break;
+               case OPT_DRAG_LOCK_ENABLE:
+                       options->drag_lock = 1;
+                       break;
+               case OPT_DRAG_LOCK_DISABLE:
+                       options->drag_lock = 0;
+                       break;
+               case OPT_NATURAL_SCROLL_ENABLE:
+                       options->natural_scroll = 1;
+                       break;
+               case OPT_NATURAL_SCROLL_DISABLE:
+                       options->natural_scroll = 0;
+                       break;
+               case OPT_LEFT_HANDED_ENABLE:
+                       options->left_handed = 1;
+                       break;
+               case OPT_LEFT_HANDED_DISABLE:
+                       options->left_handed = 0;
+                       break;
+               case OPT_MIDDLEBUTTON_ENABLE:
+                       options->middlebutton = 1;
+                       break;
+               case OPT_MIDDLEBUTTON_DISABLE:
+                       options->middlebutton = 0;
+                       break;
+               case OPT_DWT_ENABLE:
+                       options->dwt = LIBINPUT_CONFIG_DWT_ENABLED;
+                       break;
+               case OPT_DWT_DISABLE:
+                       options->dwt = LIBINPUT_CONFIG_DWT_DISABLED;
+                       break;
+               case OPT_CLICK_METHOD:
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       if (streq(optarg, "none")) {
+                               options->click_method =
+                               LIBINPUT_CONFIG_CLICK_METHOD_NONE;
+                       } else if (streq(optarg, "clickfinger")) {
+                               options->click_method =
+                               LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
+                       } else if (streq(optarg, "buttonareas")) {
+                               options->click_method =
+                               LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
+                       } else {
                                tools_usage();
-                               exit(0);
-                       case OPT_DEVICE: /* --device */
-                               options->backend = BACKEND_DEVICE;
-                               if (!optarg) {
-                                       tools_usage();
-                                       return 1;
-                               }
-                               options->device = optarg;
-                               break;
-                       case OPT_UDEV: /* --udev */
-                               options->backend = BACKEND_UDEV;
-                               if (optarg)
-                                       options->seat = optarg;
-                               break;
-                       case OPT_VERBOSE: /* --verbose */
-                               options->verbose = 1;
-                               break;
-                       case OPT_TAP_ENABLE:
-                               options->tapping = 1;
-                               break;
-                       case OPT_TAP_DISABLE:
-                               options->tapping = 0;
-                               break;
-                       case OPT_NATURAL_SCROLL_ENABLE:
-                               options->natural_scroll = 1;
-                               break;
-                       case OPT_NATURAL_SCROLL_DISABLE:
-                               options->natural_scroll = 0;
-                               break;
-                       case OPT_LEFT_HANDED_ENABLE:
-                               options->left_handed = 1;
-                               break;
-                       case OPT_LEFT_HANDED_DISABLE:
-                               options->left_handed = 0;
-                               break;
-                       case OPT_CLICK_METHOD:
-                               if (!optarg) {
-                                       tools_usage();
-                                       return 1;
-                               }
-                               if (strcmp(optarg, "none") == 0) {
-                                       options->click_method =
-                                               LIBINPUT_CONFIG_CLICK_METHOD_NONE;
-                               } else if (strcmp(optarg, "clickfinger") == 0) {
-                                       options->click_method =
-                                               LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER;
-                               } else if (strcmp(optarg, "buttonareas") == 0) {
-                                       options->click_method =
-                                               LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS;
-                               } else {
-                                       tools_usage();
-                                       return 1;
-                               }
-                               break;
-                       default:
+                               return 1;
+                       }
+                       break;
+               case OPT_SCROLL_METHOD:
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       if (streq(optarg, "none")) {
+                               options->scroll_method =
+                               LIBINPUT_CONFIG_SCROLL_NO_SCROLL;
+                       } else if (streq(optarg, "twofinger")) {
+                               options->scroll_method =
+                               LIBINPUT_CONFIG_SCROLL_2FG;
+                       } else if (streq(optarg, "edge")) {
+                               options->scroll_method =
+                               LIBINPUT_CONFIG_SCROLL_EDGE;
+                       } else if (streq(optarg, "button")) {
+                               options->scroll_method =
+                               LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
+                       } else {
+                               tools_usage();
+                               return 1;
+                       }
+                       break;
+               case OPT_SCROLL_BUTTON:
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       options->scroll_button =
+                       libevdev_event_code_from_name(EV_KEY,
+                                                     optarg);
+                       if (options->scroll_button == -1) {
+                               fprintf(stderr,
+                                       "Invalid button %s\n",
+                                       optarg);
+                               return 1;
+                       }
+                       break;
+               case OPT_SPEED:
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       options->speed = atof(optarg);
+                       break;
+               case OPT_PROFILE:
+                       if (!optarg) {
+                               tools_usage();
+                               return 1;
+                       }
+                       if (streq(optarg, "adaptive")) {
+                               options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
+                       } else if (streq(optarg, "flat")) {
+                               options->profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
+                       } else {
                                tools_usage();
                                return 1;
+                       }
+                       break;
+               default:
+                       tools_usage();
+                       return 1;
                }
 
        }
@@ -258,17 +419,44 @@ open_device(const struct libinput_interface *interface,
        return li;
 }
 
+static int
+open_restricted(const char *path, int flags, void *user_data)
+{
+       const struct tools_context *context = user_data;
+       int fd = open(path, flags);
+
+       if (fd < 0)
+               fprintf(stderr, "Failed to open %s (%s)\n",
+                       path, strerror(errno));
+       else if (context->options.grab &&
+                ioctl(fd, EVIOCGRAB, (void*)1) == -1)
+               fprintf(stderr, "Grab requested, but failed for %s (%s)\n",
+                       path, strerror(errno));
+
+       return fd < 0 ? -errno : fd;
+}
+
+static void
+close_restricted(int fd, void *user_data)
+{
+       close(fd);
+}
+
+static const struct libinput_interface interface = {
+       .open_restricted = open_restricted,
+       .close_restricted = close_restricted,
+};
+
 struct libinput *
-tools_open_backend(struct tools_options *options,
-                  void *userdata,
-                  const struct libinput_interface *interface)
+tools_open_backend(struct tools_context *context)
 {
        struct libinput *li = NULL;
+       struct tools_options *options = &context->options;
 
        if (options->backend == BACKEND_UDEV) {
-               li = open_udev(interface, userdata, options->seat, options->verbose);
+               li = open_udev(&interface, context, options->seat, options->verbose);
        } else if (options->backend == BACKEND_DEVICE) {
-               li = open_device(interface, userdata, options->device, options->verbose);
+               li = open_device(&interface, context, options->device, options->verbose);
        } else
                abort();
 
@@ -281,12 +469,42 @@ tools_device_apply_config(struct libinput_device *device,
 {
        if (options->tapping != -1)
                libinput_device_config_tap_set_enabled(device, options->tapping);
+       if (options->tap_map != -1)
+               libinput_device_config_tap_set_button_map(device,
+                                                         options->tap_map);
+       if (options->drag != -1)
+               libinput_device_config_tap_set_drag_enabled(device,
+                                                           options->drag);
+       if (options->drag_lock != -1)
+               libinput_device_config_tap_set_drag_lock_enabled(device,
+                                                                options->drag_lock);
        if (options->natural_scroll != -1)
                libinput_device_config_scroll_set_natural_scroll_enabled(device,
                                                                         options->natural_scroll);
        if (options->left_handed != -1)
                libinput_device_config_left_handed_set(device, options->left_handed);
+       if (options->middlebutton != -1)
+               libinput_device_config_middle_emulation_set_enabled(device,
+                                                                   options->middlebutton);
+
+       if (options->dwt != -1)
+               libinput_device_config_dwt_set_enabled(device, options->dwt);
 
        if (options->click_method != -1)
                libinput_device_config_click_set_method(device, options->click_method);
+
+       if (options->scroll_method != -1)
+               libinput_device_config_scroll_set_method(device,
+                                                        options->scroll_method);
+       if (options->scroll_button != -1)
+               libinput_device_config_scroll_set_button(device,
+                                                        options->scroll_button);
+
+       if (libinput_device_config_accel_is_available(device)) {
+               libinput_device_config_accel_set_speed(device,
+                                                      options->speed);
+               if (options->profile != LIBINPUT_CONFIG_ACCEL_PROFILE_NONE)
+                       libinput_device_config_accel_set_profile(device,
+                                                                options->profile);
+       }
 }
index fcf748fdd787639deeda70db2012e836ac87299d..c0bb103693ffc67e3a2fc7fe44316b1bb36bb6e6 100644 (file)
@@ -1,23 +1,24 @@
 /*
  * 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.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * THE 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.
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
 #ifndef _SHARED_H_
@@ -34,19 +35,32 @@ struct tools_options {
        enum tools_backend backend;
        const char *device; /* if backend is BACKEND_DEVICE */
        const char *seat; /* if backend is BACKEND_UDEV */
+       int grab; /* EVIOCGRAB */
 
        int verbose;
        int tapping;
+       int drag;
+       int drag_lock;
        int natural_scroll;
        int left_handed;
+       int middlebutton;
        enum libinput_config_click_method click_method;
+       enum libinput_config_scroll_method scroll_method;
+       enum libinput_config_tap_button_map tap_map;
+       int scroll_button;
+       double speed;
+       int dwt;
+       enum libinput_config_accel_profile profile;
+};
+
+struct tools_context {
+       struct tools_options options;
+       void *user_data;
 };
 
-void tools_init_options(struct tools_options *options);
-int tools_parse_args(int argc, char **argv, struct tools_options *options);
-struct libinput* tools_open_backend(struct tools_options *options,
-                                   void *userdata,
-                                   const struct libinput_interface *interface);
+void tools_init_context(struct tools_context *context);
+int tools_parse_args(int argc, char **argv, struct tools_context *context);
+struct libinput* tools_open_backend(struct tools_context *context);
 void tools_device_apply_config(struct libinput_device *device,
                               struct tools_options *options);
 void tools_usage();
index d8e1456ba6846f3941dc4e84dfaca51b728536ed..cad377a8e843e72f715a61fe609f78cb74fa3403 100644 (file)
@@ -1 +1,6 @@
 libinput-device-group
+libinput-model-quirks
+80-libinput-device-groups-litest.rules
+80-libinput-device-groups.rules
+90-libinput-model-quirks-litest.rules
+90-libinput-model-quirks.rules
diff --git a/udev/80-libinput-device-groups.rules b/udev/80-libinput-device-groups.rules
deleted file mode 100644 (file)
index f826bec..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-ACTION!="add|change", GOTO="libinput_device_group_end"
-KERNEL!="event[0-9]*", GOTO="libinput_device_group_end"
-
-ATTRS{phys}=="?*", \
-       PROGRAM="libinput-device-group %S%p", \
-       ENV{LIBINPUT_DEVICE_GROUP}="%c"
-
-LABEL="libinput_device_group_end"
diff --git a/udev/80-libinput-device-groups.rules.in b/udev/80-libinput-device-groups.rules.in
new file mode 100644 (file)
index 0000000..1a26f78
--- /dev/null
@@ -0,0 +1,9 @@
+ACTION!="add|change", GOTO="libinput_device_group_end"
+KERNEL!="event[0-9]*", GOTO="libinput_device_group_end"
+
+ATTRS{phys}=="?*", \
+       ENV{LIBINPUT_DEVICE_GROUP}=="", \
+       PROGRAM="@UDEV_TEST_PATH@libinput-device-group %S%p", \
+       ENV{LIBINPUT_DEVICE_GROUP}="%c"
+
+LABEL="libinput_device_group_end"
diff --git a/udev/80-libinput-test-device.rules b/udev/80-libinput-test-device.rules
new file mode 100644 (file)
index 0000000..d37b2ab
--- /dev/null
@@ -0,0 +1 @@
+KERNELS=="*input*", ATTRS{name}=="litest *", ENV{LIBINPUT_TEST_DEVICE}="1"
diff --git a/udev/90-libinput-model-quirks.hwdb b/udev/90-libinput-model-quirks.hwdb
new file mode 100644 (file)
index 0000000..fed28e2
--- /dev/null
@@ -0,0 +1,171 @@
+# Do not edit this file, it will be overwritten on update
+#
+# This file contains hwdb matches for libinput model-specific quirks.
+# The contents of this file are a contract between libinput, udev rules and
+# the hwdb.
+# IT IS NOT A STABLE API AND SUBJECT TO CHANGE AT ANY TIME
+
+# The lookup keys are composed in:
+#      90-libinput-model-quirks.rules
+#
+# Match string formats:
+#      libinput:mouse:<modalias>
+#      libinput:touchpad:<modalias>
+#      libinput:name:<name>:dmi:<dmi string>
+#      libinput:name:<name>:fwversion:<version>
+#
+# Sort by brand, model
+
+##########################################
+# ALPS
+##########################################
+libinput:name:*AlpsPS/2 ALPS DualPoint TouchPad:dmi:*
+libinput:name:*AlpsPS/2 ALPS GlidePoint:dmi:*
+ LIBINPUT_MODEL_ALPS_TOUCHPAD=1
+
+libinput:name:*AlpsPS/2 ALPS DualPoint TouchPad:fwversion:800
+libinput:name:*AlpsPS/2 ALPS GlidePoint:fwversion:800
+ LIBINPUT_ATTR_SIZE_HINT=100x55
+
+##########################################
+# Apple
+##########################################
+libinput:touchpad:input:b0003v05ACp*
+libinput:touchpad:input:b0005v05ACp*
+ LIBINPUT_MODEL_APPLE_TOUCHPAD=1
+ LIBINPUT_ATTR_SIZE_HINT=104x75
+
+libinput:name:*Apple Inc. Apple Internal Keyboard*:dmi:*
+ LIBINPUT_MODEL_APPLE_INTERNAL_KEYBOARD=1
+
+libinput:mouse:input:b0005v05ACp030D*
+ LIBINPUT_MODEL_APPLE_MAGICMOUSE=1
+
+##########################################
+# Cyborg
+##########################################
+# Saitek Cyborg R.A.T.5 Mouse
+libinput:mouse:input:b0003v06A3p0CD5*
+ LIBINPUT_MODEL_CYBORG_RAT=1
+
+##########################################
+# Dell
+##########################################
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnDellInc.:*
+libinput:name:* Touchpad:dmi:*svnDellInc.:*
+ LIBINPUT_MODEL_DELL_TOUCHPAD=1
+
+##########################################
+# Elantech
+##########################################
+libinput:name:*ETPS/2 Elantech Touchpad*:dmi:*
+ LIBINPUT_ATTR_RESOLUTION_HINT=31x31
+ LIBINPUT_MODEL_ELANTECH_TOUCHPAD=1
+
+##########################################
+# Google
+##########################################
+
+# The various chromebooks, info from modinfo chromeos_laptop, touchpad names
+# extrapolated from the chromiumos touchad-tests repo
+# https://chromium.googlesource.com/chromiumos/platform/touchpad-tests
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*pnFalco:pvr*
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pn*Mario*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*pn*Butterfly*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*pn*Peppy*
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pn*ZGB*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*pn*Parrot*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*bvn*coreboot*:pn*Leon*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*bvn*coreboot*:pn*Falco*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*bvn*coreboot*:pn*Wolf*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*svn*GOOGLE*:pn*Link*
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pn*Alex*
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*svn*SAMSUNG*:pn*Lumpy*
+libinput:name:Atmel maXTouch Touchpad:dmi:*svn*GOOGLE*:pn*Samus*
+ LIBINPUT_MODEL_CHROMEBOOK=1
+
+libinput:name:Cypress APA Trackpad ?cyapa?:dmi:*
+ LIBINPUT_MODEL_CYAPA=1
+
+##########################################
+# HP
+##########################################
+
+# HP 8510w
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnHewlett-Packard:*pnHPCompaq8510w*
+ LIBINPUT_MODEL_HP8510_TOUCHPAD=1
+
+# HP Stream 11
+libinput:name:SYN1EDE:00 06CB:7442:dmi:*svnHewlett-Packard:pnHPStreamNotebookPC11*
+ LIBINPUT_MODEL_HP_STREAM11_TOUCHPAD=1
+
+##########################################
+# LENOVO
+##########################################
+
+# X220 after a bios update updating the touchpad firmware version to 8.1
+# See https://bugzilla.redhat.com/show_bug.cgi?id=1264453 for details
+# If the touchpad is unresponsive and dmesg includes this line
+#      psmouse serio1: synaptics: Touchpad model: 1, fw: 8.1 [...]
+# then copy the two lines below into a new file
+# /etc/udev/hwdb.d/90-libinput-x220-touchpad-fw81.hwdb, then run
+# sudo udevadm hwdb --update and reboot.
+#
+# Copy the two lines below:
+#libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPadX220*
+# LIBINPUT_MODEL_LENOVO_X220_TOUCHPAD_FW81=1
+
+# X230 (Tablet)
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPadX230*
+ LIBINPUT_MODEL_LENOVO_X230=1
+
+# Lenovo T450/T460 and all other Lenovos of the *50 and *60 generation,
+# including the X1 Carbon 3rd gen
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPad??50*:
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPad??60*:
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO:*:pvrThinkPadX1Carbon3rd:*
+ LIBINPUT_MODEL_LENOVO_T450_TOUCHPAD=1
+
+##########################################
+# Logitech
+##########################################
+libinput:name:*Logitech M570*:dmi:*
+ LIBINPUT_MODEL_TRACKBALL=1
+
+##########################################
+# Synaptics
+##########################################
+libinput:touchpad:input:b0011v0002p0007*
+ LIBINPUT_MODEL_SYNAPTICS_SERIAL_TOUCHPAD=1
+
+##########################################
+# System76
+##########################################
+
+# Bonobo Professional
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnSystem76*pvrbonp5*
+ LIBINPUT_MODEL_SYSTEM76_BONOBO=1
+
+# Clevo
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*pnW740SU*rnW740SU*
+ LIBINPUT_MODEL_CLEVO_W740SU=1
+
+# Galago Ultra Pro
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnSystem76*pvrgalu1*
+ LIBINPUT_MODEL_SYSTEM76_GALAGO=1
+
+# Kudu Professional
+libinput:name:SynPS/2 Synaptics TouchPad:dmi:*svnSystem76*pvrkudp1*
+ LIBINPUT_MODEL_SYSTEM76_KUDU=1
+
+##########################################
+# Wacom
+##########################################
+libinput:touchpad:input:b0003v056Ap*
+ LIBINPUT_MODEL_WACOM_TOUCHPAD=1
+
+##########################################
+# Anything that has trackball in the name
+##########################################
+libinput:name:*Trackball*:dmi:*
+ LIBINPUT_MODEL_TRACKBALL=1
diff --git a/udev/90-libinput-model-quirks.rules.in b/udev/90-libinput-model-quirks.rules.in
new file mode 100644 (file)
index 0000000..8bff192
--- /dev/null
@@ -0,0 +1,40 @@
+# Do not edit this file, it will be overwritten on update
+#
+# This file contains lookup rules for libinput model-specific quirks.
+# The contents of this file are a contract between libinput, udev rules and
+# the hwdb.
+# IT IS NOT A STABLE API AND SUBJECT TO CHANGE AT ANY TIME
+#
+# The hwdb database is in:
+#      90-libinput-model-quirks.hwdb
+
+ACTION!="add|change", GOTO="libinput_model_quirks_end"
+KERNEL!="event*", GOTO="libinput_model_quirks_end"
+
+# Touchpad firmware detection, two-stage process.
+# First, run the program and import the LIBINPUT_MODEL_FIRMWARE_VERSION
+# environment (if any)
+KERNELS=="*input*", \
+  ENV{ID_INPUT_TOUCHPAD}=="1", \
+  IMPORT{program}="@UDEV_TEST_PATH@libinput-model-quirks %S%p"
+
+# Second, match on anything with that env set and import from the hwdb
+KERNELS=="*input*", \
+  ENV{ID_INPUT_TOUCHPAD}=="1", \
+  ENV{LIBINPUT_MODEL_FIRMWARE_VERSION}!="", \
+  IMPORT{builtin}="hwdb 'libinput:name:$attr{name}:fwversion:$env{LIBINPUT_MODEL_FIRMWARE_VERSION}'"
+# End of touchpad firmware detection
+
+# libinput:touchpad:<modalias>
+ENV{ID_INPUT_TOUCHPAD}=="1", \
+  IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=libinput:touchpad:"
+
+# libinput:mouse:<modalias>
+ENV{ID_INPUT_MOUSE}=="1", \
+  IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=libinput:mouse:"
+
+# libinput:name:<name>:dmi:<dmi string>
+KERNELS=="input*", \
+  IMPORT{builtin}="hwdb 'libinput:name:$attr{name}:$attr{[dmi/id]modalias}'"
+
+LABEL="libinput_model_quirks_end"
index 3691172c30c6c1d3903705b99db76b458058f531..f06dc564b51c5e359e1ffd8ab110778521558493 100644 (file)
@@ -1,9 +1,43 @@
 udevdir=$(UDEV_DIR)
-udev_PROGRAMS = libinput-device-group
+udev_PROGRAMS = libinput-device-group \
+               libinput-model-quirks
+
+litest_rules = 80-libinput-device-groups-litest.rules \
+              90-libinput-model-quirks-litest.rules
+noinst_SCRIPTS = $(litest_rules)
 
 libinput_device_group_SOURCES = libinput-device-group.c
-libinput_device_group_CFLAGS = $(LIBUDEV_CFLAGS) $(GCC_CFLAGS)
+libinput_device_group_CFLAGS = -I$(top_srcdir)/src \
+                              $(LIBUDEV_CFLAGS) \
+                              $(GCC_CFLAGS)
 libinput_device_group_LDADD = $(LIBUDEV_LIBS)
 
+if HAVE_LIBWACOM_GET_PAIRED_DEVICE
+libinput_device_group_CFLAGS += $(LIBWACOM_CFLAGS)
+libinput_device_group_LDADD += $(LIBWACOM_LIBS)
+endif
+
+libinput_model_quirks_SOURCES = libinput-model-quirks.c
+libinput_model_quirks_CFLAGS = \
+                              -I$(top_srcdir)/src \
+                              $(LIBUDEV_CFLAGS) \
+                              $(GCC_CFLAGS)
+libinput_model_quirks_LDADD = $(LIBUDEV_LIBS)
+
 udev_rulesdir=$(UDEV_DIR)/rules.d
-dist_udev_rules_DATA = 80-libinput-device-groups.rules
+dist_udev_rules_DATA = \
+       80-libinput-device-groups.rules \
+       90-libinput-model-quirks.rules
+
+udev_hwdbdir=$(UDEV_DIR)/hwdb.d
+dist_udev_hwdb_DATA = \
+       90-libinput-model-quirks.hwdb
+
+%-litest.rules: %.rules.in
+       $(SED) -e "s|\@UDEV_TEST_PATH\@|$(abs_builddir)/|" < $^ > $@
+
+CLEANFILES = $(litest_rules)
+DISTCLEANFILES = \
+                80-libinput-device-groups.rules \
+                90-libinput-model-quirks.rules 
+EXTRA_DIST = 80-libinput-test-device.rules
index 50bfbe02d0d5ef0588073481310623f3734e45dc..fa70e115f16955467aa7d976a7a49c1f947eba26 100644 (file)
@@ -1,8 +1,69 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <libudev.h>
 
+#include "libinput-util.h"
+
+#if HAVE_LIBWACOM_GET_PAIRED_DEVICE
+#include <libwacom/libwacom.h>
+
+static void
+wacom_handle_paired(struct udev_device *device,
+                   int *vendor_id,
+                   int *product_id)
+{
+       WacomDeviceDatabase *db = NULL;
+       WacomDevice *tablet = NULL;
+       const WacomMatch *paired;
+
+       db = libwacom_database_new();
+       if (!db)
+               goto out;
+
+       tablet = libwacom_new_from_usbid(db, *vendor_id, *product_id, NULL);
+       if (!tablet)
+               goto out;
+       paired = libwacom_get_paired_device(tablet);
+       if (!paired)
+               goto out;
+
+       *vendor_id = libwacom_match_get_vendor_id(paired);
+       *product_id = libwacom_match_get_product_id(paired);
+
+out:
+       if (tablet)
+               libwacom_destroy(tablet);
+       if (db)
+               libwacom_database_destroy(db);
+}
+#endif
+
 int main(int argc, char **argv)
 {
        int rc = 1;
@@ -10,8 +71,10 @@ int main(int argc, char **argv)
        struct udev_device *device = NULL;
        const char *syspath,
                   *phys = NULL;
-       char *group,
-            *str;
+       const char *product;
+       int bustype, vendor_id, product_id, version;
+       char group[1024];
+       char *str;
 
        if (argc != 2)
                return 1;
@@ -45,9 +108,33 @@ int main(int argc, char **argv)
        if (!phys)
                goto out;
 
-       group = strdup(phys);
-       if (!group)
-               goto out;
+       /* udev sets PRODUCT on the same device we find PHYS on, let's rely
+          on that*/
+       product = udev_device_get_property_value(device, "PRODUCT");
+       if (!product)
+               product = "00/00/00/00";
+
+       if (sscanf(product,
+                  "%x/%x/%x/%x",
+                  &bustype,
+                  &vendor_id,
+                  &product_id,
+                  &version) != 4) {
+               snprintf(group, sizeof(group), "%s:%s", product, phys);
+       } else {
+#if HAVE_LIBWACOM_GET_PAIRED_DEVICE
+           if (vendor_id == VENDOR_ID_WACOM)
+                   wacom_handle_paired(device, &vendor_id, &product_id);
+#endif
+           snprintf(group,
+                    sizeof(group),
+                    "%x/%x/%x/%x:%s",
+                    bustype,
+                    vendor_id,
+                    product_id,
+                    version,
+                    phys);
+       }
 
        str = strstr(group, "/input");
        if (str)
@@ -64,7 +151,6 @@ int main(int argc, char **argv)
                *str = '\0';
 
        printf("%s\n", group);
-       free(group);
 
        rc = 0;
 out:
diff --git a/udev/libinput-model-quirks.c b/udev/libinput-model-quirks.c
new file mode 100644 (file)
index 0000000..2dc917d
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libudev.h>
+#include <linux/input.h>
+
+#include "libinput-util.h"
+
+static inline const char *
+prop_value(struct udev_device *device,
+          const char *prop_name)
+{
+       struct udev_device *parent;
+       const char *prop_value = NULL;
+
+       parent = device;
+       while (parent && !prop_value) {
+               prop_value = udev_device_get_property_value(parent, prop_name);
+               parent = udev_device_get_parent(parent);
+       }
+
+       return prop_value;
+}
+
+static void
+handle_touchpad_alps(struct udev_device *device)
+{
+       const char *product;
+       int bus, vid, pid, version;
+
+       product = prop_value(device, "PRODUCT");
+       if (!product)
+               return;
+
+       if (sscanf(product, "%x/%x/%x/%x", &bus, &vid, &pid, &version) != 4)
+               return;
+
+       /* ALPS' firmware version is the version */
+       if (version)
+               printf("LIBINPUT_MODEL_FIRMWARE_VERSION=%x\n", version);
+}
+
+static void
+handle_touchpad_synaptics(struct udev_device *device)
+{
+       const char *product, *props;
+       int bus, vid, pid, version;
+       int prop;
+
+       product = prop_value(device, "PRODUCT");
+       if (!product)
+               return;
+
+       if (sscanf(product, "%x/%x/%x/%x", &bus, &vid, &pid, &version) != 4)
+               return;
+
+       if (bus != BUS_I8042 || vid != 0x2 || pid != 0x7)
+               return;
+
+       props = prop_value(device, "PROP");
+       if (sscanf(props, "%x", &prop) != 1)
+               return;
+       if (prop & (1 << INPUT_PROP_SEMI_MT))
+               printf("LIBINPUT_MODEL_JUMPING_SEMI_MT=1\n");
+}
+
+static void
+handle_touchpad(struct udev_device *device)
+{
+       const char *name = NULL;
+
+       name = prop_value(device, "NAME");
+       if (!name)
+               return;
+
+       if (strstr(name, "AlpsPS/2 ALPS") != NULL)
+               handle_touchpad_alps(device);
+       if (strstr(name, "Synaptics ") != NULL)
+               handle_touchpad_synaptics(device);
+}
+
+int main(int argc, char **argv)
+{
+       int rc = 1;
+       struct udev *udev = NULL;
+       struct udev_device *device = NULL;
+       const char *syspath;
+
+       if (argc != 2)
+               return 1;
+
+       syspath = argv[1];
+
+       udev = udev_new();
+       if (!udev)
+               goto out;
+
+       device = udev_device_new_from_syspath(udev, syspath);
+       if (!device)
+               goto out;
+
+       if (prop_value(device, "ID_INPUT_TOUCHPAD"))
+               handle_touchpad(device);
+
+       rc = 0;
+
+out:
+       if (device)
+               udev_device_unref(device);
+       if (udev)
+               udev_unref(udev);
+
+       return rc;
+}