[PATCH libinput 02/23] evdev: Add basic support for tablet devices

Stephen Chandler Paul thatslyude at gmail.com
Thu Jun 12 20:28:23 PDT 2014


These devices set the LIBINPUT_DEVICE_CAP_STYLUS flag, and emit a lot more axis
information then mice and touchpads. As such, tablet events are in a whole new
group of events that is separate from everything else.

In this commit, only X and Y axes are reported in libinput.

Based off the patch originally written by Carlos Garnacho

Signed-off-by: Stephen Chandler Paul <thatslyude at gmail.com>
---
 src/Makefile.am        |   2 +
 src/evdev-tablet.c     | 170 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/evdev-tablet.h     |  64 +++++++++++++++++++
 src/evdev.c            |   8 +++
 src/evdev.h            |   3 +
 src/libinput-private.h |   6 ++
 src/libinput-util.h    |   2 +
 src/libinput.c         | 108 +++++++++++++++++++++++++++++++
 src/libinput.h         | 121 ++++++++++++++++++++++++++++++++++-
 9 files changed, 483 insertions(+), 1 deletion(-)
 create mode 100644 src/evdev-tablet.c
 create mode 100644 src/evdev-tablet.h

diff --git a/src/Makefile.am b/src/Makefile.am
index bf56184..b880a69 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,6 +11,8 @@ libinput_la_SOURCES =			\
 	libinput-util.h			\
 	evdev.c				\
 	evdev.h				\
