[PATCH libinput 08/15] tablet: support z-rotation for the mouse/lens tool

Peter Hutterer peter.hutterer at who-t.net
Tue Feb 17 21:45:09 PST 2015


Needs to be calculated from the x/y tilt values, the mouse has a fixed offset
of 175 degrees counterclockwise.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 src/evdev-tablet.c     |  95 ++++++++++++++++++++++++++++++++++++------
 src/libinput-private.h |   2 +-
 src/libinput.c         |   1 +
 src/libinput.h         |   6 +++
 test/tablet.c          | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
 tools/event-debug.c    |   8 ++++
 6 files changed, 210 insertions(+), 13 deletions(-)

diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c
index 8b8573a..589cb9c 100644
--- a/src/evdev-tablet.c
+++ b/src/evdev-tablet.c
@@ -40,12 +40,25 @@ static int
 tablet_device_has_axis(struct tablet_dispatch *tablet,
 		       enum libinput_tablet_axis axis)
 {
+	struct libevdev *evdev = tablet->device->evdev;
+	bool has_axis = false;
 	unsigned int code;
 
-	code = axis_to_evcode(axis);
-	return libevdev_has_event_code(tablet->device->evdev,
-				       EV_ABS,
-				       code);
+	if (axis == LIBINPUT_TABLET_AXIS_ROTATION_Z) {
+		has_axis = (libevdev_has_event_code(evdev,
+						    EV_ABS,
+						    ABS_TILT_X) &&
+			    libevdev_has_event_code(evdev,
+						    EV_ABS,
+						    ABS_TILT_Y));
+	} else {
+		code = axis_to_evcode(axis);
+		has_axis = libevdev_has_event_code(evdev,
+						   EV_ABS,
+						   code);
+	}
+
+	return has_axis;
 }
 
 static void
@@ -165,6 +178,33 @@ invert_axis(const struct input_absinfo *absinfo)
 }
 
 static void
