[PATCH xf86-input-libinput 1/2] Add drag lock support

Peter Hutterer peter.hutterer at who-t.net
Tue Aug 11 18:28:13 PDT 2015


Mostly the same functionality that evdev provides with two options on how it
works:
* a single button number configures the given button to lock the next button
  pressed in a logically down state until a press+ release of that same button
  again
* a set of button number pairs configures each button with the to-be-locked
  logical button, i.e. a pair of "1 3" will hold 3 logically down after a
  button 1 press

The property and the xorg.conf options take the same configuration as the
evdev driver (though the property has a different prefix, libinput instead of
Evdev).

The behavior difference to evdev is in how releases are handled, evdev sends
the release on the second button press event, this implementation sends the
release on the second release event.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 Makefile.am                   |   2 +-
 configure.ac                  |   1 +
 include/libinput-properties.h |   6 +
 man/libinput.man              |  46 +++-
 src/Makefile.am               |   4 +-
 src/draglock.c                | 282 ++++++++++++++++++++++
 src/draglock.h                | 159 +++++++++++++
 src/xf86libinput.c            | 146 +++++++++++-
 test/.gitignore               |   1 +
 test/Makefile.am              |  13 +
 test/test-draglock.c          | 540 ++++++++++++++++++++++++++++++++++++++++++
 11 files changed, 1195 insertions(+), 5 deletions(-)
 create mode 100644 src/draglock.c
 create mode 100644 src/draglock.h
 create mode 100644 test/.gitignore
 create mode 100644 test/Makefile.am
 create mode 100644 test/test-draglock.c

diff --git a/Makefile.am b/Makefile.am
index 99e6808..ef17c35 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -21,7 +21,7 @@
 
 DISTCHECK_CONFIGURE_FLAGS = --with-sdkdir='$${includedir}/xorg'
 
-SUBDIRS = src include man
+SUBDIRS = src include man test
 MAINTAINERCLEANFILES = ChangeLog INSTALL
 
 pkgconfigdir = $(libdir)/pkgconfig
diff --git a/configure.ac b/configure.ac
index c149a1b..26e0e70 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,5 +71,6 @@ AC_CONFIG_FILES([Makefile
 		 include/Makefile
 		 src/Makefile
 		 man/Makefile
+		 test/Makefile
 		 xorg-libinput.pc])
 AC_OUTPUT
diff --git a/include/libinput-properties.h b/include/libinput-properties.h
index f54cee7..ed009d5 100644
--- a/include/libinput-properties.h
+++ b/include/libinput-properties.h
@@ -114,4 +114,10 @@
 /* Disable while typing: BOOL, 1 value, read-only */
 #define LIBINPUT_PROP_DISABLE_WHILE_TYPING_DEFAULT "libinput Disable While Typing Enabled Default"
 
+/* Drag lock buttons, either:
+   CARD8, one value, the meta lock button, or
+   CARD8, n * 2 values, the drag lock pairs with n being the button and n+1
+   the target button number */
+#define LIBINPUT_PROP_DRAG_LOCK_BUTTONS "libinput Drag Lock Buttons"
+
 #endif /* _LIBINPUT_PROPERTIES_H_ */
diff --git a/man/libinput.man b/man/libinput.man
index ac546e6..ff7a411 100644
--- a/man/libinput.man
+++ b/man/libinput.man
@@ -123,6 +123,27 @@ continues.
 .BI "Option \*qDisableWhileTyping\*q \*q" bool \*q
 Indicates if the touchpad should be disabled while typing on the keyboard
 (this does not apply to modifier keys such as Ctrl or Alt).
+.TP 7
+.BI "Option \*qDragLockButtons\*q \*q" "L1 B1 L2 B2 ..." \*q
+Sets "drag lock buttons" that simulate a button logically down even when it has
+been physically released. To logically release a locked button, a second click
+of the same button is required.
+.IP
+If the option is a single button number, that button acts as the
+"meta" locking button for the next button number. See section
+.B BUTTON DRAG LOCK
+for details.
+.IP
+If the option is a list of button number pairs, the first number of each
+number pair is the lock button, the second number the logical button number
+to be locked. See section
+.B BUTTON DRAG LOCK
+for details.
+.IP
+For both meta and button pair configuration, the button numbers are
+device button numbers, i.e. the
+.B ButtonMapping
+applies after drag lock.
 .PP
 For all options, the options are only parsed if the device supports that
 configuration option. For all options, the default value is the one used by
@@ -195,11 +216,16 @@ disabled.
 .BI "libinput Disable While Typing Enabled"
 1 boolean value (8 bit, 0 or 1). Indicates if disable while typing is
 enabled or disabled.
