Merge branch 'master' into tablet-support
authorPeter Hutterer <peter.hutterer@who-t.net>
Fri, 22 May 2015 04:21:21 +0000 (14:21 +1000)
committerPeter Hutterer <peter.hutterer@who-t.net>
Fri, 22 May 2015 04:21:21 +0000 (14:21 +1000)
20 files changed:
1  2 
configure.ac
doc/Makefile.am
src/Makefile.am
src/evdev-mt-touchpad.c
src/evdev-tablet.c
src/evdev.c
src/evdev.h
src/libinput-private.h
src/libinput-util.h
src/libinput.c
src/libinput.h
src/libinput.sym
test/Makefile.am
test/device.c
test/litest.c
test/litest.h
test/misc.c
test/tablet.c
test/valgrind.suppressions
tools/event-gui.c

diff --cc configure.ac
Simple merge
diff --cc doc/Makefile.am
index 271960e36c032e7415d316ef7327896a6b246741,3d81d7cea853b9763ffb80c5204984a00e902425..48e68b8ad39fb9fcd99d067d233358117a7a1b02
@@@ -15,8 -18,8 +18,9 @@@ header_files = 
        $(srcdir)/scrolling.dox \
        $(srcdir)/seats.dox \
        $(srcdir)/t440-support.dox \
-       $(srcdir)/tapping.dox
 +      $(srcdir)/tablet-support.dox \
+       $(srcdir)/tapping.dox \
+       $(srcdir)/test-suite.dox
  
  diagram_files = \
        $(srcdir)/dot/seats-sketch.gv \
diff --cc src/Makefile.am
index d5cd4f41c730d5448239c72ba470339889329e6c,90bdc9a8ca0f5224d2e61bd55ccb3e733acebda8..343e75c715452fa59b4abe0ffc5c2ebe55cad0c0
@@@ -18,6 -18,6 +18,8 @@@ libinput_la_SOURCES =                 
        evdev-mt-touchpad-buttons.c     \
        evdev-mt-touchpad-edge-scroll.c \
        evdev-mt-touchpad-gestures.c    \
++      evdev-tablet.c                  \
++      evdev-tablet.h                  \
        filter.c                        \
        filter.h                        \
        filter-private.h                \
index 6282b71e3f4a0cd4f865bd24dd90721f1d805ee3,409d81e93dbfe6b021ca1bcd0ddd93e1c735e550..79cc05190fc1ca57df47051db2e1848ef772859d
@@@ -896,15 -1068,15 +1068,16 @@@ tp_interface_tag_device(struct evdev_de
  }
  
  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,
-       NULL,              /* post_added */
+       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 */
+       tp_interface_tag_device,
++      NULL,                        /* post_added */
  };
  
  static void