+convert_tilt_to_rotation(struct tablet_dispatch *tablet)
+{
+	const int offset = 5;
+	double x, y;
+	double angle = 0.0;
+
+	/* Wacom Intuos 4, 5, Pro mouse calculates rotation from the x/y tilt
+	   values. The device has a 175 degree CCW hardware offset but since we use
+	   atan2 the effective offset is just 5 degrees.
+	   */
+	x = tablet->axes[LIBINPUT_TABLET_AXIS_TILT_X];
+	y = tablet->axes[LIBINPUT_TABLET_AXIS_TILT_Y];
+	clear_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_X);
+	clear_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_Y);
+
+	/* atan2 is CCW, we want CW -> negate x */
+	if (x || y)
+		angle = ((180.0 * atan2(-x, y)) / M_PI);
+
+	angle = fmod(360 + angle - offset, 360);
+
+
+	tablet->axes[LIBINPUT_TABLET_AXIS_ROTATION_Z] = angle;
+	set_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_ROTATION_Z);
+}
+
+static void
 tablet_check_notify_axes(struct tablet_dispatch *tablet,
 			 struct evdev_device *device,
 			 uint32_t time,
@@ -173,12 +213,30 @@ tablet_check_notify_axes(struct tablet_dispatch *tablet,
 	struct libinput_device *base = &device->base;
 	bool axis_update_needed = false;
 	int a;
+	double axes[LIBINPUT_TABLET_AXIS_MAX + 1] = {0};
 
 	for (a = LIBINPUT_TABLET_AXIS_X; a <= LIBINPUT_TABLET_AXIS_MAX; a++) {
 		const struct input_absinfo *absinfo;
 
-		if (!bit_is_set(tablet->changed_axes, a))
+		if (!bit_is_set(tablet->changed_axes, a)) {
+			axes[a] = tablet->axes[a];
 			continue;
+		}
+
+		axis_update_needed = true;
+
+		/* ROTATION_Z is higher than TILT_X/Y so we know that the
+		   tilt axes are already normalized and set */
+		if (a == LIBINPUT_TABLET_AXIS_ROTATION_Z) {
+			if (tablet->current_tool_type == LIBINPUT_TOOL_MOUSE ||
+			    tablet->current_tool_type == LIBINPUT_TOOL_LENS) {
+				convert_tilt_to_rotation(tablet);
+				axes[LIBINPUT_TABLET_AXIS_TILT_X] = 0;
+				axes[LIBINPUT_TABLET_AXIS_TILT_Y] = 0;
+				axes[a] = tablet->axes[a];
+			}
+			continue;
+		}
 
 		absinfo = libevdev_get_abs_info(device->evdev,
 						axis_to_evcode(a));
@@ -205,7 +263,7 @@ tablet_check_notify_axes(struct tablet_dispatch *tablet,
 			break;
 		}
 
-		axis_update_needed = true;
+		axes[a] = tablet->axes[a];
 	}
 
 	/* We need to make sure that we check that the tool is not out of
@@ -222,13 +280,13 @@ tablet_check_notify_axes(struct tablet_dispatch *tablet,
 						tool,
 						LIBINPUT_TOOL_PROXIMITY_IN,
 						tablet->changed_axes,
-						tablet->axes);
+						axes);
 		else
 			tablet_notify_axis(base,
 					   time,
 					   tool,
 					   tablet->changed_axes,
-					   tablet->axes);
+					   axes);
 	}
 
 	memset(tablet->changed_axes, 0, sizeof(tablet->changed_axes));
@@ -369,10 +427,8 @@ tablet_get_tool(struct tablet_dispatch *tablet,
 		 *   with rotation
 		 * - All of normal pens and the airbrush support all of the
 		 *   extra axes if the tablet can report them
-		 * - All of the mouse like devices don't really report any of
-		 *   the extra axes except for rotation.
-		 * (as of writing this comment, rotation isn't supported, so you
-		 * won't see the mouse or art pen here)
+		 * - All of the mouse-like devices don't report any of
+		 *   the extra axes except for rotation (calculated from tilt x/y).
 		 */
 		switch (type) {
 		case LIBINPUT_TOOL_PEN:
@@ -397,6 +453,13 @@ tablet_get_tool(struct tablet_dispatch *tablet,
 				set_bit(tool->axis_caps,
 					LIBINPUT_TABLET_AXIS_TILT_Y);
 			break;
+		case LIBINPUT_TOOL_MOUSE:
+		case LIBINPUT_TOOL_LENS:
+			if (bit_is_set(tablet->axis_caps,
+				       LIBINPUT_TABLET_AXIS_ROTATION_Z))
+				set_bit(tool->axis_caps,
+					LIBINPUT_TABLET_AXIS_ROTATION_Z);
+			break;
 		default:
 			break;
 		}
@@ -487,6 +550,14 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet)
 		else
 			tablet->axes[LIBINPUT_TABLET_AXIS_PRESSURE] = 0;
 	}
+
+	/* If we have a mouse/lens cursor and the tilt changed, the rotation
+	   changed. Mark this, calculate the angle later */
+	if ((tablet->current_tool_type == LIBINPUT_TOOL_MOUSE ||
+	    tablet->current_tool_type == LIBINPUT_TOOL_LENS) &&
+	    (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_X) ||
+	     bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_AXIS_TILT_Y)))
+		set_bit(tablet->changed_axes, LIBINPUT_TABLET_AXIS_ROTATION_Z);
 }
 
 static void
diff --git a/src/libinput-private.h b/src/libinput-private.h
index ac8f9fb..df59d28 100644
--- a/src/libinput-private.h
+++ b/src/libinput-private.h
@@ -30,7 +30,7 @@
 #include "libinput.h"
 #include "libinput-util.h"
 
-#define LIBINPUT_TABLET_AXIS_MAX LIBINPUT_TABLET_AXIS_TILT_Y
+#define LIBINPUT_TABLET_AXIS_MAX LIBINPUT_TABLET_AXIS_ROTATION_Z
 
 struct libinput_source;
 