-.TP7
 .PP
 The above properties have a
 .BI "libinput <property name> Default"
 equivalent that indicates the default value for this setting on this device.
+.TP 7
+.BI "libinput Drag Lock Buttons"
+Either one 8-bit value specifying the meta drag lock button, or a list of
+button pairs. See section
+.B BUTTON DRAG LOCK
+for details.
 
 .SH BUTTON MAPPING
 X clients receive events with logical button numbers, where 1, 2, 3
@@ -226,6 +252,24 @@ __xservername__ input driver does not use the button mapping after setup.
 Use XSetPointerMapping(__libmansuffix__) to modify the button mapping at
 runtime.
 
+.SH BUTTON DRAG LOCK
+Button drag lock holds a button logically down even when the button itself
+has been physically released since. Button drag lock comes in two modes.
+.PP
+If in "meta" mode, a meta button click activates drag lock for the next
+button press of any other button. A button click in the future will keep
+that button held logically down until a subsequent click of that same
+button. The meta button events themselves are discarded. A separate meta
+button click is required each time a drag lock should be activated for a
+button in the future.
+.PP
+If in "pairs" mode, each button can be assigned a target locking button.
+On button click, the target lock button is held logically down until the
+next click of the same button. The button events themselves are discarded
+and only the target button events are sent.
+.TP
+This feature is provided by this driver, not by libinput.
+
 .SH AUTHORS
 Peter Hutterer
 .SH "SEE ALSO"
diff --git a/src/Makefile.am b/src/Makefile.am
index 6085a9a..60703e6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -30,8 +30,10 @@ AM_CPPFLAGS =-I$(top_srcdir)/include $(LIBINPUT_CFLAGS)
 
 @DRIVER_NAME at _drv_la_LTLIBRARIES = @DRIVER_NAME at _drv.la
 @DRIVER_NAME at _drv_la_LDFLAGS = -module -avoid-version
- at DRIVER_NAME@_drv_la_LIBADD = $(LIBINPUT_LIBS)
+ at DRIVER_NAME@_drv_la_LIBADD = $(LIBINPUT_LIBS) libdraglock.la
 @DRIVER_NAME at _drv_ladir = @inputdir@
 
 @DRIVER_NAME at _drv_la_SOURCES = xf86libinput.c
 
