From 5eb71b65e477b9c429d04d67edd7dcce79db30cc Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 11 Jul 2016 15:39:57 +1000 Subject: [PATCH] pad: implement kernel-based pad led handling 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 --- 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 25e6562a..83374678 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 8b162a61..8ac00f51 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 +#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