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.
This commit is contained in:
Peter Hutterer 2024-01-15 17:07:17 +10:00
parent 47f0bce7ec
commit 4bc27543e9
8 changed files with 750 additions and 39 deletions

View file

@ -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.

View file

@ -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:
------------------------------------------------------------------------------

View file

@ -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);

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -332,4 +332,13 @@ LIBINPUT_1.23 {
libinput_config_accel_destroy;
libinput_device_config_accel_apply;
libinput_config_accel_set_points;
} LIBINPUT_1.21;
} 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;

View file

@ -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);