+	evdev-tablet.c			\
+	evdev-tablet.h			\
 	evdev-mt-touchpad.c		\
 	evdev-mt-touchpad.h		\
 	evdev-mt-touchpad-tap.c		\
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
new file mode 100644
index 0000000..5c73bcb
--- /dev/null
+++ b/src/evdev-tablet.c
@@ -0,0 +1,170 @@
+/*
+ * 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 <math.h>
+#include <stdbool.h>
+#include <string.h>
+
+#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 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:
+		axis = evcode_to_axis(e->code);
+		if (axis == LIBINPUT_TABLET_AXIS_NONE) {
+			log_bug_libinput("Invalid ABS event code %#x\n",
+					 e->code);
+			break;
+		}
+
+		tablet->absinfo[axis] = libevdev_get_abs_info(device->evdev,
+							      e->code);
+
+		set_bit(tablet->changed_axes, axis);
+		tablet_set_status(tablet, TABLET_AXES_UPDATED);
+		break;
+	default:
+		log_info("Unhandled ABS event code %#x\n", e->code);
+		break;
+	}
+}
+
+static void
+tablet_notify_axes(struct tablet_dispatch *tablet,
+		   struct evdev_device *device,
+		   uint32_t time)
+{
+	struct libinput_device *base = &device->base;
+	bool axis_update_needed = false;
+	int a;
+
+	for (a = 0; a < LIBINPUT_TABLET_AXIS_CNT; a++) {
+		if (!bit_is_set(tablet->changed_axes, a))
+			continue;
+
+		switch (a) {
+		case LIBINPUT_TABLET_AXIS_X:
+		case LIBINPUT_TABLET_AXIS_Y:
+			tablet->axes[a] = tablet->absinfo[a]->value;
+			break;
+		default:
+			log_bug_libinput("Invalid axis update: %d\n", a);
+			break;
+		}
+
+		axis_update_needed = true;
+	}
+
+	if (axis_update_needed) {
+		tablet_notify_axis(base, time, tablet->changed_axes, tablet->axes);
+		memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes));
+	}
+}
+
+static void
+tablet_flush(struct tablet_dispatch *tablet,
+	     struct evdev_device *device,
+	     uint32_t time)
+{
+	if (tablet_has_status(tablet, TABLET_AXES_UPDATED)) {
+		tablet_notify_axes(tablet, device, time);
+		tablet_unset_status(tablet, TABLET_AXES_UPDATED);
+	}
+}
+
+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_SYN:
+		tablet_flush(tablet, device, time);
+		break;
+	default:
+		log_error("Unexpected event type %#x\n", e->type);
+		break;
+	}
+}
+
+static void
+tablet_destroy(struct evdev_dispatch *dispatch)
+{
+	struct tablet_dispatch *tablet =
+		(struct tablet_dispatch*)dispatch;
+
+	free(tablet);
+}
+
+static struct evdev_dispatch_interface tablet_interface = {
+	tablet_process,
+	tablet_destroy
+};
+
+static int
+tablet_init(struct tablet_dispatch *tablet,
+	    struct evdev_device *device)
+{
+	tablet->base.interface = &tablet_interface;
+	tablet->device = device;
+	tablet->status = TABLET_NONE;
+
+	return 0;
+}
+
+struct evdev_dispatch *
+evdev_tablet_create(struct evdev_device *device)
+{
+	struct tablet_dispatch *tablet;
+
+	tablet = zalloc(sizeof *tablet);
+	if (!tablet)
+		return NULL;
+
+	if (tablet_init(tablet, device) != 0) {
+		tablet_destroy(&tablet->base);
+		return NULL;
+	}
+
+	return &tablet->base;
+}
diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h
new file mode 100644
index 0000000..d832c17
--- /dev/null
+++ b/src/evdev-tablet.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2014 Red Hat, Inc.
+ * Copyright © 2014 Stephen Chandler "Lyude" Paul
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#ifndef EVDEV_TABLET_H
+#define EVDEV_TABLET_H
+
+#include "evdev.h"
+
+enum tablet_status {
+	TABLET_NONE = 0,
+	TABLET_AXES_UPDATED = 1 << 0
+};
+
+struct tablet_dispatch {
+	struct evdev_dispatch base;
+	struct evdev_device *device;
+	unsigned char status;
+	unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_AXIS_CNT)];
+	const struct input_absinfo *absinfo[LIBINPUT_TABLET_AXIS_CNT];
+	double axes[LIBINPUT_TABLET_AXIS_CNT];
+};
+
+static inline enum libinput_tablet_axis
+evcode_to_axis(const uint32_t evcode)
+{
+	enum libinput_tablet_axis axis;
+
+	switch (evcode) {
+	case ABS_X:
+		axis = LIBINPUT_TABLET_AXIS_X;
+		break;
+	case ABS_Y:
+		axis = LIBINPUT_TABLET_AXIS_Y;
+		break;
+	default:
+		axis = LIBINPUT_TABLET_AXIS_NONE;
+		break;
+	}
+
+	return axis;
+}
+
+#endif
diff --git a/src/evdev.c b/src/evdev.c
index 03f52e4..597977c 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -672,7 +672,15 @@ evdev_configure_device(struct evdev_device *device)
 			device->dispatch = evdev_mt_touchpad_create(device);
 			log_info("input device '%s', %s is a touchpad\n",
 				 device->devname, device->devnode);
+		} else if (!libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_FINGER) &&
+			   libevdev_has_event_code(device->evdev, EV_KEY, BTN_TOOL_PEN) &&
+			   has_abs) {
+			device->dispatch = evdev_tablet_create(device);
+			device->seat_caps |= EVDEV_DEVICE_TABLET;
+			log_info("input device '%s', %s is a tablet\n",
+				 device->devname, device->devnode);
 		}
+
 		for (i = KEY_ESC; i < KEY_MAX; i++) {
 			if (i >= BTN_MISC && i < KEY_OK)
 				continue;
diff --git a/src/evdev.h b/src/evdev.h
index bcb7e79..1164b7c 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -127,6 +127,9 @@ evdev_touchpad_create(struct evdev_device *device);
 struct evdev_dispatch *
 evdev_mt_touchpad_create(struct evdev_device *device);
 
+struct evdev_dispatch *
+evdev_tablet_create(struct evdev_device *device);
+
 void
 evdev_device_proces_event(struct libinput_event *event);
 
diff --git a/src/libinput-private.h b/src/libinput-private.h
index f0bda1f..f6ba51c 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -195,6 +195,12 @@ touch_notify_touch_up(struct libinput_device *device,
 		      int32_t seat_slot);
 
 void
+tablet_notify_axis(struct libinput_device *device,
+		   uint32_t time,
+		   unsigned char *changed_axes,
+		   double *axes);
+
+void
 touch_notify_frame(struct libinput_device *device,
 		   uint32_t time);
 #endif /* LIBINPUT_PRIVATE_H */
