[PATCH v2 libinput 6/7] tablet: hook up relative motion events

Peter Hutterer peter.hutterer at who-t.net
Mon Jan 18 17:22:47 PST 2016


Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
Acked-by: Jason Gerecke <jason.gerecke at wacom.com>
---
Changes to v1:
- updated for removal of the dx/dy unaccel coordinates

 doc/pointer-acceleration.dox |   6 ++
 src/evdev-tablet.c           | 116 +++++++++++++++++++++---
 src/evdev.c                  |  13 +++
 src/evdev.h                  |   4 +
 src/filter.c                 |  94 ++++++++++++++++++++
 src/filter.h                 |   3 +
 src/libinput-util.h          |  10 +++
 test/tablet.c                | 204 +++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 440 insertions(+), 10 deletions(-)

diff --git a/doc/pointer-acceleration.dox b/doc/pointer-acceleration.dox
index 7ec5e74..2fbb4cc 100644
--- a/doc/pointer-acceleration.dox
+++ b/doc/pointer-acceleration.dox
@@ -124,4 +124,10 @@ velocity of the pointer and each delta (dx, dy) results in an accelerated delta
 (dx * factor, dy * factor). This provides 1:1 movement between the device
 and the pointer on-screen.
 
+ at section ptraccel-tablet Pointer acceleration on tablets
+
+Pointer acceleration for relative motion on tablet devices is a flat
+acceleration, with the speed seeting slowing down or speeding up the pointer
+motion by a constant factor. Tablets do not allow for switchable profiles.
+
 */
diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 1870e7e..ae20550 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -285,19 +285,29 @@ normalize_wheel(struct tablet_dispatch *tablet,
 	return value * device->scroll.wheel_click_angle;
 }
 
-static inline struct device_coords
-tablet_handle_xy(struct tablet_dispatch *tablet, struct evdev_device *device)
+static inline void
+tablet_handle_xy(struct tablet_dispatch *tablet,
+		 struct evdev_device *device,
+		 struct device_coords *point_out,
+		 struct device_coords *delta_out)
 {
 	struct device_coords point;
+	struct device_coords delta = { 0, 0 };
 	const struct input_absinfo *absinfo;
+	int value;
 
 	if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X)) {
 		absinfo = libevdev_get_abs_info(device->evdev, ABS_X);
 
 		if (device->left_handed.enabled)
-			tablet->axes.point.x = invert_axis(absinfo);
+			value = invert_axis(absinfo);
 		else
-			tablet->axes.point.x = absinfo->value;
+			value = absinfo->value;
+
+		if (!tablet_has_status(tablet,
+				       TABLET_TOOL_ENTERING_PROXIMITY))
+			delta.x = value - tablet->axes.point.x;
+		tablet->axes.point.x = value;
 	}
 	point.x = tablet->axes.point.x;
 
@@ -305,15 +315,41 @@ tablet_handle_xy(struct tablet_dispatch *tablet, struct evdev_device *device)
 		absinfo = libevdev_get_abs_info(device->evdev, ABS_Y);
 
 		if (device->left_handed.enabled)
-			tablet->axes.point.y = invert_axis(absinfo);
+			value = invert_axis(absinfo);
 		else
-			tablet->axes.point.y = absinfo->value;
+			value = absinfo->value;
+
+		if (!tablet_has_status(tablet,
+				       TABLET_TOOL_ENTERING_PROXIMITY))
+			delta.y = value - tablet->axes.point.y;
+		tablet->axes.point.y = value;
 	}
 	point.y = tablet->axes.point.y;
 
 	evdev_transform_absolute(device, &point);
+	evdev_transform_relative(device, &delta);
 
-	return point;
+	*delta_out = delta;
+	*point_out = point;
+}
+
+static inline struct normalized_coords
+tablet_process_delta(struct tablet_dispatch *tablet,
+		     const struct evdev_device *device,
+		     const struct device_coords *delta,
+		     uint64_t time)
+{
+	struct normalized_coords accel;
+
+	/* The tablet accel code uses mm as input */
+	accel.x = 1.0 * delta->x/device->abs.absinfo_x->resolution;
+	accel.y = 1.0 * delta->y/device->abs.absinfo_y->resolution;
+
+	if (normalized_is_zero(accel))
+		return accel;
+
+	return filter_dispatch(device->pointer.filter,
+			       &accel, tablet, time);
 }
 
 static inline double
