[PATCH libinput 6/7] Implement tablet pad group mode support

Peter Hutterer peter.hutterer at who-t.net
Mon Jun 6 06:50:43 UTC 2016


Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 configure.ac                  |   2 +-
 src/Makefile.am               |   1 +
 src/evdev-tablet-pad-leds.c   | 621 ++++++++++++++++++++++++++++++++++++++++++
 src/evdev-tablet-pad.c        |  76 +++++-
 src/evdev-tablet-pad.h        |  12 +
 src/evdev.h                   |  16 ++
 src/libinput-private.h        |  22 +-
 src/libinput-util.h           |   1 +
 src/libinput.c                |  82 +++++-
 tools/libinput-list-devices.c |   6 +-
 10 files changed, 814 insertions(+), 25 deletions(-)
 create mode 100644 src/evdev-tablet-pad-leds.c

diff --git a/configure.ac b/configure.ac
index 8c14efe..8278be2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -203,7 +203,7 @@ AC_ARG_ENABLE(libwacom,
 	      [use_libwacom="$enableval"],
 	      [use_libwacom="yes"])
 if test "x$use_libwacom" = "xyes"; then
-	PKG_CHECK_MODULES(LIBWACOM, [libwacom >= 0.12], [HAVE_LIBWACOM="yes"])
+	PKG_CHECK_MODULES(LIBWACOM, [libwacom >= 0.17], [HAVE_LIBWACOM="yes"])
 	AC_DEFINE(HAVE_LIBWACOM, 1, [Build with libwacom])
 fi
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 39c22c2..48e704a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,7 @@ libinput_la_SOURCES =			\
 	evdev-tablet.h			\
 	evdev-tablet-pad.c		\
 	evdev-tablet-pad.h		\
+	evdev-tablet-pad-leds.c		\
 	filter.c			\
 	filter.h			\
 	filter-private.h		\
diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c
new file mode 100644
index 0000000..4aa1590
--- /dev/null
+++ b/src/evdev-tablet-pad-leds.c
@@ -0,0 +1,621 @@
+/*
+ * Copyright © 2016 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 <assert.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "evdev-tablet-pad.h"
+
+#if HAVE_LIBWACOM
+#include <libwacom/libwacom.h>
+#endif
+
+struct pad_led_group {
+	struct libinput_tablet_pad_mode_group base;
+
+	/* /sys/devices/<hid device>/wacom_led/status_led0_select */
+	int led_status_fd;
+	/* /sys/devices/<hid device>/wacom_led/status0_luminance */
+	int led_luminance_fd;
+
+	struct list toggle_button_list;
+};
+
+struct pad_mode_toggle_button {
+	struct list link;
+	unsigned int button_index;
+	unsigned int target_mode;
+};
+
+static inline void
+pad_led_group_set_brightness(struct pad_led_group *group,
+			     double brightness)
+{
+	char buf[4] = {0};
+	int b;
+
+	assert(brightness > 0.0);
+
+	/* FIXME: check what the range is on all models */
+	b = 127 * brightness;
+	if (sprintf(buf, "%d", b) > -1) {
+		write(group->led_luminance_fd, buf, strlen(buf));
+		fsync(group->led_luminance_fd);
+	}
+}
+
+static inline int
+pad_led_group_get_mode(struct pad_led_group *group)
+{
+	char buf[4] = {0};
+	int rc;
+	unsigned int mode;
+
+	rc = read(group->led_status_fd, buf, sizeof(buf) - 1);
+	if (rc == -1)
+		return -errno;
+
+	rc = sscanf(buf, "%u\n", &mode);
+	if (rc != 1)
+		return -EINVAL;
+
+	return mode;
+}
+
+static inline void
+pad_led_group_set_mode(struct pad_led_group *group,
+		       unsigned int mode)
+{
+	char buf[4] = {0};
+	int rc;
+
+	rc = sprintf(buf, "%d", mode);
+	if (rc == -1)
+		return;
+
+	rc = write(group->led_status_fd, buf, strlen(buf));
+	if (rc == -1)
+		return;
+
+	fsync(group->led_status_fd);
+	group->base.current_mode = mode;
+}
+
+static inline void
+pad_led_group_set_next_mode(struct pad_led_group *group)
+{
+	unsigned int next;
+
+	next = (group->base.current_mode + 1) % group->base.num_modes;
+	pad_led_group_set_mode(group, next);
+}
+
+static void
+pad_led_group_destroy(struct libinput_tablet_pad_mode_group *g)
+{
+	struct pad_led_group *group = (struct pad_led_group *)g;
+	if (group->led_status_fd != -1)
+		close_restricted(g->device->seat->libinput,
+				 group->led_status_fd);
+	if (group->led_luminance_fd != -1)
+		close_restricted(g->device->seat->libinput,
+				 group->led_luminance_fd);
+
+	free(group);
+}
+static struct pad_led_group *
+pad_group_new_basic(struct pad_dispatch *pad,
+		    unsigned int group_index,
+		    int nleds)
+{
+	struct pad_led_group *group;
+
+	group = zalloc(sizeof *group);
+	if (!group)
+		return NULL;
+
+	group->base.device = &pad->device->base;
+	group->base.refcount = 1;
+	group->base.index = group_index;
+	group->base.current_mode = 0;
+	group->base.num_modes = nleds;
+	group->base.destroy = pad_led_group_destroy;
+	group->led_status_fd = -1;
+	group->led_luminance_fd = -1;
+	list_init(&group->toggle_button_list);
+
+	return group;
+}
+
+static inline struct pad_led_group *
+pad_group_new(struct pad_dispatch *pad,
+	      unsigned int group_index,
+	      int nleds,
+	      const char *syspath)
+{
+	struct libinput *libinput = pad->device->base.seat->libinput;
+	struct pad_led_group *group;
+	int rc;
+	char path[PATH_MAX];
+
+	group = pad_group_new_basic(pad, group_index, nleds);
+	if (!group)
+		return NULL;
+
+	rc = sprintf(path, "%s/status_led%d_select", syspath, group_index);
+	if (rc == -1)
+		goto error;
+
+	group->led_status_fd = open_restricted(libinput, path, O_RDWR);
+	if (group->led_status_fd < 0)
+		goto error;
+
+	rc = sprintf(path, "%s/status%d_luminance", syspath, group_index);
+	if (rc == -1)
+		goto error;
+
+	group->led_luminance_fd = open_restricted(libinput, path, O_RDWR);
+	if (group->led_luminance_fd < 0)
+		goto error;
+
+	rc = pad_led_group_get_mode(group);
+	if (rc < 0) {
+		errno = -rc;
+		goto error;
+	}
+
+	group->base.current_mode = rc;
+
+	return group;
+
+error:
+	log_error(libinput, "Unable to init LED group: %s\n", strerror(errno));
+
+	close_restricted(libinput, group->led_status_fd);
+	close_restricted(libinput, group->led_luminance_fd);
+	free(group);
+
+	return NULL;
+}
+
+static inline struct pad_mode_toggle_button *
+pad_mode_toggle_button_new(struct pad_dispatch *pad,
+			   struct libinput_tablet_pad_mode_group *group,
+			   unsigned int button_index)
+{
+	struct pad_mode_toggle_button *button;
+
+	button = zalloc(sizeof *button);
+	if (!button)
+		return NULL;
+
+	button->button_index = button_index;
+	button->target_mode = LIBINPUT_TABLET_PAD_MODE_GROUP_TARGET_NEXT;
+
+	return button;
+}
+
+static inline void
+pad_mode_toggle_button_destroy(struct pad_mode_toggle_button* button)
+{
+	list_remove(&button->link);
+	free(button);
+}
+
+static inline char *
+pad_led_get_sysfs_base_path(struct evdev_device *device)
+{
+	struct udev_device *udev_device = device->udev_device,
+			   *hid_device;
+	const char *hid_sysfs_path;
+	char path[PATH_MAX];
+	char *base_path;
+	int rc;
+
+	hid_device = udev_device_get_parent_with_subsystem_devtype(udev_device,
+								   "hid",
+								   NULL);
+	if (!hid_device)
+		return NULL;
+
+	hid_sysfs_path = udev_device_get_syspath(hid_device);
+
+	rc = xasprintf(&base_path,
+		       "%s/wacom_led",
+		       hid_sysfs_path);
+	if (rc == -1)
+		return NULL;
+
+	/* to check if the leds exist */
+	rc = snprintf(path, sizeof(path), "%s/status_led0_select", base_path);
+	if (rc == -1) {
+		free(base_path);
+		return NULL;
+	}
+
+	rc = access(path, R_OK|W_OK);
+	if (rc == 0)
+		return base_path;
+
+	/* In theory we could return read-only LEDs here but let's make life
+	 * simple and just return NULL and pretend we don't have LEDs. We
+	 * can't change them anyway. Only the EKR has read-only LEDs but
+	 * they're in a different sysfs path.
+	 */
+	if (errno != ENOENT)
+		log_error(device->base.seat->libinput,
+			  "Unable to access tablet LED syspath %s (%s)\n",
+			  path,
+			  strerror(errno));
+	free(base_path);
+	return NULL;
+}
+
+#if HAVE_LIBWACOM
+static int
+pad_init_led_groups(struct pad_dispatch *pad,
+		    struct evdev_device *device,
+		    WacomDevice *wacom,
+		    const char *syspath)
+{
+	struct libinput *libinput = device->base.seat->libinput;
+	const WacomStatusLEDs *leds;
+	int nleds, nmodes;
+	int i;
+	struct pad_led_group *group;
+
+	leds = libwacom_get_status_leds(wacom, &nleds);
+	if (nleds == 0)
+		return 0;
+
+	for (i = 0; i < nleds; i++) {
+		switch(leds[i]) {
+		case WACOM_STATUS_LED_UNAVAILABLE:
+			log_bug_libinput(libinput,
+					 "Invalid led type %d\n",
+					 leds[i]);
+			return 1;
+		case WACOM_STATUS_LED_RING:
+			/* ring is always group 0 */
+			nmodes = libwacom_get_ring_num_modes(wacom);
+			group = pad_group_new(pad, 0, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		case WACOM_STATUS_LED_RING2:
+			/* ring2 is always group 1 */
+			nmodes = libwacom_get_ring2_num_modes(wacom);
+			group = pad_group_new(pad, 1, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		case WACOM_STATUS_LED_TOUCHSTRIP:
+			nmodes = 1; /* something we know... */
+			group = pad_group_new(pad, 0, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		case WACOM_STATUS_LED_TOUCHSTRIP2:
+			nmodes = 1; /* something we know... */
+			group = pad_group_new(pad, 1, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+#endif
+
+static inline struct libinput_tablet_pad_mode_group *
+pad_get_mode_group(struct pad_dispatch *pad, unsigned int index)
+{
+	struct libinput_tablet_pad_mode_group *group;
+
+	list_for_each(group, &pad->modes.mode_group_list, link) {
+		if (group->index == index)
+			return group;
+	}
+
+	return NULL;
+}
+
+#if HAVE_LIBWACOM
+
+static void
+pad_init_mode_buttons(struct pad_dispatch *pad,
+		      WacomDevice *wacom)
+{
+	struct libinput_tablet_pad_mode_group *group;
+	unsigned int group_idx;
+	int i;
+	WacomButtonFlags flags;
+
+	/* libwacom numbers buttons as 'A', 'B', etc. We number them with 0,
+	 * 1, ...
+	 */
+	for (i = 0; i < libwacom_get_num_buttons(wacom); i++) {
+		group_idx = libwacom_get_button_led_group(wacom, 'A' + i);
+		flags = libwacom_get_button_flag(wacom, 'A' + i);
+
+		if ((int)group_idx == -1) {
+			/* This isn't a mode toggle button, fall back to
+			 * Left/Right positioning instead */
+			if (flags & WACOM_BUTTON_POSITION_LEFT)
+				group_idx = 0;
+			else if (flags & WACOM_BUTTON_POSITION_RIGHT)
+				group_idx = 1;
+			else
+				continue;
+		}
+
+		group = pad_get_mode_group(pad, group_idx);
+		group->button_mask |= 1 << i;
+
+		if (flags & WACOM_BUTTON_MODESWITCH) {
+			struct pad_mode_toggle_button *b;
+			struct pad_led_group *g;
+
+			b = pad_mode_toggle_button_new(pad, group, i);
+			g = (struct pad_led_group*)group;
+			list_insert(&g->toggle_button_list, &b->link);
+			group->toggle_button_mask |= 1 << i;
+		}
+	}
+}
+
+static void
+pad_init_mode_rings(struct pad_dispatch *pad, WacomDevice *wacom)
+{
+	struct libinput_tablet_pad_mode_group *group;
+
+	if (libwacom_has_ring(wacom)) {
+		/* ring is in group 0 */
+		group = pad_get_mode_group(pad, 0);
+		group->ring_mask |= 0x1;
+	}
+
+	if (libwacom_has_ring2(wacom)) {
+		/* ring2 is in group 1 */
+		group = pad_get_mode_group(pad, 1);
+		group->ring_mask |= 0x2;
+	}
+}
+
+static void
+pad_init_mode_strips(struct pad_dispatch *pad, WacomDevice *wacom)
+{
+	struct libinput_tablet_pad_mode_group *group;
+	int nleds;
+	int i;
+
+	libwacom_get_status_leds(wacom, &nleds);
+	if (nleds == 0)
+		return;
+
+	/* All devices with LEDs and strips have the strips in two different
+	 * groups (21UX2) */
+	for (i = 0; i < libwacom_get_num_strips(wacom); i++) {
+		group = pad_get_mode_group(pad, i);
+		group->strip_mask |= 1 << i;
+	}
+}
+
+static int
+pad_init_leds_from_libwacom(struct pad_dispatch *pad,
+			    struct evdev_device *device)
+{
+	struct libinput *libinput = device->base.seat->libinput;
+	WacomDeviceDatabase *db = NULL;
+	WacomDevice *wacom = NULL;
+	char *syspath = NULL;
+	int rc = 1;
+
+	db = libwacom_database_new();
+	if (!db) {
+		log_info(libinput,
+			 "Failed to initialize libwacom context.\n");
+		goto out;
+	}
+
+	wacom = libwacom_new_from_path(db,
+				       udev_device_get_devnode(device->udev_device),
+				       WFALLBACK_NONE,
+				       NULL);
+	if (!wacom)
+		goto out;
+
+	syspath = pad_led_get_sysfs_base_path(device);
+	if (!syspath)
+		goto out;
+
+	if (pad_init_led_groups(pad, device, wacom, syspath) != 0)
+		goto out;
+
+	pad_init_mode_buttons(pad, wacom);
+	pad_init_mode_rings(pad, wacom);
+	pad_init_mode_strips(pad, wacom);
+
+	rc = 0;
+out:
+	if (syspath)
+		free(syspath);
+	if (wacom)
+		libwacom_destroy(wacom);
+	if (db)
+		libwacom_database_destroy(db);
+
+	if (rc != 0)
+		pad_destroy_leds(pad);
+
+	return rc;
+}
+#endif /* HAVE_LIBWACOM */
+
+static int
+pad_init_fallback_group(struct pad_dispatch *pad)
+{
+	struct pad_led_group *group;
+
+	group = pad_group_new_basic(pad, 0, 1);
+	if (!group)
+		return 1;
+
+	/* If we only have one group, all buttons/strips/rings are part of
+	 * that group. We ely on the other layers to filter out invalid
+	 * indices */
+	group->base.button_mask = -1;
+	group->base.strip_mask = -1;
+	group->base.ring_mask = -1;
+	group->base.toggle_button_mask = 0;
+
+	list_insert(&pad->modes.mode_group_list, &group->base.link);
+
+	return 0;
+}
+
+int
+pad_init_leds(struct pad_dispatch *pad,
+	      struct evdev_device *device)
+{
+	int rc = 1;
+
+	list_init(&pad->modes.mode_group_list);
+
+	if (pad->nbuttons > 32) {
+		log_bug_libinput(device->base.seat->libinput,
+				 "Too many pad buttons for modes %d\n",
+				 pad->nbuttons);
+		return rc;
+	}
+
+	/* If libwacom fails, we init one fallback group anyway */
+#if HAVE_LIBWACOM
+	rc = pad_init_leds_from_libwacom(pad, device);
+#endif
+	if (rc != 0)
+		rc = pad_init_fallback_group(pad);
+
+	return rc;
+}
+
+void
+pad_destroy_leds(struct pad_dispatch *pad)
+{
+	struct libinput_tablet_pad_mode_group *group, *tmpgrp;
+
+	list_for_each_safe(group, tmpgrp, &pad->modes.mode_group_list, link)
+		libinput_tablet_pad_mode_group_unref(group);
+}
+
+void
+pad_button_update_mode(struct libinput_tablet_pad_mode_group *g,
+		       unsigned int button_index,
+		       enum libinput_button_state state)
+{
+	struct pad_led_group *group = (struct pad_led_group*)g;
+	struct pad_mode_toggle_button *b;
+	unsigned int mode;
+
+	if (state != LIBINPUT_BUTTON_STATE_PRESSED)
+		return;
+
+	if (!libinput_tablet_pad_mode_group_button_is_toggle(g, button_index))
+		return;
+
+	list_for_each(b, &group->toggle_button_list, link) {
+		if (b->button_index != button_index)
+			continue;
+
+		mode = b->target_mode;
+
+		switch (mode) {
+		case LIBINPUT_TABLET_PAD_MODE_GROUP_TARGET_NEXT:
+			pad_led_group_set_next_mode(group);
+			break;
+		case LIBINPUT_TABLET_PAD_MODE_GROUP_TARGET_PREVIOUS:
+			assert(!"Ooops, not implemented");
+			break;
+		default:
+			if (mode <=
+			    LIBINPUT_TABLET_PAD_MODE_GROUP_TARGET_MAX_DIRECT)
+				pad_led_group_set_mode(group, mode);
+		}
+		break;
+	}
+}
+
+int
+evdev_device_tablet_pad_get_num_mode_groups(struct evdev_device *device)
+{
+	struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
+	struct libinput_tablet_pad_mode_group *group;
+	int num_groups = 0;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+		return -1;
+
+	list_for_each(group, &pad->modes.mode_group_list, link)
+		num_groups++;
+
+	return num_groups;
+}
+
+struct libinput_tablet_pad_mode_group *
+evdev_device_tablet_pad_get_mode_group(struct evdev_device *device,
+				       unsigned int index)
+{
+	struct pad_dispatch *pad = (struct pad_dispatch*)device->dispatch;
+
+	if (!(device->seat_caps & EVDEV_DEVICE_TABLET_PAD))
+		return NULL;
+
+	return pad_get_mode_group(pad, index);
+}
+
+unsigned int
+evdev_device_tablet_pad_mode_group_get_button_target(struct libinput_tablet_pad_mode_group *g,
+						     unsigned int button_index)
+{
+	struct pad_led_group *group = (struct pad_led_group*)g;
+	struct pad_mode_toggle_button *button;
+
+	list_for_each(button, &group->toggle_button_list, link) {
+		if (button->button_index != button_index)
+			continue;
+
+		return button->target_mode;
+	}
+
+	return 0;
+}
diff --git a/src/evdev-tablet-pad.c b/src/evdev-tablet-pad.c
index 5341657..7e4cd89 100644
--- a/src/evdev-tablet-pad.c
+++ b/src/evdev-tablet-pad.c
@@ -209,12 +209,45 @@ pad_handle_strip(struct pad_dispatch *pad,
 	return pos;
 }
 
+static inline struct libinput_tablet_pad_mode_group *
+pad_ring_get_mode_group(struct pad_dispatch *pad,
+			unsigned int ring)
+{
+	struct libinput_tablet_pad_mode_group *group;
+
+	list_for_each(group, &pad->modes.mode_group_list, link) {
+		if (libinput_tablet_pad_mode_group_has_ring(group, ring))
+			return group;
+	}
+
+	assert(!"Unable to find ring mode group");
+
+	return NULL;
+}
+
+static inline struct libinput_tablet_pad_mode_group *
+pad_strip_get_mode_group(struct pad_dispatch *pad,
+			unsigned int strip)
+{
+	struct libinput_tablet_pad_mode_group *group;
+
+	list_for_each(group, &pad->modes.mode_group_list, link) {
+		if (libinput_tablet_pad_mode_group_has_strip(group, strip))
+			return group;
+	}
+
+	assert(!"Unable to find strip mode group");
+
+	return NULL;
+}
+
 static void
 pad_check_notify_axes(struct pad_dispatch *pad,
 		      struct evdev_device *device,
 		      uint64_t time)
 {
 	struct libinput_device *base = &device->base;
+	struct libinput_tablet_pad_mode_group *group;
 	double value;
 	bool send_finger_up = false;
 
@@ -229,11 +262,13 @@ pad_check_notify_axes(struct pad_dispatch *pad,
 		if (send_finger_up)
 			value = -1.0;
 
+		group = pad_ring_get_mode_group(pad, 0);
 		tablet_pad_notify_ring(base,
 				       time,
 				       0,
 				       value,
-				       LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER);
+				       LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
+				       group);
 	}
 
 	if (pad->changed_axes & PAD_AXIS_RING2) {
@@ -241,11 +276,13 @@ pad_check_notify_axes(struct pad_dispatch *pad,
 		if (send_finger_up)
 			value = -1.0;
 
+		group = pad_ring_get_mode_group(pad, 1);
 		tablet_pad_notify_ring(base,
 				       time,
 				       1,
 				       value,
-				       LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER);
+				       LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER,
+				       group);
 	}
 
 	if (pad->changed_axes & PAD_AXIS_STRIP1) {
@@ -253,11 +290,13 @@ pad_check_notify_axes(struct pad_dispatch *pad,
 		if (send_finger_up)
 			value = -1.0;
 
+		group = pad_strip_get_mode_group(pad, 0);
 		tablet_pad_notify_strip(base,
 					time,
 					0,
 					value,
-					LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER);
+					LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
+					group);
 	}
 
 	if (pad->changed_axes & PAD_AXIS_STRIP2) {
@@ -265,11 +304,13 @@ pad_check_notify_axes(struct pad_dispatch *pad,
 		if (send_finger_up)
 			value = -1.0;
 
+		group = pad_strip_get_mode_group(pad, 1);
 		tablet_pad_notify_strip(base,
 					time,
 					1,
 					value,
-					LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER);
+					LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER,
+					group);
 	}
 
 	pad->changed_axes = PAD_AXIS_NONE;
@@ -288,6 +329,22 @@ pad_process_key(struct pad_dispatch *pad,
 	pad_button_set_down(pad, button, is_press);
 }
 
+static inline struct libinput_tablet_pad_mode_group *
+pad_button_get_mode_group(struct pad_dispatch *pad,
+			  unsigned int button)
+{
+	struct libinput_tablet_pad_mode_group *group;
+
+	list_for_each(group, &pad->modes.mode_group_list, link) {
+		if (libinput_tablet_pad_mode_group_has_button(group, button))
+			return group;
+	}
+
+	assert(!"Unable to find button mode group\n");
+
+	return NULL;
+}
+
 static void
 pad_notify_button_mask(struct pad_dispatch *pad,
 		       struct evdev_device *device,
@@ -296,6 +353,7 @@ pad_notify_button_mask(struct pad_dispatch *pad,
 		       enum libinput_button_state state)
 {
 	struct libinput_device *base = &device->base;
+	struct libinput_tablet_pad_mode_group *group;
 	int32_t code;
 	unsigned int i;
 
@@ -315,8 +373,11 @@ pad_notify_button_mask(struct pad_dispatch *pad,
 				continue;
 
 			map = pad->button_map[code - 1];
-			if (map != -1)
-				tablet_pad_notify_button(base, time, map, state);
+			if (map != -1) {
+				group = pad_button_get_mode_group(pad, map);
+				pad_button_update_mode(group, map, state);
+				tablet_pad_notify_button(base, time, map, state, group);
+			}
 		}
 	}
 }
@@ -437,6 +498,7 @@ pad_destroy(struct evdev_dispatch *dispatch)
 {
 	struct pad_dispatch *pad = (struct pad_dispatch*)dispatch;
 
+	pad_destroy_leds(pad);
 	free(pad);
 }
 
@@ -500,6 +562,8 @@ pad_init(struct pad_dispatch *pad, struct evdev_device *device)
 
 	pad_init_buttons(pad, device);
 	pad_init_left_handed(device);
+	if (pad_init_leds(pad, device) != 0)
+		return 1;
 
 	return 0;
 }
diff --git a/src/evdev-tablet-pad.h b/src/evdev-tablet-pad.h
index 828ded8..2c33eb1 100644
--- a/src/evdev-tablet-pad.h
+++ b/src/evdev-tablet-pad.h
@@ -64,6 +64,18 @@ struct pad_dispatch {
 		struct libinput_device_config_send_events config;
 		enum libinput_config_send_events_mode current_mode;
 	} sendevents;
+
+	struct {
+		struct list mode_group_list;
+	} modes;
 };
 
+int
+pad_init_leds(struct pad_dispatch *pad, struct evdev_device *device);
+void
+pad_destroy_leds(struct pad_dispatch *pad);
+void
+pad_button_update_mode(struct libinput_tablet_pad_mode_group *g,
+		       unsigned int pressed_button,
+		       enum libinput_button_state state);
 #endif
diff --git a/src/evdev.h b/src/evdev.h
index 54d7ce7..087ceba 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -392,6 +392,22 @@ evdev_device_tablet_pad_get_num_rings(struct evdev_device *device);
 int
 evdev_device_tablet_pad_get_num_strips(struct evdev_device *device);
 
+int
+evdev_device_tablet_pad_get_num_mode_groups(struct evdev_device *device);
+
+struct libinput_tablet_pad_mode_group *
+evdev_device_tablet_pad_get_mode_group(struct evdev_device *device,
+				       unsigned int index);
+
+unsigned int
+evdev_device_tablet_pad_mode_group_get_button_target(
+				     struct libinput_tablet_pad_mode_group *g,
+				     unsigned int button_index);
+
+struct libinput_tablet_pad_led *
+evdev_device_tablet_pad_get_led(struct evdev_device *device,
+				unsigned int led);
+
 double
 evdev_device_transform_x(struct evdev_device *device,
 			 double x,
diff --git a/src/libinput-private.h b/src/libinput-private.h
index 98cb419..b325707 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -330,9 +330,22 @@ struct libinput_tablet_tool {
 };
 
 struct libinput_tablet_pad_mode_group {
+	struct libinput_device *device;
 	struct list link;
 	int refcount;
 	void *user_data;
+
+	unsigned int index;
+	unsigned int num_modes;
+	unsigned int current_mode;
+
+	uint32_t button_mask;
+	uint32_t ring_mask;
+	uint32_t strip_mask;
+
+	uint32_t toggle_button_mask;
+
+	void (*destroy)(struct libinput_tablet_pad_mode_group *group);
 };
 
 struct libinput_event {
@@ -572,19 +585,22 @@ void
 tablet_pad_notify_button(struct libinput_device *device,
 			 uint64_t time,
 			 int32_t button,
-			 enum libinput_button_state state);
+			 enum libinput_button_state state,
+			 struct libinput_tablet_pad_mode_group *group);
 void
 tablet_pad_notify_ring(struct libinput_device *device,
 		       uint64_t time,
 		       unsigned int number,
 		       double value,
-		       enum libinput_tablet_pad_ring_axis_source source);
+		       enum libinput_tablet_pad_ring_axis_source source,
+		       struct libinput_tablet_pad_mode_group *group);
 void
 tablet_pad_notify_strip(struct libinput_device *device,
 			uint64_t time,
 			unsigned int number,
 			double value,
-			enum libinput_tablet_pad_strip_axis_source source);
+			enum libinput_tablet_pad_strip_axis_source source,
+			struct libinput_tablet_pad_mode_group *group);
 
 static inline uint64_t
 libinput_now(struct libinput *libinput)
diff --git a/src/libinput-util.h b/src/libinput-util.h
index 701fe07..3ccd61b 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -86,6 +86,7 @@ int list_empty(const struct list *list);
 	     pos = tmp,							\
 	     tmp = container_of(pos->member.next, tmp, member))
 
+#define NBITS(b) (b * 8)
 #define LONG_BITS (sizeof(long) * 8)
 #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
 #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
diff --git a/src/libinput.c b/src/libinput.c
index e174aae..ee18b9c 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -1668,6 +1668,12 @@ libinput_event_tablet_tool_destroy(struct libinput_event_tablet_tool *event)
 	libinput_tablet_tool_unref(event->tool);
 }
 
+static void
+libinput_event_tablet_pad_destroy(struct libinput_event_tablet_pad *event)
+{
+	libinput_tablet_pad_mode_group_unref(event->mode_group);
+}
+
 LIBINPUT_EXPORT void
 libinput_event_destroy(struct libinput_event *event)
 {
@@ -1682,6 +1688,13 @@ libinput_event_destroy(struct libinput_event *event)
 		libinput_event_tablet_tool_destroy(
 		   libinput_event_get_tablet_tool_event(event));
 		break;
+	case LIBINPUT_EVENT_TABLET_PAD_RING:
+	case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+	case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+	case LIBINPUT_EVENT_TABLET_PAD_MODE:
+		libinput_event_tablet_pad_destroy(
+		   libinput_event_get_tablet_pad_event(event));
+		break;
 	default:
 		break;
 	}
@@ -2402,18 +2415,24 @@ void
 tablet_pad_notify_button(struct libinput_device *device,
 			 uint64_t time,
 			 int32_t button,
-			 enum libinput_button_state state)
+			 enum libinput_button_state state,
+			 struct libinput_tablet_pad_mode_group *group)
 {
 	struct libinput_event_tablet_pad *button_event;
+	unsigned int mode;
 
 	button_event = zalloc(sizeof *button_event);
 	if (!button_event)
 		return;
 
+	mode = libinput_tablet_pad_mode_group_get_mode(group);
+
 	*button_event = (struct libinput_event_tablet_pad) {
 		.time = time,
 		.button.button = button,
 		.button.state = state,
+		.mode_group = libinput_tablet_pad_mode_group_ref(group),
+		.mode = mode,
 	};
 
 	post_device_event(device,
@@ -2427,19 +2446,25 @@ tablet_pad_notify_ring(struct libinput_device *device,
 		       uint64_t time,
 		       unsigned int number,
 		       double value,
-		       enum libinput_tablet_pad_ring_axis_source source)
+		       enum libinput_tablet_pad_ring_axis_source source,
+		       struct libinput_tablet_pad_mode_group *group)
 {
 	struct libinput_event_tablet_pad *ring_event;
+	unsigned int mode;
 
 	ring_event = zalloc(sizeof *ring_event);
 	if (!ring_event)
 		return;
 
+	mode = libinput_tablet_pad_mode_group_get_mode(group);
+
 	*ring_event = (struct libinput_event_tablet_pad) {
 		.time = time,
 		.ring.number = number,
 		.ring.position = value,
 		.ring.source = source,
+		.mode_group = libinput_tablet_pad_mode_group_ref(group),
+		.mode = mode,
 	};
 
 	post_device_event(device,
@@ -2453,19 +2478,25 @@ tablet_pad_notify_strip(struct libinput_device *device,
 			uint64_t time,
 			unsigned int number,
 			double value,
-			enum libinput_tablet_pad_strip_axis_source source)
+			enum libinput_tablet_pad_strip_axis_source source,
+			struct libinput_tablet_pad_mode_group *group)
 {
 	struct libinput_event_tablet_pad *strip_event;
+	unsigned int mode;
 
 	strip_event = zalloc(sizeof *strip_event);
 	if (!strip_event)
 		return;
 
+	mode = libinput_tablet_pad_mode_group_get_mode(group);
+
 	*strip_event = (struct libinput_event_tablet_pad) {
 		.time = time,
 		.strip.number = number,
 		.strip.position = value,
 		.strip.source = source,
+		.mode_group = libinput_tablet_pad_mode_group_ref(group),
+		.mode = mode,
 	};
 
 	post_device_event(device,
@@ -2835,14 +2866,15 @@ libinput_device_tablet_pad_get_num_strips(struct libinput_device *device)
 LIBINPUT_EXPORT int
 libinput_device_tablet_pad_get_num_mode_groups(struct libinput_device *device)
 {
-	return 0;
+	return evdev_device_tablet_pad_get_num_mode_groups((struct evdev_device *)device);
 }
 
 LIBINPUT_EXPORT struct libinput_tablet_pad_mode_group*
 libinput_device_tablet_pad_get_mode_group(struct libinput_device *device,
 					  unsigned int index)
 {
-	return NULL;
+	return evdev_device_tablet_pad_get_mode_group((struct evdev_device *)device,
+						      index);
 }
 
 LIBINPUT_EXPORT unsigned int
@@ -2855,42 +2887,68 @@ libinput_tablet_pad_mode_group_get_num_modes(
 LIBINPUT_EXPORT unsigned int
 libinput_tablet_pad_mode_group_get_mode(struct libinput_tablet_pad_mode_group *group)
 {
-	return 1;
+	return group->current_mode;
+}
+
+LIBINPUT_EXPORT unsigned int
+libinput_tablet_pad_mode_group_get_index(struct libinput_tablet_pad_mode_group *group)
+{
+	return group->index;
 }
 
 LIBINPUT_EXPORT int
 libinput_tablet_pad_mode_group_has_button(struct libinput_tablet_pad_mode_group *group,
 					  unsigned int button)
 {
-	return 1;
+	if ((int)button >=
+	    libinput_device_tablet_pad_get_num_buttons(group->device))
+		return 0;
+
+	return !!(group->button_mask & (1 << button));
 }
 
 LIBINPUT_EXPORT int
 libinput_tablet_pad_mode_group_has_ring(struct libinput_tablet_pad_mode_group *group,
 					unsigned int ring)
 {
-	return 1;
+	if ((int)ring >=
+	    libinput_device_tablet_pad_get_num_rings(group->device))
+		return 0;
+
+	return !!(group->ring_mask & (1 << ring));
 }
 
 LIBINPUT_EXPORT int
 libinput_tablet_pad_mode_group_has_strip(struct libinput_tablet_pad_mode_group *group,
 					 unsigned int strip)
 {
-	return 1;
+	if ((int)strip >=
+	    libinput_device_tablet_pad_get_num_strips(group->device))
+		return 0;
+
+	return !!(group->strip_mask & (1 << strip));
 }
 
 LIBINPUT_EXPORT int
 libinput_tablet_pad_mode_group_button_is_toggle(struct libinput_tablet_pad_mode_group *group,
 						unsigned int button)
 {
-	return 0;
+	if ((int)button >=
+	    libinput_device_tablet_pad_get_num_buttons(group->device))
+		return 0;
+
+	return !!(group->toggle_button_mask & (1 << button));
 }
 
 LIBINPUT_EXPORT unsigned int
 libinput_tablet_pad_mode_group_button_get_target_mode(struct libinput_tablet_pad_mode_group *group,
 						      unsigned int button)
 {
-	return 0;
+	if (!libinput_tablet_pad_mode_group_button_is_toggle(group, button))
+		return 0;
+
+	return evdev_device_tablet_pad_mode_group_get_button_target(group,
+								    button);
 }
 
 LIBINPUT_EXPORT struct libinput_tablet_pad_mode_group *
@@ -2912,7 +2970,7 @@ libinput_tablet_pad_mode_group_unref(
 		return group;
 
 	list_remove(&group->link);
-	free(group);
+	group->destroy(group);
 	return NULL;
 }
 
diff --git a/tools/libinput-list-devices.c b/tools/libinput-list-devices.c
index 2869dd7..17dabc1 100644
--- a/tools/libinput-list-devices.c
+++ b/tools/libinput-list-devices.c
@@ -251,9 +251,9 @@ print_pad_info(struct libinput_device *device)
 	nstrips = libinput_device_tablet_pad_get_num_strips(device);
 
 	printf("Pad:\n");
-	printf("	Rings:   %d\n", nrings);
-	printf("	Strips:  %d\n", nstrips);
-	printf("	Buttons: %d\n", nbuttons);
+	printf("	Rings:   %d", nrings);
+	printf("	Strips:  %d", nstrips);
+	printf("	Buttons: %d", nbuttons);
 }
 
 static void
-- 
2.7.4



More information about the wayland-devel mailing list