+noinst_LTLIBRARIES = libdraglock.la
+libdraglock_la_SOURCES = draglock.c draglock.h
diff --git a/src/draglock.c b/src/draglock.c
new file mode 100644
index 0000000..b0bcac3
--- /dev/null
+++ b/src/draglock.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "draglock.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+
+static int
+draglock_parse_config(struct draglock *dl, const char *config)
+{
+    int button = 0, target = 0;
+    const char *str = NULL;
+    char *end_str = NULL;
+    int pairs[DRAGLOCK_MAX_BUTTONS] = {0};
+
+    if (!config)
+	    return 0;
+
+    /* empty string disables drag lock */
+    if (*config == '\0') {
+	    dl->mode = DRAGLOCK_DISABLED;
+	    return 0;
+    }
+
+    /* check for a single-number string first, config is "<int>" */
+    button = strtol(config, &end_str, 10);
+    if (*end_str == '\0') {
+	    if (button < 0 || button >= DRAGLOCK_MAX_BUTTONS)
+		    return 1;
+	    /* we allow for button 0 so stacked xorg.conf.d snippets can
+	     * disable the config again */
+	    if (button == 0) {
+		    dl->mode = DRAGLOCK_DISABLED;
+		    return 0;
+	    }
+
+	    return draglock_set_meta(dl, button);
+    }
+
+    dl->mode = DRAGLOCK_DISABLED;
+
+    /* check for a set of button pairs, config is
+     * "<int> <int> <int> <int>..." */
+    str = config;
+    while (*str != '\0') {
+	    button = strtol(str, &end_str, 10);
+	    if (*end_str == '\0')
+		    return 1;
+
+	    str = end_str;
+	    target = strtol(str, &end_str, 10);
+	    if (end_str == str)
+		    return 1;
+	    if (button <= 0 || button >= DRAGLOCK_MAX_BUTTONS || target >= DRAGLOCK_MAX_BUTTONS)
+		    return 1;
+
+	    pairs[button] = target;
+	    str = end_str;
+    }
+
+    return draglock_set_pairs(dl, pairs, ARRAY_SIZE(pairs));
+}
+
+int
+draglock_init_from_string(struct draglock *dl, const char *config)
+{
+	dl->mode = DRAGLOCK_DISABLED;
+
+	dl->meta_button = 0;
+	dl->meta_state = false;
+	memset(dl->lock_pair, 0, sizeof(dl->lock_pair));
+	memset(dl->lock_state, 0, sizeof(dl->lock_state));
+
+	return draglock_parse_config(dl, config);
+}
+
+enum draglock_mode
+draglock_get_mode(const struct draglock *dl)
+{
+	return dl->mode;
+}
+
+int
+draglock_get_meta(const struct draglock *dl)
+{
+	if (dl->mode == DRAGLOCK_META)
+		return dl->meta_button;
+	return 0;
+}
+
+size_t
+draglock_get_pairs(const struct draglock *dl, int *array, size_t sz)
+{
+	unsigned int i;
+	size_t last = 0;
+
+	if (dl->mode != DRAGLOCK_PAIRS)
+		return 0;
+
+	/* size 1 array with the meta button */
+	if (dl->meta_button) {
+		*array = dl->meta_button;
+		return 1;
+	}
+
+	/* size N array with a[0] == 0, the rest ordered by button number */
+	memset(array, 0, sz * sizeof(array[0]));
+	for (i = 0; i < sz && i < ARRAY_SIZE(dl->lock_pair); i++) {
+		array[i] = dl->lock_pair[i];
+		if (array[i] != 0 && i > last)
+			last = i;
+	}
+	return last;
+}
+
+int
+draglock_set_meta(struct draglock *dl, int meta_button)
+{
+	if (meta_button < 0 || meta_button >= DRAGLOCK_MAX_BUTTONS)
+		return 1;
+
+	dl->meta_button = meta_button;
+	dl->mode = meta_button ? DRAGLOCK_META : DRAGLOCK_DISABLED;
+
+	return 0;
+}
+
+int
+draglock_set_pairs(struct draglock *dl, const int *array, size_t sz)
+{
+	unsigned int i;
+
+	if (sz == 0 || array[0] != 0)
+		return 1;
+
+	for (i = 0; i < sz; i++) {
+		if (array[i] < 0 || array[i] >= DRAGLOCK_MAX_BUTTONS)
+			return 1;
+	}
+
+	dl->mode = DRAGLOCK_DISABLED;
+	for (i = 0; i < sz; i++) {
+		dl->lock_pair[i] = array[i];
+		if (dl->lock_pair[i])
+			dl->mode = DRAGLOCK_PAIRS;
+	}
+
+	return 0;
+}
+
+static int
+draglock_filter_meta(struct draglock *dl, int *button, int *press)
+{
+	int b = *button,
+	    is_press = *press;
+
+	if (b == dl->meta_button) {
+		if (is_press)
+			dl->meta_state = true;
+		*button = 0;
+		return 0;
+	}
+
+	switch (dl->lock_state[b]) {
+	case DRAGLOCK_BUTTON_STATE_NONE:
+		if (dl->meta_state && is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_DOWN_1;
+			dl->meta_state = false;
+		}
+		break;
+	case DRAGLOCK_BUTTON_STATE_DOWN_1:
+		if (!is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_UP_1;
+			b = 0;
+		}
+		break;
+	case DRAGLOCK_BUTTON_STATE_UP_1:
+		if (is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_DOWN_2;
+			b = 0;
+		}
+		break;
+	case DRAGLOCK_BUTTON_STATE_DOWN_2:
+		if (!is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_NONE;
+		}
+		break;
+	}
+
+	*button = b;
+
+	return 0;
+}
+
+static int
+draglock_filter_pair(struct draglock *dl, int *button, int *press)
+{
+	int b = *button,
+	    is_press = *press;
+
+	if (dl->lock_pair[b] == 0)
+		return 0;
+
+	switch (dl->lock_state[b]) {
+	case DRAGLOCK_BUTTON_STATE_NONE:
+		if (is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_DOWN_1;
+			b = dl->lock_pair[b];
+		}
+		break;
+	case DRAGLOCK_BUTTON_STATE_DOWN_1:
+		if (!is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_UP_1;
+			b = 0;
+		}
+		break;
+	case DRAGLOCK_BUTTON_STATE_UP_1:
+		if (is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_DOWN_2;
+			b = 0;
+		}
+		break;
+	case DRAGLOCK_BUTTON_STATE_DOWN_2:
+		if (!is_press) {
+			dl->lock_state[b] = DRAGLOCK_BUTTON_STATE_NONE;
+			b = dl->lock_pair[b];
+		}
+		break;
+	}
+
+	*button = b;
+
+	return 0;
+}
+
+int
+draglock_filter_button(struct draglock *dl, int *button, int *is_press)
+{
+	if (*button == 0)
+		return 0;
+
+	switch(dl->mode) {
+	case DRAGLOCK_DISABLED:
+		return 0;
+	case DRAGLOCK_META:
+		return draglock_filter_meta(dl, button, is_press);
+	case DRAGLOCK_PAIRS:
+		return draglock_filter_pair(dl, button, is_press);
+	default:
+		abort();
+		break;
+	}
+
+	return 0;
+}
diff --git a/src/draglock.h b/src/draglock.h
new file mode 100644
index 0000000..acc1314
--- /dev/null
+++ b/src/draglock.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2015 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef DRAGLOCK_H
+#define DRAGLOCK_H 1
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+/* 32 buttons are enough for everybody™
+ * Note that this is the limit of physical buttons as well as the highest
+ * allowed target button.
+ */
+#define DRAGLOCK_MAX_BUTTONS 32
+
+enum draglock_mode
+{
+	DRAGLOCK_DISABLED,
+	DRAGLOCK_META,
+	DRAGLOCK_PAIRS
+};
+
+enum draglock_button_state
+{
+	DRAGLOCK_BUTTON_STATE_NONE,
+	DRAGLOCK_BUTTON_STATE_DOWN_1,
+	DRAGLOCK_BUTTON_STATE_UP_1,
+	DRAGLOCK_BUTTON_STATE_DOWN_2,
+};
+
+struct draglock
+{
+	enum draglock_mode mode;
+	int meta_button;			/* meta key to lock any button */
+	bool meta_state;			/* meta_button state */
+	unsigned int lock_pair[DRAGLOCK_MAX_BUTTONS + 1];/* specify a meta/lock pair */
+	enum draglock_button_state lock_state[DRAGLOCK_MAX_BUTTONS + 1];	/* state of any locked buttons */
+};
+
+/**
+ * Initialize the draglock struct based on the config string. The string is
+ * either a single number to configure DRAGLOCK_META mode or a list of
+ * number pairs, with pair[0] as button and pair[1] as target lock number to
+ * configure DRAGLOCK_PAIRS mode.
+ *
+ * If config is NULL, the empty string, "0" or an even-numbered list of 0,
+ * the drag lock mode is DRAGLOCK_DISABLED.
+ *
+ * @return 0 on success or nonzero on error
+ */
+int
+draglock_init_from_string(struct draglock *dl, const char *config);
+
+/**
+ * Get the current drag lock mode.
+ *
+ * If the mode is DRAGLOCK_META, a meta button click will cause the next
+ * subsequent button click to be held logically down until the release of
+ * the second button click of that same button. Events from the meta button
+ * are always discarded.
+ *
+ * If the mode is DRAGLOCK_PAIRS, any button may be configured with a
+ * 'target' button number. A click of that button causes the target button
+ * to be held logically down until the release of the second button click.
+ */
+enum draglock_mode
+draglock_get_mode(const struct draglock *dl);
+
+/**
+ * @return the meta button number or 0 if the current mode is not
+ * DRAGLOCK_META.
+ */
+int
+draglock_get_meta(const struct draglock *dl);
+
+/**
+ * Get the drag lock button mapping pairs. The array is filled with the
+ * button number as index and the mapped target button number as value, i.e.
+ * array[3] == 8 means button 3 will draglock button 8.
+ *
+ * A value of 0 indicates draglock is disabled for that button.
+ *
+ * @note Button numbers start at 1, array[0] is always 0.
+ *
+ * @param[in|out] array Caller-allocated array to hold the button mappings.
+ * @param[in] sz Maximum number of elements in array
+ *
+ * @return The number of valid elements in array or 0 if the current mode is
+ * not DRAGLOCK_PAIRS
+ */
+size_t
+draglock_get_pairs(const struct draglock *dl, int *array, size_t sz);
+
+/**
+ * Set the drag lock config to the DRAGLOCK_META mode, with the given
+ * button as meta button.
+ *
+ * If the button is 0 the mode becomes DRAGLOCK_DISABLED.
+ *
+ * @return 0 on success, nonzero otherwise
+ */
+int
+draglock_set_meta(struct draglock *dl, int meta_button);
+
+/**
+ * Set the drag lock config to the DRAGLOCK_PAIRS mode. The array
+ * must be filled with the button number as index and the mapped target
+ * button number as value, i.e.
+ * array[3] == 8 means button 3 will draglock button 8.
+ *
+ * A value of 0 indicates draglock is disabled for that button. If all
+ * buttons are 0, the mode becomes DRAGLOCK_DISABLED.
+ *
+ * @note Button numbers start at 1, array[0] is always 0.
+ *
+ * @return 0 on successor nonzero otherwise
+ */
+int
+draglock_set_pairs(struct draglock *dl, const int *array, size_t sz);
+
+/**
+ * Process the given button event through the drag lock state machine.
+ * If the event is to be discarded by the caller, button is set to 0.
+ * Otherwise, button is set to the button event to process and is_press is
+ * set to the button state to process.
+ *
+ * @param[in|out] button The button number to process
+ * @param[in|out] is_press nonzero for press, zero for release
+ *
+ * @return 0 on success or 1 on error
+ */
+int
+draglock_filter_button(struct draglock *dl, int *button, int *is_press);
+
+#endif /* DRAGLOCK_H */
diff --git a/src/xf86libinput.c b/src/xf86libinput.c
index 041aedf..8500792 100644
--- a/src/xf86libinput.c
+++ b/src/xf86libinput.c
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2013 Red Hat, Inc.
+ * Copyright © 2013-2015 Red Hat, Inc.
  *
  * Permission to use, copy, modify, distribute, and sell this software
  * and its documentation for any purpose is hereby granted without