diff --git a/src/libinput-util.h b/src/libinput-util.h
index 4488fbf..a1d6616 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -76,6 +76,8 @@ int list_empty(const struct list *list);
 #define min(a, b) (((a) < (b)) ? (a) : (b))
 #define max(a, b) (((a) > (b)) ? (a) : (b))
 
+#define NCHARS(x) ((size_t)(((x) + 7) / 8))
+
 #define LIBINPUT_EXPORT __attribute__ ((visibility("default")))
 
 static inline void *
diff --git a/src/libinput.c b/src/libinput.c
index 5b10a10..fee500e 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -80,6 +80,13 @@ struct libinput_event_touch {
 	double y;
 };
 
+struct libinput_event_tablet {
+	struct libinput_event base;
+	uint32_t time;
+	double *axes;
+	unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_AXIS_CNT)];
+};
+
 static void
 libinput_default_log_func(enum libinput_log_priority priority,
 			  void *data,
@@ -191,6 +198,7 @@ libinput_event_get_pointer_event(struct libinput_event *event)
 	case LIBINPUT_EVENT_TOUCH_MOTION:
 	case LIBINPUT_EVENT_TOUCH_CANCEL:
 	case LIBINPUT_EVENT_TOUCH_FRAME:
+	case LIBINPUT_EVENT_TABLET_AXIS:
 		break;
 	}
 
@@ -217,6 +225,7 @@ libinput_event_get_keyboard_event(struct libinput_event *event)
 	case LIBINPUT_EVENT_TOUCH_MOTION:
 	case LIBINPUT_EVENT_TOUCH_CANCEL:
 	case LIBINPUT_EVENT_TOUCH_FRAME:
+	case LIBINPUT_EVENT_TABLET_AXIS:
 		break;
 	}
 
@@ -243,6 +252,34 @@ libinput_event_get_touch_event(struct libinput_event *event)
 	case LIBINPUT_EVENT_TOUCH_CANCEL:
 	case LIBINPUT_EVENT_TOUCH_FRAME:
 		return (struct libinput_event_touch *) event;
+	case LIBINPUT_EVENT_TABLET_AXIS:
+		break;
+	}
+
+	return NULL;
+}
+
+LIBINPUT_EXPORT struct libinput_event_tablet *
+libinput_event_get_tablet_event(struct libinput_event *event)
+{
+	switch (event->type) {
+	case LIBINPUT_EVENT_NONE:
+		abort(); /* not used as actual event type */
+	case LIBINPUT_EVENT_DEVICE_ADDED:
+	case LIBINPUT_EVENT_DEVICE_REMOVED:
+	case LIBINPUT_EVENT_KEYBOARD_KEY:
+	case LIBINPUT_EVENT_POINTER_MOTION:
+	case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
+	case LIBINPUT_EVENT_POINTER_BUTTON:
+	case LIBINPUT_EVENT_POINTER_AXIS:
+	case LIBINPUT_EVENT_TOUCH_DOWN:
+	case LIBINPUT_EVENT_TOUCH_UP:
+	case LIBINPUT_EVENT_TOUCH_MOTION:
+	case LIBINPUT_EVENT_TOUCH_CANCEL:
+	case LIBINPUT_EVENT_TOUCH_FRAME:
+		break;
+	case LIBINPUT_EVENT_TABLET_AXIS:
+		return (struct libinput_event_tablet *) event;
 	}
 
 	return NULL;
@@ -267,6 +304,7 @@ libinput_event_get_device_notify_event(struct libinput_event *event)
 	case LIBINPUT_EVENT_TOUCH_MOTION:
 	case LIBINPUT_EVENT_TOUCH_CANCEL:
 	case LIBINPUT_EVENT_TOUCH_FRAME:
+	case LIBINPUT_EVENT_TABLET_AXIS:
 		break;
 	}
 
