tablet: revamp the tablet pressure range handling

Commit 48cd4c7287 ("tablet: track pressure ranges per tablet") added
tracking of pressure ranges per tablet by storing ranges for multiple
(up to 4) tablets in the tool. This doesn't scale well, had the
disadvantage of the range only being updated on out-of-proximity, and is
the wrong approach anyway.

Turns out we can update the pressure range on proximity in since we
haven't processed the pressure values yet at that stage. This gives us
better behavior when switching between tablet devices (including unplug)
as the pen will only lag behind once (when setting the range) instead
of once per new tablet.

However, since the offset (which is a tool issue) applies on top of the
pressure range (which is a tablet property) this requires that we now
track the offset as percent of the range.

So on proximity in we apply the new tablet range, then apply the e.g. 5%
pressure offset within this range.

This means we no longer have to track multiple tablets since it'll just
apply on the current tablet when in proximity.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1172>
This commit is contained in:
Peter Hutterer 2025-04-02 11:40:19 +10:00 committed by Marge Bot
parent 63a8ad2ead
commit e9f3fc080c
3 changed files with 109 additions and 94 deletions

View file

@ -76,21 +76,7 @@ static struct libinput_tablet_tool_pressure_threshold*
tablet_tool_get_threshold(struct tablet_dispatch *tablet, tablet_tool_get_threshold(struct tablet_dispatch *tablet,
struct libinput_tablet_tool *tool) struct libinput_tablet_tool *tool)
{ {
ARRAY_FOR_EACH(tool->pressure.thresholds, threshold) { return &tool->pressure.threshold;
if (threshold->tablet_id == tablet->tablet_id) {
return threshold;
}
}
/* If we ever get here, we failed detecting the proximity for this tablet
* (or we have too many tablets). Return the first one which will
* make things work incorrectly but we don't need to NULL-check
* everything for an extremely unlikely situtation */
evdev_log_bug_libinput(tablet->device,
"Failed to find tablet_id %d for pressure offsets\n",
tablet->tablet_id);
return &tool->pressure.thresholds[0];
} }
/* Merge the previous state with the current one so all buttons look like /* Merge the previous state with the current one so all buttons look like
@ -1213,40 +1199,8 @@ apply_pressure_range_configuration(struct tablet_dispatch *tablet,
tool->pressure.range.max == tool->pressure.wanted_range.max)) tool->pressure.range.max == tool->pressure.wanted_range.max))
return; 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);
}
struct libinput_tablet_tool_pressure_threshold *threshold =
tablet_tool_get_threshold(tablet, tool);
threshold->abs_pressure = abs;
threshold->threshold.upper = hi;
threshold->threshold.lower = lo;
tool->pressure.range.min = tool->pressure.wanted_range.min; tool->pressure.range.min = tool->pressure.wanted_range.min;
tool->pressure.range.max = tool->pressure.wanted_range.max; tool->pressure.range.max = tool->pressure.wanted_range.max;
/* Disable any heuristics */
if (tool->pressure.has_configured_range) {
threshold->has_offset = true;
threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
}
} }
static inline void static inline void
@ -1258,7 +1212,7 @@ tool_init_pressure_thresholds(struct tablet_dispatch *tablet,
const struct input_absinfo *pressure, *distance; const struct input_absinfo *pressure, *distance;
threshold->tablet_id = tablet->tablet_id; threshold->tablet_id = tablet->tablet_id;
threshold->offset = 0; threshold->offset = pressure_offset_from_double(0.0);
threshold->has_offset = false; threshold->has_offset = false;
threshold->threshold.upper = 1; threshold->threshold.upper = 1;
threshold->threshold.lower = 0; threshold->threshold.lower = 0;
@ -1271,10 +1225,10 @@ tool_init_pressure_thresholds(struct tablet_dispatch *tablet,
distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE);
if (distance) { if (distance) {
threshold->offset = pressure->minimum; threshold->offset = pressure_offset_from_double(0.0);
threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
} else { } else {
threshold->offset = pressure->maximum; threshold->offset = pressure_offset_from_double(1.0);
threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1; threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1;
} }
@ -1339,7 +1293,7 @@ tablet_new_tool(struct tablet_dispatch *tablet,
.config.pressure_range.get_default = pressure_range_get_default, .config.pressure_range.get_default = pressure_range_get_default,
}; };
tool_init_pressure_thresholds(tablet, tool, &tool->pressure.thresholds[0]); tool_init_pressure_thresholds(tablet, tool, &tool->pressure.threshold);
tool_set_bits(tablet, tool); tool_set_bits(tablet, tool);
return tool; return tool;
@ -1397,15 +1351,6 @@ tablet_get_tool(struct tablet_dispatch *tablet,
if (!tool) { if (!tool) {
tool = tablet_new_tool(tablet, type, tool_id, serial); tool = tablet_new_tool(tablet, type, tool_id, serial);
list_insert(tool_list, &tool->link); list_insert(tool_list, &tool->link);
} else {
ARRAY_FOR_EACH(tool->pressure.thresholds, t) {
if (t->tablet_id == tablet->tablet_id)
break;
if (t->tablet_id == 0) {
tool_init_pressure_thresholds(tablet, tool, t);
break;
}
}
} }
return tool; return tool;
@ -1540,18 +1485,20 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet,
} }
static void static void
set_pressure_offset(struct libinput_tablet_tool_pressure_threshold *threshold, int offset) set_pressure_offset(struct libinput_tablet_tool_pressure_threshold *threshold,
pressure_offset_t offset_in_percent)
{ {
threshold->offset = offset; threshold->offset = offset_in_percent;
threshold->has_offset = true; threshold->has_offset = true;
/* Adjust the tresholds accordingly - we use the same gap (4% in /* Adjust the tresholds accordingly - we use the same gap (4% in
* device coordinates) between upper and lower as before which isn't * device coordinates) between upper and lower as before which isn't
* technically correct (our range shrunk) but it's easy to calculate. * technically correct (our range shrunk) but it's easy to calculate.
*/ */
int units = pressure_offset_to_absinfo(offset_in_percent, &threshold->abs_pressure);
int gap = threshold->threshold.upper - threshold->threshold.lower; int gap = threshold->threshold.upper - threshold->threshold.lower;
threshold->threshold.lower = offset; threshold->threshold.lower = units;
threshold->threshold.upper = offset + gap; threshold->threshold.upper = units + gap;
} }
static void static void
@ -1574,14 +1521,14 @@ update_pressure_offset(struct tablet_dispatch *tablet,
* If we are still pending the offset decision, only update the observed * If we are still pending the offset decision, only update the observed
* offset value, don't actually set it to have an offset. * offset value, don't actually set it to have an offset.
*/ */
int offset = pressure->value; pressure_offset_t offset = pressure_offset_from_absinfo(pressure, pressure->value);
struct libinput_tablet_tool_pressure_threshold *threshold = struct libinput_tablet_tool_pressure_threshold *threshold =
tablet_tool_get_threshold(tablet, tool); tablet_tool_get_threshold(tablet, tool);
if (threshold->has_offset) { if (threshold->has_offset) {
if (offset < threshold->offset) if (pressure_offset_cmp(offset, threshold->offset) < 0)
set_pressure_offset(threshold, offset); set_pressure_offset(threshold, offset);
} else if (threshold->heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) { } else if (threshold->heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) {
threshold->offset = min(offset, threshold->offset); threshold->offset = pressure_offset_min(offset, threshold->offset);
} }
} }
@ -1591,7 +1538,6 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
struct libinput_tablet_tool *tool) struct libinput_tablet_tool *tool)
{ {
const struct input_absinfo *pressure, *distance; const struct input_absinfo *pressure, *distance;
int offset;
if (tool->pressure.has_configured_range || if (tool->pressure.has_configured_range ||
!bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE))
@ -1608,10 +1554,11 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
if (!pressure) if (!pressure)
return; return;
offset = pressure->value; int units = pressure->value;
if (offset <= pressure->minimum) if (units <= pressure->minimum)
return; return;
pressure_offset_t offset = pressure_offset_from_absinfo(pressure, units);
if (distance) { if (distance) {
/* If we're closer than 50% of the distance axis, skip pressure /* If we're closer than 50% of the distance axis, skip pressure
* offset detection, too likely to be wrong */ * offset detection, too likely to be wrong */
@ -1624,8 +1571,8 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
* offset is updated during motion events so by the time the * offset is updated during motion events so by the time the
* deciding prox-in arrives we should know the minimum offset. * deciding prox-in arrives we should know the minimum offset.
*/ */
if (offset > pressure->minimum) if (units > pressure->minimum)
threshold->offset = min(offset, threshold->offset); threshold->offset = pressure_offset_min(offset, threshold->offset);
switch (threshold->heuristic_state) { switch (threshold->heuristic_state) {
case PRESSURE_HEURISTIC_STATE_PROXIN1: case PRESSURE_HEURISTIC_STATE_PROXIN1:
@ -1634,6 +1581,7 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
return; return;
case PRESSURE_HEURISTIC_STATE_DECIDE: case PRESSURE_HEURISTIC_STATE_DECIDE:
threshold->heuristic_state++; threshold->heuristic_state++;
units = pressure_offset_to_absinfo(threshold->offset, pressure);
offset = threshold->offset; offset = threshold->offset;
break; break;
case PRESSURE_HEURISTIC_STATE_DONE: case PRESSURE_HEURISTIC_STATE_DONE:
@ -1641,10 +1589,11 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
} }
} }
if (offset <= pressure->minimum) if (units <= pressure->minimum) {
return; return;
}
if (offset > axis_range_percentage(pressure, 50)) { if (pressure_offset_gt(offset, 0.5)) {
evdev_log_error(device, evdev_log_error(device,
"Ignoring pressure offset greater than 50%% detected on tool %s (serial %#x). " "Ignoring pressure offset greater than 50%% detected on tool %s (serial %#x). "
"See %s/tablet-support.html\n", "See %s/tablet-support.html\n",
@ -2182,6 +2131,48 @@ tablet_get_current_tool(struct tablet_dispatch *tablet)
tablet->current_tool.serial); tablet->current_tool.serial);
} }
static void
update_pressure_range(struct tablet_dispatch *tablet,
struct evdev_device *device,
struct libinput_tablet_tool *tool)
{
double min = tool->pressure.range.min;
double max = tool->pressure.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.range.min != 0.0 ||
tool->pressure.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);
}
struct libinput_tablet_tool_pressure_threshold *threshold =
tablet_tool_get_threshold(tablet, tool);
threshold->abs_pressure = abs;
threshold->threshold.upper = hi;
threshold->threshold.lower = lo;
if (threshold->has_offset)
set_pressure_offset(threshold, threshold->offset);
/* Disable any heuristics */
if (tool->pressure.has_configured_range) {
threshold->has_offset = true;
threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
}
}
static void static void
tablet_flush(struct tablet_dispatch *tablet, tablet_flush(struct tablet_dispatch *tablet,
struct evdev_device *device, struct evdev_device *device,
@ -2234,6 +2225,7 @@ reprocess:
const double margin = 0.03; const double margin = 0.03;
if (is_inside_area(tablet, &point, margin)) { if (is_inside_area(tablet, &point, margin)) {
tablet_mark_all_axes_changed(tablet, tool); tablet_mark_all_axes_changed(tablet, tool);
update_pressure_range(tablet, device, tool);
update_pressure_offset(tablet, device, tool); update_pressure_offset(tablet, device, tool);
detect_pressure_offset(tablet, device, tool); detect_pressure_offset(tablet, device, tool);
detect_tool_contact(tablet, device, tool); detect_tool_contact(tablet, device, tool);
@ -2454,15 +2446,6 @@ tablet_destroy(struct evdev_dispatch *dispatch)
libinput_timer_cancel(&tablet->quirks.prox_out_timer); libinput_timer_cancel(&tablet->quirks.prox_out_timer);
libinput_timer_destroy(&tablet->quirks.prox_out_timer); libinput_timer_destroy(&tablet->quirks.prox_out_timer);
list_for_each(tool, &li->tool_list, link) {
ARRAY_FOR_EACH(tool->pressure.thresholds, threshold) {
if (threshold->tablet_id == tablet->tablet_id) {
threshold->tablet_id = 0;
break;
}
}
}
list_for_each_safe(tool, &tablet->tool_list, link) { list_for_each_safe(tool, &tablet->tool_list, link) {
libinput_tablet_tool_unref(tool); libinput_tablet_tool_unref(tool);
} }

View file

@ -88,13 +88,11 @@ totem_new_tool(struct totem_dispatch *totem)
.refcount = 1, .refcount = 1,
}; };
ARRAY_FOR_EACH(tool->pressure.thresholds, t) { tool->pressure.threshold.tablet_id = 0;
t->tablet_id = 0; tool->pressure.threshold.offset = pressure_offset_from_double(0.0);
t->offset = 0; tool->pressure.threshold.has_offset = false;
t->has_offset = false; tool->pressure.threshold.threshold.lower = 0;
t->threshold.lower = 0; tool->pressure.threshold.threshold.upper = 1;
t->threshold.upper = 1;
}
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X); set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X);
set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y); set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y);

