[PATCH libinput] pad: implement kernel-based pad led handling

Peter Hutterer peter.hutterer at who-t.net
Fri Jan 20 05:10:14 UTC 2017


For each device open the various led devices (brightness only) and map the one
nonzero brightness to the current mode.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
Ok, now that 4.9 is out and commonplace enough, let's get this merged.
This implements the background bits for the tabled pad mode support. Since
the kernel does almost everything, 

 configure.ac                |   2 +-
 src/evdev-tablet-pad-leds.c | 477 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 470 insertions(+), 9 deletions(-)

diff --git a/configure.ac b/configure.ac
index 25e6562..8337467 100644
--- a/configure.ac
+++ b/configure.ac
@@ -232,7 +232,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.20], [HAVE_LIBWACOM="yes"])
 	AC_DEFINE(HAVE_LIBWACOM, 1, [Build with libwacom])
 
 	OLD_LIBS=$LIBS
diff --git a/src/evdev-tablet-pad-leds.c b/src/evdev-tablet-pad-leds.c
index 8b162a6..8ac00f5 100644
--- a/src/evdev-tablet-pad-leds.c
+++ b/src/evdev-tablet-pad-leds.c
@@ -29,14 +29,143 @@
 
 #include "evdev-tablet-pad.h"
 
+#if HAVE_LIBWACOM
+#include <libwacom/libwacom.h>
+#endif
+
 struct pad_led_group {
 	struct libinput_tablet_pad_mode_group base;
+	struct list led_list;
+	struct list toggle_button_list;
 };
 
+struct pad_mode_toggle_button {
+	struct list link;
+	unsigned int button_index;
+};
+
+struct pad_mode_led {
+	struct list link;
+	/* /sys/devices/..../input1235/input1235::wacom-led_0.1/brightness */
+	int brightness_fd;
+	int mode_idx;
+};
+
+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;
+
+	return button;
+}
+
+static inline void
+pad_mode_toggle_button_destroy(struct pad_mode_toggle_button* button)
+{
+	list_remove(&button->link);
+	free(button);
+}
+
+static inline int
+pad_led_group_get_mode(struct pad_led_group *group)
+{
+	char buf[4] = {0};
+	int rc;
+	unsigned int brightness;
+	struct pad_mode_led *led;
+
+	list_for_each(led, &group->led_list, link) {
+		rc = lseek(led->brightness_fd, 0, SEEK_SET);
+		if (rc == -1)
+			return -errno;
+
+		rc = read(led->brightness_fd, buf, sizeof(buf) - 1);
+		if (rc == -1)
+			return -errno;
+
+		rc = sscanf(buf, "%u\n", &brightness);
+		if (rc != 1)
+			return -EINVAL;
+
+		/* Assumption: only one LED lit up at any time */
+		if (brightness != 0)
+			return led->mode_idx;
+	}
+
+	return -EINVAL;
+}
+
+static inline void
+pad_led_destroy(struct libinput *libinput,
+		struct pad_mode_led *led)
+{
+	list_remove(&led->link);
+	if (led->brightness_fd != -1)
+		close_restricted(libinput, led->brightness_fd);
+	free(led);
+}
+
+static inline struct pad_mode_led *
+pad_led_new(struct libinput *libinput, const char *prefix, int group, int mode)
+{
+	struct pad_mode_led *led;
+	char path[PATH_MAX];
+	int rc, fd;
+
+	led = zalloc(sizeof *led);
+	if (!led)
+		return NULL;
+
+	led->brightness_fd = -1;
+	led->mode_idx = mode;
+	list_init(&led->link);
+
+	/* /sys/devices/..../input1235/input1235::wacom-0.1/brightness,
+	 * where 0 and 1 are group and mode index. */
+	rc = snprintf(path,
+		      sizeof(path),
+		      "%s%d.%d/brightness",
+		      prefix,
+		      group,
+		      mode);
+	if (rc == -1)
+		goto error;
+
+	fd = open_restricted(libinput, path, O_RDONLY);
+	if (fd < 0) {
+		errno = -fd;
+		goto error;
+	}
+
+	led->brightness_fd = fd;
+
+	return led;
+
+error:
+	pad_led_destroy(libinput, led);
+	return NULL;
+}
+
 static void
 pad_led_group_destroy(struct libinput_tablet_pad_mode_group *g)
 {
 	struct pad_led_group *group = (struct pad_led_group *)g;
+	struct pad_mode_toggle_button *button, *tmp;
+	struct pad_mode_led *led, *tmpled;
+
+	list_for_each_safe(button, tmp, &group->toggle_button_list, link)
+		pad_mode_toggle_button_destroy(button);
+
+	list_for_each_safe(led, tmpled, &group->led_list, link)
+		pad_led_destroy(g->device->seat->libinput, led);
 
 	free(group);
 }
