summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2024-01-15 17:07:17 +1000
committerPeter Hutterer <peter.hutterer@who-t.net>2024-01-30 14:29:25 +1000
commit4bc27543e9079c90b4cb3968737c0a0b14364413 (patch)
tree04a1cb609657454dfcb1ebe038f04e039ed7573d
parent47f0bce7ec269190e8c15eff4b89bdaee619a8e5 (diff)
tablet: add tablet tool pressure range configuration
Add a configuration option to reduce the available hardware range to a fraction thereof. This is done by copying the absinfo struct for the pressure value and adjusting that copy's minimum/maximum value for scaling into the target normalized range. The 1%/5% tip thresholds are kept but pressure offset detection is disabled if there is a custom pressure range. Unlike the pressure curve which is implemented in the compositor, the pressure min/max range needs to be in libinput, primarily because the tip threshold needs to adjust to any new minimum, allowing for light touches with a pen without triggering tip down even at a higher hardware pressure.
-rw-r--r--doc/user/configuration.rst14
-rw-r--r--doc/user/tablet-support.rst46
-rw-r--r--src/evdev-tablet.c182
-rw-r--r--src/libinput-private.h22
-rw-r--r--src/libinput.c69
-rw-r--r--src/libinput.h130
-rw-r--r--src/libinput.sym11
-rw-r--r--test/test-tablet.c315
8 files changed, 750 insertions, 39 deletions
diff --git a/doc/user/configuration.rst b/doc/user/configuration.rst
index 16207285..8ce31f21 100644
--- a/doc/user/configuration.rst
+++ b/doc/user/configuration.rst
@@ -177,3 +177,17 @@ the non-dominant hand.
Note that where a device rotation is higher than 160 but less than 200 degrees,
the direction of wheels is also inverted. For all other angles, the wheel
direction is left as-is.
+
+.. _config-tablet-pressure-range:
+
+------------------------------------------------------------------------------
+Tablet tool pressure range
+------------------------------------------------------------------------------
+
+The pressure range on a :ref:`Tablet tool <tablet-tools>` can be reduced
+from the full available hardware range to a subset of that range. The effect
+of this is that the tablet will not register pressure below the given
+the given threshold is met, and will reach the maximum logical pressure
+before the maximum hardware-supported pressure is reached.
+
+See :ref:`tablet-pressure-range` for more info.
diff --git a/doc/user/tablet-support.rst b/doc/user/tablet-support.rst
index 32bf162b..fededac0 100644
--- a/doc/user/tablet-support.rst
+++ b/doc/user/tablet-support.rst
@@ -189,6 +189,52 @@ specifically:
Pressure offsets are not detected on **LIBINPUT_TABLET_TOOL_TYPE_MOUSE**
and **LIBINPUT_TABLET_TOOL_TYPE_LENS** tools.
+
+.. _tablet-pressure-range:
+
+------------------------------------------------------------------------------
+Custom tablet tool pressure ranges
+------------------------------------------------------------------------------
+
+On tablets supporting pressure, libinput provides that hardware pressure as
+a logical range of ``0.0`` up to ``1.0`` for the maximum supported pressure.
+By default, the hardware range thus maps into the following logical range::
+
+
+ hw minimum hw maximum
+ hw range: |------|-----------------------------------|
+ logical range: |----|-----------------------------------|
+ 0.0 | 1.0
+ Tip
+
+Note that libinput always has some built-in thresholds to filter out erroneous
+touches with near-zero pressure but otherwise the hardware range maps as-is
+into the logical range. The :ref:`tip event <tablet-tip>` threshold is defined
+within this range.
+
+For some use-cases the full hardware range is not suitable, it may require either
+too light a pressure for the user to interact or it may require too hard a
+pressure before the logical maximum is reached. libinput provides
+the **libinput_tablet_tool_config_pressure_range_set()** function that allows
+reducing the usable range of the tablet::
+
+ hw minimum hw maximum
+ hw range: |----------|-------------------------------|
+ adjusted range: |------|---------------------|
+ logical range: |----|---------------------|
+ 0.0 | 1.0
+ Tip
+
+A reduced range as shown above will result in
+
+- all hw pressure below the new minimum to register as logical pressure ``0.0``
+- all hw pressure above the new maximum to register as logical pressure ``1.0``
+- the tip event threshold to be relative to the new minimum
+
+In other words, adjusting the pressure range of a tablet tool is equivalent to
+reducing the hardware range of said tool. Note that where a custom pressure
+range is set, detection of :ref:`tablet-pressure-offset` is disabled.
+
.. _tablet-serial-numbers:
------------------------------------------------------------------------------
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index d3b6f825..c2dce5bf 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -346,6 +346,7 @@ normalize_distance(const struct input_absinfo *absinfo)
static inline double
normalize_pressure(const struct input_absinfo *absinfo,
+ int abs_value,
struct libinput_tablet_tool *tool)
{
/**
@@ -360,9 +361,10 @@ normalize_pressure(const struct input_absinfo *absinfo,
* threshold is 0 pressure.
*/
struct input_absinfo abs = *absinfo;
+
abs.minimum = tool->pressure.threshold.lower;
- return absinfo_normalize(&abs);
+ return absinfo_normalize_value(&abs, abs_value);
}
static inline double
@@ -522,15 +524,15 @@ tablet_update_pressure(struct tablet_dispatch *tablet,
struct evdev_device *device,
struct libinput_tablet_tool *tool)
{
- const struct input_absinfo *absinfo;
-
- if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_PRESSURE))
+ const struct input_absinfo *abs = libevdev_get_abs_info(device->evdev,
+ ABS_PRESSURE);
+ if (!abs)
return;
- if (bit_is_set(tablet->changed_axes,
- LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) {
- absinfo = libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
- tablet->axes.pressure = normalize_pressure(absinfo, tool);
+ if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) {
+ tablet->axes.pressure = normalize_pressure(&tool->pressure.abs_pressure,
+ abs->value,
+ tool);
}
}
@@ -1076,26 +1078,94 @@ axis_range_percentage(const struct input_absinfo *a, double percent)
return (a->maximum - a->minimum) * percent/100.0 + a->minimum;
}
+static bool
+tablet_get_quirked_pressure_thresholds(struct tablet_dispatch *tablet,
+ int *hi,
+ int *lo)
+{
+ struct evdev_device *device = tablet->device;
+ struct quirks_context *quirks = evdev_libinput_context(device)->quirks;
+ struct quirks *q = quirks_fetch_for_device(quirks, device->udev_device);
+ struct quirk_range r;
+ bool status = false;
+
+ /* Note: the quirk term "range" refers to the hi/lo settings, not the
+ * full available range for the pressure axis */
+ if (q && quirks_get_range(q, QUIRK_ATTR_PRESSURE_RANGE, &r)) {
+ if (r.lower < r.upper) {
+ *hi = r.lower;
+ *lo = r.upper;
+ status = true;
+ } else {
+ evdev_log_info(device, "Invalid pressure range, using defaults\n");
+ }
+ }
+
+ quirks_unref(q);
+ return status;
+}
+
+static void
+apply_pressure_range_configuration(struct tablet_dispatch *tablet,
+ struct libinput_tablet_tool *tool)
+{
+ struct evdev_device *device = tablet->device;
+
+ if (!libevdev_has_event_code(device->evdev, EV_ABS, ABS_PRESSURE) ||
+ (tool->pressure.range.min == tool->pressure.wanted_range.min &&
+ tool->pressure.range.max == tool->pressure.wanted_range.max))
+ return;
+
+ double min = tool->pressure.wanted_range.min;
+ double max = tool->pressure.wanted_range.max;
+
+ struct input_absinfo abs = *libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
+
+ int minimum = axis_range_percentage(&abs, min * 100.0);
+ int maximum = axis_range_percentage(&abs, max * 100.0);
+
+ abs.minimum = minimum;
+ abs.maximum = maximum;
+
+ /* Only use the quirk pressure range if we don't have a custom range */
+ int hi, lo;
+ if (tool->pressure.wanted_range.min != 0.0 ||
+ tool->pressure.wanted_range.max != 1.0 ||
+ !tablet_get_quirked_pressure_thresholds(tablet, &hi, &lo)) {
+ /* 5 and 1% of the pressure range */
+ hi = axis_range_percentage(&abs, 5);
+ lo = axis_range_percentage(&abs, 1);
+ }
+
+ tool->pressure.abs_pressure = abs;
+ tool->pressure.threshold.upper = hi;
+ tool->pressure.threshold.lower = lo;
+ tool->pressure.range.min = tool->pressure.wanted_range.min;
+ tool->pressure.range.max = tool->pressure.wanted_range.max;
+
+ /* Disable any heuristics */
+ if (tool->pressure.has_configured_range) {
+ tool->pressure.has_offset = true;
+ tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
+ }
+}
+
static inline void
-tool_set_pressure_thresholds(struct tablet_dispatch *tablet,
- struct libinput_tablet_tool *tool)
+tool_init_pressure_thresholds(struct tablet_dispatch *tablet,
+ struct libinput_tablet_tool *tool)
{
struct evdev_device *device = tablet->device;
const struct input_absinfo *pressure, *distance;
- struct quirks_context *quirks = NULL;
- struct quirks *q = NULL;
- struct quirk_range r;
- int lo = 0, hi = 1;
tool->pressure.offset = 0;
tool->pressure.has_offset = false;
pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
- if (!pressure)
- goto out;
-
- quirks = evdev_libinput_context(device)->quirks;
- q = quirks_fetch_for_device(quirks, device->udev_device);
+ if (!pressure) {
+ tool->pressure.threshold.upper = 1;
+ tool->pressure.threshold.lower = 0;
+ return;
+ }
distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE);
if (distance) {
@@ -1106,24 +1176,40 @@ tool_set_pressure_thresholds(struct tablet_dispatch *tablet,
tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1;
}
- /* 5 and 1% of the pressure range */
- hi = axis_range_percentage(pressure, 5);
- lo = axis_range_percentage(pressure, 1);
+ apply_pressure_range_configuration(tablet, tool);
+}
- if (q && quirks_get_range(q, QUIRK_ATTR_PRESSURE_RANGE, &r)) {
- if (r.lower >= r.upper) {
- evdev_log_info(device,
- "Invalid pressure range, using defaults\n");
- } else {
- hi = r.upper;
- lo = r.lower;
- }
- }
-out:
- tool->pressure.threshold.upper = hi;
- tool->pressure.threshold.lower = lo;
+static int
+pressure_range_is_available(struct libinput_tablet_tool *tool)
+{
+ return bit_is_set(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE);
+}
- quirks_unref(q);
+static enum libinput_config_status
+pressure_range_set(struct libinput_tablet_tool *tool, double min, double max)
+{
+ if (min < 0.0 || min >= 1.0 || max <= 0.0 || max > 1.0 || max <= min)
+ return LIBINPUT_CONFIG_STATUS_INVALID;
+
+ tool->pressure.wanted_range.min = min;
+ tool->pressure.wanted_range.max = max;
+ tool->pressure.has_configured_range = true;
+
+ return LIBINPUT_CONFIG_STATUS_SUCCESS;
+}
+
+static void
+pressure_range_get(struct libinput_tablet_tool *tool, double *min, double *max)
+{
+ *min = tool->pressure.wanted_range.min;
+ *max = tool->pressure.wanted_range.max;
+}
+
+static void
+pressure_range_get_default(struct libinput_tablet_tool *tool, double *min, double *max)
+{
+ *min = 0.0;
+ *max = 1.0;
}
static struct libinput_tablet_tool *
@@ -1139,14 +1225,31 @@ tablet_new_tool(struct tablet_dispatch *tablet,
.serial = serial,
.tool_id = tool_id,
.refcount = 1,
+
+ .pressure.range.min = 0.0,
+ .pressure.range.max = 0.0, /* to trigger configuration */
+ .pressure.wanted_range.min = 0.0,
+ .pressure.wanted_range.max = 1.0,
+
+ .config.pressure_range.is_available = pressure_range_is_available,
+ .config.pressure_range.set = pressure_range_set,
+ .config.pressure_range.get = pressure_range_get,
+ .config.pressure_range.get_default = pressure_range_get_default,
};
+ /* Copy the pressure axis for configuring the range later */
+ struct evdev_device *device = tablet->device;
+ const struct input_absinfo *abs = libevdev_get_abs_info(device->evdev,
+ ABS_PRESSURE);
+ if (abs)
+ tool->pressure.abs_pressure = *abs;
+
/* FIXME: known bug - the pressure threshold is only set once on the
* first tablet, if a tool is used across multiple tablets with
* different pressure ranges this will be wrong. This case is niche
* enough that we can fix it if we ever run into it.
*/
- tool_set_pressure_thresholds(tablet, tool);
+ tool_init_pressure_thresholds(tablet, tool);
tool_set_bits(tablet, tool);
return tool;
@@ -1272,6 +1375,8 @@ sanitize_pressure_distance(struct tablet_dispatch *tablet,
*pressure;
distance = libevdev_get_abs_info(tablet->device->evdev, ABS_DISTANCE);
+ /* Note: for pressure/distance sanitization we use the real pressure
+ axis, not our configured one */
pressure = libevdev_get_abs_info(tablet->device->evdev, ABS_PRESSURE);
if (!pressure || !distance)
@@ -1354,7 +1459,7 @@ update_pressure_offset(struct tablet_dispatch *tablet,
const struct input_absinfo *pressure =
libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
- if (!pressure ||
+ if (!pressure || tool->pressure.has_configured_range ||
!bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE))
return;
@@ -1383,7 +1488,7 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
const struct input_absinfo *pressure, *distance;
int offset;
- if (tool->pressure.has_offset ||
+ if (tool->pressure.has_offset || tool->pressure.has_configured_range ||
!bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE))
return;
@@ -1999,6 +2104,7 @@ reprocess:
tablet_set_status(tablet, TABLET_BUTTONS_RELEASED);
if (tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT))
tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT);
+ apply_pressure_range_configuration(tablet, tool);
} else if (tablet_has_status(tablet, TABLET_TOOL_ENTERING_PROXIMITY)) {
tablet_mark_all_axes_changed(tablet, tool);
update_pressure_offset(tablet, device, tool);
diff --git a/src/libinput-private.h b/src/libinput-private.h
index f3f441cb..b218b390 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -72,6 +72,11 @@ struct normalized_range_coords {
double x, y;
};
+/* A [0.0, 1.0] normalized range */
+struct normalized_range {
+ double min, max;
+};
+
/* A pair of angles in degrees */
struct wheel_angle {
double x, y;
@@ -449,6 +454,13 @@ enum pressure_heuristic_state {
PRESSURE_HEURISTIC_STATE_DONE, /** Decision's been made, live with it */
};
+struct libinput_tablet_tool_config_pressure_range {
+ int (*is_available)(struct libinput_tablet_tool *tool);
+ enum libinput_config_status (*set)(struct libinput_tablet_tool *tool, double min, double max);
+ void (*get)(struct libinput_tablet_tool *tool, double *min, double *max);
+ void (*get_default)(struct libinput_tablet_tool *tool, double *min, double *max);
+};
+
struct libinput_tablet_tool {
struct list link;
uint32_t serial;
@@ -460,12 +472,22 @@ struct libinput_tablet_tool {
void *user_data;
struct {
+ /* The configured axis we actually work with */
+ struct input_absinfo abs_pressure;
+ struct normalized_range range;
+ struct normalized_range wanted_range;
+ bool has_configured_range;
+
struct threshold threshold; /* in device coordinates */
int offset; /* in device coordinates */
bool has_offset;
enum pressure_heuristic_state heuristic_state;
} pressure;
+
+ struct {
+ struct libinput_tablet_tool_config_pressure_range pressure_range;
+ } config;
};
struct libinput_tablet_pad_mode_group {
diff --git a/src/libinput.c b/src/libinput.c
index f656dd9c..42be3f92 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -4728,6 +4728,75 @@ libinput_device_config_rotation_get_default_angle(struct libinput_device *device
return device->config.rotation->get_default_angle(device);
}
+LIBINPUT_EXPORT int
+libinput_tablet_tool_config_pressure_range_is_available(struct libinput_tablet_tool *tool)
+{
+ if (!tool->config.pressure_range.is_available)
+ return 0;
+
+ return tool->config.pressure_range.is_available(tool);
+}
+
+LIBINPUT_EXPORT enum libinput_config_status
+libinput_tablet_tool_config_pressure_range_set(struct libinput_tablet_tool *tool,
+ double minimum,
+ double maximum)
+{
+ if (!libinput_tablet_tool_config_pressure_range_is_available(tool))
+ return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+
+ if (minimum < 0.0 || minimum >= 1.0 ||
+ maximum <= 0.0 || maximum > 1.0 ||
+ maximum <= minimum)
+ return LIBINPUT_CONFIG_STATUS_INVALID;
+
+ return tool->config.pressure_range.set(tool, minimum, maximum);
+}
+
+LIBINPUT_EXPORT double
+libinput_tablet_tool_config_pressure_range_get_minimum(struct libinput_tablet_tool *tool)
+{
+ double min = 0.0, max = 1.0;
+
+ if (libinput_tablet_tool_config_pressure_range_is_available(tool))
+ tool->config.pressure_range.get(tool, &min, &max);
+
+ return min;
+}
+
+LIBINPUT_EXPORT double
+libinput_tablet_tool_config_pressure_range_get_maximum(struct libinput_tablet_tool *tool)
+{
+ double min = 0.0, max = 1.0;
+
+ if (libinput_tablet_tool_config_pressure_range_is_available(tool))
+ tool->config.pressure_range.get(tool, &min, &max);
+
+ return max;
+}
+
+LIBINPUT_EXPORT double
+libinput_tablet_tool_config_pressure_range_get_default_minimum(struct libinput_tablet_tool *tool)
+{
+ double min = 0.0, max = 1.0;
+
+ if (libinput_tablet_tool_config_pressure_range_is_available(tool))
+ tool->config.pressure_range.get_default(tool, &min, &max);
+
+ return min;
+}
+
+LIBINPUT_EXPORT double
+libinput_tablet_tool_config_pressure_range_get_default_maximum(struct libinput_tablet_tool *tool)
+{
+ double min = 0.0, max = 1.0;
+
+ if (libinput_tablet_tool_config_pressure_range_is_available(tool))
+ tool->config.pressure_range.get_default(tool, &min, &max);
+
+ return max;
+}
+
#if HAVE_LIBWACOM
WacomDeviceDatabase *
libinput_libwacom_ref(struct libinput *li)
diff --git a/src/libinput.h b/src/libinput.h
index 6258a1af..67445946 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -6398,6 +6398,136 @@ libinput_device_config_rotation_get_angle(struct libinput_device *device);
unsigned int
libinput_device_config_rotation_get_default_angle(struct libinput_device *device);
+/**
+ * @ingroup config
+ *
+ * Check if a tablet tool can have a custom pressure range.
+ *
+ * @param tool The libinput tool
+ * @return Non-zero if a device has an adjustible pressure range, zero otherwise.
+ *
+ * @see libinput_tablet_tool_config_pressure_range_set
+ * @see libinput_tablet_tool_config_pressure_range_get_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ *
+ * @since 1.25
+ */
+int
+libinput_tablet_tool_config_pressure_range_is_available(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Set the pressure range for this tablet tool. This maps the given logical
+ * pressure range into the available hardware pressure range so that a hardware
+ * pressure of the given minimum value maps into a logical pressure of 0.0 (as
+ * returned by libinput_event_tablet_tool_get_pressure()) and the hardware
+ * pressure of the given maximum value is mapped into the logical pressure
+ * of 1.0 (as returned by . libinput_event_tablet_tool_get_pressure())
+ *
+ * The minimum value must be less than the maximum value, libinput may
+ * libinput may require the values to have a specific distance to each other,
+ * i.e. that (maximium - minimum > N) for an implementation-defined value of N.
+ *
+ * @param tool The libinput tool
+ * @param minimum The minimum pressure value in the range [0.0, 1.0)
+ * @param maximum The maximum pressure value in the range (0.0, 1.0]
+ *
+ * @return A config status code
+ *
+ * @see libinput_tablet_tool_config_pressure_range_is_available
+ * @see libinput_tablet_tool_config_pressure_range_get_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ */
+enum libinput_config_status
+libinput_tablet_tool_config_pressure_range_set(struct libinput_tablet_tool *tool,
+ double minimum,
+ double maximum);
+
+/**
+ * @ingroup config
+ *
+ * Get the minimum pressure value for this tablet tool, normalized to the
+ * range [0.0, 1.0] of the available hardware pressure.
+ *
+ * If the tool does not support pressure range configuration, the return value
+ * of this function is always 0.0.
+ *
+ * @param tool The libinput tool
+ * @return The minimum pressure value for this tablet tool
+ *
+ * @see libinput_tablet_tool_config_pressure_range_is_available
+ * @see libinput_tablet_tool_config_pressure_range_get_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ */
+double
+libinput_tablet_tool_config_pressure_range_get_minimum(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Get the maximum pressure value for this tablet tool, normalized to the
+ * range [0.0, 1.0] of the available hardware pressure.
+ *
+ * If the tool does not support pressure range configuration, the return value
+ * of this function is always 1.0.
+ *
+ * @param tool The libinput tool
+ * @return The maximum pressure value for this tablet tool
+ *
+ * @see libinput_tablet_tool_config_pressure_range_is_available
+ * @see libinput_tablet_tool_config_pressure_range_get_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ */
+double
+libinput_tablet_tool_config_pressure_range_get_maximum(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Get the minimum pressure value for this tablet tool, normalized to the
+ * range [0.0, 1.0] of the available hardware pressure.
+ *
+ * If the tool does not support pressure range configuration, the return value
+ * of this function is always 0.0.
+ *
+ * @param tool The libinput tool
+ * @return The minimum pressure value for this tablet tool
+ *
+ * @see libinput_tablet_tool_config_pressure_range_is_available
+ * @see libinput_tablet_tool_config_pressure_range_get_minimum
+ * @see libinput_tablet_tool_config_pressure_range_get_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ */
+double
+libinput_tablet_tool_config_pressure_range_get_default_minimum(struct libinput_tablet_tool *tool);
+
+/**
+ * @ingroup config
+ *
+ * Get the maximum pressure value for this tablet tool, normalized to the
+ * range [0.0, 1.0] of the available hardware pressure.
+ *
+ * If the tool does not support pressure range configuration, the return value
+ * of this function is always 1.0.
+ *
+ * @param tool The libinput tool
+ * @return The maximum pressure value for this tablet tool
+ *
+ * @see libinput_tablet_tool_config_pressure_range_is_available
+ * @see libinput_tablet_tool_config_pressure_range_get_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_maximum
+ * @see libinput_tablet_tool_config_pressure_range_get_default_maximum
+ */
+double
+libinput_tablet_tool_config_pressure_range_get_default_maximum(struct libinput_tablet_tool *tool);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/libinput.sym b/src/libinput.sym
index b89059c1..78739059 100644
--- a/src/libinput.sym
+++ b/src/libinput.sym
@@ -332,4 +332,13 @@ LIBINPUT_1.23 {
libinput_config_accel_destroy;
libinput_device_config_accel_apply;
libinput_config_accel_set_points;
-} LIBINPUT_1.21; \ No newline at end of file
+} LIBINPUT_1.21;
+
+LIBINPUT_1.26 {
+ libinput_tablet_tool_config_pressure_range_is_available;
+ libinput_tablet_tool_config_pressure_range_set;
+ libinput_tablet_tool_config_pressure_range_get_minimum;
+ libinput_tablet_tool_config_pressure_range_get_maximum;
+ libinput_tablet_tool_config_pressure_range_get_default_minimum;
+ libinput_tablet_tool_config_pressure_range_get_default_maximum;
+} LIBINPUT_1.23;
diff --git a/test/test-tablet.c b/test/test-tablet.c
index b94f3568..804720d3 100644
--- a/test/test-tablet.c
+++ b/test/test-tablet.c
@@ -4150,6 +4150,317 @@ START_TEST(tablet_pressure_range)
}
END_TEST
+START_TEST(tablet_pressure_config)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_tablet_tool *tev;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 0 },
+ { ABS_PRESSURE, 10 },
+ { -1, -1 },
+ };
+
+ litest_tablet_proximity_in(dev, 5, 100, axes);
+ litest_drain_events(li);
+ libinput_dispatch(li);
+
+ litest_tablet_motion(dev, 70, 70, axes);
+ libinput_dispatch(li);
+
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ struct libinput_tablet_tool *tool = libinput_event_tablet_tool_get_tool(tev);
+
+ ck_assert(libinput_tablet_tool_config_pressure_range_is_available(tool));
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_minimum(tool), 0.0);
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_maximum(tool), 1.0);
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_default_minimum(tool), 0.0);
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_default_maximum(tool), 1.0);
+
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.0, 1.0),
+ LIBINPUT_CONFIG_STATUS_SUCCESS);
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.2, 0.5),
+ LIBINPUT_CONFIG_STATUS_SUCCESS);
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, -0.1, 1.0),
+ LIBINPUT_CONFIG_STATUS_INVALID);
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.0, 0.0),
+ LIBINPUT_CONFIG_STATUS_INVALID);
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 1.0, 1.0),
+ LIBINPUT_CONFIG_STATUS_INVALID);
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.0, 1.1),
+ LIBINPUT_CONFIG_STATUS_INVALID);
+
+ /* The last successful one */
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_minimum(tool), 0.2);
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_maximum(tool), 0.5);
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_default_minimum(tool), 0.0);
+ ck_assert_double_eq(libinput_tablet_tool_config_pressure_range_get_default_maximum(tool), 1.0);
+
+ libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(tablet_pressure_config_set_minimum)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_tablet_tool *tev;
+ struct libinput_tablet_tool *tool;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 0 },
+ { ABS_PRESSURE, 10 },
+ { -1, -1 },
+ };
+ double p, old_pressure;
+
+ litest_tablet_proximity_in(dev, 5, 100, axes);
+ litest_drain_events(li);
+ libinput_dispatch(li);
+
+ litest_tablet_motion(dev, 70, 70, axes);
+ libinput_dispatch(li);
+
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ tool = libinput_event_tablet_tool_get_tool(tev);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_gt(p, 0.0);
+ old_pressure = p;
+
+ ck_assert(libinput_tablet_tool_config_pressure_range_is_available(tool));
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.4, 1.0),
+ LIBINPUT_CONFIG_STATUS_SUCCESS);
+ libinput_event_destroy(event);
+
+ /* config doesn't take effect until we're out of prox */
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_eq(p, old_pressure);
+ libinput_event_destroy(event);
+ }
+
+ litest_tablet_proximity_out(dev);
+ litest_timeout_tablet_proxout();
+ litest_drain_events(li);
+
+ /* 10% hw value is below our thresholds, so logical zero */
+ litest_axis_set_value(axes, ABS_PRESSURE, 10);
+ litest_tablet_proximity_in(dev, 70, 70, axes);
+ litest_drain_events(li);
+
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_eq(p, 0.00);
+ libinput_event_destroy(event);
+ }
+
+ /* 50% hw value mapped into a reduced range of 60% from hw range,
+ plus the 1% minimum offset, so our output pressure is actually ~15% */
+ litest_axis_set_value(axes, ABS_PRESSURE, 50);
+ litest_tablet_motion(dev, 70, 70, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_gt(p, 0.15);
+ ck_assert_double_le(p, 0.16);
+ libinput_event_destroy(event);
+
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_ge(p, 0.15);
+ ck_assert_double_le(p, 0.16);
+ libinput_event_destroy(event);
+ }
+}
+END_TEST
+
+START_TEST(tablet_pressure_config_set_maximum)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_tablet_tool *tev;
+ struct libinput_tablet_tool *tool;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 0 },
+ { ABS_PRESSURE, 10 },
+ { -1, -1 },
+ };
+ double p, old_pressure;
+
+ litest_tablet_proximity_in(dev, 5, 100, axes);
+ litest_drain_events(li);
+ libinput_dispatch(li);
+
+ litest_tablet_motion(dev, 70, 70, axes);
+ libinput_dispatch(li);
+
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ tool = libinput_event_tablet_tool_get_tool(tev);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_gt(p, 0.0);
+ old_pressure = p;
+
+ ck_assert(libinput_tablet_tool_config_pressure_range_is_available(tool));
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.0, 0.6),
+ LIBINPUT_CONFIG_STATUS_SUCCESS);
+ libinput_event_destroy(event);
+
+ /* config doesn't take effect until we're out of prox */
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_eq(p, old_pressure);
+ libinput_event_destroy(event);
+ }
+
+ litest_tablet_proximity_out(dev);
+ litest_timeout_tablet_proxout();
+ libinput_dispatch(li);
+
+ litest_axis_set_value(axes, ABS_PRESSURE, 10);
+ litest_tablet_proximity_in(dev, 70, 70, axes);
+ litest_drain_events(li);
+
+ /* 10% hw value mapped into a reduced range of 60% from hw range,
+ plus the 1% minimum offset so our output pressure is actually ~15% */
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_ge(p, 0.15);
+ ck_assert_double_le(p, 0.16);
+ ck_assert_double_gt(p, old_pressure);
+ libinput_event_destroy(event);
+ }
+
+ /* 50% hw value mapped into a reduced range of 60% from hw range,
+ plus the 1% minimum offset, so our output pressure is actually ~83% */
+ litest_axis_set_value(axes, ABS_PRESSURE, 50);
+
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_ge(p, 0.82);
+ ck_assert_double_le(p, 0.84);
+ libinput_event_destroy(event);
+ }
+
+ for (int hwp = 60; hwp < 100; hwp += 10) {
+ litest_axis_set_value(axes, ABS_PRESSURE, hwp);
+
+ for (int pos = 71; pos < 80; pos++) {
+ litest_tablet_motion(dev, pos, pos, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_eq(p, 1.0);
+ libinput_event_destroy(event);
+ }
+ }
+}
+END_TEST
+
+START_TEST(tablet_pressure_config_set_range)
+{
+ struct litest_device *dev = litest_current_device();
+ struct libinput *li = dev->libinput;
+ struct libinput_event *event;
+ struct libinput_event_tablet_tool *tev;
+ struct libinput_tablet_tool *tool;
+ struct axis_replacement axes[] = {
+ { ABS_DISTANCE, 0 },
+ { ABS_PRESSURE, 10 },
+ { -1, -1 },
+ };
+ double p, old_pressure;
+
+ litest_tablet_proximity_in(dev, 5, 100, axes);
+ litest_drain_events(li);
+ libinput_dispatch(li);
+
+ litest_tablet_motion(dev, 70, 70, axes);
+ libinput_dispatch(li);
+
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ tool = libinput_event_tablet_tool_get_tool(tev);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_gt(p, 0.0);
+ old_pressure = p;
+
+ ck_assert(libinput_tablet_tool_config_pressure_range_is_available(tool));
+ ck_assert_int_eq(libinput_tablet_tool_config_pressure_range_set(tool, 0.4, 0.6),
+ LIBINPUT_CONFIG_STATUS_SUCCESS);
+ libinput_event_destroy(event);
+
+ /* config doesn't take effect until we're out of prox */
+ for (int i = 71; i < 80; i++) {
+ litest_tablet_motion(dev, i, i, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ ck_assert_double_eq(p, old_pressure);
+ libinput_event_destroy(event);
+ }
+
+ litest_tablet_proximity_out(dev);
+ litest_timeout_tablet_proxout();
+ litest_drain_events(li);
+
+ litest_tablet_proximity_in(dev, 70, 70, axes);
+ litest_drain_events(li);
+
+ for (double pressure = 0.0, i = 71; pressure <= 100; pressure += 5, i += 0.2) {
+ litest_axis_set_value(axes, ABS_PRESSURE, pressure);
+ litest_tablet_motion(dev, i, i, axes);
+ libinput_dispatch(li);
+ event = libinput_get_event(li);
+ if (libinput_event_get_type(event) == LIBINPUT_EVENT_TABLET_TOOL_AXIS)
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+ else
+ tev = litest_is_tablet_event(event, LIBINPUT_EVENT_TABLET_TOOL_TIP);
+ p = libinput_event_tablet_tool_get_pressure(tev);
+ if (pressure <= 40) {
+ ck_assert_double_eq(p, 0.0);
+ } else if (pressure >= 60) {
+ ck_assert_double_eq(p, 1.0);
+ } else {
+ ck_assert_double_ge(p, (pressure - 1 - 40)/20.0);
+ ck_assert_double_le(p, (pressure - 40)/20.0);
+ }
+ libinput_event_destroy(event);
+ }
+}
+END_TEST
+
static void
pressure_threshold_warning(struct libinput *libinput,
enum libinput_log_priority priority,
@@ -6335,6 +6646,10 @@ TEST_COLLECTION(tablet)
litest_add_for_device(tablet_pressure_offset_increase, LITEST_WACOM_HID4800_PEN);
litest_add_for_device(tablet_pressure_offset_exceed_threshold, LITEST_WACOM_HID4800_PEN);
+ litest_add(tablet_pressure_config, LITEST_TABLET, LITEST_TOTEM);
+ litest_add(tablet_pressure_config_set_minimum, LITEST_TABLET, LITEST_TOTEM);
+ litest_add(tablet_pressure_config_set_maximum, LITEST_TABLET, LITEST_TOTEM);
+ litest_add(tablet_pressure_config_set_range, LITEST_TABLET, LITEST_TOTEM);
litest_add_for_device(tablet_distance_range, LITEST_WACOM_INTUOS);