From e9f3fc080cdaeca259fbe006fa490a5ab00b32ec Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 2 Apr 2025 11:40:19 +1000 Subject: [PATCH] tablet: revamp the tablet pressure range handling Commit 48cd4c72870e ("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: --- src/evdev-tablet.c | 149 ++++++++++++++++++----------------------- src/evdev-totem.c | 12 ++-- src/libinput-private.h | 42 ++++++++++-- 3 files changed, 109 insertions(+), 94 deletions(-) diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index 6e1281ae..b4d1b7f2 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -76,21 +76,7 @@ static struct libinput_tablet_tool_pressure_threshold* tablet_tool_get_threshold(struct tablet_dispatch *tablet, struct libinput_tablet_tool *tool) { - ARRAY_FOR_EACH(tool->pressure.thresholds, 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]; + return &tool->pressure.threshold; } /* 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)) 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.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 @@ -1258,7 +1212,7 @@ tool_init_pressure_thresholds(struct tablet_dispatch *tablet, const struct input_absinfo *pressure, *distance; threshold->tablet_id = tablet->tablet_id; - threshold->offset = 0; + threshold->offset = pressure_offset_from_double(0.0); threshold->has_offset = false; threshold->threshold.upper = 1; 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); if (distance) { - threshold->offset = pressure->minimum; + threshold->offset = pressure_offset_from_double(0.0); threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; } else { - threshold->offset = pressure->maximum; + threshold->offset = pressure_offset_from_double(1.0); 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, }; - tool_init_pressure_thresholds(tablet, tool, &tool->pressure.thresholds[0]); + tool_init_pressure_thresholds(tablet, tool, &tool->pressure.threshold); tool_set_bits(tablet, tool); return tool; @@ -1397,15 +1351,6 @@ tablet_get_tool(struct tablet_dispatch *tablet, if (!tool) { tool = tablet_new_tool(tablet, type, tool_id, serial); 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; @@ -1540,18 +1485,20 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet, } 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; /* Adjust the tresholds accordingly - we use the same gap (4% in * device coordinates) between upper and lower as before which isn't * 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; - threshold->threshold.lower = offset; - threshold->threshold.upper = offset + gap; + threshold->threshold.lower = units; + threshold->threshold.upper = units + gap; } 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 * 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 = tablet_tool_get_threshold(tablet, tool); if (threshold->has_offset) { - if (offset < threshold->offset) + if (pressure_offset_cmp(offset, threshold->offset) < 0) set_pressure_offset(threshold, offset); } 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) { const struct input_absinfo *pressure, *distance; - int offset; if (tool->pressure.has_configured_range || !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) @@ -1608,10 +1554,11 @@ detect_pressure_offset(struct tablet_dispatch *tablet, if (!pressure) return; - offset = pressure->value; - if (offset <= pressure->minimum) + int units = pressure->value; + if (units <= pressure->minimum) return; + pressure_offset_t offset = pressure_offset_from_absinfo(pressure, units); if (distance) { /* If we're closer than 50% of the distance axis, skip pressure * 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 * deciding prox-in arrives we should know the minimum offset. */ - if (offset > pressure->minimum) - threshold->offset = min(offset, threshold->offset); + if (units > pressure->minimum) + threshold->offset = pressure_offset_min(offset, threshold->offset); switch (threshold->heuristic_state) { case PRESSURE_HEURISTIC_STATE_PROXIN1: @@ -1634,6 +1581,7 @@ detect_pressure_offset(struct tablet_dispatch *tablet, return; case PRESSURE_HEURISTIC_STATE_DECIDE: threshold->heuristic_state++; + units = pressure_offset_to_absinfo(threshold->offset, pressure); offset = threshold->offset; break; 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; + } - if (offset > axis_range_percentage(pressure, 50)) { + if (pressure_offset_gt(offset, 0.5)) { evdev_log_error(device, "Ignoring pressure offset greater than 50%% detected on tool %s (serial %#x). " "See %s/tablet-support.html\n", @@ -2182,6 +2131,48 @@ tablet_get_current_tool(struct tablet_dispatch *tablet) 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 tablet_flush(struct tablet_dispatch *tablet, struct evdev_device *device, @@ -2234,6 +2225,7 @@ reprocess: const double margin = 0.03; if (is_inside_area(tablet, &point, margin)) { tablet_mark_all_axes_changed(tablet, tool); + update_pressure_range(tablet, device, tool); update_pressure_offset(tablet, device, tool); detect_pressure_offset(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_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) { libinput_tablet_tool_unref(tool); } diff --git a/src/evdev-totem.c b/src/evdev-totem.c index 6707c18e..103b78cd 100644 --- a/src/evdev-totem.c +++ b/src/evdev-totem.c @@ -88,13 +88,11 @@ totem_new_tool(struct totem_dispatch *totem) .refcount = 1, }; - ARRAY_FOR_EACH(tool->pressure.thresholds, t) { - t->tablet_id = 0; - t->offset = 0; - t->has_offset = false; - t->threshold.lower = 0; - t->threshold.upper = 1; - } + tool->pressure.threshold.tablet_id = 0; + tool->pressure.threshold.offset = pressure_offset_from_double(0.0); + tool->pressure.threshold.has_offset = false; + tool->pressure.threshold.threshold.lower = 0; + tool->pressure.threshold.threshold.upper = 1; set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X); set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y); diff --git a/src/libinput-private.h b/src/libinput-private.h index 16955cee..80fff4bf 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -41,9 +41,45 @@ #include "libinput-private-config.h" #include "libinput-util.h" #include "libinput-version.h" +#include "util-newtype.h" 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 */ struct device_coords { int x, y; @@ -490,7 +526,7 @@ struct libinput_tablet_tool_pressure_threshold { /* The configured axis we actually work with */ struct input_absinfo abs_pressure; struct threshold threshold; /* in device coordinates */ - int offset; /* in device coordinates */ + pressure_offset_t offset; bool has_offset; /* This gives us per-tablet heuristic state which is arguably @@ -517,9 +553,7 @@ struct libinput_tablet_tool { struct normalized_range wanted_range; bool has_configured_range; - /* Hard-coded because I doubt we have users with more - * than 4 tablets at the same time */ - struct libinput_tablet_tool_pressure_threshold thresholds[4]; + struct libinput_tablet_tool_pressure_threshold threshold; } pressure; struct {