diff --git a/src/libinput.c b/src/libinput.c
index 3aa684a..d2af72f 100644
--- a/src/libinput.c
+++ b/src/libinput.c
@@ -584,6 +584,7 @@ libinput_event_tablet_get_axis_value(struct libinput_event_tablet *event,
 		case LIBINPUT_TABLET_AXIS_PRESSURE:
 		case LIBINPUT_TABLET_AXIS_TILT_X:
 		case LIBINPUT_TABLET_AXIS_TILT_Y:
+		case LIBINPUT_TABLET_AXIS_ROTATION_Z:
 			return event->axes[axis];
 		default:
 			return 0;
diff --git a/src/libinput.h b/src/libinput.h
index 3cc041e..dbd7bc4 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -143,6 +143,7 @@ enum libinput_tablet_axis {
 	LIBINPUT_TABLET_AXIS_PRESSURE = 4,
 	LIBINPUT_TABLET_AXIS_TILT_X = 5,
 	LIBINPUT_TABLET_AXIS_TILT_Y = 6,
+	LIBINPUT_TABLET_AXIS_ROTATION_Z = 7,
 };
 
 /**
@@ -1038,6 +1039,11 @@ libinput_event_tablet_axis_has_changed(struct libinput_event_tablet *event,
  * - @ref LIBINPUT_TABLET_AXIS_TILT_X and @ref LIBINPUT_TABLET_AXIS_TILT_Y -
  *   normalized value between -1 and 1 that indicates the X or Y tilt of the
  *   tool
+ * - @ref LIBINPUT_TABLET_AXIS_ROTATION_Z - The z rotation of the tool in
+ *   degrees, clockwise from the tool's logical neutral position. For the
+ *   @ref LIBINPUT_TOOL_MOUSE and @ref LIBINPUT_TOOL_LENS tools the logical
+ *   neutral position is pointing to the current logical north of the
+ *   tablet.
  *
  * @note This function may be called for a specific axis even if
  * libinput_event_tablet_axis_has_changed() returns 0 for that axis.
diff --git a/test/tablet.c b/test/tablet.c
index 746f643..3373f92 100644
--- a/test/tablet.c
+++ b/test/tablet.c
@@ -1038,6 +1038,115 @@ START_TEST(tool_capabilities)
 }
 END_TEST
 
+START_TEST(mouse_tool)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct libinput_event *event;
+	struct libinput_event_tablet *tev;
+	struct libinput_tool *tool;
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	litest_drain_events(li);
+
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_event(dev, EV_MSC, MSC_SERIAL, 1000);
+	litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+	litest_wait_for_event_of_type(li,
+				      LIBINPUT_EVENT_TABLET_PROXIMITY,
+				      -1);
+	event = libinput_get_event(li);
+	tev = libinput_event_get_tablet_event(event);
+	tool = libinput_event_tablet_get_tool(tev);
+	ck_assert_notnull(tool);
+	ck_assert_int_eq(libinput_tool_get_type(tool),
+			 LIBINPUT_TOOL_MOUSE);
+
+	libinput_event_destroy(event);
+}
+END_TEST
+
+START_TEST(mouse_rotation)
+{
+	struct litest_device *dev = litest_current_device();
+	struct libinput *li = dev->libinput;
+	struct libinput_event *event;
+	struct libinput_event_tablet *tev;
+	int angle;
+	int tilt_center_x, tilt_center_y;
+	const struct input_absinfo *abs;
+	double val, old_val = 0;
+
+	struct axis_replacement axes[] = {
+		{ ABS_DISTANCE, 10 },
+		{ ABS_TILT_X, 0 },
+		{ ABS_TILT_Y, 0 },
+		{ -1, -1 }
+	};
+
+	if (!libevdev_has_event_code(dev->evdev,
+				    EV_KEY,
+				    BTN_TOOL_MOUSE))
+		return;
+
+	abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_X);
+	ck_assert_notnull(abs);
+	tilt_center_x = (abs->maximum - abs->minimum + 1) / 2;
+
+	abs = libevdev_get_abs_info(dev->evdev, ABS_TILT_Y);
+	ck_assert_notnull(abs);
+	tilt_center_y = (abs->maximum - abs->minimum + 1) / 2;
+
+	litest_drain_events(li);
+
+	litest_push_event_frame(dev);
+	litest_tablet_proximity_in(dev, 10, 10, axes);
+	litest_event(dev, EV_KEY, BTN_TOOL_MOUSE, 1);
+	litest_pop_event_frame(dev);
+
+	litest_drain_events(li);
+
+	/* cos/sin are 90 degrees offset from the north-is-zero that
+	   libinput uses. 175 is the CCW offset in the mouse HW */
+	for (angle = 5; angle < 360; angle += 5) {
+		double a = (angle - 90 - 175)/180.0 * M_PI;
+		int x, y;
+
+		x = cos(a) * 20 + tilt_center_x;
+		y = sin(a) * 20 + tilt_center_y;
+
+		litest_event(dev, EV_ABS, ABS_TILT_X, x);
+		litest_event(dev, EV_ABS, ABS_TILT_Y, y);
+		litest_event(dev, EV_SYN, SYN_REPORT, 0);
+
+		litest_wait_for_event_of_type(li,
+					      LIBINPUT_EVENT_TABLET_AXIS,
+					      -1);
+		event = libinput_get_event(li);
+		tev = libinput_event_get_tablet_event(event);
+		ck_assert(libinput_event_tablet_axis_has_changed(tev,
+					 LIBINPUT_TABLET_AXIS_ROTATION_Z));
+		val = libinput_event_tablet_get_axis_value(tev,
+					 LIBINPUT_TABLET_AXIS_ROTATION_Z);
+
+		/* rounding error galore, we can't test for anything more
+		   precise than these */
+		litest_assert_double_lt(val, 360.0);
+		litest_assert_double_gt(val, old_val);
+		litest_assert_double_lt(val, angle + 5);
+
+		old_val = val;
+		libinput_event_destroy(event);
+		litest_assert_empty_queue(li);
+	}
+}
+END_TEST
+
 int
 main(int argc, char **argv)
 {
@@ -1057,6 +1166,8 @@ main(int argc, char **argv)
 	litest_add("tablet:left_handed", left_handed, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:normalization", normalization, LITEST_TABLET, LITEST_ANY);
 	litest_add("tablet:pad", pad_buttons_ignored, LITEST_TABLET, LITEST_ANY);
+	litest_add("tablet:mouse", mouse_tool, LITEST_TABLET, LITEST_ANY);
+	litest_add("tablet:mouse", mouse_rotation, LITEST_TABLET, LITEST_ANY);
 
 	return litest_run(argc, argv);
 }
diff --git a/tools/event-debug.c b/tools/event-debug.c
index f74344e..e8186dd 100644
--- a/tools/event-debug.c
+++ b/tools/event-debug.c
@@ -293,6 +293,7 @@ print_tablet_axis_event(struct libinput_event *ev)
 	struct libinput_event_tablet *t = libinput_event_get_tablet_event(ev);
 	double x, y;
 	double dist, pressure;
+	double rotation;
 
 	print_event_time(libinput_event_tablet_get_time(t));
 
@@ -316,6 +317,13 @@ print_tablet_axis_event(struct libinput_event *ev)
 	else
 		printf("pressure: %.2f%s",
 		       pressure, tablet_axis_changed_sym(t, LIBINPUT_TABLET_AXIS_PRESSURE));
+
+	rotation = libinput_event_tablet_get_axis_value(t,
+					LIBINPUT_TABLET_AXIS_ROTATION_Z);
+	printf(" rotation: %.2f%s",
+	       rotation,
+	       tablet_axis_changed_sym(t, LIBINPUT_TABLET_AXIS_ROTATION_Z));
+
 	printf("\n");
 }
 
-- 
2.1.0



More information about the wayland-devel mailing list