@@ -40,6 +40,7 @@
 
 #include <X11/Xatom.h>
 
+#include "draglock.h"
 #include "libinput-properties.h"
 
 #ifndef XI86_SERVER_FD
@@ -111,6 +112,8 @@ struct xf86libinput {
 
 		unsigned char btnmap[MAX_BUTTONS + 1];
 	} options;
+
+	struct draglock draglock;
 };
 
 /*
@@ -769,12 +772,18 @@ static void
 xf86libinput_handle_button(InputInfoPtr pInfo, struct libinput_event_pointer *event)
 {
 	DeviceIntPtr dev = pInfo->dev;
+	struct xf86libinput *driver_data = pInfo->private;
 	int button;
 	int is_press;
 
 	button = btn_linux2xorg(libinput_event_pointer_get_button(event));
 	is_press = (libinput_event_pointer_get_button_state(event) == LIBINPUT_BUTTON_STATE_PRESSED);
-	xf86PostButtonEvent(dev, Relative, button, is_press, 0, 0);
+
+	if (draglock_get_mode(&driver_data->draglock) != DRAGLOCK_DISABLED)
+		draglock_filter_button(&driver_data->draglock, &button, &is_press);
+
+	if (button)
+		xf86PostButtonEvent(dev, Relative, button, is_press, 0, 0);
 }
 
 static void
@@ -1402,6 +1411,20 @@ xf86libinput_parse_buttonmap_option(InputInfoPtr pInfo,
 	free(mapping);
 }
 
+static inline void
+xf86libinput_parse_draglock_option(InputInfoPtr pInfo,
+				   struct xf86libinput *driver_data)
+{
+	char *str;
+
+	str = xf86CheckStrOption(pInfo->options, "DragLockButtons",NULL);
+	if (draglock_init_from_string(&driver_data->draglock, str) != 0)
+		xf86IDrvMsg(pInfo, X_ERROR,
+			    "Invalid DragLockButtons option: \"%s\"\n",
+			    str);
+	free(str);
+}
+
 static void
 xf86libinput_parse_options(InputInfoPtr pInfo,
 			   struct xf86libinput *driver_data,
@@ -1427,6 +1450,8 @@ xf86libinput_parse_options(InputInfoPtr pInfo,
 	xf86libinput_parse_buttonmap_option(pInfo,
 					    options->btnmap,
 					    sizeof(options->btnmap));
+	if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER))
+		xf86libinput_parse_draglock_option(pInfo, driver_data);
 }
 
 static int
@@ -1620,6 +1645,9 @@ static Atom prop_middle_emulation_default;
 static Atom prop_disable_while_typing;
 static Atom prop_disable_while_typing_default;
 
+/* driver properties */
+static Atom prop_draglock;
+
 /* general properties */
 static Atom prop_float;
 static Atom prop_device;