index 3e3924ca2716d3fe1308f220aa01797f720708b6,0000000000000000000000000000000000000000..301fe246cd696b12f15b5346d18c952e9ce8b00d
mode 100644,000000..100644
--- /dev/null
@@@ -1,1120 -1,0 +1,1121 @@@
 +/*
 + * 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 "evdev-tablet.h"
 +
 +#include <assert.h>
 +#include <stdbool.h>
 +#include <string.h>
 +
 +#if HAVE_LIBWACOM
 +#include <libwacom/libwacom.h>
 +#endif
 +
 +#define tablet_set_status(tablet_,s_) (tablet_)->status |= (s_)
 +#define tablet_unset_status(tablet_,s_) (tablet_)->status &= ~(s_)
 +#define tablet_has_status(tablet_,s_) (!!((tablet_)->status & (s_)))
 +
 +static inline void
 +tablet_get_pressed_buttons(struct tablet_dispatch *tablet,
 +                         unsigned char *buttons,
 +                         unsigned int buttons_len)
 +{
 +      size_t i;
 +      const struct button_state *state = &tablet->button_state,
 +                                *prev_state = &tablet->prev_button_state;
 +
 +      assert(buttons_len <= ARRAY_LENGTH(state->stylus_buttons));
 +
 +      for (i = 0; i < buttons_len; i++)
 +              buttons[i] = state->stylus_buttons[i] &
 +                                      ~(prev_state->stylus_buttons[i]);
 +}
 +
 +static inline void
 +tablet_get_released_buttons(struct tablet_dispatch *tablet,
 +                          unsigned char *buttons,
 +                          unsigned int buttons_len)
 +{
 +      size_t i;
 +      const struct button_state *state = &tablet->button_state,
 +                                *prev_state = &tablet->prev_button_state;
 +
 +      assert(buttons_len <= ARRAY_LENGTH(state->stylus_buttons));
 +
 +      for (i = 0; i < buttons_len; i++)
 +              buttons[i] = prev_state->stylus_buttons[i] &
 +                                      ~(state->stylus_buttons[i]);
 +}
 +
 +static int
 +tablet_device_has_axis(struct tablet_dispatch *tablet,
 +                     enum libinput_tablet_axis axis)
 +{
 +      struct libevdev *evdev = tablet->device->evdev;
 +      bool has_axis = false;
 +      unsigned int code;
 +
 +      if (axis == LIBINPUT_TABLET_AXIS_ROTATION_Z) {
 +              has_axis = (libevdev_has_event_code(evdev,
 +                                                  EV_ABS,
 +                                                  ABS_TILT_X) &&
 +                          libevdev_has_event_code(evdev,
 +                                                  EV_ABS,
 +                                                  ABS_TILT_Y));
 +      } else if (axis == LIBINPUT_TABLET_AXIS_REL_WHEEL) {
 +              has_axis = libevdev_has_event_code(evdev,
 +                                                 EV_REL,
 +                                                 REL_WHEEL);
 +      } else {
 +              code = axis_to_evcode(axis);
 +              has_axis = libevdev_has_event_code(evdev,
 +                                                 EV_ABS,
 +                                                 code);
 +      }
 +
 +      return has_axis;
 +}
 +
 +static void
 +tablet_process_absolute(struct tablet_dispatch *tablet,
 +                      struct evdev_device *device,
 +                      struct input_event *e,
 +                      uint32_t time)
 +{
 +      enum libinput_tablet_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_AXIS_NONE) {
 +                      log_bug_libinput(device->base.seat->libinput,
 +                                       "Invalid ABS event code %#x\n",
 +                                       e->code);
 +                      break;
 +              }
 +
 +              set_bit(tablet->changed_axes, axis);
 +              tablet_set_status(tablet, TABLET_AXES_UPDATED);
 +              break;
 +      /* tool_id is the identifier for the tool we can use in libwacom
 +       * to identify it (if we have one anyway) */
 +      case ABS_MISC:
 +              tablet->current_tool_id = e->value;
 +              break;
 +      /* Intuos 3 strip data. Should only happen on the Pad device, not on
 +         the Pen device. */
 +      case ABS_RX:
 +      case ABS_RY:
 +      /* Only on the 4D mouse (Intuos2), obsolete */
 +      case ABS_RZ:
 +      /* Only on the 4D mouse (Intuos2), obsolete.
 +         The 24HD sends ABS_THROTTLE on the Pad device for the second
 +         wheel but we shouldn't get here on kernel >= 3.17.
 +         */
 +      case ABS_THROTTLE:
 +      default:
 +              log_info(device->base.seat->libinput,
 +                       "Unhandled ABS event code %#x\n", e->code);
 +              break;
 +      }
 +}
 +
 +static void
 +tablet_mark_all_axes_changed(struct tablet_dispatch *tablet,
 +                           struct evdev_device *device)
 +{
 +      enum libinput_tablet_axis a;
 +
 +      for (a = LIBINPUT_TABLET_AXIS_X; a <= LIBINPUT_TABLET_AXIS_MAX; a++) {
 +              if (tablet_device_has_axis(tablet, a))
 +                      set_bit(tablet->changed_axes, a);
 +      }
 +
 +      tablet_set_status(tablet, TABLET_AXES_UPDATED);
 +}
 +
 +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_tool_type tool,
 +                 bool enabled)
 +{
 +      assert(tool != LIBINPUT_TOOL_NONE);
 +
 +      if (enabled) {
 +              tablet->current_tool_type = tool;
 +              tablet_mark_all_axes_changed(tablet, device);
 +              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_pressure_dist_slider(const struct input_absinfo *absinfo)
 +{
 +      double range = absinfo->maximum - absinfo->minimum;
 +      double value = (absinfo->value - absinfo->minimum) / range;
 +
 +      return value;
 +}
 +
 +static inline double
 +normalize_tilt(const struct input_absinfo *absinfo)
 +{
 +      double range = absinfo->maximum - absinfo->minimum;
 +      double value = (absinfo->value - absinfo->minimum) / range;
 +
 +      /* Map to the (-1, 1) range */
 +      return (value * 2) - 1;
 +}
 +
 +static inline int32_t
 +invert_axis(const struct input_absinfo *absinfo)
 +{
 +      return absinfo->maximum - (absinfo->value - absinfo->minimum);
 +}
 +
 +static void
 +convert_tilt_to_rotation(struct tablet_dispatch *tablet)
 +{
 +      const int offset = 5;
 +      double x, y;
 +      double angle = 0.0;
 +
 +      /* Wacom Intuos 4, 5, Pro mouse calculates rotation from the x/y tilt
 +         values. The device has a 175 degree CCW hardware offset but since we use
 +         atan2 the effective offset is just 5 degrees.
 +         */
 +      x = tablet->axes[LIBINPUT_TABLET_AXIS_TILT_X];
 +      y = tablet->axes[LIBINPUT_TABLET_AXIS_TILT_Y];
 +      clear_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_X);
 +      clear_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_Y);
 +
 +      /* atan2 is CCW, we want CW -> negate x */
 +      if (x || y)
 +              angle = ((180.0 * atan2(-x, y)) / M_PI);
 +
 +      angle = fmod(360 + angle - offset, 360);
 +
 +      tablet->axes[LIBINPUT_TABLET_AXIS_ROTATION_Z] = angle;
 +      set_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +}
 +
 +static double
 +convert_to_degrees(const struct input_absinfo *absinfo, double offset)
 +{
 +      /* range is [0, 360[, i.e. range + 1 */
 +      double range = absinfo->maximum - absinfo->minimum + 1;
 +      double value = (absinfo->value - absinfo->minimum) / range;
 +
 +      return fmod(value * 360.0 + offset, 360.0);
 +}
 +
 +static inline double
 +normalize_wheel(struct tablet_dispatch *tablet,
 +              int value)
 +{
 +      struct evdev_device *device = tablet->device;
 +
 +      return value * device->scroll.wheel_click_angle;
 +}
 +
 +static inline double
 +guess_wheel_delta(double current, double old)
 +{
 +       double d1, d2, d3;
 +
 +       d1 = current - old;
 +       d2 = (current + 360.0) - old;
 +       d3 = current - (old + 360.0);
 +
 +       if (fabs(d2) < fabs(d1))
 +               d1 = d2;
 +
 +       if (fabs(d3) < fabs(d1))
 +               d1 = d3;
 +
 +       return d1;
 +}
 +
 +static inline double
 +get_delta(enum libinput_tablet_axis axis, double current, double old)
 +{
 +      double delta = 0;
 +
 +      switch (axis) {
 +      case LIBINPUT_TABLET_AXIS_X:
 +      case LIBINPUT_TABLET_AXIS_Y:
 +      case LIBINPUT_TABLET_AXIS_DISTANCE:
 +      case LIBINPUT_TABLET_AXIS_PRESSURE:
 +      case LIBINPUT_TABLET_AXIS_SLIDER:
 +      case LIBINPUT_TABLET_AXIS_TILT_X:
 +      case LIBINPUT_TABLET_AXIS_TILT_Y:
 +              delta = current - old;
 +              break;
 +      case LIBINPUT_TABLET_AXIS_ROTATION_Z:
 +              delta = guess_wheel_delta(current, old);
 +              break;
 +      default:
 +              abort();
 +      }
 +      return delta;
 +}
 +
 +static void
 +tablet_check_notify_axes(struct tablet_dispatch *tablet,
 +                       struct evdev_device *device,
 +                       uint32_t time,
 +                       struct libinput_tool *tool)
 +{
 +      struct libinput_device *base = &device->base;
 +      bool axis_update_needed = false;
 +      int a;
 +      double axes[LIBINPUT_TABLET_AXIS_MAX + 1] = {0};
 +      double deltas[LIBINPUT_TABLET_AXIS_MAX + 1] = {0};
 +      double deltas_discrete[LIBINPUT_TABLET_AXIS_MAX + 1] = {0};
 +      double oldval;
 +
 +      for (a = LIBINPUT_TABLET_AXIS_X; a <= LIBINPUT_TABLET_AXIS_MAX; a++) {
 +              const struct input_absinfo *absinfo;
 +
 +              if (!bit_is_set(tablet->changed_axes, a)) {
 +                      axes[a] = tablet->axes[a];
 +                      continue;
 +              }
 +
 +              axis_update_needed = true;
 +              oldval = tablet->axes[a];
 +
 +              /* ROTATION_Z is higher than TILT_X/Y so we know that the
 +                 tilt axes are already normalized and set */
 +              if (a == LIBINPUT_TABLET_AXIS_ROTATION_Z &&
 +                 (tablet->current_tool_type == LIBINPUT_TOOL_MOUSE ||
 +                  tablet->current_tool_type == LIBINPUT_TOOL_LENS)) {
 +                      convert_tilt_to_rotation(tablet);
 +                      axes[LIBINPUT_TABLET_AXIS_TILT_X] = 0;
 +                      axes[LIBINPUT_TABLET_AXIS_TILT_Y] = 0;
 +                      axes[a] = tablet->axes[a];
 +                      deltas[a] = get_delta(a, tablet->axes[a], oldval);
 +                      continue;
 +              } else if (a == LIBINPUT_TABLET_AXIS_REL_WHEEL) {
 +                      deltas_discrete[a] = tablet->deltas[a];
 +                      deltas[a] = normalize_wheel(tablet,
 +                                                  tablet->deltas[a]);
 +                      axes[a] = 0;
 +                      continue;
 +              }
 +
 +              absinfo = libevdev_get_abs_info(device->evdev,
 +                                              axis_to_evcode(a));
 +
 +              switch (a) {
 +              case LIBINPUT_TABLET_AXIS_X:
 +              case LIBINPUT_TABLET_AXIS_Y:
 +                      if (device->left_handed.enabled)
 +                              tablet->axes[a] = invert_axis(absinfo);
 +                      else
 +                              tablet->axes[a] = absinfo->value;
 +                      break;
 +              case LIBINPUT_TABLET_AXIS_DISTANCE:
 +              case LIBINPUT_TABLET_AXIS_PRESSURE:
 +              case LIBINPUT_TABLET_AXIS_SLIDER:
 +                      tablet->axes[a] = normalize_pressure_dist_slider(absinfo);
 +                      break;
 +              case LIBINPUT_TABLET_AXIS_TILT_X:
 +              case LIBINPUT_TABLET_AXIS_TILT_Y:
 +                      tablet->axes[a] = normalize_tilt(absinfo);
 +                      break;
 +              case LIBINPUT_TABLET_AXIS_ROTATION_Z:
 +                      /* artpen has 0 with buttons pointing east */
 +                      tablet->axes[a] = convert_to_degrees(absinfo, 90);
 +                      break;
 +              default:
 +                      log_bug_libinput(device->base.seat->libinput,
 +                                       "Invalid axis update: %d\n", a);
 +                      break;
 +              }
 +
 +              axes[a] = tablet->axes[a];
 +              deltas[a] = get_delta(a, tablet->axes[a], oldval);
 +      }
 +
 +      /* 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 (axis_update_needed &&
 +          !tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY) &&
 +          !tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
 +              if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY))
 +                      tablet_notify_proximity(&device->base,
 +                                              time,
 +                                              tool,
 +                                              LIBINPUT_TOOL_PROXIMITY_IN,
 +                                              tablet->changed_axes,
 +                                              axes);
 +              else
 +                      tablet_notify_axis(base,
 +                                         time,
 +                                         tool,
 +                                         tablet->changed_axes,
 +                                         axes,
 +                                         deltas,
 +                                         deltas_discrete);
 +      }
 +
 +      memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes));
 +}
 +
 +static void
 +tablet_update_button(struct tablet_dispatch *tablet,
 +                   uint32_t evcode,
 +                   uint32_t enable)
 +{
 +      switch (evcode) {
 +      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_TOUCH:
 +      case BTN_STYLUS:
 +      case BTN_STYLUS2:
 +              break;
 +      default:
 +              log_info(tablet->device->base.seat->libinput,
 +                       "Unhandled button %s (%#x)\n",
 +                       libevdev_event_code_get_name(EV_KEY, evcode), evcode);
 +              return;
 +      }
 +
 +      if (enable) {
 +              set_bit(tablet->button_state.stylus_buttons, evcode);
 +              tablet_set_status(tablet, TABLET_BUTTONS_PRESSED);
 +      } else {
 +              clear_bit(tablet->button_state.stylus_buttons, evcode);
 +              tablet_set_status(tablet, TABLET_BUTTONS_RELEASED);
 +      }
 +}
 +
 +static inline enum libinput_tool_type
 +tablet_evcode_to_tool(int code)
 +{
 +      enum libinput_tool_type type;
 +
 +      switch (code) {
 +      case BTN_TOOL_PEN:      type = LIBINPUT_TOOL_PEN;       break;
 +      case BTN_TOOL_RUBBER:   type = LIBINPUT_TOOL_ERASER;    break;
 +      case BTN_TOOL_BRUSH:    type = LIBINPUT_TOOL_BRUSH;     break;
 +      case BTN_TOOL_PENCIL:   type = LIBINPUT_TOOL_PENCIL;    break;
 +      case BTN_TOOL_AIRBRUSH: type = LIBINPUT_TOOL_AIRBRUSH;  break;
 +      case BTN_TOOL_FINGER:   type = LIBINPUT_TOOL_FINGER;    break;
 +      case BTN_TOOL_MOUSE:    type = LIBINPUT_TOOL_MOUSE;     break;
 +      case BTN_TOOL_LENS:     type = LIBINPUT_TOOL_LENS;      break;
 +      default:
 +              abort();
 +      }
 +
 +      return type;
 +}
 +
 +static void
 +tablet_process_key(struct tablet_dispatch *tablet,
 +                 struct evdev_device *device,
 +                 struct input_event *e,
 +                 uint32_t time)
 +{
 +      switch (e->code) {
 +      case BTN_TOOL_PEN:
 +      case BTN_TOOL_RUBBER:
 +      case BTN_TOOL_BRUSH:
 +      case BTN_TOOL_PENCIL:
 +      case BTN_TOOL_AIRBRUSH:
 +      case BTN_TOOL_FINGER:
 +      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 (e->value)
 +                      tablet_set_status(tablet, TABLET_STYLUS_IN_CONTACT);
 +              else
 +                      tablet_unset_status(tablet, TABLET_STYLUS_IN_CONTACT);
 +
 +              /* Fall through */
 +      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,
 +                      uint32_t time)
 +{
 +      enum libinput_tablet_axis axis;
 +
 +      switch (e->code) {
 +      case REL_WHEEL:
 +              axis = rel_evcode_to_axis(e->code);
 +              if (axis == LIBINPUT_TABLET_AXIS_NONE) {
 +                      log_bug_libinput(device->base.seat->libinput,
 +                                       "Invalid ABS event code %#x\n",
 +                                       e->code);
 +                      break;
 +              }
 +              set_bit(tablet->changed_axes, axis);
 +              tablet->deltas[axis] = -1 * e->value;
 +              tablet_set_status(tablet, TABLET_AXES_UPDATED);
 +              break;
 +      default:
 +              log_info(tablet->device->base.seat->libinput,
 +                       "Unhandled relative axis %s (%#x)\n",
 +                       libevdev_event_code_get_name(EV_REL, e->code),
 +                       e->code);
 +              return;
 +      }
 +}
 +
 +static void
 +tablet_process_misc(struct tablet_dispatch *tablet,
 +                  struct evdev_device *device,
 +                  struct input_event *e,
 +                  uint32_t time)
 +{
 +      switch (e->code) {
 +      case MSC_SERIAL:
 +              if (e->value != -1)
 +                      tablet->current_tool_serial = e->value;
 +
 +              break;
 +      default:
 +              log_info(device->base.seat->libinput,
 +                       "Unhandled MSC event code %s (%#x)\n",
 +                       libevdev_event_code_get_name(EV_MSC, e->code),
 +                       e->code);
 +              break;
 +      }
 +}
 +
 +static inline void
 +copy_axis_cap(const struct tablet_dispatch *tablet,
 +            struct libinput_tool *tool,
 +            enum libinput_tablet_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_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_tool *tool)
 +{
 +      int rc = 1;
 +
 +#if HAVE_LIBWACOM
 +      struct libinput *libinput = tablet->device->base.seat->libinput;
 +      WacomDeviceDatabase *db;
 +      const WacomStylus *s = NULL;
 +      int code;
 +      WacomStylusType type;
 +
 +      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);
 +              copy_button_cap(tablet, tool, BTN_TOUCH);
 +      }
 +
 +      /* Eventually we want libwacom to tell us each axis on each device
 +         separately. */
 +      switch(type) {
 +      case WSTYLUS_AIRBRUSH:
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_SLIDER);
 +              /* fall-through */
 +      case WSTYLUS_MARKER:
 +              if (type == WSTYLUS_MARKER)
 +                      copy_axis_cap(tablet, tool,
 +                                    LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +              /* fallthrough */
 +      case WSTYLUS_GENERAL:
 +      case WSTYLUS_INKING:
 +      case WSTYLUS_CLASSIC:
 +      case WSTYLUS_STROKE:
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_PRESSURE);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_DISTANCE);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_TILT_X);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_TILT_Y);
 +              break;
 +      case WSTYLUS_PUCK:
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_DISTANCE);
 +              /* lens cursors don't have a wheel */
 +              if (!libwacom_stylus_has_lens(s))
 +                      copy_axis_cap(tablet,
 +                                    tool,
 +                                    LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      rc = 0;
 +out:
 +      if (db)
 +              libwacom_database_destroy(db);
 +#endif
 +      return rc;
 +}
 +
 +static void
 +tool_set_bits(const struct tablet_dispatch *tablet,
 +            struct libinput_tool *tool)
 +{
 +      enum libinput_tool_type type = tool->type;
 +
 +#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_TOOL_PEN:
 +      case LIBINPUT_TOOL_ERASER:
 +      case LIBINPUT_TOOL_PENCIL:
 +      case LIBINPUT_TOOL_BRUSH:
 +      case LIBINPUT_TOOL_AIRBRUSH:
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_PRESSURE);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_DISTANCE);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_TILT_X);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_TILT_Y);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_SLIDER);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +              break;
 +      case LIBINPUT_TOOL_MOUSE:
 +      case LIBINPUT_TOOL_LENS:
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +              copy_axis_cap(tablet, tool, LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      /* If we don't have libwacom, copy all pen-related ones from the
 +         tablet vs all mouse-related ones */
 +      switch (type) {
 +      case LIBINPUT_TOOL_PEN:
 +      case LIBINPUT_TOOL_BRUSH:
 +      case LIBINPUT_TOOL_AIRBRUSH:
 +      case LIBINPUT_TOOL_PENCIL:
 +      case LIBINPUT_TOOL_ERASER:
 +              copy_button_cap(tablet, tool, BTN_STYLUS);
 +              copy_button_cap(tablet, tool, BTN_STYLUS2);
 +              copy_button_cap(tablet, tool, BTN_TOUCH);
 +              break;
 +      case LIBINPUT_TOOL_MOUSE:
 +      case LIBINPUT_TOOL_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 struct libinput_tool *
 +tablet_get_tool(struct tablet_dispatch *tablet,
 +              enum libinput_tool_type type,
 +              uint32_t tool_id,
 +              uint32_t serial)
 +{
 +      struct libinput_tool *tool = NULL, *t;
 +      struct list *tool_list;
 +
 +      if (serial) {
 +              tool_list = &tablet->device->base.seat->libinput->tool_list;
 +
 +              /* Check if we already have the tool in our list of tools */
 +              list_for_each(t, tool_list, link) {
 +                      if (type == t->type && serial == t->serial) {
 +                              tool = t;
 +                              break;
 +                      }
 +              }
 +      } else {
 +              /* We can't guarantee that tools without serial numbers are
 +               * unique, so we keep them local to the tablet that they come
 +               * into proximity of instead of storing them in the global tool
 +               * list */
 +              tool_list = &tablet->tool_list;
 +
 +              /* Same as above, but don't bother checking the serial number */
 +              list_for_each(t, tool_list, link) {
 +                      if (type == t->type) {
 +                              tool = t;
 +                              break;
 +                      }
 +              }
 +      }
 +
 +      /* If we didn't already have the new_tool in our list of tools,
 +       * add it */
 +      if (!tool) {
 +              tool = zalloc(sizeof *tool);
 +              *tool = (struct libinput_tool) {
 +                      .type = type,
 +                      .serial = serial,
 +                      .tool_id = tool_id,
 +                      .refcount = 1,
 +              };
 +
 +              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,
 +                        uint32_t time,
 +                        struct libinput_tool *tool,
 +                        const unsigned char *buttons,
 +                        unsigned int buttons_len,
 +                        enum libinput_button_state state)
 +{
 +      struct libinput_device *base = &device->base;
 +      size_t i;
 +      size_t nbits = 8 * sizeof(buttons[0]) * buttons_len;
 +
 +      for (i = 0; i < nbits; i++) {
 +              if (!bit_is_set(buttons, i))
 +                      continue;
 +
 +              tablet_notify_button(base,
 +                                   time,
 +                                   tool,
 +                                   tablet->axes,
 +                                   i,
 +                                   state);
 +      }
 +}
 +
 +static void
 +tablet_notify_buttons(struct tablet_dispatch *tablet,
 +                    struct evdev_device *device,
 +                    uint32_t time,
 +                    struct libinput_tool *tool,
 +                    enum libinput_button_state state)
 +{
 +      unsigned char buttons[ARRAY_LENGTH(tablet->button_state.stylus_buttons)];
 +
 +      if (state == LIBINPUT_BUTTON_STATE_PRESSED)
 +              tablet_get_pressed_buttons(tablet, buttons, sizeof(buttons));
 +      else
 +              tablet_get_released_buttons(tablet,
 +                                          buttons,
 +                                          sizeof(buttons));
 +
 +      tablet_notify_button_mask(tablet,
 +                                device,
 +                                time,
 +                                tool,
 +                                buttons,
 +                                sizeof(buttons),
 +                                state);
 +}
 +
 +static void
 +sanitize_tablet_axes(struct tablet_dispatch *tablet)
 +{
 +      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);
 +
 +      /* Keep distance and pressure mutually exclusive */
 +      if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_DISTANCE) &&
 +          distance->value > distance->minimum &&
 +          pressure->value > pressure->minimum) {
 +              clear_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_DISTANCE);
 +              tablet->axes[LIBINPUT_TABLET_AXIS_DISTANCE] = 0;
 +      } else if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_PRESSURE) &&
 +                 !tablet_has_status(tablet, TABLET_STYLUS_IN_CONTACT)) {
 +              /* Make sure that the last axis value sent to the caller is a 0 */
 +              if (tablet->axes[LIBINPUT_TABLET_AXIS_PRESSURE] == 0)
 +                      clear_bit(tablet->changed_axes,
 +                                LIBINPUT_TABLET_AXIS_PRESSURE);
 +              else
 +                      tablet->axes[LIBINPUT_TABLET_AXIS_PRESSURE] = 0;
 +      }
 +
 +      /* 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_TOOL_MOUSE ||
 +          tablet->current_tool_type == LIBINPUT_TOOL_LENS) &&
 +          (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_X) ||
 +           bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_Y)))
 +              set_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +}
 +
 +static void
 +tablet_flush(struct tablet_dispatch *tablet,
 +           struct evdev_device *device,
 +           uint32_t time)
 +{
 +      struct libinput_tool *tool =
 +              tablet_get_tool(tablet,
 +                              tablet->current_tool_type,
 +                              tablet->current_tool_id,
 +                              tablet->current_tool_serial);
 +
 +      if (tablet_has_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY))
 +              return;
 +
 +      if (tablet_has_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY)) {
 +              /* Release all stylus buttons */
 +              memset(tablet->button_state.stylus_buttons,
 +                     0,
 +                     sizeof(tablet->button_state.stylus_buttons));
 +              tablet_set_status(tablet, TABLET_BUTTONS_RELEASED);
 +      } else if (tablet_has_status(tablet, TABLET_AXES_UPDATED) ||
 +                 tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) {
 +              sanitize_tablet_axes(tablet);
 +              tablet_check_notify_axes(tablet, device, time, tool);
 +
 +              tablet_unset_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY);
 +              tablet_unset_status(tablet, TABLET_AXES_UPDATED);
 +      }
 +
 +      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_TOOL_PROXIMITY_OUT,
 +                                      tablet->changed_axes,
 +                                      tablet->axes);
 +
 +              tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
 +              tablet_unset_status(tablet, TABLET_TOOL_LEAVING_PROXIMITY);
 +
 +              tablet_change_to_left_handed(device);
 +      }
 +}
 +
 +static inline void
 +tablet_reset_state(struct tablet_dispatch *tablet)
 +{
 +      /* Update state */
 +      memcpy(&tablet->prev_button_state,
 +             &tablet->button_state,
 +             sizeof(tablet->button_state));
 +}
 +
 +static void
 +tablet_process(struct evdev_dispatch *dispatch,
 +             struct evdev_device *device,
 +             struct input_event *e,
 +             uint64_t time)
 +{
 +      struct tablet_dispatch *tablet =
 +              (struct tablet_dispatch *)dispatch;
 +
 +      switch (e->type) {
 +      case EV_ABS:
 +              tablet_process_absolute(tablet, device, e, time);
 +              break;
 +      case EV_REL:
 +              tablet_process_relative(tablet, device, e, time);
 +              break;
 +      case EV_KEY:
 +              tablet_process_key(tablet, device, e, time);
 +              break;
 +      case EV_MSC:
 +              tablet_process_misc(tablet, device, e, time);
 +              break;
 +      case EV_SYN:
 +              tablet_flush(tablet, device, time);
 +              tablet_reset_state(tablet);
 +              break;
 +      default:
 +              log_error(device->base.seat->libinput,
 +                        "Unexpected event type %s (%#x)\n",
 +                        libevdev_event_type_get_name(e->type),
 +                        e->type);
 +              break;
 +      }
 +}
 +
 +static void
 +tablet_destroy(struct evdev_dispatch *dispatch)
 +{
 +      struct tablet_dispatch *tablet =
 +              (struct tablet_dispatch*)dispatch;
 +      struct libinput_tool *tool, *tmp;
 +
 +      list_for_each_safe(tool, tmp, &tablet->tool_list, link) {
 +              libinput_tool_unref(tool);
 +      }
 +
 +      free(tablet);
 +}
 +
 +static void
 +tablet_check_initial_proximity(struct evdev_device *device,
 +                             struct evdev_dispatch *dispatch)
 +{
 +      bool tool_in_prox = false;
 +      int code, state;
 +      enum libinput_tool_type tool;
 +      struct tablet_dispatch *tablet = (struct tablet_dispatch*)dispatch;
 +
 +      for (tool = LIBINPUT_TOOL_PEN; tool <= LIBINPUT_TOOL_MAX; tool++) {
 +              code = tablet_tool_to_evcode(tool);
 +
 +              /* we only expect one tool to be in proximity at a time */
 +              if (libevdev_fetch_event_value(device->evdev,
 +                                              EV_KEY,
 +                                              code,
 +                                              &state) && state) {
 +                      tool_in_prox = true;
 +                      break;
 +              }
 +      }
 +
 +      if (!tool_in_prox)
 +              return;
 +
 +      tablet_update_tool(tablet, device, tool, state);
 +
 +      tablet->current_tool_id =
 +              libevdev_get_event_value(device->evdev,
 +                                       EV_ABS,
 +                                       ABS_MISC);
 +      tablet->current_tool_serial =
 +              libevdev_get_event_value(device->evdev,
 +                                       EV_MSC,
 +                                       MSC_SERIAL);
 +
 +      tablet_flush(tablet,
 +                   device,
 +                   libinput_now(device->base.seat->libinput));
 +}
 +
 +static struct evdev_dispatch_interface tablet_interface = {
 +      tablet_process,
++      NULL, /* suspend */
 +      NULL, /* remove */
 +      tablet_destroy,
 +      NULL, /* device_added */
 +      NULL, /* device_removed */
 +      NULL, /* device_suspended */
 +      NULL, /* device_resumed */
 +      NULL, /* tag_device */
 +      tablet_check_initial_proximity,
 +};
 +
 +static int
 +tablet_init(struct tablet_dispatch *tablet,
 +          struct evdev_device *device)
 +{
 +      enum libinput_tablet_axis axis;
 +
 +      tablet->base.interface = &tablet_interface;
 +      tablet->device = device;
 +      tablet->status = TABLET_NONE;
 +      tablet->current_tool_type = LIBINPUT_TOOL_NONE;
 +      list_init(&tablet->tool_list);
 +
 +      for (axis = LIBINPUT_TABLET_AXIS_X;
 +           axis <= LIBINPUT_TABLET_AXIS_MAX;
 +           axis++) {
 +              if (tablet_device_has_axis(tablet, axis))
 +                      set_bit(tablet->axis_caps, axis);
 +      }
 +
 +      tablet_mark_all_axes_changed(tablet, device);
 +
 +      tablet_set_status(tablet, TABLET_TOOL_OUT_OF_PROXIMITY);
 +
 +      return 0;
 +}
 +
 +static void
 +tablet_init_left_handed(struct evdev_device *device)
 +{
 +#if HAVE_LIBWACOM
 +      struct libinput *libinput = device->base.seat->libinput;
 +      WacomDeviceDatabase *db;
 +      WacomDevice *d = NULL;
 +      WacomError *error;
 +      int vid, pid;
 +
 +      vid = evdev_device_get_id_vendor(device);
 +      pid = evdev_device_get_id_product(device);
 +
 +      db = libwacom_database_new();
 +      if (!db) {
 +              log_info(libinput,
 +                       "Failed to initialize libwacom context.\n");
 +              return;
 +      }
 +      error = libwacom_error_new();
 +      d = libwacom_new_from_usbid(db, vid, pid, error);
 +
 +      if (d) {
 +              if (libwacom_is_reversible(d))
 +                  evdev_init_left_handed(device,
 +                                         tablet_change_to_left_handed);
 +      } else if (libwacom_error_get_code(error) == WERROR_UNKNOWN_MODEL) {
 +              log_info(libinput, "Tablet unknown to libwacom\n");
 +      } else {
 +              log_error(libinput,
 +                        "libwacom error: %s\n",
 +                        libwacom_error_get_message(error));
 +      }
 +
 +      if (error)
 +              libwacom_error_free(&error);
 +      if (d)
 +              libwacom_destroy(d);
 +      libwacom_database_destroy(db);
 +#endif
 +}
 +
 +struct evdev_dispatch *
 +evdev_tablet_create(struct evdev_device *device)
 +{
 +      struct tablet_dispatch *tablet;
 +
 +      tablet = zalloc(sizeof *tablet);
 +      if (!tablet)
 +              return NULL;
 +
 +      if (tablet_init(tablet, device) != 0) {
 +              tablet_destroy(&tablet->base);
 +              return NULL;
 +      }
 +
 +      tablet_init_left_handed(device);
 +
 +      return &tablet->base;
 +}
