--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
- 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);
+}