[RFC v3 libinput 2/2] buttonset: implement buttonset handling for Wacom tablet pads

Peter Hutterer peter.hutterer at who-t.net
Mon Jan 18 20:49:07 PST 2016


Same approach as evdev-tablet (started as copy/paste), with axis and buttons
adjusted. Wacom's handling of pad devices requires a lot of non-obvious
handling, e.g. ABS_THROTTLE is the second ring, ABS_RX is the strip, etc.

This is not generic buttonset code, if we start supporting other devices for
buttonsets we'll factor out a couple of the functions.

The wheel and strip events are a bit of a problem: Wacom sends a 0 event on the
axis when the finger is released. We can detect this if there is an ABS_MISC 0
present in the same event and suppress it.  Won't work if any finger is down
on any other wheel, strip or button but that's a kernel bug we'll fix once we
figure out how.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
Changes to v2:
- reworked for the per-axis type API
- squashed heaps of fixes piled on after the last version into this patch.
  Note, this is not a final version yet, it just serves to illustrate the
  implementation.

 src/Makefile.am             |   2 +
 src/evdev-buttonset-wacom.c | 602 ++++++++++++++++++++++++++++++++++++++++++++
 src/evdev-buttonset-wacom.h |  67 +++++
 src/evdev-mt-touchpad.c     |   3 +
 src/evdev-tablet.c          |   3 +
 src/evdev.c                 |  84 ++++++-
 src/evdev.h                 |  32 +++
 src/libinput-private.h      |  29 +++
 src/libinput.c              | 105 ++++++--
 9 files changed, 898 insertions(+), 29 deletions(-)
 create mode 100644 src/evdev-buttonset-wacom.c
 create mode 100644 src/evdev-buttonset-wacom.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 343e75c..723f7d6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,6 +11,8 @@ libinput_la_SOURCES =			\
 	libinput-private.h		\
 	evdev.c				\
 	evdev.h				\
+	evdev-buttonset-wacom.c		\
+	evdev-buttonset-wacom.h		\
 	evdev-middle-button.c		\
 	evdev-mt-touchpad.c		\
 	evdev-mt-touchpad.h		\