diff --cc src/evdev.c
Simple merge
diff --cc src/evdev.h
Simple merge
Simple merge
Simple merge
diff --cc src/libinput.c
Simple merge
diff --cc src/libinput.h
Simple merge
index deab3c20af2db142c878896f19e913f611f0d945,9c11174bb576abed56f2888553f36033de9d29d6..1ad6eef481c845e587448b981b176577a1d11579
@@@ -129,29 -128,15 +129,42 @@@ local
        *;
  };
  
- } LIBINPUT_0.12.0;
+ LIBINPUT_0.14.0 {
+ global:
+       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.15.0 {
+ global:
+       libinput_device_keyboard_has_key;
+ } LIBINPUT_0.14.0;
++
 +/* tablet APIs, they are not part of any stable API promise yet.
 + * keep them separate */
 +LIBINPUT_TABLET_SUPPORT {
 +      libinput_event_get_tablet_event;
 +      libinput_event_tablet_axis_has_changed;
 +      libinput_event_tablet_get_axis_delta;
 +      libinput_event_tablet_get_axis_delta_discrete;
 +      libinput_event_tablet_get_axis_value;
 +      libinput_event_tablet_get_button;
 +      libinput_event_tablet_get_button_state;
 +      libinput_event_tablet_get_proximity_state;
 +      libinput_event_tablet_get_seat_button_count;
 +      libinput_event_tablet_get_time;
 +      libinput_event_tablet_get_tool;
 +      libinput_event_tablet_get_x_transformed;
 +      libinput_event_tablet_get_y_transformed;
 +      libinput_tool_get_serial;
 +      libinput_tool_get_tool_id;
 +      libinput_tool_get_type;
 +      libinput_tool_get_user_data;
 +      libinput_tool_has_button;
 +      libinput_tool_has_axis;
 +      libinput_tool_ref;
 +      libinput_tool_set_user_data;
 +      libinput_tool_unref;
++} LIBINPUT_0.15.0;
index 2875d295c8a04d34be709646b7de07d836362dc4,fc05ff6aa489a506190922bab53c0d8a7f4f495f..3f28a0a370f1de892e6621915e4711442dc19e0e
@@@ -27,29 -31,32 +31,37 @@@ liblitest_la_SOURCES = 
        litest-synaptics-t440.c \
        litest-synaptics-x1-carbon-3rd.c \
        litest-trackpoint.c \
 +      litest-wacom-bamboo-tablet.c \
 +      litest-wacom-cintiq-tablet.c \
 +      litest-wacom-intuos-tablet.c \
 +      litest-wacom-isdv4-tablet.c \
        litest-wacom-touch.c \
        litest-wacom-intuos-finger.c \
+       litest-wheel-only.c \
        litest-xen-virtual-pointer.c \
        litest-vmware-virtual-usb-mouse.c \
        litest.c
  liblitest_la_LIBADD = $(top_builddir)/src/libinput-util.la
+ liblitest_la_CFLAGS = $(AM_CFLAGS)
+ if HAVE_LIBUNWIND
+ liblitest_la_LIBADD += $(LIBUNWIND_LIBS) -ldl
+ liblitest_la_CFLAGS += $(LIBUNWIND_CFLAGS)
+ endif
  
  run_tests = \
-       test-udev \
-       test-path \
+       test-touchpad \
++      test-tablet \
+       test-device \
        test-pointer \
        test-touch \
-       test-log \
-       test-tablet \
-       test-touchpad \
        test-trackpoint \
+       test-udev \
+       test-path \
+       test-log \
        test-misc \
        test-keyboard \
-       test-device
+       test-litest-selftest
  build_tests = \
        test-build-cxx \
        test-build-linker \
@@@ -82,11 -89,6 +94,10 @@@ test_log_SOURCES = log.
  test_log_LDADD = $(TEST_LIBS)
  test_log_LDFLAGS = -no-install
  
- test_tablet_CFLAGS = $(AM_CPPFLAGS)
 +test_tablet_SOURCES = tablet.c
 +test_tablet_LDADD = $(TEST_LIBS)
 +test_tablet_LDFLAGS = -static
 +
  test_touchpad_SOURCES = touchpad.c
  test_touchpad_LDADD = $(TEST_LIBS)
  test_touchpad_LDFLAGS = -no-install
diff --cc test/device.c
index 4c4e25327d7b54991ab0c06d41f5486ced0902cc,0b589cc955f25452ea25b6e524ecca7c2217f5df..193c3ffb53fd843b729ef04e5ccf5ea8b05d33d3
@@@ -894,19 -884,84 +884,84 @@@ START_TEST(abs_mt_device_no_range
  }
  END_TEST
  
int main (int argc, char **argv)
START_TEST(abs_device_missing_res)
  {
 -      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 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
+ void
+ litest_setup_tests(void)
+ {
+       struct range abs_range = { 0, ABS_MISC };
+       struct range abs_mt_range = { ABS_MT_SLOT + 1, ABS_CNT };
 +      litest_add("device:sendevents", device_sendevents_config, LITEST_ANY, LITEST_TOUCHPAD|LITEST_TABLET);
 +      litest_add("device:sendevents", device_sendevents_config_invalid, LITEST_ANY, LITEST_TABLET);
 +      litest_add("device:sendevents", device_sendevents_config_touchpad, LITEST_TOUCHPAD, LITEST_TABLET);
 +      litest_add("device:sendevents", device_sendevents_config_touchpad_superset, LITEST_TOUCHPAD, LITEST_TABLET);
 +      litest_add("device:sendevents", device_sendevents_config_default, LITEST_ANY, LITEST_TABLET);
 +      litest_add("device:sendevents", device_disable, LITEST_RELATIVE, LITEST_TABLET);
 +      litest_add("device:sendevents", device_disable_touchpad, LITEST_TOUCHPAD, LITEST_TABLET);
 +      litest_add("device:sendevents", device_disable_events_pending, LITEST_RELATIVE, LITEST_TOUCHPAD|LITEST_TABLET);
 +      litest_add("device:sendevents", device_double_disable, LITEST_ANY, LITEST_TABLET);
 +      litest_add("device:sendevents", device_double_enable, LITEST_ANY, LITEST_TABLET);
        litest_add_no_device("device:sendevents", device_reenable_syspath_changed);
        litest_add_no_device("device:sendevents", device_reenable_device_removed);
        litest_add_for_device("device:sendevents", device_disable_release_buttons, LITEST_MOUSE);
diff --cc test/litest.c
index 9ac7d3c2925414c6491247e13b8ec6744363882b,e1db754ada8de2cc820d0e92f1e373f6d5395e34..60c601150e2942678fd528343dbffe67797faebd
@@@ -1444,27 -1842,53 +1948,74 @@@ litest_assert_button_event(struct libin
        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_int_eq(libinput_event_get_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_int_eq(libinput_event_get_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;
+ }
 +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 *tev;
 +
 +      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_TABLET_BUTTON);
 +      tev = libinput_event_get_tablet_event(event);
 +      ck_assert_int_eq(libinput_event_tablet_get_button(tev),
 +                       button);
 +      ck_assert_int_eq(libinput_event_tablet_get_button_state(tev),
 +                       state);
 +      libinput_event_destroy(event);
 +}
 +
  void
  litest_assert_scroll(struct libinput *li,
                     enum libinput_pointer_axis axis,
diff --cc test/litest.h
index 0214a7d80575f92f4ecd7cb04a16a4fa16f0c334,89f7677fe44e644ee818f49670eec2df48646e78..9efacde876c14305c870d8a9643c41adc52c4243
@@@ -53,10 -130,11 +130,15 @@@ enum litest_device_type 
        LITEST_SYNAPTICS_TRACKPOINT_BUTTONS = -17,
        LITEST_PROTOCOL_A_SCREEN = -18,
        LITEST_WACOM_FINGER = -19,
-       LITEST_WACOM_BAMBOO = -20,
-       LITEST_WACOM_CINTIQ = -21,
-       LITEST_WACOM_INTUOS = -22,
-       LITEST_WACOM_ISDV4 = -23,
+       LITEST_KEYBOARD_BLACKWIDOW = -20,
+       LITEST_WHEEL_ONLY = -21,
+       LITEST_MOUSE_ROCCAT = -22,
+       LITEST_LOGITECH_TRACKBALL = -23,
+       LITEST_ATMEL_HOVER = -24,
++      LITEST_WACOM_BAMBOO = -25,
++      LITEST_WACOM_CINTIQ = -26,
++      LITEST_WACOM_INTUOS = -27,
++      LITEST_WACOM_ISDV4 = -28,
  };
  
  enum litest_device_feature {
        LITEST_FAKE_MT = 1 << 12,
        LITEST_ABSOLUTE = 1 << 13,
        LITEST_PROTOCOL_A = 1 << 14,
-       LITEST_TABLET = 1 << 15,
-       LITEST_DISTANCE = 1 << 16,
-       LITEST_TOOL_SERIAL = 1 << 17,
+       LITEST_HOVER = 1 << 15,
++      LITEST_TABLET = 1 << 16,
++      LITEST_DISTANCE = 1 << 17,
++      LITEST_TOOL_SERIAL = 1 << 18,
  };
  
  struct litest_device {
        char *udev_rule_file;
  };
  
 +struct axis_replacement {
 +      int32_t evcode;
 +      int32_t value;
 +};
 +
+ /* 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);
@@@ -169,15 -302,25 +314,34 @@@ void litest_touch_move_two_touches(stru
                                   double x1, double y1,
                                   double dx, double dy,
                                   int steps, int sleep_ms);
 +
 +void litest_tablet_proximity_in(struct litest_device *d,
 +                              int x, int y,
 +                              struct axis_replacement *axes);
 +void litest_tablet_proximity_out(struct litest_device *d);
 +void litest_tablet_motion(struct litest_device *d,
 +                        int x, int y,
 +                        struct axis_replacement *axes);
 +
+ void litest_hover_start(struct litest_device *d,
+                       unsigned int slot,
+                       double x,
+                       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_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);
@@@ -210,25 -367,9 +391,26 @@@ struct libevdev_uinput * litest_create_
                                                         struct input_id *id,
                                                         const struct input_absinfo *abs,
                                                         ...);
 +#define litest_assert_double_eq(a_, b_)\
 +      ck_assert_int_eq((int)((a_) * 256), (int)((b_) * 256))
 +
 +#define litest_assert_double_ne(a_, b_)\
 +      ck_assert_int_ne((int)((a_) * 256), (int)((b_) * 256))
 +
 +#define litest_assert_double_lt(a_, b_)\
 +      ck_assert_int_lt((int)((a_) * 256), (int)((b_) * 256))
 +
 +#define litest_assert_double_le(a_, b_)\
 +      ck_assert_int_le((int)((a_) * 256), (int)((b_) * 256))
 +
 +#define litest_assert_double_gt(a_, b_)\
 +      ck_assert_int_gt((int)((a_) * 256), (int)((b_) * 256))
 +
 +#define litest_assert_double_ge(a_, b_)\
 +      ck_assert_int_ge((int)((a_) * 256), (int)((b_) * 256))
  
  void litest_timeout_tap(void);
+ void litest_timeout_tapndrag(void);
  void litest_timeout_softbuttons(void);
  void litest_timeout_buttonscroll(void);
  void litest_timeout_edgescroll(void);
diff --cc test/misc.c
Simple merge
diff --cc test/tablet.c
index ba61e0e0d2804fef0d9a15ab166d759030ea23f5,0000000000000000000000000000000000000000..a4bf6b2b7ae614620336b9762bb1b8568f1e36dc
mode 100644,000000..100644
--- /dev/null
@@@ -1,1697 -1,0 +1,1695 @@@
- int
- main(int argc, char **argv)
 +/*
 + * 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 <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(proximity_in_out)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event_tablet *tablet_event;
 +      struct libinput_event *event;
 +      bool have_tool_update = false,
 +           have_proximity_out = false;
 +
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { -1, -1 }
 +      };
 +
 +      litest_drain_events(dev->libinput);
 +
 +      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_PROXIMITY) {
 +                      struct libinput_tool * tool;
 +
 +                      have_tool_update++;
 +                      tablet_event = libinput_event_get_tablet_event(event);
 +                      tool = libinput_event_tablet_get_tool(tablet_event);
 +                      ck_assert_int_eq(libinput_tool_get_type(tool),
 +                                       LIBINPUT_TOOL_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_PROXIMITY) {
 +                      struct libinput_event_tablet *t =
 +                              libinput_event_get_tablet_event(event);
 +
 +                      if (libinput_event_tablet_get_proximity_state(t) ==
 +                          LIBINPUT_TOOL_PROXIMITY_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_AXIS);
 +
 +              libinput_event_destroy(event);
 +      }
 +}
 +END_TEST
 +
 +START_TEST(proximity_out_clear_buttons)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event_tablet *tablet_event;
 +      struct libinput_event *event;
 +      uint32_t button;
 +
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { -1, -1 }
 +      };
 +
 +      litest_drain_events(dev->libinput);
 +
 +      /* Test that proximity out events send button releases for any currently
 +       * pressed stylus buttons
 +       */
 +      for (button = BTN_TOUCH; button <= BTN_STYLUS2; button++) {
 +              bool button_released = false;
 +              uint32_t event_button;
 +              enum libinput_button_state state;
 +
 +              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_event(event);
 +
 +                      if (libinput_event_get_type(event) ==
 +                          LIBINPUT_EVENT_TABLET_BUTTON) {
 +
 +                              event_button = libinput_event_tablet_get_button(tablet_event);
 +                              state = libinput_event_tablet_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 %d was not released.",
 +                            event_button);
 +      }
 +}
 +END_TEST
 +
 +START_TEST(proximity_has_axes)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event_tablet *tablet_event;
 +      struct libinput_event *event;
 +      struct libinput_tool *tool;
 +      double x, y,
 +             distance;
 +
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { ABS_TILT_X, 10 },
 +              { ABS_TILT_Y, 10 },
 +              { -1, -1}
 +      };
 +
 +      litest_drain_events(dev->libinput);
 +
 +      litest_tablet_proximity_in(dev, 10, 10, axes);
 +
 +      litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_PROXIMITY, -1);
 +
 +      event = libinput_get_event(li);
 +
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tablet_event);
 +
 +      ck_assert(libinput_event_tablet_axis_has_changed(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_X));
 +      ck_assert(libinput_event_tablet_axis_has_changed(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_Y));
 +
 +      x = libinput_event_tablet_get_axis_value(tablet_event,
 +                                               LIBINPUT_TABLET_AXIS_X);
 +      y = libinput_event_tablet_get_axis_value(tablet_event,
 +                                               LIBINPUT_TABLET_AXIS_Y);
 +
 +      litest_assert_double_ne(x, 0);
 +      litest_assert_double_ne(y, 0);
 +
 +      if (libinput_tool_has_axis(tool, LIBINPUT_TABLET_AXIS_DISTANCE)) {
 +              ck_assert(libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_DISTANCE));
 +
 +              distance = libinput_event_tablet_get_axis_value(
 +                      tablet_event,
 +                      LIBINPUT_TABLET_AXIS_DISTANCE);
 +              litest_assert_double_ne(distance, 0);
 +      }
 +
 +      if (libinput_tool_has_axis(tool, LIBINPUT_TABLET_AXIS_TILT_X) &&
 +          libinput_tool_has_axis(tool, LIBINPUT_TABLET_AXIS_TILT_Y)) {
 +              ck_assert(libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_X));
 +              ck_assert(libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_Y));
 +
 +              x = libinput_event_tablet_get_axis_value(
 +                      tablet_event,
 +                      LIBINPUT_TABLET_AXIS_TILT_X);
 +              y = libinput_event_tablet_get_axis_value(
 +                      tablet_event,
 +                      LIBINPUT_TABLET_AXIS_TILT_Y);
 +
 +              litest_assert_double_ne(x, 0);
 +              litest_assert_double_ne(y, 0);
 +      }
 +
 +      litest_assert_empty_queue(li);
 +      libinput_event_destroy(event);
 +
 +      /* Make sure that the axes are still present on proximity out */
 +      litest_tablet_proximity_out(dev);
 +
 +      litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_PROXIMITY, -1);
 +      event = libinput_get_event(li);
 +
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tablet_event);
 +
 +      ck_assert(!libinput_event_tablet_axis_has_changed(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_X));
 +      ck_assert(!libinput_event_tablet_axis_has_changed(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_Y));
 +
 +      x = libinput_event_tablet_get_axis_value(tablet_event,
 +                                               LIBINPUT_TABLET_AXIS_X);
 +      y = libinput_event_tablet_get_axis_value(tablet_event,
 +                                               LIBINPUT_TABLET_AXIS_Y);
 +
 +      litest_assert_double_ne(x, 0);
 +      litest_assert_double_ne(y, 0);
 +
 +      if (libinput_tool_has_axis(tool, LIBINPUT_TABLET_AXIS_DISTANCE)) {
 +              ck_assert(!libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_DISTANCE));
 +
 +              distance = libinput_event_tablet_get_axis_value(
 +                      tablet_event,
 +                      LIBINPUT_TABLET_AXIS_DISTANCE);
 +              litest_assert_double_ne(distance, 0);
 +      }
 +
 +      if (libinput_tool_has_axis(tool, LIBINPUT_TABLET_AXIS_TILT_X) &&
 +          libinput_tool_has_axis(tool, LIBINPUT_TABLET_AXIS_TILT_Y)) {
 +              ck_assert(!libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_X));
 +              ck_assert(!libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_Y));
 +
 +              x = libinput_event_tablet_get_axis_value(
 +                      tablet_event,
 +                      LIBINPUT_TABLET_AXIS_TILT_X);
 +              y = libinput_event_tablet_get_axis_value(
 +                      tablet_event,
 +                      LIBINPUT_TABLET_AXIS_TILT_Y);
 +
 +              litest_assert_double_ne(x, 0);
 +              litest_assert_double_ne(y, 0);
 +      }
 +
 +      litest_assert_empty_queue(li);
 +      libinput_event_destroy(event);
 +}
 +END_TEST
 +
 +START_TEST(motion)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event_tablet *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 },
 +              { -1, -1 }
 +      };
 +
 +      litest_drain_events(li);
 +
 +      litest_tablet_proximity_in(dev, 5, 100, axes);
 +      libinput_dispatch(li);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +
 +      while ((event = libinput_get_event(li))) {
 +              bool x_changed, y_changed;
 +              double reported_x, reported_y;
 +
 +              tablet_event = libinput_event_get_tablet_event(event);
 +              ck_assert_int_eq(libinput_event_get_type(event),
 +                               LIBINPUT_EVENT_TABLET_PROXIMITY);
 +
 +              x_changed = libinput_event_tablet_axis_has_changed(
 +                  tablet_event, LIBINPUT_TABLET_AXIS_X);
 +              y_changed = libinput_event_tablet_axis_has_changed(
 +                  tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +              ck_assert(x_changed);
 +              ck_assert(y_changed);
 +
 +              reported_x = libinput_event_tablet_get_axis_value(
 +                  tablet_event, LIBINPUT_TABLET_AXIS_X);
 +              reported_y = libinput_event_tablet_get_axis_value(
 +                  tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +              litest_assert_double_lt(reported_x, reported_y);
 +
 +              last_reported_x = reported_x;
 +              last_reported_y = reported_y;
 +
 +              libinput_event_destroy(event);
 +      }
 +
 +      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_event(event);
 +                      type = libinput_event_get_type(event);
 +
 +                      if (type == LIBINPUT_EVENT_TABLET_AXIS) {
 +                              x_changed = libinput_event_tablet_axis_has_changed(
 +                                  tablet_event, LIBINPUT_TABLET_AXIS_X);
 +                              y_changed = libinput_event_tablet_axis_has_changed(
 +                                  tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +                              ck_assert(x_changed);
 +                              ck_assert(y_changed);
 +
 +                              reported_x = libinput_event_tablet_get_axis_value(
 +                                  tablet_event, LIBINPUT_TABLET_AXIS_X);
 +                              reported_y = libinput_event_tablet_get_axis_value(
 +                                  tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +                              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(motion_delta)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event_tablet *tablet_event;
 +      struct libinput_event *event;
 +      double x1, y1, x2, y2, dist1, dist2;
 +      double delta;
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { -1, -1 }
 +      };
 +
 +      litest_drain_events(li);
 +
 +      litest_tablet_proximity_in(dev, 5, 100, axes);
 +      libinput_dispatch(li);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +
 +      event = libinput_get_event(li);
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      x1 = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_X);
 +      y1 = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_Y);
 +      dist1 = libinput_event_tablet_get_axis_value(tablet_event,
 +                                        LIBINPUT_TABLET_AXIS_DISTANCE);
 +      libinput_event_destroy(event);
 +
 +      axes[0].value = 40;
 +      litest_tablet_motion(dev, 40, 100, axes);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_AXIS,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      x2 = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_X);
 +      y2 = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_Y);
 +      dist2 = libinput_event_tablet_get_axis_value(tablet_event,
 +                                        LIBINPUT_TABLET_AXIS_DISTANCE);
 +
 +      delta = libinput_event_tablet_get_axis_delta(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_X);
 +      litest_assert_double_eq(delta, x2 - x1);
 +      delta = libinput_event_tablet_get_axis_delta(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_Y);
 +      litest_assert_double_eq(delta, y2 - y1);
 +      delta = libinput_event_tablet_get_axis_delta(tablet_event,
 +                                                LIBINPUT_TABLET_AXIS_DISTANCE);
 +      litest_assert_double_eq(delta, dist2 - dist1);
 +
 +      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 *tablet_event;
 +      double libinput_max_x, libinput_max_y;
 +      double last_x, last_y;
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { -1, -1 }
 +      };
 +
 +      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);
 +
 +      litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_PROXIMITY, -1);
 +
 +      while ((event = libinput_get_event(li))) {
 +              tablet_event = libinput_event_get_tablet_event(event);
 +
 +              last_x = libinput_event_tablet_get_axis_value(
 +                              tablet_event, LIBINPUT_TABLET_AXIS_X);
 +              last_y = libinput_event_tablet_get_axis_value(
 +                              tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +              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);
 +      litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_AXIS, -1);
 +
 +      while ((event = libinput_get_event(li))) {
 +              double x, y;
 +              tablet_event = libinput_event_get_tablet_event(event);
 +
 +              x = libinput_event_tablet_get_axis_value(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_X);
 +              y = libinput_event_tablet_get_axis_value(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +              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);
 +
 +      litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_PROXIMITY, -1);
 +
 +      while ((event = libinput_get_event(li))) {
 +              tablet_event = libinput_event_get_tablet_event(event);
 +
 +              last_x = libinput_event_tablet_get_axis_value(
 +                              tablet_event, LIBINPUT_TABLET_AXIS_X);
 +              last_y = libinput_event_tablet_get_axis_value(
 +                              tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +              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);
 +      litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_AXIS, -1);
 +
 +      while ((event = libinput_get_event(li))) {
 +              double x, y;
 +              tablet_event = libinput_event_get_tablet_event(event);
 +
 +              x = libinput_event_tablet_get_axis_value(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_X);
 +              y = libinput_event_tablet_get_axis_value(
 +                      tablet_event, LIBINPUT_TABLET_AXIS_Y);
 +
 +              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(motion_event_state)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event *event;
 +      struct libinput_event_tablet *tablet_event;
 +      int test_x, test_y;
 +      double last_x, last_y;
 +
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { -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_AXIS)
 +                      break;
 +              libinput_event_destroy(event);
 +      }
 +
 +      /* pop the first event off */
 +      ck_assert_notnull(event);
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      ck_assert_notnull(tablet_event);
 +
 +      last_x = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                    LIBINPUT_TABLET_AXIS_X);
 +      last_y = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                    LIBINPUT_TABLET_AXIS_Y);
 +
 +      /* 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_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_AXIS)
 +                      break;
 +
 +              tablet_event = libinput_event_get_tablet_event(event);
 +              ck_assert_notnull(tablet_event);
 +
 +              x = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                       LIBINPUT_TABLET_AXIS_X);
 +              y = libinput_event_tablet_get_axis_value(tablet_event,
 +                                                       LIBINPUT_TABLET_AXIS_Y);
 +
 +              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_BUTTON);
 +      libinput_event_destroy(event);
 +}
 +END_TEST
 +
 +START_TEST(bad_distance_events)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      const struct input_absinfo *absinfo;
 +      struct axis_replacement axes[] = {
 +              { -1, -1 },
 +      };
 +
 +      litest_tablet_proximity_in(dev, 10, 10, axes);
 +      litest_tablet_proximity_out(dev);
 +      litest_drain_events(dev->libinput);
 +
 +      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 *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(dev->libinput);
 +
 +      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_AXIS) {
 +                      tablet_event = libinput_event_get_tablet_event(event);
 +
 +                      if (libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_PRESSURE)) {
 +                              pressure = libinput_event_tablet_get_axis_value(
 +                                  tablet_event, LIBINPUT_TABLET_AXIS_PRESSURE);
 +
 +                              litest_assert_double_eq(pressure, 0);
 +                      }
 +
 +                      if (libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_X)) {
 +                              tilt_vertical =
 +                                      libinput_event_tablet_get_axis_value(
 +                                          tablet_event,
 +                                          LIBINPUT_TABLET_AXIS_TILT_X);
 +
 +                              litest_assert_double_eq(tilt_vertical, -1);
 +                      }
 +
 +                      if (libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_Y)) {
 +                              tilt_horizontal =
 +                                      libinput_event_tablet_get_axis_value(
 +                                          tablet_event,
 +                                          LIBINPUT_TABLET_AXIS_TILT_Y);
 +
 +                              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_AXIS) {
 +                      tablet_event = libinput_event_get_tablet_event(event);
 +
 +                      if (libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_PRESSURE)) {
 +                              pressure = libinput_event_tablet_get_axis_value(
 +                                  tablet_event, LIBINPUT_TABLET_AXIS_PRESSURE);
 +
 +                              litest_assert_double_eq(pressure, 1);
 +                      }
 +
 +                      if (libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_X)) {
 +                              tilt_vertical =
 +                                      libinput_event_tablet_get_axis_value(
 +                                          tablet_event,
 +                                          LIBINPUT_TABLET_AXIS_TILT_X);
 +
 +                              litest_assert_double_eq(tilt_vertical, 1);
 +                      }
 +
 +                      if (libinput_event_tablet_axis_has_changed(
 +                              tablet_event,
 +                              LIBINPUT_TABLET_AXIS_TILT_Y)) {
 +                              tilt_horizontal =
 +                                      libinput_event_tablet_get_axis_value(
 +                                          tablet_event,
 +                                          LIBINPUT_TABLET_AXIS_TILT_Y);
 +
 +                              litest_assert_double_eq(tilt_horizontal, 1);
 +                      }
 +              }
 +
 +              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 *tablet_event;
 +      struct libinput_event *event;
 +      struct libinput_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_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tablet_event);
 +      ck_assert_uint_eq(libinput_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 *tablet_event;
 +      struct libinput_event *event;
 +      struct libinput_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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tablet_event);
 +
 +      ck_assert_uint_eq(libinput_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 *tablet_event;
 +      struct libinput_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_PROXIMITY) {
 +                      tablet_event = libinput_event_get_tablet_event(event);
 +                      tool = libinput_event_tablet_get_tool(tablet_event);
 +
 +                      ck_assert_uint_eq(libinput_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 *tablet_event;
 +      struct libinput_event *event;
 +      struct libinput_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_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tablet_event = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tablet_event);
 +
 +      ck_assert_notnull(tool);
 +      ck_assert(tool == libinput_tool_ref(tool));
 +      ck_assert(tool == libinput_tool_unref(tool));
 +      ck_assert(libinput_tool_unref(tool) == NULL);
 +
 +      libinput_event_destroy(event);
 +}
 +END_TEST
 +
 +START_TEST(pad_buttons_ignored)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event *event;
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { -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_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_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_tool *tool[2] = {0};
 +      struct libinput_event *event;
 +      int i;
 +
 +      for (i = 0; i < 2; i++) {
 +              dev[i] = litest_add_device_with_overrides(li,
 +                                                        LITEST_WACOM_INTUOS,
 +                                                        NULL,
 +                                                        NULL,
 +                                                        NULL,
 +                                                        NULL);
 +              /* 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_event(dev[i], EV_KEY, BTN_TOOL_PEN, 1);
 +              litest_event(dev[i], EV_MSC, MSC_SERIAL, 100);
 +              litest_event(dev[i], EV_SYN, SYN_REPORT, 0);
 +
 +              libinput_dispatch(li);
 +              while ((event = libinput_get_event(li))) {
 +                      if (libinput_event_get_type(event) ==
 +                          LIBINPUT_EVENT_TABLET_PROXIMITY) {
 +                              struct libinput_event_tablet *t =
 +                                      libinput_event_get_tablet_event(event);
 +
 +                              tool[i] = libinput_event_tablet_get_tool(t);
 +                      }
 +
 +                      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_tool *tool[2] = {0};
 +      struct libinput_event *event;
 +      int i;
 +
 +      for (i = 0; i < 2; i++) {
 +              dev[i] = litest_add_device_with_overrides(li,
 +                                                        LITEST_WACOM_ISDV4,
 +                                                        NULL,
 +                                                        NULL,
 +                                                        NULL,
 +                                                        NULL);
 +
 +              /* 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_event(dev[i], EV_KEY, BTN_TOOL_PEN, 1);
 +              litest_event(dev[i], EV_SYN, SYN_REPORT, 0);
 +
 +              libinput_dispatch(li);
 +              while ((event = libinput_get_event(li))) {
 +                      if (libinput_event_get_type(event) ==
 +                          LIBINPUT_EVENT_TABLET_PROXIMITY) {
 +                              struct libinput_event_tablet *t =
 +                                      libinput_event_get_tablet_event(event);
 +
 +                              tool[i] = libinput_event_tablet_get_tool(t);
 +                      }
 +
 +                      libinput_event_destroy(event);
 +              }
 +      }
 +
 +      /* We should get different tool objects for each device */
 +      ck_assert_notnull(tool[0]);
 +      ck_assert_notnull(tool[1]);
 +      ck_assert_ptr_ne(tool[0], tool[1]);
 +
 +      litest_delete_device(dev[0]);
 +      litest_delete_device(dev[1]);
 +      libinput_unref(li);
 +}
 +END_TEST
 +
 +START_TEST(tool_capabilities)
 +{
 +      struct libinput *li = litest_create_context();
 +      struct litest_device *intuos;
 +      struct litest_device *bamboo;
 +      struct libinput_event *event;
 +      struct libinput_event_tablet *t;
 +      struct libinput_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_event(bamboo, EV_KEY, BTN_TOOL_PEN, 1);
 +      litest_event(bamboo, EV_SYN, SYN_REPORT, 0);
 +
 +      libinput_dispatch(li);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +
 +      event = libinput_get_event(li);
 +      t = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(t);
 +
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_PRESSURE));
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_DISTANCE));
 +      ck_assert(!libinput_tool_has_axis(tool,
 +                                        LIBINPUT_TABLET_AXIS_TILT_X));
 +      ck_assert(!libinput_tool_has_axis(tool,
 +                                        LIBINPUT_TABLET_AXIS_TILT_Y));
 +
 +      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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +
 +      event = libinput_get_event(li);
 +      t = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(t);
 +
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_PRESSURE));
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_DISTANCE));
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_TILT_X));
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_TILT_Y));
 +
 +      libinput_event_destroy(event);
 +      litest_assert_empty_queue(li);
 +
 +      litest_delete_device(bamboo);
 +      litest_delete_device(intuos);
 +      libinput_unref(li);
 +}
 +END_TEST
 +
 +START_TEST(tool_in_prox_before_start)
 +{
 +      struct libinput *li;
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput_event *event;
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { ABS_TILT_X, 0 },
 +              { ABS_TILT_Y, 0 },
 +              { -1, -1 }
 +      };
 +      const char *devnode;
 +
 +      litest_tablet_proximity_in(dev, 10, 10, axes);
 +
 +      /* for simplicity, we create a new litest context */
 +      devnode = libevdev_uinput_get_devnode(dev->uinput);
 +      li = litest_create_context();
 +      libinput_path_add_device(li, devnode);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_DEVICE_ADDED,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      libinput_event_destroy(event);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      libinput_event_destroy(event);
 +      litest_assert_empty_queue(li);
 +
 +      litest_tablet_motion(dev, 10, 20, axes);
 +      litest_tablet_motion(dev, 30, 40, axes);
 +
 +      litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_AXIS);
 +      litest_assert_empty_queue(li);
 +      litest_event(dev, EV_KEY, BTN_STYLUS, 1);
 +      litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +      litest_event(dev, EV_KEY, BTN_STYLUS, 1);
 +      litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +      litest_assert_only_typed_events(li, LIBINPUT_EVENT_TABLET_BUTTON);
 +      litest_tablet_proximity_out(dev);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_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 *tev;
 +      struct libinput_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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tev = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tev);
 +      ck_assert_notnull(tool);
 +      ck_assert_int_eq(libinput_tool_get_type(tool),
 +                       LIBINPUT_TOOL_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 *tev;
 +      struct libinput_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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tev = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tev);
 +      ck_assert_notnull(tool);
 +      libinput_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_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_tool_unref(tool);
 +}
 +END_TEST
 +
 +START_TEST(mouse_rotation)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event *event;
 +      struct libinput_event_tablet *tev;
 +      int angle;
 +      int tilt_center_x, tilt_center_y;
 +      const struct input_absinfo *abs;
 +      double val, old_val = 0;
 +
 +      struct axis_replacement axes[] = {
 +              { ABS_DISTANCE, 10 },
 +              { ABS_TILT_X, 0 },
 +              { ABS_TILT_Y, 0 },
 +              { -1, -1 }
 +      };
 +
 +      if (!libevdev_has_event_code(dev->evdev,
 +                                  EV_KEY,
 +                                  BTN_TOOL_MOUSE))
 +              return;
 +
 +      abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_X);
 +      ck_assert_notnull(abs);
 +      tilt_center_x = (abs->maximum - abs->minimum + 1) / 2;
 +
 +      abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_Y);
 +      ck_assert_notnull(abs);
 +      tilt_center_y = (abs->maximum - abs->minimum + 1) / 2;
 +
 +      litest_drain_events(li);
 +
 +      litest_push_event_frame(dev);
 +      litest_tablet_proximity_in(dev, 10, 10, axes);
 +      litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
 +      litest_pop_event_frame(dev);
 +
 +      litest_drain_events(li);
 +
 +      /* cos/sin are 90 degrees offset from the north-is-zero that
 +         libinput uses. 175 is the CCW offset in the mouse HW */
 +      for (angle = 5; angle < 360; angle += 5) {
 +              double a = (angle - 90 - 175)/180.0 * M_PI;
 +              int x, y;
 +
 +              x = cos(a) * 20 + tilt_center_x;
 +              y = sin(a) * 20 + tilt_center_y;
 +
 +              litest_event(dev, EV_ABS, ABS_TILT_X, x);
 +              litest_event(dev, EV_ABS, ABS_TILT_Y, y);
 +              litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +
 +              litest_wait_for_event_of_type(li,
 +                                            LIBINPUT_EVENT_TABLET_AXIS,
 +                                            -1);
 +              event = libinput_get_event(li);
 +              tev = libinput_event_get_tablet_event(event);
 +              ck_assert(libinput_event_tablet_axis_has_changed(tev,
 +                                       LIBINPUT_TABLET_AXIS_ROTATION_Z));
 +              val = libinput_event_tablet_get_axis_value(tev,
 +                                       LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +
 +              /* rounding error galore, we can't test for anything more
 +                 precise than these */
 +              litest_assert_double_lt(val, 360.0);
 +              litest_assert_double_gt(val, old_val);
 +              litest_assert_double_lt(val, angle + 5);
 +
 +              old_val = val;
 +              libinput_event_destroy(event);
 +              litest_assert_empty_queue(li);
 +      }
 +}
 +END_TEST
 +
 +START_TEST(mouse_wheel)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event *event;
 +      struct libinput_event_tablet *tev;
 +      struct libinput_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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tev = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tev);
 +      ck_assert_notnull(tool);
 +      libinput_tool_ref(tool);
 +
 +      libinput_event_destroy(event);
 +
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_REL_WHEEL));
 +
 +      for (i = 0; i < 3; i++) {
 +              litest_event(dev, EV_REL, REL_WHEEL, -1);
 +              litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +
 +              litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_AXIS, -1);
 +
 +              event = libinput_get_event(li);
 +              tev = libinput_event_get_tablet_event(event);
 +              ck_assert(libinput_event_tablet_axis_has_changed(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL));
 +              val = libinput_event_tablet_get_axis_value(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              ck_assert_int_eq(val, 0);
 +
 +              val = libinput_event_tablet_get_axis_delta(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              ck_assert_int_eq(val, 15);
 +
 +              val = libinput_event_tablet_get_axis_delta_discrete(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              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);
 +
 +              litest_wait_for_event_of_type(li, LIBINPUT_EVENT_TABLET_AXIS, -1);
 +
 +              event = libinput_get_event(li);
 +              tev = libinput_event_get_tablet_event(event);
 +              ck_assert(!libinput_event_tablet_axis_has_changed(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL));
 +              val = libinput_event_tablet_get_axis_value(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              ck_assert_int_eq(val, 0);
 +
 +              val = libinput_event_tablet_get_axis_delta(tev,
 +                                              LIBINPUT_TABLET_AXIS_REL_WHEEL);
 +              ck_assert_int_eq(val, 0);
 +
 +              val = libinput_event_tablet_get_axis_delta_discrete(tev,
 +                                              LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +              ck_assert_int_eq(val, 0);
 +
 +              libinput_event_destroy(event);
 +
 +              litest_assert_empty_queue(li);
 +      }
 +
 +      libinput_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 *tev;
 +      struct libinput_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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tev = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tev);
 +      ck_assert_notnull(tool);
 +      ck_assert_int_eq(libinput_tool_get_type(tool),
 +                       LIBINPUT_TOOL_AIRBRUSH);
 +
 +      libinput_event_destroy(event);
 +}
 +END_TEST
 +
 +START_TEST(airbrush_wheel)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event *event;
 +      struct libinput_event_tablet *tev;
 +      const struct input_absinfo *abs;
 +      double val;
 +      double scale;
 +      int v;
 +
 +      if (!libevdev_has_event_code(dev->evdev,
 +                                  EV_KEY,
 +                                  BTN_TOOL_AIRBRUSH))
 +              return;
 +
 +      litest_drain_events(li);
 +
 +      abs = libevdev_get_abs_info(dev->evdev, ABS_WHEEL);
 +      ck_assert_notnull(abs);
 +
 +      litest_event(dev, EV_KEY, BTN_TOOL_AIRBRUSH, 1);
 +      litest_event(dev, EV_MSC, MSC_SERIAL, 1000);
 +      litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +
 +      /* start with non-zero */
 +      litest_event(dev, EV_ABS, ABS_WHEEL, 10);
 +      litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +
 +      litest_drain_events(li);
 +
 +      scale = abs->maximum - abs->minimum;
 +      for (v = abs->minimum; v < abs->maximum; v += 8) {
 +              litest_event(dev, EV_ABS, ABS_WHEEL, v);
 +              litest_event(dev, EV_SYN, SYN_REPORT, 0);
 +
 +              litest_wait_for_event_of_type(li,
 +                                            LIBINPUT_EVENT_TABLET_AXIS,
 +                                            -1);
 +              event = libinput_get_event(li);
 +              tev = libinput_event_get_tablet_event(event);
 +              ck_assert(libinput_event_tablet_axis_has_changed(tev,
 +                                       LIBINPUT_TABLET_AXIS_SLIDER));
 +              val = libinput_event_tablet_get_axis_value(tev,
 +                                       LIBINPUT_TABLET_AXIS_SLIDER);
 +
 +              ck_assert_int_eq(val, (v - abs->minimum)/scale);
 +              libinput_event_destroy(event);
 +              litest_assert_empty_queue(li);
 +      }
 +}
 +END_TEST
 +
 +START_TEST(artpen_tool)
 +{
 +      struct litest_device *dev = litest_current_device();
 +      struct libinput *li = dev->libinput;
 +      struct libinput_event *event;
 +      struct libinput_event_tablet *tev;
 +      struct libinput_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);
 +
 +      litest_wait_for_event_of_type(li,
 +                                    LIBINPUT_EVENT_TABLET_PROXIMITY,
 +                                    -1);
 +      event = libinput_get_event(li);
 +      tev = libinput_event_get_tablet_event(event);
 +      tool = libinput_event_tablet_get_tool(tev);
 +      ck_assert_notnull(tool);
 +      ck_assert_int_eq(libinput_tool_get_type(tool),
 +                       LIBINPUT_TOOL_PEN);
 +      ck_assert(libinput_tool_has_axis(tool,
 +                                       LIBINPUT_TABLET_AXIS_ROTATION_Z));
 +
 +      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 *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);
 +
 +              litest_wait_for_event_of_type(li,
 +                                            LIBINPUT_EVENT_TABLET_AXIS,
 +                                            -1);
 +              event = libinput_get_event(li);
 +              tev = libinput_event_get_tablet_event(event);
 +              ck_assert(libinput_event_tablet_axis_has_changed(tev,
 +                                       LIBINPUT_TABLET_AXIS_ROTATION_Z));
 +              val = libinput_event_tablet_get_axis_value(tev,
 +                                       LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +
 +              /* artpen has a 90 deg offset cw */
 +              ck_assert_int_eq(round(val), (angle + 90) % 360);
 +
 +              val = libinput_event_tablet_get_axis_delta(tev,
 +                                       LIBINPUT_TABLET_AXIS_ROTATION_Z);
 +              ck_assert_int_eq(val, 8);
 +
 +              libinput_event_destroy(event);
 +              litest_assert_empty_queue(li);
 +
 +      }
 +}
 +END_TEST
 +