@@ -58,10 +187,154 @@ pad_group_new_basic(struct pad_dispatch *pad,
 	group->base.current_mode = 0;
 	group->base.num_modes = nleds;
 	group->base.destroy = pad_led_group_destroy;
+	list_init(&group->toggle_button_list);
+	list_init(&group->led_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;
+
+	group = pad_group_new_basic(pad, group_index, nleds);
+	if (!group)
+		return NULL;
+
+	while (nleds--) {
+		struct pad_mode_led *led;
+
+		led = pad_led_new(libinput, syspath, group_index, nleds);
+		if (!led)
+			goto error;
+
+		list_insert(&group->led_list, &led->link);
+	}
+
+	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));
+	pad_led_group_destroy(&group->base);
+
+	return NULL;
+}
+
+static inline bool
+pad_led_get_sysfs_base_path(struct evdev_device *device,
+			    char *path_out,
+			    size_t path_out_sz)
+{
+	struct udev_device *parent, *udev_device;
+	const char *test_path;
+	int rc;
+
+	udev_device = device->udev_device;
+
+	/* For testing purposes only allow for a base path set through a
+	 * udev rule. We still expect the normal directory hierarchy inside */
+	test_path = udev_device_get_property_value(udev_device,
+						   "LIBINPUT_TEST_TABLET_PAD_SYSFS_PATH");
+	if (test_path) {
+		rc = snprintf(path_out, path_out_sz, "%s", test_path);
+		return rc != -1;
+	}
+
+	parent = udev_device_get_parent_with_subsystem_devtype(udev_device,
+							       "input",
+							       NULL);
+	if (!parent)
+		return false;
+
+	rc = snprintf(path_out,
+		      path_out_sz,
+		      "%s/%s::wacom-",
+		      udev_device_get_syspath(parent),
+		      udev_device_get_sysname(parent));
+
+	return rc != -1;
+}
+
+#if HAVE_LIBWACOM
+static int
+pad_init_led_groups(struct pad_dispatch *pad,
+		    struct evdev_device *device,
+		    WacomDevice *wacom)
+{
+	struct libinput *libinput = device->base.seat->libinput;
+	const WacomStatusLEDs *leds;
+	int nleds, nmodes;
+	int i;
+	struct pad_led_group *group;
+	char syspath[PATH_MAX];
+
+	leds = libwacom_get_status_leds(wacom, &nleds);
+	if (nleds == 0)
+		return 1;
+
+	/* syspath is /sys/class/leds/input1234/input12345::wacom-" and
+	   only needs the group + mode appended */
+	if (!pad_led_get_sysfs_base_path(device, syspath, sizeof(syspath)))
+		return 1;
+
+	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:
+			nmodes = libwacom_get_ring_num_modes(wacom);
+			group = pad_group_new(pad, i, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		case WACOM_STATUS_LED_RING2:
+			nmodes = libwacom_get_ring2_num_modes(wacom);
+			group = pad_group_new(pad, i, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		case WACOM_STATUS_LED_TOUCHSTRIP:
+			nmodes = libwacom_get_strips_num_modes(wacom);
+			group = pad_group_new(pad, i, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		case WACOM_STATUS_LED_TOUCHSTRIP2:
+			/* there is no get_strips2_... */
+			nmodes = libwacom_get_strips_num_modes(wacom);
+			group = pad_group_new(pad, i, nmodes, syspath);
+			if (!group)
+				return 1;
+			list_insert(&pad->modes.mode_group_list, &group->base.link);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+#endif
+
 static inline struct libinput_tablet_pad_mode_group *
 pad_get_mode_group(struct pad_dispatch *pad, unsigned int index)
 {
@@ -75,6 +348,192 @@ pad_get_mode_group(struct pad_dispatch *pad, unsigned int index)
 	return NULL;
 }
 
+#if HAVE_LIBWACOM
+
+static inline int
+pad_find_button_group(WacomDevice *wacom,
+		      int button_index,
+		      WacomButtonFlags button_flags)
+{
+	int i;
+	WacomButtonFlags flags;
+
+	for (i = 0; i < libwacom_get_num_buttons(wacom); i++) {
+		if (i == button_index)
+			continue;
+
+		flags = libwacom_get_button_flag(wacom, 'A' + i);
+		if ((flags & WACOM_BUTTON_MODESWITCH) == 0)
+			continue;
+
+		if ((flags & WACOM_BUTTON_DIRECTION) ==
+			(button_flags & WACOM_BUTTON_DIRECTION))
+			return libwacom_get_button_led_group(wacom, 'A' + i);
+	}
+
+	return -1;
+}
+
+static int
+pad_init_mode_buttons(struct pad_dispatch *pad,
+		      WacomDevice *wacom)
+{
+	struct libinput *libinput = pad_libinput_context(pad);
+	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 this button is not a mode toggle button, find the mode
+		 * toggle button with the same position flags and take that
+		 * button's group idx */
+		if ((int)group_idx == -1) {
+			group_idx = pad_find_button_group(wacom, i, flags);
+		}
+
+		if ((int)group_idx == -1) {
+			log_bug_libinput(libinput,
+					 "%s: unhandled position for button %i\n",
+					 pad->device->devname,
+					 i);
+			return 1;
+		}
+
+		group = pad_get_mode_group(pad, group_idx);
+		if (!group) {
+			log_bug_libinput(libinput,
+					 "%s: Failed to find group %d for button %i\n",
+					 pad->device->devname,
+					 group_idx,
+					 i);
+			return 1;
+		}
+
+		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);
+			if (!b)
+				return 1;
+			g = (struct pad_led_group*)group;
+			list_insert(&g->toggle_button_list, &b->link);
+			group->toggle_button_mask |= 1 << i;
+		}
+	}
+
+	return 0;
+}
+
+static void
+pad_init_mode_rings(struct pad_dispatch *pad, WacomDevice *wacom)
+{
+	struct libinput_tablet_pad_mode_group *group;
+	const WacomStatusLEDs *leds;
+	int i, nleds;
+
+	leds = libwacom_get_status_leds(wacom, &nleds);
+	if (nleds == 0)
+		return;
+
+	for (i = 0; i < nleds; i++) {
+		switch(leds[i]) {
+		case WACOM_STATUS_LED_RING:
+			group = pad_get_mode_group(pad, i);
+			group->ring_mask |= 0x1;
+			break;
+		case WACOM_STATUS_LED_RING2:
+			group = pad_get_mode_group(pad, i);
+			group->ring_mask |= 0x2;
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static void
+pad_init_mode_strips(struct pad_dispatch *pad, WacomDevice *wacom)
+{
+	struct libinput_tablet_pad_mode_group *group;
+	const WacomStatusLEDs *leds;
+	int i, nleds;
+
+	leds = libwacom_get_status_leds(wacom, &nleds);
+	if (nleds == 0)
+		return;
+
+	for (i = 0; i < nleds; i++) {
+		switch(leds[i]) {
+		case WACOM_STATUS_LED_TOUCHSTRIP:
+			group = pad_get_mode_group(pad, i);
+			group->strip_mask |= 0x1;
+			break;
+		case WACOM_STATUS_LED_TOUCHSTRIP2:
+			group = pad_get_mode_group(pad, i);
+			group->strip_mask |= 0x2;
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+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;
+	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;
+
+	rc = pad_init_led_groups(pad, device, wacom);
+	if (rc != 0)
+		goto out;
+
+	if ((rc = pad_init_mode_buttons(pad, wacom)) != 0)
+		goto out;
+
+	pad_init_mode_rings(pad, wacom);
+	pad_init_mode_strips(pad, wacom);
+
+out:
+	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)
 {
@@ -112,11 +571,12 @@ pad_init_leds(struct pad_dispatch *pad,
 		return rc;
 	}
 
-	/* Eventually we slot the libwacom-based led detection in here. That
-	 * requires getting the kernel ready first. For now we just init the
-	 * fallback single-mode group.
-	 */
-	rc = pad_init_fallback_group(pad);
+	/* 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;
 }
@@ -136,6 +596,7 @@ pad_button_update_mode(struct libinput_tablet_pad_mode_group *g,
 		       enum libinput_button_state state)
 {
 	struct pad_led_group *group = (struct pad_led_group*)g;
+	int rc;
 
 	if (state != LIBINPUT_BUTTON_STATE_PRESSED)
 		return;
@@ -143,9 +604,9 @@ pad_button_update_mode(struct libinput_tablet_pad_mode_group *g,
 	if (!libinput_tablet_pad_mode_group_button_is_toggle(g, button_index))
 		return;
 
-	log_bug_libinput(group->base.device->seat->libinput,
-			 "Button %d should not be a toggle button",
-			 button_index);
+	rc = pad_led_group_get_mode(group);
+	if (rc >= 0)
+		group->base.current_mode = rc;
 }
 
 int
-- 
2.9.3



More information about the wayland-devel mailing list