Signed-off-by: Peter Hutterer <[email protected]>
---
 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

_______________________________________________
wayland-devel mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/wayland-devel

Reply via email to