tablet: track pressure ranges per tablet

Tablets may have different ABS_PRESSURE ranges with the oldest tablets
having 1k pressure range, then 2k, and the newer ones 8k.

If the same tool is used across two tablets with different ABS_PRESSURE
ranges, the first tablet in proximity calculated the range on where to
normalize to. As a result the other tablet either couldn't reach the
full pressure (2k pressure first, then 8k) or the full pressure range
was reached at a fraction of the full range (8k pressure first, then
2k).

Fix this by moving the threshold handling into a separate struct and
hardcoding up to 4 of those per tool. That is 2 more than the more
complicated setups I've heard of (and this only applies to tracking the
same stylus across those tablets anyway).

This duplicates the pressure offset heuristics but that's easier than
figuring out how to handle heuristics across potentially two tablets.

The range configuration is left as-is on the assumption that this one is
per tool, not per tablet.

Closes #1089

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1143>
This commit is contained in:
Peter Hutterer 2025-02-17 15:29:26 +10:00 committed by Marge Bot
parent 1de6ac8916
commit 48cd4c7287
5 changed files with 207 additions and 71 deletions

View file

@ -72,6 +72,27 @@ tablet_get_released_buttons(struct tablet_dispatch *tablet,
~(state->bits[i]); ~(state->bits[i]);
} }
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];
}
/* 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
* they just got pressed in this frame */ * they just got pressed in this frame */
static inline void static inline void
@ -379,9 +400,8 @@ normalize_distance(const struct input_absinfo *absinfo)
} }
static inline double static inline double
normalize_pressure(const struct input_absinfo *absinfo, normalize_pressure(struct libinput_tablet_tool_pressure_threshold *threshold,
int abs_value, int abs_value)
struct libinput_tablet_tool *tool)
{ {
/** /**
* Note: the upper threshold takes the offset into account so that * Note: the upper threshold takes the offset into account so that
@ -394,9 +414,9 @@ normalize_pressure(const struct input_absinfo *absinfo,
* The axis is scaled into the range [lower, max] so that the lower * The axis is scaled into the range [lower, max] so that the lower
* threshold is 0 pressure. * threshold is 0 pressure.
*/ */
struct input_absinfo abs = *absinfo; struct input_absinfo abs = threshold->abs_pressure;
abs.minimum = tool->pressure.threshold.lower; abs.minimum = threshold->threshold.lower;
return absinfo_normalize_value(&abs, abs_value); return absinfo_normalize_value(&abs, abs_value);
} }
@ -613,9 +633,9 @@ tablet_update_pressure(struct tablet_dispatch *tablet,
return; return;
if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) { if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) {
tablet->axes.pressure = normalize_pressure(&tool->pressure.abs_pressure, struct libinput_tablet_tool_pressure_threshold *threshold =
abs->value, tablet_tool_get_threshold(tablet, tool);
tool); tablet->axes.pressure = normalize_pressure(threshold, abs->value);
} }
} }
@ -1212,43 +1232,48 @@ apply_pressure_range_configuration(struct tablet_dispatch *tablet,
lo = axis_range_percentage(&abs, 1); lo = axis_range_percentage(&abs, 1);
} }
tool->pressure.abs_pressure = abs; struct libinput_tablet_tool_pressure_threshold *threshold =
tool->pressure.threshold.upper = hi; tablet_tool_get_threshold(tablet, tool);
tool->pressure.threshold.lower = lo; 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 */ /* Disable any heuristics */
if (tool->pressure.has_configured_range) { if (tool->pressure.has_configured_range) {
tool->pressure.has_offset = true; threshold->has_offset = true;
tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
} }
} }
static inline void static inline void
tool_init_pressure_thresholds(struct tablet_dispatch *tablet, tool_init_pressure_thresholds(struct tablet_dispatch *tablet,
struct libinput_tablet_tool *tool) struct libinput_tablet_tool *tool,
struct libinput_tablet_tool_pressure_threshold *threshold)
{ {
struct evdev_device *device = tablet->device; struct evdev_device *device = tablet->device;
const struct input_absinfo *pressure, *distance; const struct input_absinfo *pressure, *distance;
tool->pressure.offset = 0; threshold->tablet_id = tablet->tablet_id;
tool->pressure.has_offset = false; threshold->offset = 0;
threshold->has_offset = false;
pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
if (!pressure) { if (!pressure) {
tool->pressure.threshold.upper = 1; threshold->threshold.upper = 1;
tool->pressure.threshold.lower = 0; threshold->threshold.lower = 0;
return; return;
} }
threshold->abs_pressure = *pressure;
distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE);
if (distance) { if (distance) {
tool->pressure.offset = pressure->minimum; threshold->offset = pressure->minimum;
tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE;
} else { } else {
tool->pressure.offset = pressure->maximum; threshold->offset = pressure->maximum;
tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1; threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1;
} }
apply_pressure_range_configuration(tablet, tool); apply_pressure_range_configuration(tablet, tool);
@ -1312,19 +1337,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,
}; };
/* Copy the pressure axis for configuring the range later */ tool_init_pressure_thresholds(tablet, tool, &tool->pressure.thresholds[0]);
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_init_pressure_thresholds(tablet, tool);
tool_set_bits(tablet, tool); tool_set_bits(tablet, tool);
return tool; return tool;
@ -1382,7 +1395,16 @@ 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;
} }
@ -1468,7 +1490,9 @@ sanitize_pressure_distance(struct tablet_dispatch *tablet,
/* Note: this is an arbitrary "in contact" decision rather than "tip /* Note: this is an arbitrary "in contact" decision rather than "tip
* down". We use the lower threshold as minimum pressure value, * down". We use the lower threshold as minimum pressure value,
* anything less than that gets filtered away */ * anything less than that gets filtered away */
tool_in_contact = (pressure->value > tool->pressure.threshold.lower); struct libinput_tablet_tool_pressure_threshold* threshold =
tablet_tool_get_threshold(tablet, tool);
tool_in_contact = (pressure->value > threshold->threshold.lower);
/* Keep distance and pressure mutually exclusive */ /* Keep distance and pressure mutually exclusive */
if (distance && if (distance &&
@ -1514,18 +1538,18 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet,
} }
static void static void
set_pressure_offset(struct libinput_tablet_tool *tool, int offset) set_pressure_offset(struct libinput_tablet_tool_pressure_threshold *threshold, int offset)
{ {
tool->pressure.offset = offset; threshold->offset = offset;
tool->pressure.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 gap = tool->pressure.threshold.upper - tool->pressure.threshold.lower; int gap = threshold->threshold.upper - threshold->threshold.lower;
tool->pressure.threshold.lower = offset; threshold->threshold.lower = offset;
tool->pressure.threshold.upper = offset + gap; threshold->threshold.upper = offset + gap;
} }
static void static void
@ -1549,11 +1573,13 @@ update_pressure_offset(struct tablet_dispatch *tablet,
* 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; int offset = pressure->value;
if (tool->pressure.has_offset) { struct libinput_tablet_tool_pressure_threshold *threshold =
if (offset < tool->pressure.offset) tablet_tool_get_threshold(tablet, tool);
set_pressure_offset(tool, offset); if (threshold->has_offset) {
} else if (tool->pressure.heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) { if (offset < threshold->offset)
tool->pressure.offset = min(offset, tool->pressure.offset); set_pressure_offset(threshold, offset);
} else if (threshold->heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) {
threshold->offset = min(offset, threshold->offset);
} }
} }
@ -1565,10 +1591,15 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
const struct input_absinfo *pressure, *distance; const struct input_absinfo *pressure, *distance;
int offset; int offset;
if (tool->pressure.has_offset || 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))
return; return;
struct libinput_tablet_tool_pressure_threshold *threshold =
tablet_tool_get_threshold(tablet, tool);
if (threshold->has_offset)
return;
pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE);
distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE);
@ -1592,16 +1623,16 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
* 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 (offset > pressure->minimum)
tool->pressure.offset = min(offset, tool->pressure.offset); threshold->offset = min(offset, threshold->offset);
switch (tool->pressure.heuristic_state) { switch (threshold->heuristic_state) {
case PRESSURE_HEURISTIC_STATE_PROXIN1: case PRESSURE_HEURISTIC_STATE_PROXIN1:
case PRESSURE_HEURISTIC_STATE_PROXIN2: case PRESSURE_HEURISTIC_STATE_PROXIN2:
tool->pressure.heuristic_state++; threshold->heuristic_state++;
return; return;
case PRESSURE_HEURISTIC_STATE_DECIDE: case PRESSURE_HEURISTIC_STATE_DECIDE:
tool->pressure.heuristic_state++; threshold->heuristic_state++;
offset = tool->pressure.offset; offset = threshold->offset;
break; break;
case PRESSURE_HEURISTIC_STATE_DONE: case PRESSURE_HEURISTIC_STATE_DONE:
return; return;
@ -1628,7 +1659,7 @@ detect_pressure_offset(struct tablet_dispatch *tablet,
tool->serial, tool->serial,
HTTP_DOC_LINK); HTTP_DOC_LINK);
set_pressure_offset(tool, offset); set_pressure_offset(threshold, offset);
} }
static void static void
@ -1659,10 +1690,12 @@ detect_tool_contact(struct tablet_dispatch *tablet,
} }
pressure = p->value; pressure = p->value;
if (pressure <= tool->pressure.threshold.lower && struct libinput_tablet_tool_pressure_threshold *threshold =
tablet_tool_get_threshold(tablet, tool);
if (pressure <= threshold->threshold.lower &&
tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) {
tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT); tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT);
} else if (pressure >= tool->pressure.threshold.upper && } else if (pressure >= threshold->threshold.upper &&
!tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { !tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) {
tablet_set_status(tablet, TABLET_TOOL_ENTERING_CONTACT); tablet_set_status(tablet, TABLET_TOOL_ENTERING_CONTACT);
} }
@ -2913,6 +2946,7 @@ static int
tablet_init(struct tablet_dispatch *tablet, tablet_init(struct tablet_dispatch *tablet,
struct evdev_device *device) struct evdev_device *device)
{ {
static unsigned int tablet_ids = 0;
struct libinput *li = evdev_libinput_context(device); struct libinput *li = evdev_libinput_context(device);
struct libevdev *evdev = device->evdev; struct libevdev *evdev = device->evdev;
enum libinput_tablet_tool_axis axis; enum libinput_tablet_tool_axis axis;
@ -2943,6 +2977,7 @@ tablet_init(struct tablet_dispatch *tablet,
} }
#endif #endif
tablet->tablet_id = ++tablet_ids;
tablet->base.dispatch_type = DISPATCH_TABLET; tablet->base.dispatch_type = DISPATCH_TABLET;
tablet->base.interface = &tablet_interface; tablet->base.interface = &tablet_interface;
tablet->device = device; tablet->device = device;

View file

@ -61,6 +61,8 @@ struct button_state {
struct tablet_dispatch { struct tablet_dispatch {
struct evdev_dispatch base; struct evdev_dispatch base;
struct evdev_device *device; struct evdev_device *device;
unsigned int tablet_id; /* incremental ID */
unsigned int status; unsigned int status;
unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)];
struct tablet_axes axes; /* for assembling the current state */ struct tablet_axes axes; /* for assembling the current state */

View file

@ -88,10 +88,13 @@ totem_new_tool(struct totem_dispatch *totem)
.refcount = 1, .refcount = 1,
}; };
tool->pressure.offset = 0; ARRAY_FOR_EACH(tool->pressure.thresholds, t) {
tool->pressure.has_offset = false; t->tablet_id = 0;
tool->pressure.threshold.lower = 0; t->offset = 0;
tool->pressure.threshold.upper = 1; t->has_offset = false;
t->threshold.lower = 0;
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

@ -484,6 +484,21 @@ struct libinput_tablet_tool_config_pressure_range {
void (*get_default)(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_pressure_threshold {
unsigned int tablet_id;
/* The configured axis we actually work with */
struct input_absinfo abs_pressure;
struct threshold threshold; /* in device coordinates */
int offset; /* in device coordinates */
bool has_offset;
/* This gives us per-tablet heuristic state which is arguably
* wrong but >99% of users have one tablet and it's easier to
* implement it this way */
enum pressure_heuristic_state heuristic_state;
};
struct libinput_tablet_tool { struct libinput_tablet_tool {
struct list link; struct list link;
uint32_t serial; uint32_t serial;
@ -495,17 +510,16 @@ struct libinput_tablet_tool {
void *user_data; void *user_data;
struct { struct {
/* The configured axis we actually work with */ /* We're assuming that the *configured* pressure range is per
struct input_absinfo abs_pressure; * tool, not per tablet. The *adjusted* thresholds are then
struct normalized_range range; * per-tablet. */
struct normalized_range range;
struct normalized_range wanted_range; struct normalized_range wanted_range;
bool has_configured_range; bool has_configured_range;
struct threshold threshold; /* in device coordinates */ /* Hard-coded because I doubt we have users with more
int offset; /* in device coordinates */ * than 4 tablets at the same time */
bool has_offset; struct libinput_tablet_tool_pressure_threshold thresholds[4];
enum pressure_heuristic_state heuristic_state;
} pressure; } pressure;
struct { struct {

View file

@ -5066,6 +5066,88 @@ START_TEST(tablet_pressure_offset_none_for_small_distance)
} }
END_TEST END_TEST
START_TEST(tablet_pressure_across_multiple_tablets)
{
struct litest_device *cintiq12wx = litest_current_device();
struct libinput *li = cintiq12wx->libinput;
struct litest_device *mobilestudio = litest_add_device(li, LITEST_WACOM_CINTIQ_PRO16_PEN);
bool direction = litest_test_param_get_bool(test_env->params, "8k-to-1k");
struct litest_device *first = direction ? mobilestudio : cintiq12wx;
struct litest_device *second = direction ? cintiq12wx : mobilestudio;
struct axis_replacement axes[] = {
{ ABS_DISTANCE, 20 },
{ ABS_PRESSURE, 0 },
{ -1, -1 },
};
bool have_cintiq12wx = false;
bool have_mobilestudio = false;
libinput_dispatch(li);
while (!have_cintiq12wx || !have_mobilestudio) {
litest_wait_for_event_of_type(li, LIBINPUT_EVENT_DEVICE_ADDED);
struct libinput_event *ev = libinput_get_event(li);
litest_assert_event_type(ev, LIBINPUT_EVENT_DEVICE_ADDED);
if (libinput_event_get_device(ev) == cintiq12wx->libinput_device)
have_cintiq12wx = true;
if (libinput_event_get_device(ev) == mobilestudio->libinput_device)
have_mobilestudio = true;
litest_checkpoint("Have Cintiq 12WX: %s, MobileStudio: %s", yesno(have_cintiq12wx), yesno(have_mobilestudio));
libinput_event_destroy(ev);
libinput_dispatch(li);
}
litest_drain_events(li);
/* Proximity in followed by pressure up to 70%, on the first
* device, then on the second one. They have different pressure
* ranges but we expect the normalized range to be the same
* proportionate range */
struct litest_device *dev = first;
for (int i = 0; i < 2; i++, dev = second) {
litest_checkpoint("Putting pen into proximity on %s", libinput_device_get_name(dev->libinput_device));
litest_tablet_proximity_in(dev, 50, 50, axes);
litest_axis_set_value(axes, ABS_DISTANCE, 0);
litest_axis_set_value(axes, ABS_PRESSURE, 10);
litest_tablet_motion(dev, 50, 50, axes);
litest_dispatch(li);
for (size_t pressure = 10; pressure <= 70; pressure += 10) {
litest_axis_set_value(axes, ABS_PRESSURE, pressure);
litest_tablet_motion(dev, 50, 50, axes);
litest_dispatch(li);
}
litest_tablet_proximity_out(dev);
litest_timeout_tablet_proxout();
libinput_dispatch(li);
litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_DOWN);
do {
struct libinput_event *ev = libinput_get_event(li);
struct libinput_event_tablet_tool *tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_AXIS);
double pressure = libinput_event_tablet_tool_get_pressure(tev);
/* We start at device range 10% but we always have a small threshold */
litest_assert_double_gt(pressure, 0.09);
litest_assert_double_le(pressure, 0.7);
libinput_event_destroy(ev);
} while (libinput_next_event_type(li) == LIBINPUT_EVENT_TABLET_TOOL_AXIS);
litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_UP);
litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT);
}
litest_delete_device(mobilestudio);
}
END_TEST
START_TEST(tablet_distance_range) START_TEST(tablet_distance_range)
{ {
struct litest_device *dev = litest_current_device(); struct litest_device *dev = litest_current_device();
@ -7164,6 +7246,8 @@ TEST_COLLECTION(tablet)
litest_add_for_device(tablet_pressure_offset_decrease, LITEST_WACOM_HID4800_PEN); litest_add_for_device(tablet_pressure_offset_decrease, LITEST_WACOM_HID4800_PEN);
litest_add_for_device(tablet_pressure_offset_increase, LITEST_WACOM_HID4800_PEN); 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_for_device(tablet_pressure_offset_exceed_threshold, LITEST_WACOM_HID4800_PEN);
litest_with_parameters(params, "8k-to-1k", 'b')
litest_add_parametrized_for_device(tablet_pressure_across_multiple_tablets, LITEST_WACOM_CINTIQ_12WX, params);
litest_add(tablet_pressure_config, LITEST_TABLET, LITEST_TOTEM); 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_minimum, LITEST_TABLET, LITEST_TOTEM);
@ -7219,6 +7303,4 @@ TEST_COLLECTION(tablet_left_handed)
litest_add_parametrized(tablet_rotation_left_handed_add_touchpad, LITEST_TABLET, LITEST_ANY, params); litest_add_parametrized(tablet_rotation_left_handed_add_touchpad, LITEST_TABLET, LITEST_ANY, params);
litest_add_parametrized(tablet_rotation_left_handed_add_tablet, LITEST_TOUCHPAD, LITEST_ANY, params); litest_add_parametrized(tablet_rotation_left_handed_add_tablet, LITEST_TOUCHPAD, LITEST_ANY, params);
} }
} }