@@ -445,19 +481,22 @@ static bool
 tablet_check_notify_axes(struct tablet_dispatch *tablet,
 			 struct evdev_device *device,
 			 struct libinput_tablet_tool *tool,
-			 struct tablet_axes *axes_out)
+			 struct tablet_axes *axes_out,
+			 uint64_t time)
 {
 	struct tablet_axes axes = {0};
 	const char tmp[sizeof(tablet->changed_axes)] = {0};
+	struct device_coords delta;
 
 	if (memcmp(tmp, tablet->changed_axes, sizeof(tmp)) == 0)
 		return false;
 
-	axes.point = tablet_handle_xy(tablet, device);
+	tablet_handle_xy(tablet, device, &axes.point, &delta);
 	axes.pressure = tablet_handle_pressure(tablet, device, tool);
 	axes.distance = tablet_handle_distance(tablet, device);
 	axes.slider = tablet_handle_slider(tablet, device);
 	axes.tilt = tablet_handle_tilt(tablet, device);
+	axes.delta = tablet_process_delta(tablet, device, &delta, time);
 
 	/* We must check ROTATION_Z after TILT_X/Y so that the tilt axes are
 	 * already normalized and set if we have the mouse/lens tool */
@@ -1186,7 +1225,8 @@ tablet_send_axis_proximity_tip_down_events(struct tablet_dispatch *tablet,
 	} else if (!tablet_check_notify_axes(tablet,
 					     device,
 					     tool,
-					     &axes)) {
+					     &axes,
+					     time)) {
 		goto out;
 	}
 
@@ -1463,11 +1503,64 @@ tablet_init_proximity_threshold(struct tablet_dispatch *tablet,
 	tablet->cursor_proximity_threshold = 42;
 }
 
