diff --git a/doc/device-configuration-via-udev.dox b/doc/device-configuration-via-udev.dox
index fc1c0af8..87e92e6e 100644
--- a/doc/device-configuration-via-udev.dox
+++ b/doc/device-configuration-via-udev.dox
@@ -57,6 +57,11 @@ See @ref motion_normalization for details.
The angle in degrees for each click on a mouse wheel. See
libinput_pointer_get_axis_source() for details.
+TOUCHPAD_RESOLUTION
+The x and y resolution in units/mm for a touchpad. This value is only
+used if the touchpad kernel driver does not supply a valid resolution. It
+is only used on touchpad devices. The format is two unsigned integer values
+separated by a literal 'x', e.g. "42x129".
Below is an example udev rule to assign "seat1" to a device from vendor
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index b7760c23..998249ff 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -1117,6 +1117,32 @@ tp_init_sendevents(struct tp_dispatch *tp,
return 0;
}
+static void
+tp_fix_resolution(struct tp_dispatch *tp, struct evdev_device *device)
+{
+ struct libinput *libinput = device->base.seat->libinput;
+ const char *prop;
+ unsigned int resx, resy;
+
+ prop = udev_device_get_property_value(device->udev_device,
+ "TOUCHPAD_RESOLUTION");
+ if (!prop)
+ return;
+
+ if (parse_touchpad_resolution_property(prop, &resx, &resy) == -1) {
+ log_error(libinput,
+ "Touchpad resolution property set for '%s', but invalid.\n",
+ device->devname);
+ return;
+ }
+
+ if (evdev_fix_abs_resolution(device,
+ tp->has_mt ? ABS_MT_POSITION_X : ABS_X,
+ tp->has_mt ? ABS_MT_POSITION_Y : ABS_Y,
+ resx, resy))
+ device->abs.fake_resolution = 0;
+}
+
static int
tp_init(struct tp_dispatch *tp,
struct evdev_device *device)
@@ -1130,6 +1156,8 @@ tp_init(struct tp_dispatch *tp,
if (tp_init_slots(tp, device) != 0)
return -1;
+ tp_fix_resolution(tp, device);
+
width = abs(device->abs.absinfo_x->maximum -
device->abs.absinfo_x->minimum);
height = abs(device->abs.absinfo_y->maximum -
diff --git a/src/libinput-util.c b/src/libinput-util.c
index 49e297af..cd3b18dc 100644
--- a/src/libinput-util.c
+++ b/src/libinput-util.c
@@ -201,3 +201,33 @@ parse_mouse_wheel_click_angle_property(const char *prop)
return angle;
}
+
+/**
+ * Helper function to parse the TOUCHPAD_RESOLUTION property from udev.
+ * Property is of the form
+ * TOUCHPAD_RESOLUTION=x
+ * With both integer values in device units per mm.
+ * @param prop The value of the udev property (without the
+ * TOUCHPAD_RESOLUTION=)
+ * @return
+ */
+int
+parse_touchpad_resolution_property(const char *prop,
+ unsigned int *res_x,
+ unsigned int *res_y)
+{
+ int nconverted = 0;
+ unsigned int rx, ry;
+ nconverted = sscanf(prop, "%ux%u", &rx, &ry);
+ if (nconverted != 2 || rx == 0 || ry == 0)
+ return -1;
+
+ if (rx > 1000 || ry > 1000 || /* yeah, right... */
+ rx < 10 || ry < 10) /* what is this? the 90s? */
+ return -1;
+
+ *res_x = rx;
+ *res_y = ry;
+
+ return 0;
+}
diff --git a/src/libinput-util.h b/src/libinput-util.h
index f76439f1..bd71a1ff 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -299,5 +299,8 @@ enum ratelimit_state ratelimit_test(struct ratelimit *r);
int parse_mouse_dpi_property(const char *prop);
int parse_mouse_wheel_click_angle_property(const char *prop);
+int parse_touchpad_resolution_property(const char *prop,
+ unsigned int *res_x,
+ unsigned int *res_y);
#endif /* LIBINPUT_UTIL_H */
diff --git a/test/misc.c b/test/misc.c
index db26d67e..414d7cb3 100644
--- a/test/misc.c
+++ b/test/misc.c
@@ -557,6 +557,54 @@ START_TEST(wheel_click_parser)
}
END_TEST
+struct res_parser_test {
+ const char *value;
+ int retvalue;
+ int rx, ry;
+};
+
+START_TEST(touchpad_resolution_parser)
+{
+ struct res_parser_test tests[] = {
+ { "43x85", 0, 43, 85},
+ { "242x428", 0, 242, 428 },
+ { "1x1", -1, 0, 0},
+ { "abcd", -1, 0, 0},
+ { "", -1, 0, 0 },
+ { "x", -1, 0, 0 },
+ { "23x", -1, 0, 0 },
+ { "x58", -1, 0, 0 },
+ { "1x1", -1, 0, 0 },
+ { "9x9", -1, 0, 0 },
+ { "-34x-19", -1, 0, 0 },
+ { "-34x19", -1, 0, 0 },
+ { "34x-19", -1, 0, 0 },
+ { NULL, 0, 0, 0 }
+
+ };
+
+ struct res_parser_test *test = tests;
+ int rc;
+ unsigned int rx, ry;
+
+ while (test->value != NULL) {
+ rx = 0xab;
+ ry = 0xcd;
+ rc = parse_touchpad_resolution_property(test->value, &rx, &ry);
+ ck_assert_int_eq(rc, test->retvalue);
+ if (rc == 0) {
+ ck_assert_int_eq(rx, test->rx);
+ ck_assert_int_eq(ry, test->ry);
+ } else {
+ ck_assert_int_eq(rx, 0xab);
+ ck_assert_int_eq(ry, 0xcd);
+ }
+
+ test++;
+ }
+}
+END_TEST
+
int main (int argc, char **argv) {
litest_add_no_device("events:conversion", event_conversion_device_notify);
litest_add_for_device("events:conversion", event_conversion_pointer, LITEST_MOUSE);
@@ -572,6 +620,7 @@ int main (int argc, char **argv) {
litest_add_no_device("misc:ratelimit", ratelimit_helpers);
litest_add_no_device("misc:dpi parser", dpi_parser);
litest_add_no_device("misc:wheel click parser", wheel_click_parser);
+ litest_add_no_device("misc:touchpad resolution parser", touchpad_resolution_parser);
return litest_run(argc, argv);
}