[xf86-input-libinput] Implement the custom acceleration curve options

Submitted by Peter Hutterer on April 20, 2018, 4:14 a.m.

Details

Message ID 20180420041440.GA18436@jelly
State New
Headers show
Series "Implement the custom acceleration curve options" ( rev: 1 ) in X.org

Not browsing as part of any series.

Commit Message

Peter Hutterer April 20, 2018, 4:14 a.m.
One new property, and the existing accel profile gets extended to keep one
extra value. The new property libinput Accel Curve Points is a list of pairs
of points to be added to the acceleration curve.

libinput only supports adding points to the curve so we simply declare the
behavior as undefined when the curve is set multiple times. Also helps to
identify those that bother to read the man page before playing with random
driver values.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
---
Note, the matching libinput patch is not yet on git master.

 configure.ac                  |  17 ++++
 include/libinput-properties.h |   8 ++
 man/libinput.man              |  53 +++++++++-
 src/xf86libinput.c            | 227 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 298 insertions(+), 7 deletions(-)

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 34f2274..5892c5e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -47,6 +47,23 @@  XORG_DEFAULT_OPTIONS
 PKG_CHECK_MODULES(XORG, [xorg-server >= 1.10] xproto [inputproto >= 2.2])
 PKG_CHECK_MODULES(LIBINPUT, [libinput >= 1.4.901])
 
