[PATCH weston v2 11/12] clients: add a new touchscreen calibrator

Pekka Paalanen ppaalanen at gmail.com
Mon Apr 30 13:03:33 UTC 2018


From: Louis-Francis Ratté-Boulianne <lfrb at collabora.com>

The new calibrator uses weston_touch_calibration protocol extension and
provides the following features:

- chooses the physical touch device to be calibrated by DEVPATH or by
  the output/head name; device enumeration provided

- the compositor ensures the calibrator window is shown in the correct
  position and size

- no matter how wrong the old calibration is, the touch events will
  always arrive in the application

- the calibration is complete, not incremental; the received touch
  events are guaranteed to be unmodified

- computes a libinput style calibration matrix directly, not the
  WL_CALIBRATION format

- supports multiple touch devices: calibrate one device at a time, and
  show user feedback on touching a wrong device instead of recording bad
  data

- uses four touch point samples: three to compute the calibration, and
  one to verify the calibration is roughly correct

- consistent exit codes

- upload the new calibration into the server after successful
  and verified calibration

Due to using special touchscreen calibration protocol extension, this
application cannot be tested without touch input from the compositor.

Practically all of the above mentioned are unlike how the old
calibrator client worked.

Co-developed by Louis-Francis and Pekka.

v2:
- improve help() text
- rename wrong_touch_handler() to invalid_touch_handler()
- improve debug prints by adding sample number
- reorganize code into sample funcs vs. touch funcs
- add a state machine to properly process touch and related events

Signed-off-by: Louis-Francis Ratté-Boulianne <lfrb at collabora.com>
Signed-off-by: Pekka Paalanen <pekka.paalanen at collabora.co.uk>
v1 Tested-by: Matt Hoosier <matt.hoosier at gmail.com>
---
 .gitignore                 |   1 +
 Makefile.am                |  14 +
 clients/touch-calibrator.c | 970 +++++++++++++++++++++++++++++++++++++++++++++
 clients/window.c           |   4 +-
 clients/window.h           |   4 +
 5 files changed, 991 insertions(+), 2 deletions(-)
 create mode 100644 clients/touch-calibrator.c

diff --git a/.gitignore b/.gitignore
index 661f4882..69c5d61b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,7 @@ weston-simple-damage
 weston-smoke
 weston-stacking
 weston-subsurfaces
+weston-touch-calibrator
 weston-transformed
 weston-view
 
diff --git a/Makefile.am b/Makefile.am
index 342daf13..f1a9eae7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -546,6 +546,7 @@ demo_clients =					\
 	weston-fullscreen			\
 	weston-stacking				\
 	weston-calibrator			\
+	weston-touch-calibrator			\
 	weston-scaler
 
 if INSTALL_DEMO_CLIENTS
@@ -784,6 +785,17 @@ weston_calibrator_SOURCES = 				\
 weston_calibrator_LDADD = libtoytoolkit.la
 weston_calibrator_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS)
 
+weston_touch_calibrator_SOURCES = 			\
+	clients/touch-calibrator.c			\
+	shared/helpers.h				\
+	shared/matrix.c					\
+	shared/matrix.h
+nodist_weston_touch_calibrator_SOURCES =		\
+	protocol/weston-touch-calibration-protocol.c	\
+	protocol/weston-touch-calibration-client-protocol.h
+weston_touch_calibrator_LDADD = libtoytoolkit.la
+weston_touch_calibrator_CFLAGS = $(AM_CFLAGS) $(CLIENT_CFLAGS)
+
 if BUILD_SUBSURFACES_CLIENT
 demo_clients += weston-subsurfaces
 weston_subsurfaces_SOURCES = 			\
@@ -869,6 +881,8 @@ endif
 BUILT_SOURCES +=					\
 	protocol/weston-screenshooter-protocol.c			\
 	protocol/weston-screenshooter-client-protocol.h			\
+	protocol/weston-touch-calibration-protocol.c			\
+	protocol/weston-touch-calibration-client-protocol.h		\
 	protocol/text-cursor-position-client-protocol.h	\
 	protocol/text-cursor-position-protocol.c	\
 	protocol/text-input-unstable-v1-protocol.c			\