@@ -2059,6 +2087,85 @@ LibinputSetPropertyDisableWhileTyping(DeviceIntPtr dev,
 	return Success;
 }
 
+static inline int
+prop_draglock_set_meta(struct xf86libinput *driver_data,
+		       const BYTE *values,
+		       int len,
+		       BOOL checkonly)
+{
+	struct draglock *dl,
+			dummy; /* for checkonly */
+	int meta;
+
+	if (len > 1)
+		return BadImplementation; /* should not happen */
+
+	dl = (checkonly) ? &dummy : &driver_data->draglock;
+	meta = len > 0 ? values[0] : 0;
+
+	return draglock_set_meta(dl, meta) == 0 ? Success: BadValue;
+}
+
+static inline int
+prop_draglock_set_pairs(struct xf86libinput *driver_data,
+			const BYTE* pairs,
+			int len,
+			BOOL checkonly)
+{
+	struct draglock *dl,
+			dummy; /* for checkonly */
+	int data[MAX_BUTTONS + 1] = {0};
+	int i;
+	int highest = 0;
+
+	if (len >= ARRAY_SIZE(data))
+		return BadMatch;
+
+	if (len < 2 || len % 2)
+		return BadImplementation; /* should not happen */
+
+	dl = (checkonly) ? &dummy : &driver_data->draglock;
+
+	for (i = 0; i < len; i += 2) {
+		if (pairs[i] > MAX_BUTTONS)
+			return BadValue;
+
+		data[pairs[i]] = pairs[i+1];
+		highest = max(highest, pairs[i]);
+	}
+
+	return draglock_set_pairs(dl, data, highest + 1) == 0 ? Success : BadValue;
+}
+
+static inline int
+LibinputSetPropertyDragLockButtons(DeviceIntPtr dev,
+				   Atom atom,
+				   XIPropertyValuePtr val,
+				   BOOL checkonly)
+{
+	InputInfoPtr pInfo = dev->public.devicePrivate;
+	struct xf86libinput *driver_data = pInfo->private;
+
+	if (val->format != 8 || val->type != XA_INTEGER)
+		return BadMatch;
+
+	/* either a single value, or pairs of values */
+	if (val->size > 1 && val->size % 2)
+		return BadMatch;
+
+	if (!xf86libinput_check_device(dev, atom))
+		return BadMatch;
+
+	if (val->size <= 1)
+		return prop_draglock_set_meta(driver_data,
+					      (BYTE*)val->data,
+					      val->size, checkonly);
+	else
+		return prop_draglock_set_pairs(driver_data,
+					       (BYTE*)val->data,
+					       val->size, checkonly);
+}
+
 static int
 LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
                  BOOL checkonly)
