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

Jason Gerecke killertofu at gmail.com
Thu Mar 19 14:01:29 PDT 2015


David's already asked the API questions that popped in my head, so I'll 
just wait for feedback on his thread. As far as this patch goes, things 
look pretty good though there are two comments:

On 3/17/2015 11:58 PM, Peter Hutterer wrote:
> From: Benjamin Tissoires <benjamin.tissoires at gmail.com>
>
> 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: Benjamin Tissoires <benjamin.tissoires at redhat.com>
> Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
> ---
>   src/Makefile.am             |   2 +
>   src/evdev-buttonset-wacom.c | 533 ++++++++++++++++++++++++++++++++++++++++++++
>   src/evdev-buttonset-wacom.h |  63 ++++++
>   src/evdev.c                 |  18 +-
>   src/evdev.h                 |   3 +
>   src/libinput-private.h      |  16 ++
>   src/libinput.c              |  65 ++++++
>   7 files changed, 691 insertions(+), 9 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 d5cd4f4..3e36f63 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -10,6 +10,8 @@ libinput_la_SOURCES =			\
>   	libinput-private.h		\
>   	evdev.c				\
>   	evdev.h				\
> +	evdev-buttonset-wacom.c		\
> +	evdev-buttonset-wacom.h		\
>   	evdev-tablet.c			\
>   	evdev-tablet.h			\
>   	evdev-mt-touchpad.c		\
> diff --git a/src/evdev-buttonset-wacom.c b/src/evdev-buttonset-wacom.c
> new file mode 100644
> index 0000000..402c6f5
> --- /dev/null
> +++ b/src/evdev-buttonset-wacom.c
> @@ -0,0 +1,533 @@
> +/*
> + * 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 unsigned long *
> +buttonset_get_pressed_buttons(struct buttonset_dispatch *buttonset)
> +{
> +	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++)
> +		state->buttons_pressed[i] = state->buttons[i]
> +						& ~(prev_state->buttons[i]);
> +
> +	return state->buttons_pressed;
> +}
> +
> +static unsigned long *
> +buttonset_get_released_buttons(struct buttonset_dispatch *buttonset)
> +{
> +	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++)
> +		state->buttons_pressed[i] = prev_state->buttons[i]
> +						& ~(state->buttons[i]);
> +
> +	return state->buttons_pressed;
> +}
> +
> +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 void
> +buttonset_mark_all_axes_changed(struct buttonset_dispatch *buttonset,
> +				struct evdev_device *device)
> +{
> +	unsigned int a;
> +
> +	for (a = 0; a < buttonset->naxes; a++)
> +		set_bit(buttonset->changed_axes, a);
> +
> +	buttonset_set_status(buttonset, BUTTONSET_AXES_UPDATED);
> +}
> +
> +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
> +unnormalize_ring_value(const struct input_absinfo *absinfo,
> +		       double value)
> +{
> +	double range = absinfo->maximum - absinfo->minimum + 1;
> +
> +	return value * range;
> +}
> +
> +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 = 1,
> +	       max = log2(absinfo->maximum);
> +	double range = max - min;
> +	double value = (log2(absinfo->value) - min) / range;
> +
> +	return value;
> +}
> +
> +/* Detect ring wraparound, current and old are normalized to [0, 1[ */
> +static inline double
> +guess_ring_delta(double current, double old)
> +{
> +	double d1, d2, d3;
> +
> +	d1 = current - old;
> +	d2 = (current + 1) - old;
> +	d3 = current - (old + 1);
> +
> +	if (fabs(d2) < fabs(d1))
> +		d1 = d2;
> +
> +	if (fabs(d3) < fabs(d1))
> +		d1 = d3;
> +
> +	return d1;
> +}
> +
> +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;
> +	double deltas[LIBINPUT_BUTTONSET_MAX_NUM_AXES] = {0};
> +	double deltas_discrete[LIBINPUT_BUTTONSET_MAX_NUM_AXES] = {0};
> +	unsigned int a;
> +	unsigned int code;
> +
> +	for (a = 0; a <= buttonset->naxes; a++) {
> +		const struct input_absinfo *absinfo;
> +
> +		if (!bit_is_set(buttonset->changed_axes, a))
> +			continue;
> +
> +		code = buttonset->axis_map[a];
> +		assert(code != 0);
> +		absinfo = libevdev_get_abs_info(device->evdev, code);
> +		assert(absinfo);
> +
> +		switch (buttonset->types[a]) {
> +		case LIBINPUT_BUTTONSET_AXIS_RING:
> +			buttonset->axes[a] = normalize_ring(absinfo);
> +			deltas[a] = guess_ring_delta(buttonset->axes[a],
> +						     buttonset->axes_prev[a]);
> +			deltas_discrete[a] = unnormalize_ring_value(absinfo,
> +								    deltas[a]);
> +			break;
> +		case LIBINPUT_BUTTONSET_AXIS_STRIP:
> +			buttonset->axes[a] = normalize_strip(absinfo);
> +			deltas[a] = buttonset->axes[a] - buttonset->axes_prev[a];
> +			break;
> +		default:
> +			log_bug_libinput(device->base.seat->libinput,
> +					 "Invalid axis update: %u\n", a);
> +			break;
> +		}
> +
> +		if (buttonset->have_abs_misc_terminator) {
> +			/* Suppress the reset to 0 on finger up. See the
> +			   comment in buttonset_process_absolute */
> +			if (libevdev_get_event_value(device->evdev,
> +						     EV_ABS,
> +						     ABS_MISC) == 0) {
> +				clear_bit(buttonset->changed_axes, a);
> +				buttonset->axes[a] = buttonset->axes_prev[a];
> +				continue;
> +			/* on finger down, reset the delta to 0 */
> +			} else {
> +				deltas[a] = 0;
> +				deltas_discrete[a] = 0;
> +			}
> +		}
> +
> +		axis_update_needed = true;
> +	}
> +
> +	if (axis_update_needed)
> +		buttonset_notify_axis(base,
> +				      time,
> +				      LIBINPUT_BUTTONSET_AXIS_SOURCE_UNKNOWN,
> +				      buttonset->changed_axes,
> +				      buttonset->axes,
> +				      deltas,
> +				      deltas_discrete);
> +
> +	memset(buttonset->changed_axes, 0, sizeof(buttonset->changed_axes));
> +	memcpy(buttonset->axes_prev,
> +	       buttonset->axes,
> +	       sizeof(buttonset->axes_prev));
> +	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 enable = e->value;
> +
> +	if (enable) {
> +		long_set_bit(buttonset->button_state.buttons, button);
> +		buttonset_set_status(buttonset, BUTTONSET_BUTTONS_PRESSED);
> +	} else {
> +		long_clear_bit(buttonset->button_state.buttons, button);
> +		buttonset_set_status(buttonset, BUTTONSET_BUTTONS_RELEASED);
> +	}
> +}
> +
> +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;
> +
> +	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,
> +						buttonset->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;
> +
> +	if (state == LIBINPUT_BUTTON_STATE_PRESSED)
> +		buttons = buttonset_get_pressed_buttons(buttonset);
> +	else
> +		buttons = buttonset_get_released_buttons(buttonset);
> +
> +	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_destroy(struct evdev_dispatch *dispatch)
> +{
> +	struct buttonset_dispatch *buttonset =
> +		(struct buttonset_dispatch*)dispatch;
> +
> +	free(buttonset);
> +}
> +
> +static double
> +buttonset_to_phys(struct evdev_device *device, unsigned int axis, double value)
> +{
> +	struct buttonset_dispatch *buttonset =
> +				(struct buttonset_dispatch*)device->dispatch;
> +
> +	switch(buttonset->types[axis]) {
> +	case LIBINPUT_BUTTONSET_AXIS_NONE:
> +	case LIBINPUT_BUTTONSET_AXIS_X:
> +	case LIBINPUT_BUTTONSET_AXIS_Y:
> +	case LIBINPUT_BUTTONSET_AXIS_Z:
> +	case LIBINPUT_BUTTONSET_AXIS_REL_X:
> +	case LIBINPUT_BUTTONSET_AXIS_REL_Y:
> +	case LIBINPUT_BUTTONSET_AXIS_REL_Z:
> +	case LIBINPUT_BUTTONSET_AXIS_ROTATION_X:
> +	case LIBINPUT_BUTTONSET_AXIS_ROTATION_Y:
> +	case LIBINPUT_BUTTONSET_AXIS_ROTATION_Z:
> +		abort();

Yikes! I understand the point (no reasonable value to return, no way a 
caller would be given one of these axes in the first place, etc.) but 
it'd be good to document that doing this is bad and that the caller 
should feel bad.

> +	case LIBINPUT_BUTTONSET_AXIS_RING:
> +		value *= 360;
> +		break;
> +	case LIBINPUT_BUTTONSET_AXIS_STRIP:
> +		value *= 52; /* FIXME: correct for Intuos3 and 21UX */
> +		break;
> +	}
> +
> +	return value;
> +}
> +
> +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->types[axis];
> +	else
> +		return LIBINPUT_BUTTONSET_AXIS_NONE;
> +}
> +
> +static struct evdev_dispatch_interface buttonset_interface = {
> +	buttonset_process,
> +	NULL, /* remove */
> +	buttonset_destroy,
> +	NULL, /* device_added */
> +	NULL, /* device_removed */
> +	NULL, /* device_suspended */
> +	NULL, /* device_resumed */
> +	NULL, /* tag_device */
> +	NULL, /* post_added */
> +	buttonset_to_phys,
> +	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, they're dead on wacom pads */
> +	for (code = ABS_Z; code <= ABS_MAX; code++) {

Note: the 27QHD pad uses ABS_{X,Y,Z} to report accelerometer data. 
Probably should skip Z too unless you consider it appropriate data for 
this interface.

-- 
Jason
---
Now instead of four in the eights place /
you’ve got three, ‘Cause you added one /
(That is to say, eight) to the two, /
But you can’t take seven from three, /
So you look at the sixty-fours....

> +		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->axis_map[naxes] = code;
> +		buttonset->evcode_map[code] = naxes;
> +		buttonset->types[naxes] = type;
> +		naxes++;
> +	}
> +
> +	buttonset->naxes = naxes;
> +	buttonset_mark_all_axes_changed(buttonset, device);
> +
> +	return 0;
> +}
> +
> +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;
> +	}
> +
> +	return &buttonset->base;
> +}
> diff --git a/src/evdev-buttonset-wacom.h b/src/evdev-buttonset-wacom.h
> new file mode 100644
> index 0000000..b3fa440
> --- /dev/null
> +++ b/src/evdev-buttonset-wacom.h
> @@ -0,0 +1,63 @@
> +/*
> + * 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_H
> +#define EVDEV_BUTTONSET_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)];
> +	unsigned long buttons_pressed[NLONGS(KEY_CNT)];
> +	unsigned long buttons_released[NLONGS(KEY_CNT)];
> +};
> +
> +struct buttonset_dispatch {
> +	struct evdev_dispatch base;
> +	struct evdev_device *device;
> +	unsigned char status;
> +	unsigned int naxes;
> +	unsigned int axis_map[LIBINPUT_BUTTONSET_MAX_NUM_AXES]; /* number to evcode */
> +	unsigned int evcode_map[ABS_CNT]; /* evcode to number */
> +	unsigned char changed_axes[NCHARS(LIBINPUT_BUTTONSET_MAX_NUM_AXES)];
> +	enum libinput_buttonset_axis_type types[LIBINPUT_BUTTONSET_MAX_NUM_AXES];
> +
> +	double axes[LIBINPUT_BUTTONSET_MAX_NUM_AXES];
> +	double axes_prev[LIBINPUT_BUTTONSET_MAX_NUM_AXES];
> +
> +	struct button_state button_state;
> +	struct button_state prev_button_state;
> +
> +	bool have_abs_misc_terminator;
> +};
> +
> +#endif
> diff --git a/src/evdev.c b/src/evdev.c
> index 4beacaa..9ab027d 100644
> --- a/src/evdev.c
> +++ b/src/evdev.c
> @@ -1583,14 +1583,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",
> @@ -1622,10 +1614,18 @@ evdev_configure_device(struct evdev_device *device)
>   		}
>   	}
>
> +	/* 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 */
> -	if ((udev_tags & (EVDEV_UDEV_TAG_TABLET|EVDEV_UDEV_TAG_TOUCHPAD)) ==
> +	} else if ((udev_tags & (EVDEV_UDEV_TAG_TABLET|EVDEV_UDEV_TAG_TOUCHPAD)) ==
>   	     EVDEV_UDEV_TAG_TABLET) {
>   		device->dispatch = evdev_tablet_create(device);
>   		device->seat_caps |= EVDEV_DEVICE_TABLET;
> diff --git a/src/evdev.h b/src/evdev.h
> index a8d2d38..0221ea8 100644
> --- a/src/evdev.h
> +++ b/src/evdev.h
> @@ -254,6 +254,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_device_led_update(struct evdev_device *device, enum libinput_led leds);
>
> diff --git a/src/libinput-private.h b/src/libinput-private.h
> index 5ce8fdd..feeb7c1 100644
> --- a/src/libinput-private.h
> +++ b/src/libinput-private.h
> @@ -382,6 +382,22 @@ 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,
> +		      enum libinput_buttonset_axis_source source,
> +		      unsigned char *changed_axes,
> +		      double *axes,
> +		      double *deltas,
> +		      double *deltas_discrete);
> +
> +void
> +buttonset_notify_button(struct libinput_device *device,
> +			uint32_t time,
> +			double *axes,
> +			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 d52ca0d..30c0599 100644
> --- a/src/libinput.c
> +++ b/src/libinput.c
> @@ -1799,6 +1799,71 @@ tablet_notify_button(struct libinput_device *device,
>   			  &button_event->base);
>   }
>
> +void
> +buttonset_notify_axis(struct libinput_device *device,
> +		   uint32_t time,
> +		   enum libinput_buttonset_axis_source source,
> +		   unsigned char *changed_axes,
> +		   double *axes,
> +		   double *deltas,
> +		   double *deltas_discrete)
> +{
> +	struct libinput_event_buttonset *axis_event;
> +
> +	axis_event = zalloc(sizeof *axis_event);
> +	if (!axis_event)
> +		return;
> +
> +	*axis_event = (struct libinput_event_buttonset) {
> +		.time = time,
> +		.source = source,
> +	};
> +
> +	memcpy(axis_event->changed_axes,
> +	       changed_axes,
> +	       sizeof(axis_event->changed_axes));
> +	memcpy(axis_event->axes, axes, sizeof(axis_event->axes));
> +	memcpy(axis_event->deltas, deltas, sizeof(axis_event->deltas));
> +	memcpy(axis_event->deltas_discrete, deltas_discrete, sizeof(axis_event->deltas_discrete));
> +
> +	post_device_event(device,
> +			  time,
> +			  LIBINPUT_EVENT_BUTTONSET_AXIS,
> +			  &axis_event->base);
> +}
> +
> +void
> +buttonset_notify_button(struct libinput_device *device,
> +			uint32_t time,
> +			double *axes,
> +			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
>   libinput_post_event(struct libinput *libinput,
>   		    struct libinput_event *event)
>



More information about the wayland-devel mailing list