-       return litest_run(argc, argv);
++void
++litest_setup_tests(void)
 +{
 +      litest_add("tablet:tool", tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
 +      litest_add_no_device("tablet:tool", tool_capabilities);
 +      litest_add("tablet:tool", tool_in_prox_before_start, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:tool_serial", tool_serial, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
 +      litest_add("tablet:tool_serial", serial_changes_tool, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
 +      litest_add("tablet:tool_serial", invalid_serials, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
 +      litest_add_no_device("tablet:tool_serial", tools_with_serials);
 +      litest_add_no_device("tablet:tool_serial", tools_without_serials);
 +      litest_add("tablet:proximity", proximity_out_clear_buttons, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:proximity", proximity_in_out, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:proximity", proximity_has_axes, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:proximity", bad_distance_events, LITEST_TABLET | LITEST_DISTANCE, LITEST_ANY);
 +      litest_add("tablet:motion", motion, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:motion", motion_delta, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:motion", motion_event_state, LITEST_TABLET, LITEST_ANY);
 +      litest_add_for_device("tablet:left_handed", left_handed, 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_ANY);
 +      litest_add("tablet:airbrush", airbrush_tool, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:airbrush", airbrush_wheel, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:artpen", artpen_tool, LITEST_TABLET, LITEST_ANY);
 +      litest_add("tablet:artpen", artpen_rotation, LITEST_TABLET, LITEST_ANY);
 +}
index 4ad22ab2bb8284d8418f8b85e4a3ab904e601dd2,50b5c58ac7551f33280895edd7629471e5fa8ca4..b7f43499c3a0ee960a582ea3bdc381416a4160a5
     ...
     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
+ }
Simple merge