@@ -2096,6 +2203,8 @@ LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
 		rc = LibinputSetPropertyMiddleEmulation(dev, atom, val, checkonly);
 	else if (atom == prop_disable_while_typing)
 		rc = LibinputSetPropertyDisableWhileTyping(dev, atom, val, checkonly);
+	else if (atom == prop_draglock)
+		rc = LibinputSetPropertyDragLockButtons(dev, atom, val, checkonly);
 	else if (atom == prop_device || atom == prop_product_id ||
 		 atom == prop_tap_default ||
 		 atom == prop_tap_drag_lock_default ||
@@ -2564,6 +2673,37 @@ LibinputInitDisableWhileTypingProperty(DeviceIntPtr dev,
 }
 
 static void
+LibinputInitDragLockProperty(DeviceIntPtr dev,
+			     struct xf86libinput *driver_data)
+{
+	size_t sz;
+	int dl_values[MAX_BUTTONS + 1];
+
+	if (!libinput_device_has_capability(driver_data->device,
+					    LIBINPUT_DEVICE_CAP_POINTER))
+		return;
+
+	switch (draglock_get_mode(&driver_data->draglock)) {
+	case DRAGLOCK_DISABLED:
+		sz = 0; /* will be an empty property */
+		break;
+	case DRAGLOCK_META:
+		dl_values[0] = draglock_get_meta(&driver_data->draglock);
+		sz = 1;
+		break;
+	case DRAGLOCK_PAIRS:
+		sz = draglock_get_pairs(&driver_data->draglock,
+					dl_values, sizeof(dl_values));
+		break;
+	}
+
+	prop_draglock = LibinputMakeProperty(dev,
+					     LIBINPUT_PROP_DRAG_LOCK_BUTTONS,
+					     XA_INTEGER, 8,
+					     sz, dl_values);
+}
+
+static void
 LibinputInitProperty(DeviceIntPtr dev)
 {
 	InputInfoPtr pInfo  = dev->public.devicePrivate;
@@ -2612,4 +2752,6 @@ LibinputInitProperty(DeviceIntPtr dev)
 		return;
 
 	XISetDevicePropertyDeletable(dev, prop_product_id, FALSE);
+
+	LibinputInitDragLockProperty(dev, driver_data);
 }
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..48a46e3
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+test-draglock
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..6f94abe
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,13 @@
+AM_CPPFLAGS = $(XORG_CFLAGS) \
+	      $(CWARNFLAGS) \
+	      -I$(top_srcdir)/include \
+	      -I$(top_srcdir)/src
+
+tests = test-draglock
+
+noinst_PROGRAMS = $(tests)
+
+test_draglock_SOURCES = test-draglock.c
+test_draglock_LDADD = ../src/libdraglock.la
+
+TESTS = $(tests)
diff --git a/test/test-draglock.c b/test/test-draglock.c
new file mode 100644
index 0000000..96ef5bb
--- /dev/null
+++ b/test/test-draglock.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright © 2013-2015 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission.  Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS 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 "draglock.h"
+
+#include <assert.h>
+#include <string.h>
+
+static void
+test_config_empty(void)
+{
+	struct draglock dl;
+	int rc;
+
+	rc = draglock_init_from_string(&dl, NULL);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+	assert(rc == 0);
+}
+
+static void
+test_config_invalid(void)
+{
+	struct draglock dl;
+	int rc;
+
+	/* no trailing space */
+	rc = draglock_init_from_string(&dl, "1 ");
+	assert(rc != 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_init_from_string(&dl, "256");
+	assert(rc != 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_init_from_string(&dl, "-1");
+	assert(rc != 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_init_from_string(&dl, "1 2 3");
+	assert(rc != 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_init_from_string(&dl, "0 2");
+	assert(rc != 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_init_from_string(&dl, "0 0");
+	assert(rc != 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+}
+
+static void
+test_config_disable(void)
+{
+	struct draglock dl;
+	int rc;
+
+	rc = draglock_init_from_string(&dl, "");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_init_from_string(&dl, "0");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+}
+
+static void
+test_config_meta_button(void)
+{
+	struct draglock dl;
+	int rc;
+
+	rc = draglock_init_from_string(&dl, "1");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_META);
+	assert(dl.meta_button == 1);
+
+	rc = draglock_init_from_string(&dl, "2");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_META);
+	assert(dl.meta_button == 2);
+
+	rc = draglock_init_from_string(&dl, "10");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_META);
+	assert(dl.meta_button == 10);
+}
+
+static void
+test_config_button_pairs(void)
+{
+	struct draglock dl;
+	int rc;
+
+	rc = draglock_init_from_string(&dl, "1 1");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_PAIRS);
+
+	rc = draglock_init_from_string(&dl, "1 2 3 4 5 6 7 8");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_PAIRS);
+
+	rc = draglock_init_from_string(&dl, "1 2 3 4 5 0 7 8");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_PAIRS);
+
+	/* all disabled */
+	rc = draglock_init_from_string(&dl, "1 0 3 0 5 0 7 0");
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+}
+
+static void
+test_config_get(void)
+{
+	struct draglock dl;
+	int rc;
+	const int sz = 32;
+	int map[sz];
+
+	draglock_init_from_string(&dl, "");
+	rc = draglock_get_meta(&dl);
+	assert(rc == 0);
+	rc = draglock_get_pairs(&dl, map, sz);
+	assert(rc == 0);
+
+	draglock_init_from_string(&dl, "8");
+	rc = draglock_get_meta(&dl);
+	assert(rc == 8);
+	rc = draglock_get_pairs(&dl, map, sz);
+	assert(rc == 0);
+
+	draglock_init_from_string(&dl, "1 2 3 4 5 6");
+	rc = draglock_get_meta(&dl);
+	assert(rc == 0);
+	rc = draglock_get_pairs(&dl, map, sz);
+	assert(rc == 5);
+	assert(map[0] == 0);
+	assert(map[1] == 2);
+	assert(map[2] == 0);
+	assert(map[3] == 4);
+	assert(map[4] == 0);
+	assert(map[5] == 6);
+}
+
+static void
+test_set_meta(void)
+{
+	struct draglock dl;
+	int rc;
+
+	draglock_init_from_string(&dl, "");
+
+	rc = draglock_set_meta(&dl, 0);
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_set_meta(&dl, 1);
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_META);
+
+	rc = draglock_set_meta(&dl, -1);
+	assert(rc == 1);
+	rc = draglock_set_meta(&dl, 32);
+	assert(rc == 1);
+}
+
+static void
+test_set_pairs(void)
+{
+	struct draglock dl;
+	int rc;
+	const int sz = 32;
+	int map[sz];
+
+	draglock_init_from_string(&dl, "");
+	memset(map, 0, sizeof(map));
+
+	rc = draglock_set_pairs(&dl, map, sz);
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	rc = draglock_set_pairs(&dl, map, 1);
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_DISABLED);
+
+	map[0] = 1;
+	rc = draglock_set_pairs(&dl, map, 1);
+	assert(rc == 1);
+
+	map[0] = 0;
+	map[1] = 2;
+	rc = draglock_set_pairs(&dl, map, sz);
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_PAIRS);
+
+	map[0] = 0;
+	map[1] = 0;
+	map[10] = 8;
+	rc = draglock_set_pairs(&dl, map, sz);
+	assert(rc == 0);
+	assert(dl.mode == DRAGLOCK_PAIRS);
+}
+
+static void
+test_filter_meta_passthrough(void)
+{
+	struct draglock dl;
+	int rc;
+	int button, press;
+	int i;
+
+	rc = draglock_init_from_string(&dl, "10");
+
+	for (i = 0; i < 10; i++) {
+		button = i;
+		press = 1;
+
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+		assert(press == 1);
+
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+		assert(press == 1);
+	}
+}
+
+static void
+test_filter_meta_click_meta_only(void)
+{
+	struct draglock dl;
+	int rc;
+	int button, press;
+
+	rc = draglock_init_from_string(&dl, "10");
+
+	button = 10;
+	press = 1;
+
+	rc = draglock_filter_button(&dl, &button, &press);
+	assert(rc == 0);
+	assert(button == 0);
+
+	button = 10;
+	press = 0;
+	rc = draglock_filter_button(&dl, &button, &press);
+	assert(rc == 0);
+	assert(button == 0);
+}
+
+static void
+test_filter_meta(void)
+{
+	struct draglock dl;
+	int rc;
+	int button, press;
+	int i;
+
+	rc = draglock_init_from_string(&dl, "10");
+
+	for (i = 1; i < 10; i++) {
+		/* meta down */
+		button = 10;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* meta up */
+		button = 10;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button down -> passthrough */
+		button = i;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+
+		/* button up -> eaten */
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button down -> eaten */
+		button = i;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button up -> passthrough */
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+		assert(press == 0);
+	}
+}
+
+static void
+test_filter_meta_extra_click(void)
+{
+	struct draglock dl;
+	int rc;
+	int button, press;
+	int i;
+
+	rc = draglock_init_from_string(&dl, "10");
+
+	for (i = 1; i < 10; i++) {
+		/* meta down */
+		button = 10;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* meta up */
+		button = 10;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button down -> passthrough */
+		button = i;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+
+		/* button up -> eaten */
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* meta down */
+		button = 10;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* meta up */
+		button = 10;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button down -> eaten */
+		button = i;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button up -> passthrough */
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+		assert(press == 0);
+	}
+}
+
+static void
+test_filter_meta_interleaved(void)
+{
+	struct draglock dl;
+	int rc;
+	int button, press;
+	int i;
+
+	rc = draglock_init_from_string(&dl, "10");
+
+	for (i = 1; i < 10; i++) {
+		/* meta down */
+		button = 10;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* meta up */
+		button = 10;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button down -> passthrough */
+		button = i;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+
+		/* button up -> eaten */
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+	}
+
+	for (i = 0; i < 10; i++) {
+		/* button down -> eaten */
+		button = i;
+		press = 1;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == 0);
+
+		/* button up -> passthrough */
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		assert(button == i);
+		assert(press == 0);
+	}
+}
+
+static void
+test_filter_pairs(void)
+{
+	struct draglock dl;
+	int rc;
+	int button, press;
+	int i;
+
+	rc = draglock_init_from_string(&dl, "1 11 2 0 3 13 4 0 5 15 6 0 7 17 8 0 9 19");
+
+	for (i = 1; i < 10; i++) {
+		button = i;
+		press = 1;
+
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		if (i % 2)
+			assert(button == i + 10);
+		else
+			assert(button == i);
+		assert(press == 1);
+
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		if (i % 2) {
+			assert(button == 0);
+		} else {
+			assert(button == i);
+			assert(press == 0);
+		}
+	}
+
+	for (i = 1; i < 10; i++) {
+		button = i;
+		press = 1;
+
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		if (i % 2) {
+			assert(button == 0);
+		} else {
+			assert(button == i);
+			assert(press == 1);
+		}
+
+		button = i;
+		press = 0;
+		rc = draglock_filter_button(&dl, &button, &press);
+		assert(rc == 0);
+		if (i % 2)
+			assert(button == i + 10);
+		else
+			assert(button == i);
+		assert(press == 0);
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	test_config_empty();
+	test_config_invalid();
+	test_config_disable();
+	test_config_meta_button();
+	test_config_button_pairs();
+
+	test_config_get();
+	test_set_meta();
+	test_set_pairs();
+
+	test_filter_meta_passthrough();
+	test_filter_meta_click_meta_only();
+	test_filter_meta();
+	test_filter_meta_extra_click();
+	test_filter_meta_interleaved();
+
+	test_filter_pairs();
+
+	return 0;
+}
-- 
2.4.3



More information about the xorg-devel mailing list