tablet: fix tilt handling for even-ranged tablets

The tablet tilt range may be set as [-N, M] in which case we assume that
a value of zero is vertical (and thus should result in a libinput tilt
value of zero). Unfortunately some tablets report an even total value
range, e.g. [-64, 63] so zero is not actually the mathematical center of
the axis.

Fix this by bumping the axis maximum so zero becomes the logical center.
All devices we've seen so far have [-A, (A-1)] as range so bumping it by
one makes it symmetric.
This commit is contained in:
Peter Hutterer 2024-01-16 16:10:59 +10:00
parent 72eca2db56
commit 0322403ea4
2 changed files with 135 additions and 1 deletions

View file

@ -368,7 +368,7 @@ normalize_pressure(const struct input_absinfo *absinfo,
static inline double
adjust_tilt(const struct input_absinfo *absinfo)
{
double value = (absinfo->value - absinfo->minimum) / absinfo_range(absinfo);
double value = absinfo_normalize(absinfo);
const int WACOM_MAX_DEGREES = 64;
/* If resolution is nonzero, it's in units/radian. But require
@ -2562,6 +2562,57 @@ tablet_reject_device(struct evdev_device *device)
return true;
}
static void
tablet_fix_tilt(struct tablet_dispatch *tablet,
struct evdev_device *device)
{
struct libevdev *evdev = device->evdev;
if (libevdev_has_event_code(evdev, EV_ABS, ABS_TILT_X) !=
libevdev_has_event_code(evdev, EV_ABS, ABS_TILT_Y)) {
libevdev_disable_event_code(evdev, EV_ABS, ABS_TILT_X);
libevdev_disable_event_code(evdev, EV_ABS, ABS_TILT_Y);
return;
}
if (!libevdev_has_event_code(evdev, EV_ABS, ABS_TILT_X))
return;
/* Wacom has three types of devices:
* - symmetrical: [-90, 90], like the ISDv4 524c
* - asymmetrical: [-64, 63], like the Cintiq l3HDT
* - zero-based: [0, 127], like the Cintiq 12WX
*
* Note how the latter two cases have an even range and thus do
* not have a logical center value. But this is tilt and at
* least in the asymmetrical case we assume that hardware zero
* means vertical. So we cheat and adjust the range depending
* on whether it's odd, then use the center value.
*
* Since it's always the max that's one too low let's go with that and
* fix it if we run into a device where that isn't the case.
*/
for (unsigned int axis = ABS_TILT_X; axis <= ABS_TILT_Y; axis++) {
struct input_absinfo abs = *libevdev_get_abs_info(evdev, axis);
/* Don't touch axes reporting radians */
if (abs.resolution != 0)
continue;
if ((int)absinfo_range(&abs) % 2 == 1)
continue;
abs.maximum += 1;
libevdev_set_abs_info(evdev, axis, &abs);
evdev_log_debug(device,
"Adjusting %s range to [%d, %d]\n",
libevdev_event_code_get_name(EV_ABS, axis),
abs.minimum,
abs.maximum);
}
}
static int
tablet_init(struct tablet_dispatch *tablet,
struct evdev_device *device)
@ -2592,6 +2643,7 @@ tablet_init(struct tablet_dispatch *tablet,
libevdev_disable_event_code(evdev, EV_KEY, BTN_TOOL_LENS);
}
tablet_fix_tilt(tablet, device);
tablet_init_calibration(tablet, device);
tablet_init_proximity_threshold(tablet, device);
rc = tablet_init_accel(tablet, device);

View file

@ -37,6 +37,12 @@
#include "litest.h"
#include "util-input-event.h"
enum {
TILT_MINIMUM,
TILT_CENTER,
TILT_MAXIMUM,
};
static inline unsigned int
pick_stylus_or_btn0(struct litest_device *dev)
{
@ -4487,6 +4493,80 @@ START_TEST(tilt_y)
}
END_TEST
START_TEST(tilt_fixed_points)
{
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, 10 },
{ ABS_PRESSURE, 0 },
{ -1, -1 }
};
int testcase = _i; /* ranged test */
int axis_value;
double expected;
/* On devices with a range of [-N, M], make sure we calculate the hw zero position
* as zero and that the respective min/max resolve to our (hardcoded) min/max degree
* values
*/
const struct input_absinfo *abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_X);
if (abs->minimum >= 0)
return;
/* If the tablet reports physical resolutions we don't need to test them */
if (abs->resolution != 0)
return;
/* see tablet_fix_tilt() */
bool is_adjusted = (int)absinfo_range(abs) % 2 == 0;
switch (testcase) {
case TILT_MINIMUM:
axis_value = abs->minimum;
expected = -64.0;
break;
case TILT_CENTER:
axis_value = 0;
expected = 0.0;
break;
case TILT_MAXIMUM:
axis_value = abs->maximum;
expected = 64.0;
break;
default:
abort();
}
litest_drain_events(li);
litest_push_event_frame(dev);
litest_tablet_proximity_in(dev, 10, 10, axes);
litest_event(dev, EV_ABS, ABS_TILT_X, axis_value);
litest_event(dev, EV_ABS, ABS_TILT_Y, axis_value);
litest_pop_event_frame(dev);
libinput_dispatch(li);
event = libinput_get_event(li);
tev = litest_is_tablet_event(event,
LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
double tx = libinput_event_tablet_tool_get_tilt_x(tev);
double ty = libinput_event_tablet_tool_get_tilt_y(tev);
ck_assert_double_eq(tx, expected);
if (is_adjusted) {
ck_assert_double_ge(ty, expected - 1);
ck_assert_double_lt(ty, expected);
} else {
ck_assert_double_eq(ty, expected);
}
libinput_event_destroy(event);
}
END_TEST
START_TEST(relative_no_profile)
{
struct litest_device *dev = litest_current_device();
@ -6152,6 +6232,7 @@ TEST_COLLECTION(tablet)
struct range with_timeout = { 0, 2 };
struct range xyaxes = { ABS_X, ABS_Y + 1 };
struct range lh_transitions = {0, 16}; /* 2 bits for in, 2 bits for out */
struct range tilt_cases = {TILT_MINIMUM, TILT_MAXIMUM + 1};
litest_add(tool_ref, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
litest_add(tool_user_data, LITEST_TABLET | LITEST_TOOL_SERIAL, LITEST_ANY);
@ -6213,6 +6294,7 @@ TEST_COLLECTION(tablet)
litest_add(tilt_not_available, LITEST_TABLET, LITEST_TILT);
litest_add(tilt_x, LITEST_TABLET|LITEST_TILT, LITEST_ANY);
litest_add(tilt_y, LITEST_TABLET|LITEST_TILT, LITEST_ANY);
litest_add_ranged(tilt_fixed_points, LITEST_TABLET|LITEST_TILT, LITEST_ANY, &tilt_cases);
litest_add_for_device(left_handed, LITEST_WACOM_INTUOS);
litest_add_for_device(left_handed_tilt, LITEST_WACOM_INTUOS);
litest_add_for_device(left_handed_mouse_rotation, LITEST_WACOM_INTUOS);