diff options
author | Peter Hutterer <peter.hutterer@who-t.net> | 2016-10-24 14:41:51 +1000 |
---|---|---|
committer | Peter Hutterer <peter.hutterer@who-t.net> | 2017-01-04 09:57:57 +1000 |
commit | 5d0470738125243c98f7a8cc40d62f53604a8051 (patch) | |
tree | a829cea9a16188d2c9315c9d0b95ed7919d3fbc2 | |
parent | f65a5c50224efc34414f44c86700e15392b7039b (diff) |
Implement stylus pressure curve support
Takes a 4-point cubic bezier curve as input and maps the pressure coordinates
to the values outlined by this curve. This is an extension of the current
implementation in the xf86-input-wacom driver which only allows the two center
control points to be modified.
Over the years a few users have noted that the wacom driver's pressure curve
makes it impossible to cap the pressure at a given value. Given our bezier
implementation here, it's effectively a freebie to add configurability of the
first and last control points. We do require all control points' x coordinates
to be in ascending order.
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
-rw-r--r-- | include/libinput-properties.h | 7 | ||||
-rw-r--r-- | man/libinput.man | 30 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/bezier.c | 7 | ||||
-rw-r--r-- | src/bezier.h | 2 | ||||
-rw-r--r-- | src/xf86libinput.c | 184 |
6 files changed, 229 insertions, 3 deletions
diff --git a/include/libinput-properties.h b/include/libinput-properties.h index 8c6942d..f76500f 100644 --- a/include/libinput-properties.h +++ b/include/libinput-properties.h @@ -183,4 +183,11 @@ /* Device rotation: FLOAT, 1 value, 32 bit, read-only */ #define LIBINPUT_PROP_ROTATION_ANGLE_DEFAULT "libinput Rotation Angle Default" +/* Tablet tool pressure curve: float, 8 values, 32 bit + * Value range is [0.0, 1.0], the values specify the x/y of the four + * control points for a cubic bezier curve. + * Default value: 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0 + */ +#define LIBINPUT_PROP_TABLET_TOOL_PRESSURECURVE "libinput Tablet Tool Pressurecurve" + #endif /* _LIBINPUT_PROPERTIES_H_ */ diff --git a/man/libinput.man b/man/libinput.man index 36775e6..88a0428 100644 --- a/man/libinput.man +++ b/man/libinput.man @@ -155,6 +155,12 @@ default scroll option for this device is used. Sets the send events mode to disabled, enabled, or "disable when an external mouse is connected". .TP 7 +.BI "Option \*qTabletToolPressureCurve\*q \*q" "x0/y0 x1/y1 x2/y2 x3/y3" \*q +Set the pressure curve for a tablet stylus to the bezier formed by the four +points. The respective x/y coordinate must be in the [0.0, 1.0] range. For +more information see section +.B TABLET STYLUS PRESSURE CURVE. +.TP 7 .BI "Option \*qTapping\*q \*q" bool \*q Enables or disables tap-to-click behavior. .TP 7 @@ -252,6 +258,11 @@ on this device. "disabled-on-external-mouse". Indicates which send-event modes is currently enabled on this device. .TP 7 +.BI "libinput Tablet Tool Pressurecurve" +4 32-bit float values [0.0 to 1.0]. See section +.B TABLET TOOL PRESSURE CURVE +for more information. +.TP 7 .BI "libinput Tapping Enabled" 1 boolean value (8 bit, 0 or 1). 1 enables tapping .TP 7 @@ -313,6 +324,25 @@ and only the target button events are sent. .TP This feature is provided by this driver, not by libinput. +.SH TABLET TOOL PRESSURECURVE +The pressure curve affects how stylus pressure is reported. By default, the +hardware pressure is reported as-is. By setting a pressure curve, the feel +of the stylus can be adjusted to be more like e.g. a pencil or a brush. +.PP +The pressure curve is a cubic Bezier curve, drawn within a normalized range +of 0.0 to 1.0 between the four points provided. This normalized range is +applied to the tablet's pressure input so that the highest pressure maps to +1.0. The points must have increasing x coordinates, if x0 is larger than 0.0 +all pressure values lower than x0 are equivalent to y0. If x3 is less than +1.0, all pressure values higher than x3 are equivalent to y3. + +The input for a linear curve (default) is "0.0/0.0 0.0/0.0 1.0/1.0 1.0/1.0"; +a slightly +depressed curve (firmer) might be "0.0/0.0 0.05/0.0 1.0/0.95 1.0/1.0"; a slightly raised +curve (softer) might be "0.0/0.0 0.0/0.05 0.95/1.0 1.0/1.0". +.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 8634b69..5dcb55e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,7 +30,7 @@ AM_CPPFLAGS =-I$(top_srcdir)/include $(LIBINPUT_CFLAGS) @DRIVER_NAME@_drv_la_LTLIBRARIES = @DRIVER_NAME@_drv.la @DRIVER_NAME@_drv_la_LDFLAGS = -module -avoid-version -@DRIVER_NAME@_drv_la_LIBADD = $(LIBINPUT_LIBS) libdraglock.la -lm +@DRIVER_NAME@_drv_la_LIBADD = $(LIBINPUT_LIBS) libdraglock.la libbezier.la -lm @DRIVER_NAME@_drv_ladir = @inputdir@ @DRIVER_NAME@_drv_la_SOURCES = xf86libinput.c diff --git a/src/bezier.c b/src/bezier.c index e571a3c..95af16a 100644 --- a/src/bezier.c +++ b/src/bezier.c @@ -31,6 +31,13 @@ #include "bezier.h" +const struct bezier_control_point bezier_defaults[4] = { + { 0.0, 0.0 }, + { 0.0, 0.0 }, + { 1.0, 1.0 }, + { 1.0, 1.0 }, +}; + struct point { int x, y; }; diff --git a/src/bezier.h b/src/bezier.h index c7f3b27..7406565 100644 --- a/src/bezier.h +++ b/src/bezier.h @@ -35,6 +35,8 @@ struct bezier_control_point { double x, y; }; +extern const struct bezier_control_point bezier_defaults[4]; + /** * Given four control points in the range [(0.0/0.0), (1.0/1.0)] * construct a Bézier curve. diff --git a/src/xf86libinput.c b/src/xf86libinput.c index d10f38d..d43f67f 100644 --- a/src/xf86libinput.c +++ b/src/xf86libinput.c @@ -42,6 +42,7 @@ #include <X11/Xatom.h> +#include "bezier.h" #include "draglock.h" #include "libinput-properties.h" @@ -165,6 +166,7 @@ struct xf86libinput { BOOL horiz_scrolling_enabled; float rotation_angle; + struct bezier_control_point pressurecurve[4]; } options; struct draglock draglock; @@ -175,6 +177,13 @@ struct xf86libinput { struct libinput_tablet_tool *tablet_tool; bool allow_mode_group_updates; + + /* Pre-calculated pressure curve. + In the 0...TABLET_AXIS_MAX range */ + struct { + int *values; + size_t sz; + } pressurecurve; }; enum event_handling { @@ -385,6 +394,30 @@ xf86libinput_shared_is_enabled(struct xf86libinput_device *shared_device) return shared_device->enabled_count > 0; } +static inline bool +xf86libinput_set_pressurecurve(struct xf86libinput *driver_data, + const struct bezier_control_point controls[4]) +{ + if (memcmp(controls, bezier_defaults, sizeof(bezier_defaults)) == 0) { + free(driver_data->pressurecurve.values); + driver_data->pressurecurve.values = NULL; + return true; + } + + if (!driver_data->pressurecurve.values) { + int *vals = calloc(TABLET_PRESSURE_AXIS_MAX + 1, sizeof(int)); + if (!vals) + return false; + + driver_data->pressurecurve.values = vals; + driver_data->pressurecurve.sz = TABLET_PRESSURE_AXIS_MAX + 1; + } + + return cubic_bezier(controls, + driver_data->pressurecurve.values, + driver_data->pressurecurve.sz); +} + static int LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, BOOL checkonly); @@ -1707,8 +1740,9 @@ xf86libinput_handle_tablet_axis(InputInfoPtr pInfo, tool = libinput_event_tablet_tool_get_tool(event); if (libinput_tablet_tool_has_pressure(tool)) { - value = libinput_event_tablet_tool_get_pressure(event); - value *= TABLET_PRESSURE_AXIS_MAX; + value = TABLET_PRESSURE_AXIS_MAX * libinput_event_tablet_tool_get_pressure(event); + if (driver_data->pressurecurve.values) + value = driver_data->pressurecurve.values[(int)value]; valuator_mask_set_double(mask, 2, value); } @@ -2690,6 +2724,69 @@ xf86libinput_parse_rotation_angle_option(InputInfoPtr pInfo, } static void +xf86libinput_parse_pressurecurve_option(InputInfoPtr pInfo, + struct xf86libinput *driver_data, + struct bezier_control_point pcurve[4]) +{ + struct bezier_control_point controls[4] = { + { 0.0, 0.0 }, + { 0.0, 0.0 }, + { 1.0, 1.0 }, + { 1.0, 1.0 }, + }; + float points[8]; + char *str; + int rc = 0; + int test_bezier[64]; + struct libinput_tablet_tool *tool = driver_data->tablet_tool; + + if ((driver_data->capabilities & CAP_TABLET_TOOL) == 0) + return; + + if (!tool || !libinput_tablet_tool_has_pressure(tool)) + return; + + str = xf86SetStrOption(pInfo->options, + "TabletToolPressureCurve", + NULL); + if (!str) + goto out; + + rc = sscanf(str, "%f/%f %f/%f %f/%f %f/%f", + &points[0], &points[1], &points[2], &points[3], + &points[4], &points[5], &points[6], &points[7]); + if (rc != 8) + goto out; + + for (int i = 0; i < 4; i++) { + if (points[i] < 0.0 || points[i] > 1.0) + goto out; + } + + controls[0].x = points[0]; + controls[0].y = points[1]; + controls[1].x = points[2]; + controls[1].y = points[3]; + controls[2].x = points[4]; + controls[2].y = points[5]; + controls[3].x = points[6]; + controls[3].y = points[7]; + + if (!cubic_bezier(controls, test_bezier, ARRAY_SIZE(test_bezier))) { + memcpy(controls, bezier_defaults, sizeof(controls)); + goto out; + } + + rc = 0; +out: + if (rc != 0) + xf86IDrvMsg(pInfo, X_ERROR, "Invalid pressure curve: %s\n", str); + free(str); + memcpy(pcurve, controls, sizeof(controls)); + xf86libinput_set_pressurecurve(driver_data, controls); +} + +static void xf86libinput_parse_options(InputInfoPtr pInfo, struct xf86libinput *driver_data, struct libinput_device *device) @@ -2722,6 +2819,10 @@ xf86libinput_parse_options(InputInfoPtr pInfo, xf86libinput_parse_draglock_option(pInfo, driver_data); options->horiz_scrolling_enabled = xf86libinput_parse_horiz_scroll_option(pInfo); } + + xf86libinput_parse_pressurecurve_option(pInfo, + driver_data, + options->pressurecurve); } static const char* @@ -3180,6 +3281,7 @@ static Atom prop_rotation_angle_default; /* driver properties */ static Atom prop_draglock; static Atom prop_horiz_scroll; +static Atom prop_pressurecurve; /* general properties */ static Atom prop_float; @@ -3930,6 +4032,52 @@ LibinputSetPropertyRotationAngle(DeviceIntPtr dev, return Success; } +static inline int +LibinputSetPropertyPressureCurve(DeviceIntPtr dev, + Atom atom, + XIPropertyValuePtr val, + BOOL checkonly) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + struct xf86libinput *driver_data = pInfo->private; + float *vals; + struct bezier_control_point controls[4]; + + if (val->format != 32 || val->size != 8 || val->type != prop_float) + return BadMatch; + + vals = val->data; + controls[0].x = vals[0]; + controls[0].y = vals[1]; + controls[1].x = vals[2]; + controls[1].y = vals[3]; + controls[2].x = vals[4]; + controls[2].y = vals[5]; + controls[3].x = vals[6]; + controls[3].y = vals[7]; + + if (checkonly) { + int test_bezier[64]; + + for (int i = 0; i < val->size; i++) { + if (vals[i] < 0.0 || vals[i] > 1.0) + return BadValue; + } + + if (!xf86libinput_check_device (dev, atom)) + return BadMatch; + + if (!cubic_bezier(controls, test_bezier, ARRAY_SIZE(test_bezier))) + return BadValue; + } else { + xf86libinput_set_pressurecurve(driver_data, controls); + memcpy(driver_data->options.pressurecurve, controls, + sizeof(controls)); + } + + return Success; +} + static int LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, BOOL checkonly) @@ -3982,6 +4130,8 @@ LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, } else if (atom == prop_rotation_angle) rc = LibinputSetPropertyRotationAngle(dev, atom, val, checkonly); + else if (atom == prop_pressurecurve) + rc = LibinputSetPropertyPressureCurve(dev, atom, val, checkonly); else if (atom == prop_device || atom == prop_product_id || atom == prop_tap_default || atom == prop_tap_drag_default || @@ -4807,6 +4957,35 @@ LibinputInitRotationAngleProperty(DeviceIntPtr dev, } static void +LibinputInitPressureCurveProperty(DeviceIntPtr dev, + struct xf86libinput *driver_data) +{ + const struct bezier_control_point *curve = driver_data->options.pressurecurve; + struct libinput_tablet_tool *tool = driver_data->tablet_tool; + float data[8]; + + if ((driver_data->capabilities & CAP_TABLET_TOOL) == 0) + return; + + if (!tool || !libinput_tablet_tool_has_pressure(tool)) + return; + + data[0] = curve[0].x; + data[1] = curve[0].y; + data[2] = curve[1].x; + data[3] = curve[1].y; + data[4] = curve[2].x; + data[5] = curve[2].y; + data[6] = curve[3].x; + data[7] = curve[3].y; + + prop_pressurecurve = LibinputMakeProperty(dev, + LIBINPUT_PROP_TABLET_TOOL_PRESSURECURVE, + prop_float, 32, + 8, data); +} + +static void LibinputInitProperty(DeviceIntPtr dev) { InputInfoPtr pInfo = dev->public.devicePrivate; @@ -4862,4 +5041,5 @@ LibinputInitProperty(DeviceIntPtr dev) LibinputInitDragLockProperty(dev, driver_data); LibinputInitHorizScrollProperty(dev, driver_data); + LibinputInitPressureCurveProperty(dev, driver_data); } |