diff --git a/src/evdev-buttonset-wacom.c b/src/evdev-buttonset-wacom.c
new file mode 100644
index 0000000..7dec3e8
--- /dev/null
+++ b/src/evdev-buttonset-wacom.c
@@ -0,0 +1,602 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+#include "evdev-buttonset-wacom.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define buttonset_set_status(buttonset_,s_) (buttonset_)->status |= (s_)
+#define buttonset_unset_status(buttonset_,s_) (buttonset_)->status &= ~(s_)
+#define buttonset_has_status(buttonset_,s_) (!!((buttonset_)->status & (s_)))
+
+static void
+buttonset_get_buttons_pressed(struct buttonset_dispatch *buttonset,
+			      unsigned long buttons_pressed[NLONGS(KEY_CNT)])
+{
+	struct button_state *state = &buttonset->button_state;
+	struct button_state *prev_state = &buttonset->prev_button_state;
+	unsigned int i;
+
+	for (i = 0; i < NLONGS(KEY_CNT); i++)
+		buttons_pressed[i] = state->buttons[i]
+						& ~(prev_state->buttons[i]);
+}
+
+static void
+buttonset_get_buttons_released(struct buttonset_dispatch *buttonset,
+			       unsigned long buttons_released[NLONGS(KEY_CNT)])
+{
+	struct button_state *state = &buttonset->button_state;
+	struct button_state *prev_state = &buttonset->prev_button_state;
+	unsigned int i;
+
+	for (i = 0; i < NLONGS(KEY_CNT); i++)
+		buttons_released[i] = prev_state->buttons[i]
+						& ~(state->buttons[i]);
+}
+
+static inline bool
+buttonset_button_is_down(const struct buttonset_dispatch *buttonset,
+			 uint32_t button)
+{
+	return long_bit_is_set(buttonset->button_state.buttons, button);
+}
+
+static inline void
+buttonset_button_set_down(struct buttonset_dispatch *buttonset,
+			  uint32_t button,
+			  bool is_down)
+{
+	struct button_state *state = &buttonset->button_state;
+
+	if (is_down) {
+		long_set_bit(state->buttons, button);
+		buttonset_set_status(buttonset, BUTTONSET_BUTTONS_PRESSED);
+	} else {
+		long_clear_bit(state->buttons, button);
+		buttonset_set_status(buttonset, BUTTONSET_BUTTONS_RELEASED);
+	}
+}
+
+static void
+buttonset_process_absolute(struct buttonset_dispatch *buttonset,
+			   struct evdev_device *device,
+			   struct input_event *e,
+			   uint32_t time)
+{
+	unsigned int axis;
+
+	switch (e->code) {
+	case ABS_WHEEL:
+	case ABS_THROTTLE:
+	case ABS_RX:
+	case ABS_RY:
+		axis = buttonset->evcode_map[e->code];
+		if (axis == (unsigned int)-1) {
+			log_bug_libinput(device->base.seat->libinput,
+					 "Unhandled EV_ABS mapping for %#x\n",
+					 e->code);
+			break;
+		}
+
+		set_bit(buttonset->changed_axes, axis);
+		buttonset_set_status(buttonset, BUTTONSET_AXES_UPDATED);
+		break;
+	case ABS_MISC:
+		/* The wacom driver always sends a 0 axis event on finger
+		   up, but we also get an ABS_MISC 15 on touch down and
+		   ABS_MISC 0 on touch up, on top of the actual event. This
+		   is kernel behavior for xf86-input-wacom backwards
+		   compatibility after the 3.17 wacom HID move.
+
+		   We use that event to tell when we truly went a full
+		   rotation around the wheel vs. a finger release.
+
+		   FIXME: On the Intuos5 and later the kernel merges all
+		   states into that event, so if any finger is down on any
+		   button, the wheel release won't trigger the ABS_MISC 0
+		   but still send a 0 event. We can't currently detect this.
+		 */
+		buttonset->have_abs_misc_terminator = true;
+		break;
+	default:
+		log_info(device->base.seat->libinput,
+			 "Unhandled EV_ABS event code %#x\n", e->code);
+		break;
+	}
+}
+
+static inline double
+normalize_ring(const struct input_absinfo *absinfo)
+{
+	/* libinput has 0 as the ring's northernmost point in the device's
+	   current logical rotation, increasing clockwise to 1. Wacom has
+	   0 on the left-most wheel position.
+	 */
+	double range = absinfo->maximum - absinfo->minimum + 1;
+	double value = (absinfo->value - absinfo->minimum) / range - 0.25;
+	if (value < 0.0)
+		value += 1.0;
+
+	return value;
+}
+
+static inline double
+normalize_strip(const struct input_absinfo *absinfo)
+{
+	/* strip axes don't use a proper value, they just shift the bit left
+	 * for each position. 0 isn't a real value either, it's only sent on
+	 * finger release */
+	double min = 0,
+	       max = log2(absinfo->maximum);
+	double range = max - min;
+	double value = (log2(absinfo->value) - min) / range;
+
+	return value;
+}
+
+/* FIXME:
+ * - switch the loop in check_notify_axes to a similar approach as used
+ * in the tablet source
+ * - switch all special axis handling out into helper functions
+ * - switch everything over to the new axis struct
+ */
+static inline double
+buttonset_handle_ring(struct buttonset_dispatch *buttonset,
+		      struct evdev_device *device,
+		      unsigned int axis)
+{
+	unsigned int code;
+	const struct input_absinfo *absinfo;
+
+	code = buttonset->axes[axis].evcode;
+	assert(code != 0);
+	absinfo = libevdev_get_abs_info(device->evdev, code);
+	assert(absinfo);
+
+	return normalize_ring(absinfo);
+}
+
+static inline bool
+buttonset_handle_strip(struct buttonset_dispatch *buttonset,
+		       struct evdev_device *device,
+		       unsigned int axis,
+		       double *value)
+{
+	unsigned int code;
+	const struct input_absinfo *absinfo;
+
+	code = buttonset->axes[axis].evcode;
+	assert(code != 0);
+	absinfo = libevdev_get_abs_info(device->evdev, code);
+	assert(absinfo);
+
+	/* value 0 is a finger release, ignore it */
+	if (absinfo->value == 0)
+		return false;
+
+	*value = normalize_strip(absinfo);
+
+	return true;
+}
+
+static void
+buttonset_check_notify_axes(struct buttonset_dispatch *buttonset,
+			    struct evdev_device *device,
+			    uint32_t time)
+{
+	struct libinput_device *base = &device->base;
+	bool axis_update_needed = false;
+	unsigned int a;
+	double value;
+	struct libinput_buttonset_axis axes[buttonset->naxes];
+
+	/* Suppress the reset to 0 on finger up. See the
+	   comment in buttonset_process_absolute */
+	if (buttonset->have_abs_misc_terminator &&
+	    libevdev_get_event_value(device->evdev, EV_ABS, ABS_MISC) == 0) {
+		goto out;
+	}
+
+	for (a = 0; a < buttonset->naxes; a++) {
+		if (!bit_is_set(buttonset->changed_axes, a)) {
+			axes[a] = buttonset->axes[a].base;
+			continue;
+		}
+
+		switch (buttonset->axes[a].base.type) {
+		case LIBINPUT_BUTTONSET_AXIS_RING:
+			value = buttonset_handle_ring(buttonset,
+						      device,
+						      a);
+			buttonset->axes[a].base.value.degrees = value;
+			buttonset->axes[a].base.source =
+				LIBINPUT_BUTTONSET_AXIS_SOURCE_UNKNOWN;
+			break;
+		case LIBINPUT_BUTTONSET_AXIS_STRIP:
+			if (buttonset_handle_strip(buttonset,
+						   device,
+						   a,
+						   &value)) {
+				buttonset->axes[a].base.value.normalized =
+							value;
+				buttonset->axes[a].base.source =
+					LIBINPUT_BUTTONSET_AXIS_SOURCE_UNKNOWN;
+			} else {
+				clear_bit(buttonset->changed_axes, a);
+				continue;
+			}
+			break;
+		default:
+			log_bug_libinput(device->base.seat->libinput,
+					 "Invalid axis update: %u\n", a);
+			break;
+		}
+
+		axis_update_needed = true;
+		axes[a] = buttonset->axes[a].base;
+	}
+
+	if (axis_update_needed)
+		buttonset_notify_axis(base,
+				      time,
+				      buttonset->changed_axes,
+				      axes,
+				      ARRAY_LENGTH(axes));
+
+out:
+	memset(buttonset->changed_axes, 0, sizeof(buttonset->changed_axes));
+	buttonset->have_abs_misc_terminator = false;
+}
+
+static void
+buttonset_process_key(struct buttonset_dispatch *buttonset,
+		      struct evdev_device *device,
+		      struct input_event *e,
+		      uint32_t time)
+{
+	uint32_t button = e->code;
+	uint32_t is_press = e->value != 0;
+
+	buttonset_button_set_down(buttonset, button, is_press);
+}
+
+static void
+buttonset_notify_button_mask(struct buttonset_dispatch *buttonset,
+			     struct evdev_device *device,
+			     uint32_t time,
+			     unsigned long *buttons,
+			     enum libinput_button_state state)
+{
+	struct libinput_device *base = &device->base;
+	int32_t num_button;
+	unsigned int i;
+	struct libinput_buttonset_axis axes[buttonset->naxes];
+	unsigned int a;
+
+	for (a = 0; a < buttonset->naxes; a++)
+		axes[a] = buttonset->axes[a].base;
+
+	for (i = 0; i < NLONGS(KEY_CNT); i++) {
+		unsigned long buttons_slice = buttons[i];
+
+		num_button = i * LONG_BITS;
+		while (buttons_slice) {
+			int enabled;
+
+			num_button++;
+			enabled = (buttons_slice & 1);
+			buttons_slice >>= 1;
+
+			if (!enabled)
+				continue;
+
+			buttonset_notify_button(base,
+						time,
+						axes,
+						ARRAY_LENGTH(axes),
+						num_button - 1,
+						state);
+		}
+	}
+}
+
+static void
+buttonset_notify_buttons(struct buttonset_dispatch *buttonset,
+			 struct evdev_device *device,
+			 uint32_t time,
+			 enum libinput_button_state state)
+{
+	unsigned long buttons[NLONGS(KEY_CNT)];
+
+	if (state == LIBINPUT_BUTTON_STATE_PRESSED)
+		buttonset_get_buttons_pressed(buttonset,
+					      buttons);
+	else
+		buttonset_get_buttons_released(buttonset,
+					       buttons);
+
+	buttonset_notify_button_mask(buttonset,
+				     device,
+				     time,
+				     buttons,
+				     state);
+}
+
+static void
+sanitize_buttonset_axes(struct buttonset_dispatch *buttonset)
+{
+}
+
+static void
+buttonset_flush(struct buttonset_dispatch *buttonset,
+		struct evdev_device *device,
+		uint32_t time)
+{
+	if (buttonset_has_status(buttonset, BUTTONSET_AXES_UPDATED)) {
+		sanitize_buttonset_axes(buttonset);
+		buttonset_check_notify_axes(buttonset, device, time);
+		buttonset_unset_status(buttonset, BUTTONSET_AXES_UPDATED);
+	}
+
+	if (buttonset_has_status(buttonset, BUTTONSET_BUTTONS_RELEASED)) {
+		buttonset_notify_buttons(buttonset,
+					 device,
+					 time,
+					 LIBINPUT_BUTTON_STATE_RELEASED);
+		buttonset_unset_status(buttonset, BUTTONSET_BUTTONS_RELEASED);
+	}
+
+	if (buttonset_has_status(buttonset, BUTTONSET_BUTTONS_PRESSED)) {
+		buttonset_notify_buttons(buttonset,
+					 device,
+					 time,
+					 LIBINPUT_BUTTON_STATE_PRESSED);
+		buttonset_unset_status(buttonset, BUTTONSET_BUTTONS_PRESSED);
+	}
+
+	/* Update state */
+	memcpy(&buttonset->prev_button_state,
+	       &buttonset->button_state,
+	       sizeof(buttonset->button_state));
+}
+
+static void
+buttonset_process(struct evdev_dispatch *dispatch,
+		  struct evdev_device *device,
+		  struct input_event *e,
+		  uint64_t time)
+{
+	struct buttonset_dispatch *buttonset =
+		(struct buttonset_dispatch *)dispatch;
+
+	switch (e->type) {
+	case EV_ABS:
+		buttonset_process_absolute(buttonset, device, e, time);
+		break;
+	case EV_KEY:
+		buttonset_process_key(buttonset, device, e, time);
+		break;
+	case EV_SYN:
+		buttonset_flush(buttonset, device, time);
+		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
+buttonset_suspend(struct evdev_dispatch *dispatch,
+		  struct evdev_device *device)
+{
+	struct buttonset_dispatch *buttonset =
+		(struct buttonset_dispatch *)dispatch;
+	struct libinput *libinput = device->base.seat->libinput;
+	unsigned int code;
+
+	for (code = KEY_ESC; code < KEY_CNT; code++) {
+		if (buttonset_button_is_down(buttonset, code))
+			buttonset_button_set_down(buttonset, code, false);
+	}
+
+	buttonset_flush(buttonset, device, libinput_now(libinput));
+}
+
+static void
+buttonset_destroy(struct evdev_dispatch *dispatch)
+{
+	struct buttonset_dispatch *buttonset =
+		(struct buttonset_dispatch*)dispatch;
+
+	free(buttonset);
+}
+
+static unsigned int
+buttonset_get_num_axes(struct evdev_device *device)
+{
+	struct buttonset_dispatch *buttonset =
+				(struct buttonset_dispatch*)device->dispatch;
+
+	return buttonset->naxes;
+}
+
+static enum libinput_buttonset_axis_type
+buttonset_get_axis_type(struct evdev_device *device, unsigned int axis)
+{
+	struct buttonset_dispatch *buttonset =
+				(struct buttonset_dispatch*)device->dispatch;
+
+	if (axis < buttonset->naxes)
+		return buttonset->axes[axis].base.type;
+
+	log_bug_client(device->base.seat->libinput,
+		       "Axis %d does not exist on device %s\n",
+		       axis,
+		       device->devname);
+
+	return 0;
+}
+
+static struct evdev_dispatch_interface buttonset_interface = {
+	buttonset_process,
+	buttonset_suspend, /* suspend */
+	NULL, /* remove */
+	buttonset_destroy,
+	NULL, /* device_added */
+	NULL, /* device_removed */
+	NULL, /* device_suspended */
+	NULL, /* device_resumed */
+	NULL, /* post_added */
+	buttonset_get_num_axes,
+	buttonset_get_axis_type,
+};
+
+static enum libinput_buttonset_axis_type
+buttonset_guess_axis_type(struct evdev_device *device,
+			  unsigned int evcode)
+{
+	switch (evcode) {
+	case ABS_WHEEL:
+	case ABS_THROTTLE:
+		return LIBINPUT_BUTTONSET_AXIS_RING;
+	case ABS_RX:
+	case ABS_RY:
+		return LIBINPUT_BUTTONSET_AXIS_STRIP;
+	default:
+		return LIBINPUT_BUTTONSET_AXIS_NONE;
+	}
+}
+
+static int
+buttonset_init(struct buttonset_dispatch *buttonset,
+	       struct evdev_device *device)
+{
+	unsigned int naxes = 0;
+	int code;
+	enum libinput_buttonset_axis_type type;
+
+	buttonset->base.interface = &buttonset_interface;
+	buttonset->device = device;
+	buttonset->status = BUTTONSET_NONE;
+
+	/* We intentionally skip X/Y/Z, they're dead on most wacom pads and
+	   the 27QHD sends accelerometer data through those three */
+	for (code = ABS_RX; code <= ABS_MAX; code++) {
+		buttonset->evcode_map[code] = -1;
+
+		if (!libevdev_has_event_code(device->evdev, EV_ABS, code))
+			continue;
+
+		/* Ignore axes we don't know about */
+		type = buttonset_guess_axis_type(device, code);
+		if (type == LIBINPUT_BUTTONSET_AXIS_NONE)
+			continue;
+
+		buttonset->evcode_map[code] = naxes;
+		buttonset->axes[naxes].base.type = type;
+		buttonset->axes[naxes].evcode = code;
+		naxes++;
+	}
+
+	buttonset->naxes = naxes;
+
+	return 0;
+}
+
+static uint32_t
+bs_sendevents_get_modes(struct libinput_device *device)
+{
+	return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
+}
+
+static enum libinput_config_status
+bs_sendevents_set_mode(struct libinput_device *device,
+		       enum libinput_config_send_events_mode mode)
+{
+	struct evdev_device *evdev = (struct evdev_device*)device;
+	struct buttonset_dispatch *buttonset =
+			(struct buttonset_dispatch*)evdev->dispatch;
+
+	if (mode == buttonset->sendevents.current_mode)
+		return LIBINPUT_CONFIG_STATUS_SUCCESS;
+
+	switch(mode) {
+	case LIBINPUT_CONFIG_SEND_EVENTS_ENABLED:
+		break;
+	case LIBINPUT_CONFIG_SEND_EVENTS_DISABLED:
+		buttonset_suspend(evdev->dispatch, evdev);
+		break;
+	default:
+		return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+	}
+
+	buttonset->sendevents.current_mode = mode;
+
+	return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static enum libinput_config_send_events_mode
+bs_sendevents_get_mode(struct libinput_device *device)
+{
+	struct evdev_device *evdev = (struct evdev_device*)device;
+	struct buttonset_dispatch *dispatch =
+			(struct buttonset_dispatch*)evdev->dispatch;
+
+	return dispatch->sendevents.current_mode;
+}
+
+static enum libinput_config_send_events_mode
+bs_sendevents_get_default_mode(struct libinput_device *device)
+{
+	return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
+}
+
+struct evdev_dispatch *
+evdev_buttonset_create(struct evdev_device *device)
+{
+	struct buttonset_dispatch *buttonset;
+
+	buttonset = zalloc(sizeof *buttonset);
+	if (!buttonset)
+		return NULL;
+
+	if (buttonset_init(buttonset, device) != 0) {
+		buttonset_destroy(&buttonset->base);
+		return NULL;
+	}
+
+	device->base.config.sendevents = &buttonset->sendevents.config;
+	buttonset->sendevents.current_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED;
+	buttonset->sendevents.config.get_modes = bs_sendevents_get_modes;
+	buttonset->sendevents.config.set_mode = bs_sendevents_set_mode;
+	buttonset->sendevents.config.get_mode = bs_sendevents_get_mode;
+	buttonset->sendevents.config.get_default_mode = bs_sendevents_get_default_mode;
+
+	return &buttonset->base;
+}
diff --git a/src/evdev-buttonset-wacom.h b/src/evdev-buttonset-wacom.h
new file mode 100644
index 0000000..ad0afe3
--- /dev/null
+++ b/src/evdev-buttonset-wacom.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef EVDEV_BUTTONSET_WACOM_H
+#define EVDEV_BUTTONSET_WACOM_H
+
+#include "evdev.h"
+
+#define LIBINPUT_BUTTONSET_AXIS_NONE 0
+
+enum buttonset_status {
+	BUTTONSET_NONE = 0,
+	BUTTONSET_AXES_UPDATED = 1 << 0,
+	BUTTONSET_BUTTONS_PRESSED = 1 << 1,
+	BUTTONSET_BUTTONS_RELEASED = 1 << 2,
+};
+
+struct button_state {
+	/* Bitmask of pressed buttons. */
+	unsigned long buttons[NLONGS(KEY_CNT)];
+};
+
+struct buttonset_axis {
+	struct libinput_buttonset_axis base;
+	unsigned int evcode;
+};
+
+struct buttonset_dispatch {
+	struct evdev_dispatch base;
+	struct evdev_device *device;
+	unsigned char status;
+	unsigned int naxes;
+	unsigned int evcode_map[ABS_CNT]; /* evcode to axis number */
+	unsigned char changed_axes[NCHARS(LIBINPUT_BUTTONSET_MAX_NUM_AXES)];
+	struct buttonset_axis axes[LIBINPUT_BUTTONSET_MAX_NUM_AXES];
+
+	struct button_state button_state;
+	struct button_state prev_button_state;
+
+	bool have_abs_misc_terminator;
+
+	struct {
+		struct libinput_device_config_send_events config;
+		enum libinput_config_send_events_mode current_mode;
+	} sendevents;
+};
+
+#endif
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index 11cd1c3..c1cfbce 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -1426,6 +1426,9 @@ static struct evdev_dispatch_interface tp_interface = {
 	tp_interface_device_removed, /* device_suspended, treat as remove */
 	tp_interface_device_added,   /* device_resumed, treat as add */
 	NULL,                        /* post_added */
+	NULL,                        /* buttonset_to_phys */
+	NULL,                        /* buttonset_get_num_axes */
+	NULL,                        /* buttonset_get_axis_type */
 };
 
 static void
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 1870e7e..5c98654 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -1429,6 +1429,9 @@ static struct evdev_dispatch_interface tablet_interface = {
 	NULL, /* device_suspended */
 	NULL, /* device_resumed */
 	tablet_check_initial_proximity,
+	NULL, /* buttonset_to_phys */
+	NULL, /* buttonset_get_num_axes */
+	NULL, /* buttonset_get_axis_type */
 };
 
 static void
diff --git a/src/evdev.c b/src/evdev.c
index d28da06..1bf3c24 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -245,6 +245,26 @@ evdev_device_transform_y(struct evdev_device *device,
 	return scale_axis(device->abs.absinfo_y, y, height);
 }
 
+double
+evdev_device_transform_x_mm(struct evdev_device *device,
+			    double mm,
+			    uint32_t width)
+{
+	mm *= device->abs.absinfo_x->resolution;
+
+	return evdev_device_transform_x(device, mm, width);
+}
+
+double
+evdev_device_transform_y_mm(struct evdev_device *device,
+			    double mm,
+			    uint32_t height)
+{
+	mm *= device->abs.absinfo_y->resolution;
+
+	return evdev_device_transform_y(device, mm, height);
+}
+
 static inline void
 normalize_delta(struct evdev_device *device,
 		const struct device_coords *delta,
@@ -963,6 +983,9 @@ struct evdev_dispatch_interface fallback_interface = {
 	NULL, /* device_suspended */
 	NULL, /* device_resumed */
 	NULL, /* post_added */
+	NULL, /* buttonset_to_phys */
+	NULL, /* buttonset_get_num_axes */
+	NULL, /* buttonset_get_axis_type */
 };
 
 static uint32_t
@@ -2028,14 +2051,6 @@ evdev_configure_device(struct evdev_device *device)
 		return -1;
 	}
 
-	/* libwacom assigns tablet _and_ tablet_pad to the pad devices */
-	if (udev_tags & EVDEV_UDEV_TAG_BUTTONSET) {
-		log_info(libinput,
-			 "input device '%s', %s is a buttonset, ignoring\n",
-			 device->devname, devnode);
-		return -1;
-	}
-
 	if (evdev_reject_device(device) == -1) {
 		log_info(libinput,
 			 "input device '%s', %s was rejected.\n",
@@ -2065,13 +2080,23 @@ evdev_configure_device(struct evdev_device *device)
 		}
 	}
 
-	/* libwacom assigns touchpad (or touchscreen) _and_ tablet to the
-	   tablet touch bits, so make sure we don't initialize the tablet
-	   interface for the touch device */
 	tablet_tags = EVDEV_UDEV_TAG_TABLET |
 		      EVDEV_UDEV_TAG_TOUCHPAD |
 		      EVDEV_UDEV_TAG_TOUCHSCREEN;
-	if ((udev_tags & tablet_tags) == EVDEV_UDEV_TAG_TABLET) {
+
+	/* libwacom assigns tablet _and_ tablet_pad to the pad devices */
+	if (udev_tags & EVDEV_UDEV_TAG_BUTTONSET) {
+		device->dispatch = evdev_buttonset_create(device);
+		device->seat_caps |= EVDEV_DEVICE_BUTTONSET;
+		log_info(libinput,
+			 "input device '%s', %s is a buttonset\n",
+			 device->devname, devnode);
+		return device->dispatch == NULL ? -1 : 0;
+
+	/* libwacom assigns touchpad _and_ tablet to the tablet touch bits,
+	   so make sure we don't initialize the tablet interface for the
+	   touch device */
+	} else if ((udev_tags & tablet_tags) == EVDEV_UDEV_TAG_TABLET) {
 		device->dispatch = evdev_tablet_create(device);
 		device->seat_caps |= EVDEV_DEVICE_TABLET;
 		log_info(libinput,
@@ -2465,6 +2490,8 @@ evdev_device_has_capability(struct evdev_device *device,
 		return !!(device->seat_caps & EVDEV_DEVICE_GESTURE);
 	case LIBINPUT_DEVICE_CAP_TABLET_TOOL:
 		return !!(device->seat_caps & EVDEV_DEVICE_TABLET);
+	case LIBINPUT_DEVICE_CAP_BUTTONSET:
+		return !!(device->seat_caps & EVDEV_DEVICE_BUTTONSET);
 	default:
 		return 0;
 	}
@@ -2508,6 +2535,39 @@ evdev_device_keyboard_has_key(struct evdev_device *device, uint32_t code)
 	return libevdev_has_event_code(device->evdev, EV_KEY, code);
 }
 
+int
+evdev_device_buttonset_has_button(struct evdev_device *device,
+				  uint32_t code)
+{
+	if (!(device->seat_caps & EVDEV_DEVICE_BUTTONSET))
+		return -1;
+
+	return libevdev_has_event_code(device->evdev, EV_KEY, code);
+}
+
+unsigned int
+evdev_device_buttonset_get_num_axes(struct evdev_device *device)
+{
+	struct evdev_dispatch *dispatch = device->dispatch;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_BUTTONSET))
+		return 0;
+
+	return dispatch->interface->buttonset_get_num_axes(device);
+}
+
+enum libinput_buttonset_axis_type
+evdev_device_buttonset_get_axis_type(struct evdev_device *device,
+				     unsigned int axis)
+{
+	struct evdev_dispatch *dispatch = device->dispatch;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_BUTTONSET))
+		return 0;
+
+	return dispatch->interface->buttonset_get_axis_type(device, axis);
+}
+
 static inline bool
 evdev_is_scrolling(const struct evdev_device *device,
 		   enum libinput_pointer_axis axis)
diff --git a/src/evdev.h b/src/evdev.h
index d2383c1..efafdbb 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -61,6 +61,7 @@ enum evdev_device_seat_capability {
 	EVDEV_DEVICE_KEYBOARD = (1 << 1),
 	EVDEV_DEVICE_TOUCH = (1 << 2),
 	EVDEV_DEVICE_TABLET = (1 << 3),
+	EVDEV_DEVICE_BUTTONSET = (1 << 4),
 	EVDEV_DEVICE_GESTURE = (1 << 5),
 };
 
@@ -272,6 +273,14 @@ struct evdev_dispatch_interface {
 	 * was sent */
 	void (*post_added)(struct evdev_device *device,
 			   struct evdev_dispatch *dispatch);
+
+	/* Return the number of axes on the buttonset device */
+	unsigned int (*buttonset_get_num_axes)(struct evdev_device *device);
+
+	/* Return the axis type of the given axes on the buttonset device */
+	enum libinput_buttonset_axis_type (*buttonset_get_axis_type)(
+					     struct evdev_device *device,
+					     unsigned int axis);
 };
 
 struct evdev_dispatch {
@@ -309,6 +318,9 @@ evdev_mt_touchpad_create(struct evdev_device *device);
 struct evdev_dispatch *
 evdev_tablet_create(struct evdev_device *device);
 
+struct evdev_dispatch *
+evdev_buttonset_create(struct evdev_device *device);
+
 void
 evdev_tag_touchpad(struct evdev_device *device,
 		   struct udev_device *udev_device);
@@ -359,6 +371,15 @@ evdev_device_pointer_has_button(struct evdev_device *device,
 int
 evdev_device_keyboard_has_key(struct evdev_device *device,
 			      uint32_t code);
+int
+evdev_device_buttonset_has_button(struct evdev_device *device,
+				  uint32_t code);
+
+enum libinput_buttonset_axis_type
+evdev_device_buttonset_get_axis_type(struct evdev_device *device,
+				     unsigned int axis);
+unsigned int
+evdev_device_buttonset_get_num_axes(struct evdev_device *device);
 
 double
 evdev_device_transform_x(struct evdev_device *device,
@@ -369,6 +390,17 @@ double
 evdev_device_transform_y(struct evdev_device *device,
 			 double y,
 			 uint32_t height);
+
+double
+evdev_device_transform_x_mm(struct evdev_device *device,
+			    double mm,
+			    uint32_t width);
+
+double
+evdev_device_transform_y_mm(struct evdev_device *device,
+			    double mm,
+			    uint32_t height);
+
 int
 evdev_device_suspend(struct evdev_device *device);
 
diff --git a/src/libinput-private.h b/src/libinput-private.h
index 845dc00..3035537 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -302,6 +302,20 @@ struct libinput_tablet_tool {
 	bool has_pressure_offset;
 };
 
+struct libinput_buttonset_axis {
+	enum libinput_buttonset_axis_type type;
+	enum libinput_buttonset_axis_source source;
+	union {
+		double mm;
+		double normalized;
+		double delta;
+		double degrees;
+		double position;
+
+		double any;
+	} value;
+};
+
 struct libinput_event {
 	enum libinput_event_type type;
 	struct libinput_device *device;
@@ -533,6 +547,21 @@ tablet_notify_button(struct libinput_device *device,
 		     int32_t button,
 		     enum libinput_button_state state);
 
+void
+buttonset_notify_axis(struct libinput_device *device,
+		      uint32_t time,
+		      unsigned char *changed_axes,
+		      struct libinput_buttonset_axis *axes,
+		      size_t naxes);
+
+void
+buttonset_notify_button(struct libinput_device *device,
+			uint32_t time,
+			struct libinput_buttonset_axis *axes,
+			size_t naxes,
+			int32_t button,
+			enum libinput_button_state state);
+
 static inline uint64_t
 libinput_now(struct libinput *libinput)
 {
diff --git a/src/libinput.c b/src/libinput.c
index 3aba899..b41f95c 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -168,18 +168,6 @@ struct libinput_event_tablet_tool {
 	enum libinput_tablet_tool_tip_state tip_state;
 };
 
-struct libinput_buttonset_axis {
-	enum libinput_buttonset_axis_type type;
-	enum libinput_buttonset_axis_source source;
-	union {
-		double mm;
-		double normalized;
-		double delta;
-		double degrees;
-		double position;
-	} value;
-};
-
 struct libinput_event_buttonset {
 	struct libinput_event base;
 	uint32_t button;
@@ -1602,6 +1590,9 @@ libinput_event_buttonset_get_x_transformed(struct libinput_event_buttonset *even
 					   unsigned int axis,
 					   uint32_t width)
 {
+	struct evdev_device *device =
+		(struct evdev_device *) event->base.device;
+
 	require_event_type(libinput_event_get_context(&event->base),
 			   event->base.type,
 			   0.0,
@@ -1611,7 +1602,9 @@ libinput_event_buttonset_get_x_transformed(struct libinput_event_buttonset *even
 				    LIBINPUT_BUTTONSET_AXIS_X,
 				    0.0);
 
-	return event->axes[axis].value.mm; /* FIXME */
+	return evdev_device_transform_x_mm(device,
+					   event->axes[axis].value.mm,
+					   width);
 }
 
 LIBINPUT_EXPORT double
@@ -1634,6 +1627,9 @@ libinput_event_buttonset_get_y_transformed(struct libinput_event_buttonset *even
 					   unsigned int axis,
 					   uint32_t height)
 {
+	struct evdev_device *device =
+		(struct evdev_device *) event->base.device;
+
 	require_event_type(libinput_event_get_context(&event->base),
 			   event->base.type,
 			   0.0,
@@ -1643,7 +1639,9 @@ libinput_event_buttonset_get_y_transformed(struct libinput_event_buttonset *even
 				    LIBINPUT_BUTTONSET_AXIS_Y,
 				    0.0);
 
-	return event->axes[axis].value.mm; /* FIXME */
+	return evdev_device_transform_y_mm(device,
+					   event->axes[axis].value.mm,
+					   height);
 }
 
 LIBINPUT_EXPORT double
@@ -2691,6 +2689,75 @@ tablet_notify_button(struct libinput_device *device,
 			  &button_event->base);
 }
 
+void
+buttonset_notify_axis(struct libinput_device *device,
+		      uint32_t time,
+		      unsigned char *changed_axes,
+		      struct libinput_buttonset_axis *axes,
+		      size_t naxes)
+{
+	struct libinput_event_buttonset *axis_event;
+
+	if (naxes > ARRAY_LENGTH(axis_event->axes)) {
+		log_bug_libinput(device->seat->libinput,
+				 "%s: Invalid axis number %zd\n",
+				 __func__,
+				 naxes);
+		naxes = ARRAY_LENGTH(axis_event->axes);
+	}
+
+	axis_event = zalloc(sizeof *axis_event);
+	if (!axis_event)
+		return;
+
+	*axis_event = (struct libinput_event_buttonset) {
+		.time = time,
+	};
+
+	memcpy(axis_event->changed_axes,
+	       changed_axes,
+	       sizeof(axis_event->changed_axes));
+	memcpy(axis_event->axes, axes, sizeof(axis_event->axes));
+
+	post_device_event(device,
+			  time,
+			  LIBINPUT_EVENT_BUTTONSET_AXIS,
+			  &axis_event->base);
+}
+
+void
+buttonset_notify_button(struct libinput_device *device,
+			uint32_t time,
+			struct libinput_buttonset_axis *axes,
+			size_t naxes,
+			int32_t button,
+			enum libinput_button_state state)
+{
+	struct libinput_event_buttonset *button_event;
+	int32_t seat_button_count;
+
+	button_event = zalloc(sizeof *button_event);
+	if (!button_event)
+		return;
+
+	seat_button_count = update_seat_button_count(device->seat,
+						     button,
+						     state);
+
+	*button_event = (struct libinput_event_buttonset) {
+		.time = time,
+		.button = button,
+		.state = state,
+		.seat_button_count = seat_button_count,
+	};
+	memcpy(button_event->axes, axes, sizeof(button_event->axes));
+
+	post_device_event(device,
+			  time,
+			  LIBINPUT_EVENT_BUTTONSET_BUTTON,
+			  &button_event->base);
+}
+
 static void
 gesture_notify(struct libinput_device *device,
 	       uint64_t time,
@@ -3032,20 +3099,24 @@ libinput_device_keyboard_has_key(struct libinput_device *device, uint32_t code)
 LIBINPUT_EXPORT int
 libinput_device_buttonset_has_button(struct libinput_device *device, uint32_t code)
 {
-	return 0; /* FIXME */
+	return evdev_device_buttonset_has_button((struct evdev_device *)device,
+						 code);
 }
 
 LIBINPUT_EXPORT enum libinput_buttonset_axis_type
 libinput_device_buttonset_get_axis_type(struct libinput_device *device,
 					unsigned int axis)
 {
-	return 0; /* FIXME */
+	return evdev_device_buttonset_get_axis_type(
+					    (struct evdev_device *)device,
+					    axis);
 }
 
 LIBINPUT_EXPORT unsigned int
 libinput_device_buttonset_get_num_axes(struct libinput_device *device)
 {
-	return 0; /* FIXME */
+	return evdev_device_buttonset_get_num_axes(
+					   (struct evdev_device *)device);
 }
 
 LIBINPUT_EXPORT struct libinput_event *
-- 
2.5.0



More information about the wayland-devel mailing list