@@ -431,6 +469,51 @@ libinput_event_touch_get_y(struct libinput_event_touch *event)
 	return event->y;
 }
 
+LIBINPUT_EXPORT int
+libinput_event_tablet_axis_has_changed(struct libinput_event_tablet *event,
+				       enum libinput_tablet_axis axis) {
+	return (NCHARS(axis) <= sizeof(event->changed_axes)) ?
+		bit_is_set(event->changed_axes, axis) : 0;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_tablet_get_axis_value(struct libinput_event_tablet *event,
+				     enum libinput_tablet_axis axis)
+{
+	return (axis >= 0 && axis < LIBINPUT_TABLET_AXIS_CNT) ?
+		event->axes[axis] : 0;
+}
+
+LIBINPUT_EXPORT double
+libinput_event_tablet_get_x_transformed(struct libinput_event_tablet *event,
+					uint32_t width)
+{
+	struct evdev_device *device =
+		(struct evdev_device *) event->base.device;
+
+	return evdev_device_transform_x(device,
+					event->axes[LIBINPUT_TABLET_AXIS_X],
+					width);
+}
+
+LIBINPUT_EXPORT double
+libinput_event_tablet_get_y_transformed(struct libinput_event_tablet *event,
+					uint32_t height)
+{
+	struct evdev_device *device =
+		(struct evdev_device *) event->base.device;
+
+	return evdev_device_transform_y(device,
+					event->axes[LIBINPUT_TABLET_AXIS_Y],
+					height);
+}
+
+LIBINPUT_EXPORT uint32_t
+libinput_event_tablet_get_time(struct libinput_event_tablet *event)
+{
+	return event->time;
+}
+
 struct libinput_source *
 libinput_add_fd(struct libinput *libinput,
 		int fd,
@@ -1021,6 +1104,31 @@ touch_notify_frame(struct libinput_device *device,
 			  &touch_event->base);
 }
 
+void
+tablet_notify_axis(struct libinput_device *device,
+		   uint32_t time,
+		   unsigned char *changed_axes,
+		   double *axes)
+{
+	struct libinput_event_tablet *axis_event;
+
+	axis_event = zalloc(sizeof *axis_event);
+	if (!axis_event)
+		return;
+
+	*axis_event = (struct libinput_event_tablet) {
+		.time = time,
+		.axes = axes,
+	};
+
+	memcpy(&axis_event->changed_axes,
+	       changed_axes,
+	       sizeof(axis_event->changed_axes));
+
+	post_device_event(device,
+			  LIBINPUT_EVENT_TABLET_AXIS,
+			  &axis_event->base);
+}
 
 static void
 libinput_post_event(struct libinput *libinput,
diff --git a/src/libinput.h b/src/libinput.h
index d6f2588..18bb726 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -170,6 +170,19 @@ enum libinput_pointer_axis {
 };
 
 /**
+ * @ingroup device
+ *
+ * Available axis types for a device. It must have the @ref
+ * LIBINPUT_DEVICE_CAP_TABLET capability.
+ */
+enum libinput_tablet_axis {
+	LIBINPUT_TABLET_AXIS_NONE = -1,
+	LIBINPUT_TABLET_AXIS_X = 0,
+	LIBINPUT_TABLET_AXIS_Y = 1,
+	LIBINPUT_TABLET_AXIS_CNT = LIBINPUT_TABLET_AXIS_Y + 1
+};
+
+/**
  * @ingroup base
  *
  * Event type for events returned by libinput_get_event().
@@ -213,7 +226,9 @@ enum libinput_event_type {
 	 * Signals the end of a set of touchpoints at one device sample
 	 * time. This event has no coordinate information attached.
 	 */
-	LIBINPUT_EVENT_TOUCH_FRAME
+	LIBINPUT_EVENT_TOUCH_FRAME,
+
+	LIBINPUT_EVENT_TABLET_AXIS = 600
 };
 
 struct libinput;
@@ -238,6 +253,15 @@ struct libinput_event_pointer;
 struct libinput_event_touch;
 
 /**
+ * @ingroup event_tablet
+ * @struct libinput_event_tablet
+ *
+ * Tablet event representing an axis update, button press, or tool update. Valid
+ * event types for this event are @ref LIBINPUT_EVENT_TABLET_AXIS.
+ */
+struct libinput_event_tablet;
+
+/**
  * @defgroup event Accessing and destruction of events
  */
 
@@ -330,6 +354,19 @@ libinput_event_get_touch_event(struct libinput_event *event);
 /**
  * @ingroup event
  *
+ * Return the tablet event that is this input event. If the event type does not
+ * match the tablet event types, this function returns NULL.
+ *
+ * The inverse of this function is libinput_event_tablet_get_base_event().
+ *
+ * @return A touch event, or NULL for other events
+ */
+struct libinput_event_tablet *
+libinput_event_get_tablet_event(struct libinput_event *event);
+
+/**
+ * @ingroup event
+ *
  * Return the device event that is this input event. If the event type does
  * not match the device event types, this function returns NULL.
  *
@@ -756,6 +793,88 @@ struct libinput_event *
 libinput_event_touch_get_base_event(struct libinput_event_touch *event);
 
 /**
+ * @defgroup event_tablet Tablet events
+ *
+ * Events that come from tablet devices.
+ */
+
+/**
+ * @ingroup event_tablet
+ *
+ * Checks if an axis was updated in this event or return 0 otherwise.
+ * For tablet events that are not of type LIBINPUT_EVENT_TABLET_AXIS,
+ * this function returns 0.
+ *
+ * @note It is an application bug to call this function for events other than
+ * LIBINPUT_EVENT_TABLET_AXIS.
+ *
+ * @param event The libinput tablet event
+ * @param axis The axis to check for updates
+ * @return 1 if the axis was updated or 0 otherwise
+ */
+int
+libinput_event_tablet_axis_has_changed(struct libinput_event_tablet *event,
+				       enum libinput_tablet_axis axis);
+
+/**
+ * @ingroup event_tablet
+ *
+ * Return the axis value of a given axis for a tablet. The interpretation of the
+ * value is dependent on the axis:
+ * - @ref LIBINPUT_TABLET_AXIS_X and @ref LIBINPUT_TABLET_AXIS_Y - the raw X and
+ *   Y coordinates of the tablet tool. By default these are not transformed,
+ *   however libinput provides libinput_event_tablet_get_x_transformed() and
+ *   libinput_event_tablet_get_y_transformed() for transforming each respective
+ *   axis value.
+ *
+ * For tablet events that are not of type @ref LIBINPUT_EVENT_TABLET_AXIS, this
+ * function returns 0.
+ *
+ * @param event The libinput tablet event
+ * @param axis The axis to retrieve the value of
+ * @return The current value of the the axis
+ */
+double
+libinput_event_tablet_get_axis_value(struct libinput_event_tablet *event,
+				     enum libinput_tablet_axis axis);
+
+/**
+ * @ingroup event_tablet
+ *
+ * Return the current absolute x coordinate of the tablet event, transformed to
+ * screen coordinates.
+ *
+ * @param event The libinput tablet event
+ * @param width The current output screen width
+ * @return the current absolute x coordinate transformed to a screen coordinate
+ */
+double
+libinput_event_tablet_get_x_transformed(struct libinput_event_tablet *event,
+					uint32_t width);
+
+/**
+ * @ingroup event_tablet
+ *
+ * Return the current absolute y coordinate of the tablet event, transformed to
+ * screen coordinates.
+ *
+ * @param event The libinput tablet event
+ * @param height The current output screen height
+ * @return the current absolute y coordinate transformed to a screen coordinate
+ */
+double
+libinput_event_tablet_get_y_transformed(struct libinput_event_tablet *event,
+					uint32_t height);
+/**
+ * @ingroup event_tablet
+ *
+ * @param event The libinput tablet event
+ * @return The event time for this event
+ */
+uint32_t
+libinput_event_tablet_get_time(struct libinput_event_tablet *event);
+
+/**
  * @defgroup base Initialization and manipulation of libinput contexts
  */
 
-- 
1.8.5.5



More information about the wayland-devel mailing list