diff --git a/clients/touch-calibrator.c b/clients/touch-calibrator.c
new file mode 100644
index 00000000..3c7e6ece
--- /dev/null
+++ b/clients/touch-calibrator.c
@@ -0,0 +1,970 @@
+/*
+ * Copyright 2012 Intel Corporation
+ * Copyright 2017-2018 Collabora, Ltd.
+ * Copyright 2017-2018 General Electric Company
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cairo.h>
+#include <math.h>
+#include <assert.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include <wayland-client.h>
+
+#include "clients/window.h"
+#include "shared/helpers.h"
+#include "shared/matrix.h"
+
+#include "weston-touch-calibration-client-protocol.h"
+
+enum exit_code {
+	CAL_EXIT_SUCCESS = 0,
+	CAL_EXIT_ERROR = 1,
+	CAL_EXIT_CANCELLED = 2,
+};
+
+static int debug_;
+static int verbose_;
+
+#define pr_ver(...) do { \
+	if (verbose_) \
+		printf(__VA_ARGS__); \
+} while (0)
+
+#define pr_dbg(...) do { \
+	if (debug_) \
+		fprintf(stderr, __VA_ARGS__); \
+} while (0)
+
+static void
+pr_err(const char *fmt, ...) WL_PRINTF(1, 2);
+
+/* Our points for the calibration must be not be on a line */
+static const struct {
+	float x_ratio, y_ratio;
+} test_ratios[] =  {
+	{ 0.15, 0.10 }, /* three points for calibration */
+	{ 0.85, 0.13 },
+	{ 0.20, 0.80 },
+	{ 0.70, 0.75 }  /* and one for verification */
+};
+
+#define NR_SAMPLES ((int)ARRAY_LENGTH(test_ratios))
+
+struct point {
+	double x;
+	double y;
+};
+
+struct sample {
+	int ind;
+	struct point drawn;	/**< drawn point, pixels */
+	struct weston_touch_coordinate *pending;
+	struct point drawn_cal;	/**< drawn point, converted */
+	bool conv_done;
+	struct point touched;	/**< touch point, normalized */
+	bool touch_done;
+};
+
+struct poly {
+	struct color {
+		double r, g, b, a;
+	} color;
+	int n_verts;
+	const struct point *verts;
+};
+
+/** Touch event handling state machine
+ *
+ * Only a complete down->up->frame sequence should be accepted with user
+ * feedback "right", and anything that deviates from that (invalid_touch,
+ * cancel, multiple touch-downs) needs to undo the current sample and
+ * possibly show user feedback "wrong".
+ *
+ * <STATE>
+ * - <triggers>: <actions>
+ *
+ * IDLE
+ * - touch down: sample, -> DOWN
+ * - touch up: no-op
+ * - frame: no-op
+ * - invalid_touch: (undo), wrong, -> WAIT
+ * - cancel: no-op
+ * DOWN (first touch down)
+ * - touch down: undo, wrong, -> WAIT
+ * - touch up: -> UP
+ * - frame: no-op
+ * - invalid_touch: undo, wrong, -> WAIT
+ * - cancel: undo, -> IDLE
+ * UP (first touch was down and up)
+ * - touch down: undo, wrong, -> WAIT
+ * - touch up: no-op
+ * - frame: right, touch finish, -> WAIT
+ * - invalid_touch: undo, wrong, -> WAIT
+ * - cancel: undo, -> IDLE
+ * WAIT (show user feedback)
+ * - touch down: no-op
+ * - touch up: no-op
+ * - frame, cancel, timer: if num_tp == 0 && timer_done -> IDLE
+ * - invalid_touch: no-op
+ */
+enum touch_state {
+	STATE_IDLE,
+	STATE_DOWN,
+	STATE_UP,
+	STATE_WAIT
+};
+
+struct calibrator {
+	struct sample samples[NR_SAMPLES];
+	int current_sample;
+
+	struct display *display;
+	struct weston_touch_calibration *calibration;
+	struct weston_touch_calibrator *calibrator;
+	struct window *window;
+	struct widget *widget;
+
+	int n_devices_listed;
+	char *match_name;
+	char *device_name;
+
+	int width;
+	int height;
+
+	bool cancelled;
+
+	const struct poly *current_poly;
+	bool exiting;
+
+	struct toytimer wait_timer;
+	bool timer_pending;
+	enum touch_state state;
+
+	int num_tp;		/* touch points down count */
+};
+
+static struct sample *
+current_sample(struct calibrator *cal)
+{
+	return &cal->samples[cal->current_sample];
+}
+
+static void
+sample_start(struct calibrator *cal, int i)
+{
+	struct sample *s = &cal->samples[i];
+
+	assert(i >= 0 && i < NR_SAMPLES);
+
+	s->ind = i;
+	s->drawn.x = round(test_ratios[i].x_ratio * cal->width);
+	s->drawn.y = round(test_ratios[i].y_ratio * cal->height);
+	s->pending = NULL;
+	s->conv_done = false;
+	s->touch_done = false;
+
+	cal->current_sample = i;
+}
+
+static struct point
+wire_to_point(uint32_t xu, uint32_t yu)
+{
+	struct point p = {
+		.x = (double)xu / 0xffffffff,
+		.y = (double)yu / 0xffffffff
+	};
+
+	return p;
+}
+
+static void
+sample_touch_down(struct calibrator *cal, uint32_t xu, uint32_t yu)
+{
+	struct sample *s = current_sample(cal);
+
+	s->touched = wire_to_point(xu, yu);
+	s->touch_done = true;
+
+	pr_dbg("Down[%d] (%f, %f)\n", s->ind, s->touched.x, s->touched.y);
+}
+
+static void
+coordinate_result_handler(void *data, struct weston_touch_coordinate *interface,
+			  uint32_t xu, uint32_t yu)
+{
+	struct sample *s = data;
+
+	weston_touch_coordinate_destroy(s->pending);
+	s->pending = NULL;
+
+	s->drawn_cal = wire_to_point(xu, yu);
+	s->conv_done = true;
+
+	pr_dbg("Conv[%d] (%f, %f)\n", s->ind, s->drawn_cal.x, s->drawn_cal.y);
+}
+
+struct weston_touch_coordinate_listener coordinate_listener = {
+	coordinate_result_handler
+};
+
+static void
+sample_undo(struct calibrator *cal)
+{
+	struct sample *s = current_sample(cal);
+
+	pr_dbg("Undo[%d]\n", s->ind);
+
+	s->touch_done = false;
+	s->conv_done = false;
+	if (s->pending) {
+		weston_touch_coordinate_destroy(s->pending);
+		s->pending = NULL;
+	}
+}
+
+static void
+sample_finish(struct calibrator *cal)
+{
+	struct sample *s = current_sample(cal);
+
+	pr_dbg("Finish[%d]\n", s->ind);
+
+	assert(!s->pending && !s->conv_done);
+
+	s->pending = weston_touch_calibrator_convert(cal->calibrator,
+						     (int32_t)s->drawn.x,
+						     (int32_t)s->drawn.y);
+	weston_touch_coordinate_add_listener(s->pending,
+					     &coordinate_listener, s);
+
+	if (cal->current_sample + 1 < NR_SAMPLES) {
+		sample_start(cal, cal->current_sample + 1);
+	} else {
+		pr_dbg("got all touches\n");
+		cal->exiting = true;
+	}
+}
+
+/*
+ * Calibration algorithm:
+ *
+ * The equation we want to apply at event time where x' and y' are the
+ * calibrated co-ordinates.
+ *
+ * x' = Ax + By + C
+ * y' = Dx + Ey + F
+ *
+ * For example "zero calibration" would be A=1.0 B=0.0 C=0.0, D=0.0, E=1.0,
+ * and F=0.0.
+ *
+ * With 6 unknowns we need 6 equations to find the constants:
+ *
+ * x1' = Ax1 + By1 + C
+ * y1' = Dx1 + Ey1 + F
+ * ...
+ * x3' = Ax3 + By3 + C
+ * y3' = Dx3 + Ey3 + F
+ *
+ * In matrix form:
+ *
+ * x1'   x1 y1 1      A
+ * x2' = x2 y2 1  x   B
+ * x3'   x3 y3 1      C
+ *
+ * So making the matrix M we can find the constants with:
+ *
+ * A            x1'
+ * B = M^-1  x  x2'
+ * C            x3'
+ *
+ * (and similarly for D, E and F)
+ *
+ * For the calibration the desired values x, y are the same values at which
+ * we've drawn at.
+ *
+ */
+static int
+compute_calibration(struct calibrator *cal, float *result)
+{
+	struct weston_matrix m;
+	struct weston_matrix inverse;
+	struct weston_vector x_calib;
+	struct weston_vector y_calib;
+	int i;
+
+	assert(NR_SAMPLES >= 3);
+
+	/*
+	 * x1 y1  1  0
+	 * x2 y2  1  0
+	 * x3 y3  1  0
+	 *  0  0  0  1
+	 */
+	weston_matrix_init(&m);
+	for (i = 0; i < 3; i++) {
+		m.d[i + 0] = cal->samples[i].touched.x;
+		m.d[i + 4] = cal->samples[i].touched.y;
+		m.d[i + 8] = 1.0f;
+	}
+	m.type = WESTON_MATRIX_TRANSFORM_OTHER;
+
+	if (weston_matrix_invert(&inverse, &m) < 0) {
+		pr_err("non-invertible matrix during computation\n");
+		return -1;
+	}
+
+	for (i = 0; i < 3; i++) {
+		x_calib.f[i] = cal->samples[i].drawn_cal.x;
+		y_calib.f[i] = cal->samples[i].drawn_cal.y;
+	}
+	x_calib.f[3] = 0.0f;
+	y_calib.f[3] = 0.0f;
+
+	/* Multiples into the vector */
+	weston_matrix_transform(&inverse, &x_calib);
+	weston_matrix_transform(&inverse, &y_calib);
+
+	for (i = 0; i < 3; i++)
+		result[i] = x_calib.f[i];
+	for (i = 0; i < 3; i++)
+		result[i + 3] = y_calib.f[i];
+
+	return 0;
+}
+
+static int
+verify_calibration(struct calibrator *cal, const float *r)
+{
+	double thr = 0.1; /* accepted error radius */
+	struct point e; /* expected value; error */
+	const struct sample *s = &cal->samples[3];
+
+	/* transform raw touches through the matrix */
+	e.x = r[0] * s->touched.x + r[1] * s->touched.y + r[2];
+	e.y = r[3] * s->touched.x + r[4] * s->touched.y + r[5];
+
+	/* compute error */
+	e.x -= s->drawn_cal.x;
+	e.y -= s->drawn_cal.y;
+
+	pr_dbg("calibration test error: %f, %f\n", e.x, e.y);
+
+	if (e.x * e.x + e.y * e.y < thr * thr)
+		return 0;
+
+	pr_err("Calibration verification failed, too large error.\n");
+	return -1;
+}
+
+static void
+send_calibration(struct calibrator *cal, float *values)
+{
+	struct wl_array matrix;
+	float *f;
+	int i;
+
+	wl_array_init(&matrix);
+	for (i = 0; i < 6; i++) {
+		f = wl_array_add(&matrix, sizeof *f);
+		*f = values[i];
+	}
+	weston_touch_calibration_save(cal->calibration,
+				      cal->device_name, &matrix);
+	wl_array_release(&matrix);
+}
+
+static const struct point cross_verts[] = {
+	{ 0.1, 0.2 },
+	{ 0.2, 0.1 },
+	{ 0.5, 0.4 },
+	{ 0.8, 0.1 },
+	{ 0.9, 0.2 },
+	{ 0.6, 0.5 },
+	{ 0.9, 0.8 },
+	{ 0.8, 0.9 },
+	{ 0.5, 0.6 },
+	{ 0.2, 0.9 },
+	{ 0.1, 0.8 },
+	{ 0.4, 0.5 },
+};
+
+/* a red cross, for "wrong" */
+static const struct poly cross = {
+	.color = { 0.7, 0.0, 0.0, 1.0 },
+	.n_verts = ARRAY_LENGTH(cross_verts),
+	.verts = cross_verts
+};
+
+static const struct point check_verts[] = {
+	{ 0.5, 0.7 },
+	{ 0.8, 0.1 },
+	{ 0.9, 0.1 },
+	{ 0.55, 0.8 },
+	{ 0.45, 0.8 },
+	{ 0.3, 0.5 },
+	{ 0.4, 0.5 }
+};
+
+/* a green check mark, for "right" */
+static const struct poly check = {
+	.color = { 0.0, 0.7, 0.0, 1.0 },
+	.n_verts = ARRAY_LENGTH(check_verts),
+	.verts = check_verts
+};
+
+static void
+draw_poly(cairo_t *cr, const struct poly *poly)
+{
+	int i;
+
+	cairo_set_source_rgba(cr, poly->color.r, poly->color.g,
+			      poly->color.b, poly->color.a);
+	cairo_move_to(cr, poly->verts[0].x, poly->verts[0].y);
+	for (i = 1; i < poly->n_verts; i++)
+		cairo_line_to(cr, poly->verts[i].x, poly->verts[i].y);
+	cairo_close_path(cr);
+	cairo_fill(cr);
+}
+
+static void
+feedback_show(struct calibrator *cal, const struct poly *what)
+{
+	cal->current_poly = what;
+	widget_schedule_redraw(cal->widget);
+
+	toytimer_arm_once_usec(&cal->wait_timer, 1000 * 1000);
+	cal->timer_pending = true;
+}
+
+static void
+feedback_hide(struct calibrator *cal)
+{
+	cal->current_poly = NULL;
+	widget_schedule_redraw(cal->widget);
+}
+
+static void
+try_enter_state_idle(struct calibrator *cal)
+{
+	if (cal->num_tp != 0)
+		return;
+
+	if (cal->timer_pending)
+		return;
+
+	cal->state = STATE_IDLE;
+
+	feedback_hide(cal);
+
+	if (cal->exiting)
+		display_exit(cal->display);
+}
+
+static void
+enter_state_wait(struct calibrator *cal)
+{
+	assert(cal->timer_pending);
+	cal->state = STATE_WAIT;
+}
+
+static void
+wait_timer_done(struct toytimer *tt)
+{
+	struct calibrator *cal = container_of(tt, struct calibrator, wait_timer);
+
+	assert(cal->state == STATE_WAIT);
+	cal->timer_pending = false;
+	try_enter_state_idle(cal);
+}
+
+static void
+redraw_handler(struct widget *widget, void *data)
+{
+	struct calibrator *cal = data;
+	struct sample *s = current_sample(cal);
+	struct rectangle allocation;
+	cairo_surface_t *surface;
+	cairo_t *cr;
+
+	widget_get_allocation(cal->widget, &allocation);
+	assert(allocation.width == cal->width);
+	assert(allocation.height == cal->height);
+
+	surface = window_get_surface(cal->window);
+	cr = cairo_create(surface);
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+	cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
+	cairo_paint(cr);
+
+	if (!cal->current_poly) {
+		cairo_translate(cr, s->drawn.x, s->drawn.y);
+		cairo_set_line_width(cr, 2.0);
+		cairo_set_source_rgb(cr, 0.7, 0.0, 0.0);
+		cairo_move_to(cr, 0, -10.0);
+		cairo_line_to(cr, 0, 10.0);
+		cairo_stroke(cr);
+		cairo_move_to(cr, -10.0, 0);
+		cairo_line_to(cr, 10.0, 0.0);
+		cairo_stroke(cr);
+	} else {
+		cairo_scale(cr, allocation.width, allocation.height);
+		draw_poly(cr, cal->current_poly);
+	}
+
+	cairo_destroy(cr);
+	cairo_surface_destroy(surface);
+}
+
+static struct calibrator *
+calibrator_create(struct display *display, const char *match_name)
+{
+	struct calibrator *cal;
+
+	cal = zalloc(sizeof *cal);
+	if (!cal)
+		abort();
+
+	cal->match_name = match_name ? strdup(match_name) : NULL;
+	cal->window = window_create_custom(display);
+	cal->widget = window_add_widget(cal->window, cal);
+	window_inhibit_redraw(cal->window);
+	window_set_title(cal->window, "Touchscreen calibrator");
+	cal->display = display;
+
+	widget_set_redraw_handler(cal->widget, redraw_handler);
+
+	toytimer_init(&cal->wait_timer, CLOCK_MONOTONIC,
+		      display, wait_timer_done);
+
+	cal->state = STATE_IDLE;
+	cal->num_tp = 0;
+
+	return cal;
+}
+
+static void
+configure_handler(void *data, struct weston_touch_calibrator *interface,
+		  int32_t width, int32_t height)
+{
+	struct calibrator *cal = data;
+
+	pr_dbg("Configure calibrator window to size %ix%i\n", width, height);
+	cal->width = width;
+	cal->height = height;
+	window_schedule_resize(cal->window, width, height);
+	window_uninhibit_redraw(cal->window);
+
+	sample_start(cal, 0);
+	widget_schedule_redraw(cal->widget);
+}
+
+static void
+cancel_calibration_handler(void *data, struct weston_touch_calibrator *interface)
+{
+	struct calibrator *cal = data;
+
+	pr_dbg("calibration cancelled by the display server, quitting.\n");
+	cal->cancelled = true;
+	display_exit(cal->display);
+}
+
+static void
+invalid_touch_handler(void *data, struct weston_touch_calibrator *interface)
+{
+	struct calibrator *cal = data;
+
+	pr_dbg("invalid touch\n");
+
+	switch (cal->state) {
+	case STATE_IDLE:
+	case STATE_DOWN:
+	case STATE_UP:
+		sample_undo(cal);
+		feedback_show(cal, &cross);
+		enter_state_wait(cal);
+		break;
+	case STATE_WAIT:
+		/* no-op */
+		break;
+	}
+}
+
+static void
+down_handler(void *data, struct weston_touch_calibrator *interface,
+	     uint32_t time, int32_t id, uint32_t xu, uint32_t yu)
+{
+	struct calibrator *cal = data;
+
+	cal->num_tp++;
+
+	switch (cal->state) {
+	case STATE_IDLE:
+		sample_touch_down(cal, xu, yu);
+		cal->state = STATE_DOWN;
+		break;
+	case STATE_DOWN:
+	case STATE_UP:
+		sample_undo(cal);
+		feedback_show(cal, &cross);
+		enter_state_wait(cal);
+		break;
+	case STATE_WAIT:
+		/* no-op */
+		break;
+	}
+
+	if (cal->current_poly)
+		return;
+}
+
+static void
+up_handler(void *data, struct weston_touch_calibrator *interface,
+	   uint32_t time, int32_t id)
+{
+	struct calibrator *cal = data;
+
+	cal->num_tp--;
+	if (cal->num_tp < 0) {
+		pr_dbg("Unmatched touch up.\n");
+		cal->num_tp = 0;
+	}
+
+	switch (cal->state) {
+	case STATE_DOWN:
+		cal->state = STATE_UP;
+		break;
+	case STATE_IDLE:
+	case STATE_UP:
+	case STATE_WAIT:
+		/* no-op */
+		break;
+	}
+}
+
+static void
+motion_handler(void *data, struct weston_touch_calibrator *interface,
+	       uint32_t time, int32_t id, uint32_t xu, uint32_t yu)
+{
+	/* motion is ignored */
+}
+
+static void
+frame_handler(void *data, struct weston_touch_calibrator *interface)
+{
+	struct calibrator *cal = data;
+
+	switch (cal->state) {
+	case STATE_IDLE:
+	case STATE_DOWN:
+		/* no-op */
+		break;
+	case STATE_UP:
+		feedback_show(cal, &check);
+		sample_finish(cal);
+		enter_state_wait(cal);
+		break;
+	case STATE_WAIT:
+		try_enter_state_idle(cal);
+		break;
+	}
+}
+
+static void
+cancel_handler(void *data, struct weston_touch_calibrator *interface)
+{
+	struct calibrator *cal = data;
+
+	cal->num_tp = 0;
+
+	switch (cal->state) {
+	case STATE_IDLE:
+		/* no-op */
+		break;
+	case STATE_DOWN:
+	case STATE_UP:
+		sample_undo(cal);
+		try_enter_state_idle(cal);
+		break;
+	case STATE_WAIT:
+		try_enter_state_idle(cal);
+		break;
+	}
+}
+
+struct weston_touch_calibrator_listener calibrator_listener = {
+	configure_handler,
+	cancel_calibration_handler,
+	invalid_touch_handler,
+	down_handler,
+	up_handler,
+	motion_handler,
+	frame_handler,
+	cancel_handler
+};
+
+static void
+calibrator_show(struct calibrator *cal)
+{
+	struct wl_surface *surface = window_get_wl_surface(cal->window);
+
+	cal->calibrator =
+		weston_touch_calibration_create_calibrator(cal->calibration,
+							   surface,
+							   cal->device_name);
+	weston_touch_calibrator_add_listener(cal->calibrator,
+					     &calibrator_listener, cal);
+}
+
+static void
+calibrator_destroy(struct calibrator *cal)
+{
+	toytimer_fini(&cal->wait_timer);
+	if (cal->calibrator)
+		weston_touch_calibrator_destroy(cal->calibrator);
+	if (cal->calibration)
+		weston_touch_calibration_destroy(cal->calibration);
+	if (cal->widget)
+		widget_destroy(cal->widget);
+	if (cal->window)
+		window_destroy(cal->window);
+	free(cal->match_name);
+	free(cal->device_name);
+	free(cal);
+}
+
+static void
+touch_device_handler(void *data, struct weston_touch_calibration *c,
+		     const char *device, const char *head)
+{
+	struct calibrator *cal = data;
+
+	cal->n_devices_listed++;
+
+	if (!cal->match_name) {
+		printf("device \"%s\" - head \"%s\"\n", device, head);
+		return;
+	}
+
+	if (cal->device_name)
+		return;
+
+	if (strcmp(cal->match_name, device) == 0 ||
+	    strcmp(cal->match_name, head) == 0)
+		cal->device_name = strdup(device);
+}
+
+struct weston_touch_calibration_listener touch_calibration_listener = {
+	touch_device_handler
+};
+
+static void
+global_handler(struct display *display, uint32_t name,
+	       const char *interface, uint32_t version, void *data)
+{
+	struct calibrator *cal = data;
+
+	if (strcmp(interface, "weston_touch_calibration") == 0) {
+		cal->calibration = display_bind(display, name,
+						&weston_touch_calibration_interface, 1);
+		weston_touch_calibration_add_listener(cal->calibration,
+						      &touch_calibration_listener,
+						      cal);
+	}
+}
+
+static int
+calibrator_run(struct calibrator *cal)
+{
+	struct wl_display *dpy;
+	struct sample *s;
+	bool wait;
+	int i;
+	int ret;
+	float result[6];
+
+	calibrator_show(cal);
+	display_run(cal->display);
+
+	if (cal->cancelled)
+		return CAL_EXIT_CANCELLED;
+
+	/* remove the window, no more input events */
+	widget_destroy(cal->widget);
+	cal->widget = NULL;
+	window_destroy(cal->window);
+	cal->window = NULL;
+
+	/* wait for all conversions to return */
+	dpy = display_get_display(cal->display);
+	do {
+		wait = false;
+
+		for (i = 0; i < NR_SAMPLES; i++)
+			if (cal->samples[i].pending)
+				wait = true;
+
+		if (wait) {
+			ret = wl_display_roundtrip(dpy);
+			if (ret < 0)
+				return CAL_EXIT_ERROR;
+		}
+	} while (wait);
+
+	for (i = 0; i < NR_SAMPLES; i++) {
+		s = &cal->samples[i];
+		if (!s->conv_done || !s->touch_done)
+			return CAL_EXIT_ERROR;
+	}
+
+	if (compute_calibration(cal, result) < 0)
+		return CAL_EXIT_ERROR;
+
+	if (verify_calibration(cal, result) < 0)
+		return CAL_EXIT_ERROR;
+
+	pr_ver("Calibration values:");
+	for (i = 0; i < 6; i++)
+		pr_ver(" %f", result[i]);
+	pr_ver("\n");
+
+	send_calibration(cal, result);
+	ret = wl_display_roundtrip(dpy);
+	if (ret < 0)
+		return CAL_EXIT_ERROR;
+
+	return CAL_EXIT_SUCCESS;
+}
+
+static void
+pr_err(const char *fmt, ...)
+{
+	va_list argp;
+
+	va_start(argp, fmt);
+	fprintf(stderr, "%s error: ", program_invocation_short_name);
+	vfprintf(stderr, fmt, argp);
+	va_end(argp);
+}
+
+static void
+help(void)
+{
+	fprintf(stderr, "Compute a touchscreen calibration matrix for "
+		"a Wayland compositor by\n"
+		"having the user touch points on the screen.\n\n");
+	fprintf(stderr, "Usage: %s [options...] name\n\n",
+		program_invocation_short_name);
+	fprintf(stderr,
+		"Where 'name' can be a touch device sys path or a head name.\n"
+		"If 'name' is not given, all devices available for "
+		"calibration will be listed.\n"
+		"If 'name' is given, it must be exactly as listed.\n"
+		"Options:\n"
+		"  --debug         Print messages to help debugging.\n"
+		"  -h, --help      Display this help message\n"
+		"  -v, --verbose   Print list header and calibration result.\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct display *display;
+	struct calibrator *cal;
+	int c;
+	char *match_name = NULL;
+	int exit_code = CAL_EXIT_SUCCESS;
+	static const struct option opts[] = {
+		{ "help",    no_argument,       NULL,      'h' },
+		{ "debug",   no_argument,       &debug_,   1 },
+		{ "verbose", no_argument,       &verbose_, 1 },
+		{ 0,         0,                 NULL,      0  }
+	};
+
+	while ((c = getopt_long(argc, argv, "hv", opts, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			help();
+			return CAL_EXIT_SUCCESS;
+		case 'v':
+			verbose_ = 1;
+			break;
+		case 0:
+			break;
+		default:
+			return CAL_EXIT_ERROR;
+		}
+	}
+
+	if (optind < argc)
+		match_name = argv[optind++];
+
+	if (optind < argc) {
+		pr_err("extra arguments given.\n\n");
+		help();
+		return CAL_EXIT_ERROR;
+	}
+
+	display = display_create(&argc, argv);
+	if (!display)
+		return CAL_EXIT_ERROR;
+
+	cal = calibrator_create(display, match_name);
+	if (!cal)
+		return CAL_EXIT_ERROR;
+
+	display_set_user_data(display, cal);
+	display_set_global_handler(display, global_handler);
+
+	if (!match_name)
+		pr_ver("Available touch devices:\n");
+
+	/* Roundtrip to get list of available touch devices,
+	 * first globals, then touch_device events */
+	wl_display_roundtrip(display_get_display(display));
+	wl_display_roundtrip(display_get_display(display));
+
+	if (!cal->calibration) {
+		exit_code = CAL_EXIT_ERROR;
+		pr_err("the Wayland server does not expose the calibration interface.\n");
+	} else if (cal->device_name) {
+		exit_code = calibrator_run(cal);
+	} else if (match_name) {
+		exit_code = CAL_EXIT_ERROR;
+		pr_err("\"%s\" was not found.\n", match_name);
+	} else if (cal->n_devices_listed == 0) {
+		fprintf(stderr, "No devices listed.\n");
+	}
+
+	calibrator_destroy(cal);
+	display_destroy(display);
+
+	return exit_code;
+}
diff --git a/clients/window.c b/clients/window.c
index bcf2b017..d0c7581d 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -4219,7 +4219,7 @@ window_get_shadow_margin(struct window *window)
 		return 0;
 }
 
-static void
+void
 window_inhibit_redraw(struct window *window)
 {
 	window->redraw_inhibited = 1;
@@ -4228,7 +4228,7 @@ window_inhibit_redraw(struct window *window)
 	window->redraw_task_scheduled = 0;
 }
 
-static void
+void
 window_uninhibit_redraw(struct window *window)
 {
 	window->redraw_inhibited = 0;
diff --git a/clients/window.h b/clients/window.h
index 3366ab15..fde5c2f0 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -604,6 +604,10 @@ widget_set_axis_handlers(struct widget *widget,
 			widget_axis_stop_handler_t axis_stop_handler,
 			widget_axis_discrete_handler_t axis_discrete_handler);
 
+void
+window_inhibit_redraw(struct window *window);
+void
+window_uninhibit_redraw(struct window *window);
 void
 widget_schedule_redraw(struct widget *widget);
 void
-- 
2.16.1



More information about the wayland-devel mailing list