+AC_MSG_CHECKING([if libinput_device_config_accel_set_curve_point is available])
+OLD_LIBS=$LIBS
+OLD_CFLAGS=$CFLAGS
+LIBS="$LIBS $LIBINPUT_LIBS"
+CFLAGS="$CFLAGS $LIBINPUT_CFLAGS"
+AC_LINK_IFELSE(
+               [AC_LANG_PROGRAM([[#include <libinput.h>]],
+                                [[libinput_device_config_accel_set_curve_point(NULL, 0, 0)]])],
+               [AC_MSG_RESULT([yes])
+                AC_DEFINE(HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE, [1],
+                          [libinput_device_config_accel_set_curve_point() is available])
+                [libinput_have_custom_accel_curve=yes]],
+               [AC_MSG_RESULT([no])
+                [libinput_have_custom_accel_curve=no]])
+LIBS=$OLD_LIBS
+CFLAGS=$OLD_CFLAGS
+
 # Define a configure option for an alternate input module directory
 AC_ARG_WITH(xorg-module-dir,
             AC_HELP_STRING([--with-xorg-module-dir=DIR],
diff --git a/include/libinput-properties.h b/include/libinput-properties.h
index a701316..3169cfb 100644
--- a/include/libinput-properties.h
+++ b/include/libinput-properties.h
@@ -73,6 +73,14 @@ 
    only one is enabled at a time at max */
 #define LIBINPUT_PROP_ACCEL_PROFILE_ENABLED "libinput Accel Profile Enabled"
 
+/* Pointer accel custom curve: FLOAT, n values for n>=0 && n%2 == 0, where
+   each pair of values are the x/y coordinates of the acceleration curve.
+   The exact behavior depends on the acceleration profile.
+
+   This property should be considered write-only, the value of the property may
+   not reflect the actual curve points used in libinput. */
+#define LIBINPUT_PROP_ACCEL_CURVE_POINTS "libinput Accel Curve Points"
+
 /* Natural scrolling: BOOL, 1 value */
 #define LIBINPUT_PROP_NATURAL_SCROLL "libinput Natural Scrolling Enabled"
 
diff --git a/man/libinput.man b/man/libinput.man
index dbf7dee..acb447d 100644
--- a/man/libinput.man
+++ b/man/libinput.man
@@ -45,11 +45,19 @@  are supported:
 Sets the pointer acceleration profile to the given profile. Permitted values
 are
 .BI adaptive,
-.BI flat.
+.BI flat,
+.BI device-speed-curve.
 Not all devices support this option or all profiles. If a profile is
 unsupported, the default profile for this device is used. For a description
 on the profiles and their behavior, see the libinput documentation.
 .TP 7
+.BI "Option \*qAccelCurvePoints\*q \*q" string \*q
+A string of space-separated pairs of floats, each pair value separated by a
+colon, i.e. the string looks like this "A:B C:D". These pairs represent the
+x/y value of a point on the acceleration curve. See
+.B CUSTOM POINTER ACCELERATION CURVES
+for details.
+.TP 7
 .BI "Option \*qAccelSpeed\*q \*q" float \*q
 Sets the pointer acceleration speed within the range [-1, 1]
 .TP 7
@@ -204,16 +212,26 @@  on the device. The following properties are provided by the
 driver.
 .TP 7
 .BI "libinput Accel Profiles Available"
-2 boolean values (8 bit, 0 or 1), in order "adaptive", "flat".
-Indicates which acceleration profiles are available on this device.
+3 boolean values (8 bit, 0 or 1), in order "adaptive", "flat",
+"device-speed-curve". Indicates which acceleration profiles are available on
+this device.
 .TP 7
 .BI "libinput Accel Profile Enabled"
-2 boolean values (8 bit, 0 or 1), in order "adaptive", "flat".
-Indicates which acceleration profile is currently enabled on this device.
+3 boolean values (8 bit, 0 or 1), in order "adaptive", "flat",
+"device-speed-curve". Indicates which acceleration profile is currently
+enabled on this device.
 .TP 7
 .BI "libinput Accel Speed"
 1 32-bit float value, defines the pointer speed. Value range -1, 1
 .TP 7
+.BI "libinput Accel Curve Points"
+n 32-bit float values, n is a multiple of 2, each pair represents one point
+on the acceleration curve. If the acceleration profile is
+\fBdevice-speed-curve\fR, each value x is the device speed in units/ms and each
+value x+1 is the acceleration factor to apply. This property should only be
+written once per profile change, see section
+.B CUSTOM POINTER ACCELERATION CURVES
+.TP 7
 .BI "libinput Button Scrolling Button"
 1 32-bit value. Sets the button number to use for button scrolling. This
 setting is independent of the scroll method, to enable button scrolling the
@@ -381,6 +399,31 @@  it takes left-handed-ness into account.
 .TP
 This feature is provided by this driver, not by libinput.
 
+.SH CUSTOM POINTER ACCELERATION CURVES
+Some profiles require the acceleration curve to be provided
+by the user. These curve points must be provided as pairs in the option or
+the property and each curve point defines the x/y value of one point on the
+curve. libinput does linear interpolation between the defined points. See
+the libinput documentation for details.
+.PP
+If the  profile is
+.B device-speed-curve,
+the points represent the mapping of device speed (in device units/ms) to an
+acceleration factor, e.g. the values (50, 2) map a device speed of 50
+units/ms to an acceleration factor of 2. The current delta is then
+multiplied by that factor.
+.PP
+To apply a custom acceleration curve, first set the profile to
+.B device-speed-curve,
+then set the curve points. All curve points must be set with one property
+update. Once set, adding, removing or otherwise modifying the property
+values results in undefined behavior.
+.PP
+To reset the curve, change to a different acceleration profile, then change
+back to
+.B device-speed-curve
+before applying the new set of curve points.
+
 .SH BUGS
 This driver does not work with \fBOption \*qDevice\*q\fR set to an event
 node in \fI/dev/input/by-id\fR and \fI/dev/input/by-path\fR. This can be
diff --git a/src/xf86libinput.c b/src/xf86libinput.c
index 68d36d7..4a1b9e6 100644
--- a/src/xf86libinput.c
+++ b/src/xf86libinput.c
@@ -122,6 +122,11 @@  struct xf86libinput_tablet_tool {
 	struct libinput_tablet_tool *tool;
 };
 
+struct curve_point {
+	struct xorg_list node;
+	float x, fx;
+} curve_point;
+
 struct xf86libinput {
 	InputInfoPtr pInfo;
 	char *path;
@@ -173,6 +178,8 @@  struct xf86libinput {
 		struct ratio {
 			int x, y;
 		} area;
+
+		struct xorg_list curve_points;
 	} options;
 
 	struct draglock draglock;
@@ -547,6 +554,11 @@  LibinputApplyConfigAccel(DeviceIntPtr dev,
 		case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT:
 			profile = "flat";
 			break;
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+		case LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE:
+			profile = "device-speed-curve";
+			break;
+#endif
 		default:
 			profile = "unknown";
 			break;
@@ -763,6 +775,30 @@  LibinputApplyConfigRotation(DeviceIntPtr dev,
 			    driver_data->options.rotation_angle);
 }
 
+static void
+LibinputApplyConfigAccelCurvePoints(DeviceIntPtr dev,
+				    struct xf86libinput *driver_data,
+				    struct libinput_device *device)
+{
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	struct curve_point *p;
+
+	if (!subdevice_has_capabilities(dev, CAP_POINTER))
+		return;
+
+	if (!libinput_device_config_accel_is_available(device))
+		return;
+
+	if (libinput_device_config_accel_get_profile(device) !=
+	    LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE)
+		return;
+
+	xorg_list_for_each_entry(p, &driver_data->options.curve_points, node) {
+		libinput_device_config_accel_set_curve_point(device, p->x, p->fx);
+	}
+#endif
+}
+
 static inline void
 LibinputApplyConfig(DeviceIntPtr dev)
 {
@@ -781,6 +817,7 @@  LibinputApplyConfig(DeviceIntPtr dev)
 	LibinputApplyConfigMiddleEmulation(dev, driver_data, device);
 	LibinputApplyConfigDisableWhileTyping(dev, driver_data, device);
 	LibinputApplyConfigRotation(dev, driver_data, device);
+	LibinputApplyConfigAccelCurvePoints(dev, driver_data, device);
 }
 
 static int
@@ -2592,6 +2629,10 @@  xf86libinput_parse_accel_profile_option(InputInfoPtr pInfo,
 		profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
 	else if (strncasecmp(str, "flat", 4) == 0)
 		profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	else if (strncasecmp(str, "device-speed-curve", 18) == 0)
+		profile = LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE;
+#endif
 	else {
 		xf86IDrvMsg(pInfo, X_ERROR,
 			    "Unknown accel profile '%s'. Using default.\n",
@@ -2709,6 +2750,57 @@  xf86libinput_parse_calibration_option(InputInfoPtr pInfo,
 	free(str);
 }
 
+static inline void
+xf86libinput_parse_accel_curve_points_options(InputInfoPtr pInfo,
+					      struct libinput_device *device,
+					      struct xorg_list *points)
+{
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	uint32_t supported;
+	char *str, *s;
+
+	xorg_list_init(points);
+
+	if (!libinput_device_config_accel_is_available(device))
+		return;
+
+	supported = libinput_device_config_accel_get_profiles(device);
+	if ((supported & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) == 0)
+		return;
+
+	str = xf86SetStrOption(pInfo->options, "AccelCurvePoints", NULL);
+	if (!str)
+		return;
+
+	s = str;
+	while (s) {
+		char *token = strtok_r(s, " ", &s);
+		struct curve_point *p;
+		int nparsed;
+
+		if (!token)
+			break;
+
+		p = calloc(1, sizeof(*p));
+		if (!p)
+			break;
+
+		nparsed = sscanf(token, "%f:%f", &p->x, &p->fx);
+		if (nparsed != 2) {
+			xf86IDrvMsg(pInfo, X_ERROR,
+				    "Failed to parse curve points: %s\n",  str);
+			free(p);
+			break;
+		}
+
+		xorg_list_append(&p->node, points);
+		libinput_device_config_accel_set_curve_point(device, p->x, p->fx);
+	}
+
+	free(str);
+#endif
+}
+
 static inline BOOL
 xf86libinput_parse_lefthanded_option(InputInfoPtr pInfo,
 				     struct libinput_device *device)
@@ -3080,6 +3172,8 @@  xf86libinput_parse_options(InputInfoPtr pInfo,
 	options->disable_while_typing = xf86libinput_parse_disablewhiletyping_option(pInfo, device);
 	options->rotation_angle = xf86libinput_parse_rotation_angle_option(pInfo, device);
 	xf86libinput_parse_calibration_option(pInfo, device, driver_data->options.matrix);
+	xf86libinput_parse_accel_curve_points_options(pInfo, device,
+						      &driver_data->options.curve_points);
 
 	/* non-libinput options */
 	xf86libinput_parse_buttonmap_option(pInfo,
@@ -3556,6 +3650,7 @@  static Atom prop_draglock;
 static Atom prop_horiz_scroll;
 static Atom prop_pressurecurve;
 static Atom prop_area_ratio;
+static Atom prop_accel_curve_points;
 
 /* general properties */
 static Atom prop_float;
@@ -3882,7 +3977,7 @@  LibinputSetPropertyAccelProfile(DeviceIntPtr dev,
 	BOOL* data;
 	uint32_t profiles = 0;
 
-	if (val->format != 8 || val->size != 2 || val->type != XA_INTEGER)
+	if (val->format != 8 || val->size < 2 || val->size > 3 || val->type != XA_INTEGER)
 		return BadMatch;
 
 	data = (BOOL*)val->data;
@@ -3891,6 +3986,10 @@  LibinputSetPropertyAccelProfile(DeviceIntPtr dev,
 		profiles |= LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE;
 	if (data[1])
 		profiles |= LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT;
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	if (val->size > 2 && data[2])
+		profiles |= LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE;
+#endif
 
 	if (checkonly) {
 		uint32_t supported;
@@ -4456,6 +4555,64 @@  LibinputSetPropertyAreaRatio(DeviceIntPtr dev,
 	return Success;
 }
 
+static inline int
+LibinputSetPropertyAccelCurvePoints(DeviceIntPtr dev,
+				    Atom atom,
+				    XIPropertyValuePtr val,
+				    BOOL checkonly)
+{
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	InputInfoPtr pInfo = dev->public.devicePrivate;
+	struct xf86libinput *driver_data = pInfo->private;
+	struct libinput_device *device = driver_data->shared_device->device;
+	float *vals;
+
+	if (val->format != 32 || val->size % 2 != 0 || val->type != prop_float)
+		return BadMatch;
+
+	vals = val->data;
+
+	if (val->size >= 128) /* 128 curve points is enough for everybody */
+		return BadMatch;
+
+	if (checkonly) {
+		uint32_t supported;
+
+		supported = libinput_device_config_accel_get_profiles(device);
+		if ((supported & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) == 0)
+			return BadMatch;
+
+		for (size_t i = 0; i < val->size; i++) {
+			if (vals[i] < 0.0)
+				return BadValue;
+		}
+	} else {
+		struct curve_point *p, *tmp;
+
+		xorg_list_for_each_entry_safe(p, tmp,
+					      &driver_data->options.curve_points,
+					      node) {
+			xorg_list_del(&p->node);
+			free(p);
+		}
+		xorg_list_init(&driver_data->options.curve_points);
+
+		for (size_t i = 0; i < val->size; i += 2) {
+			p = calloc(1, sizeof(*p));
+			if (!p)
+				break;
+
+			p->x = vals[i];
+			p->fx = vals[i+1];
+			xorg_list_append(&p->node,
+					 &driver_data->options.curve_points);
+		}
+	}
+
+#endif
+	return Success;
+}
+
 static int
 LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
                  BOOL checkonly)
@@ -4512,6 +4669,8 @@  LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
 		rc = LibinputSetPropertyPressureCurve(dev, atom, val, checkonly);
 	else if (atom == prop_area_ratio)
 		rc = LibinputSetPropertyAreaRatio(dev, atom, val, checkonly);
+	else if (atom == prop_accel_curve_points)
+		rc = LibinputSetPropertyAccelCurvePoints(dev, atom, val, checkonly);
 	else if (atom == prop_device || atom == prop_product_id ||
 		 atom == prop_tap_default ||
 		 atom == prop_tap_drag_default ||
@@ -4750,7 +4909,7 @@  LibinputInitAccelProperty(DeviceIntPtr dev,
 	float speed = driver_data->options.speed;
 	uint32_t profile_mask;
 	enum libinput_config_accel_profile profile;
-	BOOL profiles[2] = {FALSE};
+	BOOL profiles[3] = {FALSE};
 
 	if (!subdevice_has_capabilities(dev, CAP_POINTER))
 		return;
@@ -4780,6 +4939,10 @@  LibinputInitAccelProperty(DeviceIntPtr dev,
 		profiles[0] = TRUE;
 	if (profile_mask & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE)
 		profiles[1] = TRUE;
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	if (profile_mask & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE)
+		profiles[2] = TRUE;
+#endif
 
 	prop_accel_profiles_available = LibinputMakeProperty(dev,
 							     LIBINPUT_PROP_ACCEL_PROFILES_AVAILABLE,
@@ -4799,6 +4962,11 @@  LibinputInitAccelProperty(DeviceIntPtr dev,
 	case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT:
 		profiles[1] = TRUE;
 		break;
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	case LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE:
+		profiles[2] = TRUE;
+		break;
+#endif
 	default:
 		break;
 	}
@@ -4821,6 +4989,11 @@  LibinputInitAccelProperty(DeviceIntPtr dev,
 	case LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT:
 		profiles[1] = TRUE;
 		break;
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	case LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE:
+		profiles[2] = TRUE;
+		break;
+#endif
 	default:
 		break;
 	}
@@ -5423,6 +5596,55 @@  LibinputInitTabletAreaRatioProperty(DeviceIntPtr dev,
 					       2, data);
 }
 
+static void
+LibinputInitAccelCurvePointsProperty(DeviceIntPtr dev,
+				     struct xf86libinput *driver_data,
+				     struct libinput_device *device)
+{
+#if HAVE_LIBINPUT_CUSTOM_ACCEL_CURVE
+	struct curve_point *p;
+	uint32_t supported;
+	float *data = NULL;
+	int npoints, idx;
+
+	if (!subdevice_has_capabilities(dev, CAP_POINTER))
+		return;
+
+	if (!libinput_device_config_accel_is_available(device))
+		return;
+
+	supported = libinput_device_config_accel_get_profiles(device);
+	if ((supported & LIBINPUT_CONFIG_ACCEL_PROFILE_DEVICE_SPEED_CURVE) == 0)
+		return;
+
+	npoints = 0;
+	xorg_list_for_each_entry(p, &driver_data->options.curve_points, node)
+		npoints++;
+
+	if (npoints > 0) {
+		npoints = min(npoints, 128);
+		data = alloca(npoints * 2 * sizeof *data);
+		idx = 0;
+		xorg_list_for_each_entry(p, &driver_data->options.curve_points, node) {
+			data[idx++] = p->x;
+			data[idx++] = p->fx;
+
+			if (idx >= npoints)
+				break;
+
+		}
+	}
+
+	prop_accel_curve_points = LibinputMakeProperty(dev,
+						       LIBINPUT_PROP_ACCEL_CURVE_POINTS,
+						       prop_float, 32,
+						       npoints, data);
+	if (!prop_accel_curve_points)
+		return;
+
+#endif
+}
+
 static void
 LibinputInitProperty(DeviceIntPtr dev)
 {
@@ -5481,4 +5703,5 @@  LibinputInitProperty(DeviceIntPtr dev)
 	LibinputInitHorizScrollProperty(dev, driver_data);
 	LibinputInitPressureCurveProperty(dev, driver_data);
 	LibinputInitTabletAreaRatioProperty(dev, driver_data);
+	LibinputInitAccelCurvePointsProperty(dev, driver_data, device);
 }