+static uint32_t
+tablet_accel_config_get_profiles(struct libinput_device *libinput_device)
+{
+	return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+}
+
+static enum libinput_config_status
+tablet_accel_config_set_profile(struct libinput_device *libinput_device,
+			    enum libinput_config_accel_profile profile)
+{
+	return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
+}
+
+static enum libinput_config_accel_profile
+tablet_accel_config_get_profile(struct libinput_device *libinput_device)
+{
+	return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+}
+
+static enum libinput_config_accel_profile
+tablet_accel_config_get_default_profile(struct libinput_device *libinput_device)
+{
+	return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
+}
+
+static int
+tablet_init_accel(struct tablet_dispatch *tablet, struct evdev_device *device)
+{
+	const struct input_absinfo *x, *y;
+	struct motion_filter *filter;
+	int rc;
+
+	x = device->abs.absinfo_x;
+	y = device->abs.absinfo_y;
+
+	filter = create_pointer_accelerator_filter_tablet(x->resolution,
+							  y->resolution);
+
+	rc = evdev_device_init_pointer_acceleration(device, filter);
+	if (rc != 0)
+		return rc;
+
+	/* we override the profile hooks for accel configuration with hooks
+	 * that don't allow selection of profiles */
+	device->pointer.config.get_profiles = tablet_accel_config_get_profiles;
+	device->pointer.config.set_profile = tablet_accel_config_set_profile;
+	device->pointer.config.get_profile = tablet_accel_config_get_profile;
+	device->pointer.config.get_default_profile = tablet_accel_config_get_default_profile;
+
+	return 0;
+}
+
 static int
 tablet_init(struct tablet_dispatch *tablet,
 	    struct evdev_device *device)
 {
 	enum libinput_tablet_tool_axis axis;
+	int rc;
 
 	tablet->base.interface = &tablet_interface;
 	tablet->device = device;
@@ -1477,6 +1570,9 @@ tablet_init(struct tablet_dispatch *tablet,
 
 	tablet_init_calibration(tablet, device);
 	tablet_init_proximity_threshold(tablet, device);
+	rc = tablet_init_accel(tablet, device);
+	if (rc != 0)
+		return rc;
 
 	for (axis = LIBINPUT_TABLET_TOOL_AXIS_X;
 	     axis <= LIBINPUT_TABLET_TOOL_AXIS_MAX;
diff --git a/src/evdev.c b/src/evdev.c
index 85ca640..8fa9626 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -222,6 +222,19 @@ evdev_transform_absolute(struct evdev_device *device,
 	matrix_mult_vec(&device->abs.calibration, &point->x, &point->y);
 }
 
+void
+evdev_transform_relative(struct evdev_device *device,
+			 struct device_coords *point)
+{
+	struct matrix rel_matrix;
+
+	if (!device->abs.apply_calibration)
+		return;
+
+	matrix_to_relative(&rel_matrix, &device->abs.calibration);
+	matrix_mult_vec(&rel_matrix, &point->x, &point->y);
+}
+
 static inline double
 scale_axis(const struct input_absinfo *absinfo, double val, double to_range)
 {
diff --git a/src/evdev.h b/src/evdev.h
index 09c05e9..785c754 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -292,6 +292,10 @@ evdev_transform_absolute(struct evdev_device *device,
 			 struct device_coords *point);
 
 void
+evdev_transform_relative(struct evdev_device *device,
+			 struct device_coords *point);
+
+void
 evdev_init_calibration(struct evdev_device *device,
 		       struct evdev_dispatch *dispatch);
 
diff --git a/src/filter.c b/src/filter.c
index 0d0b95d..4c39b0e 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -163,6 +163,13 @@ struct pointer_accelerator_flat {
 	double dpi_factor;
 };
 
+struct tablet_accelerator_flat {
+	struct motion_filter base;
+
+	double factor;
+	int xres, yres;
+};
+
 static void
 feed_trackers(struct pointer_accelerator *accel,
 	      const struct normalized_coords *delta,
@@ -964,3 +971,90 @@ create_pointer_accelerator_filter_flat(int dpi)
 
 	return &filter->base;
 }
+
+/* The tablet accel code uses mm as input */
+static struct normalized_coords
+tablet_accelerator_filter_flat(struct motion_filter *filter,
+			       const struct normalized_coords *mm,
+			       void *data, uint64_t time)
+{
+	struct tablet_accelerator_flat *accel_filter =
+		(struct tablet_accelerator_flat *)filter;
+	struct normalized_coords accelerated;
+
+	/* Tablet input is in mm, output is supposed to be in logical
+	 * pixels roughly equivalent to a mouse/touchpad.
+	 *
+	 * This is a magical constant found by trial and error. On a 96dpi
+	 * screen 0.4mm of movement correspond to 1px logical pixel which
+	 * is almost identical to the tablet mapped to screen in absolute
+	 * mode. Tested on a Intuos5, other tablets may vary.
+	 */
+	const double DPI_CONVERSION = 96.0/25.4 * 2.5; /* unitless factor */
+
+	accelerated.x = mm->x * accel_filter->factor * DPI_CONVERSION;
+	accelerated.y = mm->y * accel_filter->factor * DPI_CONVERSION;
+
+	return accelerated;
+}
+
+static bool
+tablet_accelerator_set_speed(struct motion_filter *filter,
+			     double speed_adjustment)
+{
+	struct tablet_accelerator_flat *accel_filter =
+		(struct tablet_accelerator_flat *)filter;
+
+	assert(speed_adjustment >= -1.0 && speed_adjustment <= 1.0);
+
+	accel_filter->factor = speed_adjustment + 1.0;
+
+	return true;
+}
+
+static void
+tablet_accelerator_destroy(struct motion_filter *filter)
+{
+	struct tablet_accelerator_flat *accel_filter =
+		(struct tablet_accelerator_flat *)filter;
+
+	free(accel_filter);
+}
+
+struct motion_filter_interface accelerator_interface_tablet = {
+	.type = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
+	.filter = tablet_accelerator_filter_flat,
+	.filter_constant = NULL,
+	.restart = NULL,
+	.destroy = tablet_accelerator_destroy,
+	.set_speed = tablet_accelerator_set_speed,
+};
+
+static struct tablet_accelerator_flat *
+create_tablet_filter_flat(int xres, int yres)
+{
+	struct tablet_accelerator_flat *filter;
+
+	filter = zalloc(sizeof *filter);
+	if (filter == NULL)
+		return NULL;
+
+	filter->xres = xres;
+	filter->yres = yres;
+
+	return filter;
+}
+
+struct motion_filter *
+create_pointer_accelerator_filter_tablet(int xres, int yres)
+{
+	struct tablet_accelerator_flat *filter;
+
+	filter = create_tablet_filter_flat(xres, yres);
+	if (!filter)
+		return NULL;
+
+	filter->base.interface = &accelerator_interface_tablet;
+
+	return &filter->base;
+}
diff --git a/src/filter.h b/src/filter.h
index e156642..c1b43a5 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -101,6 +101,9 @@ create_pointer_accelerator_filter_lenovo_x230(int dpi);
 struct motion_filter *
 create_pointer_accelerator_filter_trackpoint(int dpi);
 
+struct motion_filter *
+create_pointer_accelerator_filter_tablet(int xres, int yres);
+
 /*
  * Pointer acceleration profiles.
  */
diff --git a/src/libinput-util.h b/src/libinput-util.h
index 25ab5f1..efdaafc 100644
--- a/src/libinput-util.h
+++ b/src/libinput-util.h
@@ -269,6 +269,16 @@ matrix_to_farray6(const struct matrix *m, float out[6])
 	out[5] = m->val[1][2];
 }
 
+static inline void
+matrix_to_relative(struct matrix *dest, const struct matrix *src)
+{
+	matrix_init_identity(dest);
+	dest->val[0][0] = src->val[0][0];
+	dest->val[0][1] = src->val[0][1];
+	dest->val[1][0] = src->val[1][0];
+	dest->val[1][1] = src->val[1][1];
+}
+
 /**
  * Simple wrapper for asprintf that ensures the passed in-pointer is set
  * to NULL upon error.
diff --git a/test/tablet.c b/test/tablet.c
index 4976d1e..59aefe5 100644
--- a/test/tablet.c
+++ b/test/tablet.c
@@ -3205,6 +3205,205 @@ START_TEST(tilt_y)
 }
 END_TEST
 
+START_TEST(relative_no_profile)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput_device *device = dev->libinput_device;
+	enum libinput_config_accel_profile profile;
+	enum libinput_config_status status;
+	uint32_t profiles;
+
+	ck_assert(libinput_device_config_accel_is_available(device));
+
+	profile = libinput_device_config_accel_get_default_profile(device);
+	ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE);
+
+	profile = libinput_device_config_accel_get_profile(device);
+	ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE);
+
+	profiles = libinput_device_config_accel_get_profiles(device);
+	ck_assert_int_eq(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0);
+	ck_assert_int_eq(profiles & LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0);
+
+	status = libinput_device_config_accel_set_profile(device,
+							  LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT);
+	ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+	profile = libinput_device_config_accel_get_profile(device);
+	ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE);
+
+	status = libinput_device_config_accel_set_profile(device,
+							  LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE);
+	ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
+	profile = libinput_device_config_accel_get_profile(device);
+	ck_assert_int_eq(profile, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE);
+}
+END_TEST
+
+START_TEST(relative_no_delta_prox_in)
+{
+	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 }
+	};
+	double dx, dy;
+
+	litest_drain_events(li);
+
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx == 0.0);
+	ck_assert(dy == 0.0);
+
+	libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(relative_delta)
+{
+	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 }
+	};
+	double dx, dy;
+
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_drain_events(li);
+
+	litest_tablet_motion(dev, 20, 10, axes);
+	libinput_dispatch(li);
+
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx > 0.0);
+	ck_assert(dy == 0.0);
+	libinput_event_destroy(event);
+
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx < 0.0);
+	ck_assert(dy == 0.0);
+	libinput_event_destroy(event);
+
+	litest_tablet_motion(dev, 10, 20, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx == 0.0);
+	ck_assert(dy > 0.0);
+	libinput_event_destroy(event);
+
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx == 0.0);
+	ck_assert(dy < 0.0);
+	libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(relative_calibration)
+{
+	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 }
+	};
+	double dx, dy;
+	float calibration[] = { -1, 0, 1, 0, -1, 1 };
+	enum libinput_config_status status;
+
+	if (!libinput_device_config_calibration_has_matrix(dev->libinput_device))
+		return;
+
+	status = libinput_device_config_calibration_set_matrix(
+							dev->libinput_device,
+							calibration);
+	ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
+
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_drain_events(li);
+
+	litest_tablet_motion(dev, 20, 10, axes);
+	libinput_dispatch(li);
+
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx < 0.0);
+	ck_assert(dy == 0.0);
+	libinput_event_destroy(event);
+
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx > 0.0);
+	ck_assert(dy == 0.0);
+	libinput_event_destroy(event);
+
+	litest_tablet_motion(dev, 10, 20, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx == 0.0);
+	ck_assert(dy < 0.0);
+	libinput_event_destroy(event);
+
+	litest_tablet_motion(dev, 10, 10, axes);
+	libinput_dispatch(li);
+	event = libinput_get_event(li);
+	tev = litest_is_tablet_event(event,
+				     LIBINPUT_EVENT_TABLET_TOOL_AXIS);
+	dx = libinput_event_tablet_tool_get_dx(tev);
+	dy = libinput_event_tablet_tool_get_dy(tev);
+	ck_assert(dx == 0.0);
+	ck_assert(dy > 0.0);
+	libinput_event_destroy(event);
+}
+END_TEST
+
 void
 litest_setup_tests(void)
 {
@@ -3271,4 +3470,9 @@ litest_setup_tests(void)
 	litest_add_for_device("tablet:pressure", tablet_pressure_offset_exceed_threshold, LITEST_WACOM_INTUOS);
 	litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_zero_distance, LITEST_WACOM_INTUOS);
 	litest_add_for_device("tablet:pressure", tablet_pressure_offset_none_for_small_distance, LITEST_WACOM_INTUOS);
+
+	litest_add("tablet:relative", relative_no_profile, LITEST_TABLET, LITEST_ANY);
+	litest_add("tablet:relative", relative_no_delta_prox_in, LITEST_TABLET, LITEST_ANY);
+	litest_add("tablet:relative", relative_delta, LITEST_TABLET, LITEST_ANY);
+	litest_add("tablet:relative", relative_calibration, LITEST_TABLET, LITEST_ANY);
 }
-- 
2.5.0



More information about the wayland-devel mailing list