View file

@ -41,9 +41,45 @@
#include "libinput-private-config.h" #include "libinput-private-config.h"
#include "libinput-util.h" #include "libinput-util.h"
#include "libinput-version.h" #include "libinput-version.h"
#include "util-newtype.h"
struct libinput_source; struct libinput_source;
/* The tablet tool pressure offset */
DECLARE_NEWTYPE(pressure_offset, double);
static inline pressure_offset_t
pressure_offset_from_range(double min, double max, double value)
{
return pressure_offset_from_double((value - min)/ (max - min));
}
static inline pressure_offset_t
pressure_offset_from_hundred(double hundred)
{
assert(hundred >= 0);
assert(hundred <= 100);
return pressure_offset_from_double(hundred/100);
}
static inline double
pressure_offset_to_hundred(pressure_offset_t pressure_offset)
{
return pressure_offset_as_double(pressure_offset) * 100;
}
static inline pressure_offset_t
pressure_offset_from_absinfo(const struct input_absinfo *abs, int value)
{
return pressure_offset_from_range(abs->minimum, abs->maximum, value);
}
static inline int
pressure_offset_to_absinfo(pressure_offset_t pressure_offset, const struct input_absinfo *abs)
{
return (abs->maximum - abs->minimum) * pressure_offset_as_double(pressure_offset) + abs->minimum;
}
/* A coordinate pair in device coordinates */ /* A coordinate pair in device coordinates */
struct device_coords { struct device_coords {
int x, y; int x, y;
@ -490,7 +526,7 @@ struct libinput_tablet_tool_pressure_threshold {
/* The configured axis we actually work with */ /* The configured axis we actually work with */
struct input_absinfo abs_pressure; struct input_absinfo abs_pressure;
struct threshold threshold; /* in device coordinates */ struct threshold threshold; /* in device coordinates */
int offset; /* in device coordinates */ pressure_offset_t offset;
bool has_offset; bool has_offset;
/* This gives us per-tablet heuristic state which is arguably /* This gives us per-tablet heuristic state which is arguably
@ -517,9 +553,7 @@ struct libinput_tablet_tool {
struct normalized_range wanted_range; struct normalized_range wanted_range;
bool has_configured_range; bool has_configured_range;
/* Hard-coded because I doubt we have users with more struct libinput_tablet_tool_pressure_threshold threshold;
* than 4 tablets at the same time */
struct libinput_tablet_tool_pressure_threshold thresholds[4];
} pressure; } pressure;
struct { struct {