summaryrefslogtreecommitdiffstats
path: root/panels/wacom/calibrator
diff options
context:
space:
mode:
Diffstat (limited to 'panels/wacom/calibrator')
-rw-r--r--panels/wacom/calibrator/COPYING27
-rw-r--r--panels/wacom/calibrator/calibrator-gui.c429
-rw-r--r--panels/wacom/calibrator/calibrator-gui.h63
-rw-r--r--panels/wacom/calibrator/calibrator.c183
-rw-r--r--panels/wacom/calibrator/calibrator.css47
-rw-r--r--panels/wacom/calibrator/calibrator.h99
-rw-r--r--panels/wacom/calibrator/calibrator.ui146
-rw-r--r--panels/wacom/calibrator/cc-clock.c289
-rw-r--r--panels/wacom/calibrator/cc-clock.h41
-rw-r--r--panels/wacom/calibrator/main.c421
-rw-r--r--panels/wacom/calibrator/meson.build35
-rw-r--r--panels/wacom/calibrator/target.svg93
12 files changed, 1873 insertions, 0 deletions
diff --git a/panels/wacom/calibrator/COPYING b/panels/wacom/calibrator/COPYING
new file mode 100644
index 0000000..e0a1dc9
--- /dev/null
+++ b/panels/wacom/calibrator/COPYING
@@ -0,0 +1,27 @@
+Copyright (c) 2010 Tias Guns <tias@ulyssis.org> and others
+See the respective files for detailed copyright information.
+
+
+Source code: MIT/X11 License
+------------
+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 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.
+
diff --git a/panels/wacom/calibrator/calibrator-gui.c b/panels/wacom/calibrator/calibrator-gui.c
new file mode 100644
index 0000000..2d5d7ec
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator-gui.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Carlos Garnacho <carlosg@gnome.org>
+ * (based on previous work by Joaquim Rocha, Tias Guns and Soren Hauberg)
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+#include <gdk/x11/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "calibrator.h"
+#include "calibrator-gui.h"
+#include "cc-clock.h"
+
+struct _CcCalibArea
+{
+ GtkWindow parent_instance;
+
+ struct Calib calibrator;
+ XYinfo axis;
+ gboolean swap;
+ gboolean success;
+ GdkDevice *device;
+
+ GtkWidget *error_revealer;
+ GtkWidget *title_revealer;
+ GtkWidget *subtitle_revealer;
+ GtkWidget *clock;
+ GtkWidget *target1, *target2, *target3, *target4;
+ GtkWidget *stack;
+ GtkWidget *success_page;
+ GtkCssProvider *style_provider;
+
+ FinishCallback callback;
+ gpointer user_data;
+};
+
+G_DEFINE_TYPE (CcCalibArea, cc_calib_area, GTK_TYPE_WINDOW)
+
+/* Timeout parameters */
+#define MAX_TIME 15000 /* 15000 = 15 sec */
+#define END_TIME 750 /* 750 = 0.75 sec */
+
+static void
+cc_calib_area_notify_finish (CcCalibArea *area)
+{
+ gtk_widget_hide (GTK_WIDGET (area));
+
+ (*area->callback) (area, area->user_data);
+}
+
+static gboolean
+on_close_request (GtkWidget *widget,
+ CcCalibArea *area)
+{
+ cc_calib_area_notify_finish (area);
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+cc_calib_area_finish_idle_cb (CcCalibArea *area)
+{
+ cc_calib_area_notify_finish (area);
+ return FALSE;
+}
+
+static void
+set_success (CcCalibArea *area)
+{
+ gtk_stack_set_visible_child (GTK_STACK (area->stack), area->success_page);
+}
+
+static void
+set_calibration_status (CcCalibArea *area)
+{
+ area->success = finish (&area->calibrator, &area->axis, &area->swap);
+
+ if (area->success)
+ {
+ set_success (area);
+ g_timeout_add (END_TIME,
+ (GSourceFunc) cc_calib_area_finish_idle_cb,
+ area);
+ }
+ else
+ {
+ g_idle_add ((GSourceFunc) cc_calib_area_finish_idle_cb, area);
+ }
+}
+
+static void
+show_error_message (CcCalibArea *area)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), TRUE);
+}
+
+static void
+hide_error_message (CcCalibArea *area)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), FALSE);
+}
+
+static void
+set_active_target (CcCalibArea *area,
+ int n_target)
+{
+ GtkWidget *targets[] = {
+ area->target1,
+ area->target2,
+ area->target3,
+ area->target4,
+ };
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (targets); i++)
+ gtk_widget_set_sensitive (targets[i], i == n_target);
+}
+
+static void
+on_gesture_press (GtkGestureClick *gesture,
+ guint n_press,
+ gdouble x,
+ gdouble y,
+ CcCalibArea *area)
+{
+ gint num_clicks;
+ gboolean success;
+ GdkDevice *source;
+
+ if (area->success)
+ return;
+
+ source = gtk_gesture_get_device (GTK_GESTURE (gesture));
+
+ if (gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN)
+ return;
+
+ /* Check matching device if a device was provided */
+ if (area->device && area->device != source)
+ {
+ g_debug ("Ignoring input from device %s",
+ gdk_device_get_name (source));
+ return;
+ }
+
+ /* Handle click */
+ /* FIXME: reset clock */
+ success = add_click(&area->calibrator,
+ (int) x,
+ (int) y);
+
+ num_clicks = area->calibrator.num_clicks;
+
+ if (!success && num_clicks == 0)
+ show_error_message (area);
+ else
+ hide_error_message (area);
+
+ /* Are we done yet? */
+ if (num_clicks >= 4)
+ {
+ set_calibration_status (area);
+ return;
+ }
+
+ set_active_target (area, num_clicks);
+}
+
+static gboolean
+on_key_release (GtkEventControllerKey *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ CcCalibArea *area)
+{
+ if (area->success || keyval != GDK_KEY_Escape)
+ return GDK_EVENT_PROPAGATE;
+
+ cc_calib_area_notify_finish (area);
+ return GDK_EVENT_STOP;
+}
+
+static void
+on_clock_finished (CcClock *clock,
+ CcCalibArea *area)
+{
+ set_calibration_status (area);
+}
+
+static void
+on_title_revealed (CcCalibArea *area)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (area->subtitle_revealer), TRUE);
+}
+
+static void
+on_fullscreen (GtkWindow *window,
+ GParamSpec *pspec,
+ CcCalibArea *area)
+{
+ if (!gtk_window_is_fullscreen (window))
+ return;
+
+ g_signal_connect_swapped (area->title_revealer,
+ "notify::child-revealed",
+ G_CALLBACK (on_title_revealed),
+ area);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (area->title_revealer), TRUE);
+
+ set_active_target (area, 0);
+}
+
+static void
+cc_calib_area_finalize (GObject *object)
+{
+ CcCalibArea *area = CC_CALIB_AREA (object);
+
+ gtk_style_context_remove_provider_for_display (gtk_widget_get_display (GTK_WIDGET (area)),
+ GTK_STYLE_PROVIDER (area->style_provider));
+
+ G_OBJECT_CLASS (cc_calib_area_parent_class)->finalize (object);
+}
+
+static void
+cc_calib_area_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ CcCalibArea *calib_area = CC_CALIB_AREA (widget);
+
+ if (calib_area->calibrator.geometry.width != width ||
+ calib_area->calibrator.geometry.height != height)
+ {
+ calib_area->calibrator.geometry.width = width;
+ calib_area->calibrator.geometry.height = height;
+
+ /* reset calibration if already started */
+ reset (&calib_area->calibrator);
+ set_active_target (calib_area, 0);
+ }
+
+ GTK_WIDGET_CLASS (cc_calib_area_parent_class)->size_allocate (widget,
+ width,
+ height,
+ baseline);
+}
+
+static void
+cc_calib_area_class_init (CcCalibAreaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_calib_area_finalize;
+
+ widget_class->size_allocate = cc_calib_area_size_allocate;
+
+ g_type_ensure (CC_TYPE_CLOCK);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/calibrator/calibrator.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, error_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, title_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, subtitle_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, clock);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target1);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target2);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target3);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target4);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, stack);
+ gtk_widget_class_bind_template_child (widget_class, CcCalibArea, success_page);
+}
+
+static void
+cc_calib_area_init (CcCalibArea *calib_area)
+{
+ GtkGesture *click;
+ GtkEventController *key;
+
+ gtk_widget_init_template (GTK_WIDGET (calib_area));
+
+ calib_area->style_provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (calib_area->style_provider, "/org/gnome/control-center/wacom/calibrator/calibrator.css");
+ gtk_style_context_add_provider_for_display (gtk_widget_get_display (GTK_WIDGET (calib_area)),
+ GTK_STYLE_PROVIDER (calib_area->style_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+
+ cc_clock_set_duration (CC_CLOCK (calib_area->clock), MAX_TIME);
+ g_signal_connect (calib_area->clock, "finished",
+ G_CALLBACK (on_clock_finished), calib_area);
+
+#ifndef FAKE_AREA
+ /* No cursor */
+ gtk_widget_realize (GTK_WIDGET (calib_area));
+ gtk_widget_set_cursor_from_name (GTK_WIDGET (calib_area), "blank");
+
+ gtk_widget_set_can_focus (GTK_WIDGET (calib_area), TRUE);
+#endif /* FAKE_AREA */
+
+ g_signal_connect (calib_area,
+ "close-request",
+ G_CALLBACK (on_close_request),
+ calib_area);
+ g_signal_connect (calib_area,
+ "notify::fullscreened",
+ G_CALLBACK (on_fullscreen),
+ calib_area);
+
+ click = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), GDK_BUTTON_PRIMARY);
+ g_signal_connect (click, "pressed",
+ G_CALLBACK (on_gesture_press), calib_area);
+ gtk_widget_add_controller (GTK_WIDGET (calib_area),
+ GTK_EVENT_CONTROLLER (click));
+
+ key = gtk_event_controller_key_new ();
+ g_signal_connect (key, "key-released",
+ G_CALLBACK (on_key_release), calib_area);
+ gtk_widget_add_controller (GTK_WIDGET (calib_area), key);
+}
+
+/**
+ * Creates the windows and other objects required to do calibration
+ * under GTK. When the window is closed (timed out, calibration finished
+ * or user cancellation), callback will be called, where you should call
+ * cc_calib_area_finish().
+ */
+CcCalibArea *
+cc_calib_area_new (GdkDisplay *display,
+ GdkMonitor *monitor,
+ GdkDevice *device,
+ FinishCallback callback,
+ gpointer user_data,
+ int threshold_doubleclick,
+ int threshold_misclick)
+{
+ CcCalibArea *calib_area;
+
+ g_return_val_if_fail (callback, NULL);
+
+ calib_area = g_object_new (CC_TYPE_CALIB_AREA, NULL);
+ calib_area->callback = callback;
+ calib_area->user_data = user_data;
+ calib_area->device = device;
+ calib_area->calibrator.threshold_doubleclick = threshold_doubleclick;
+ calib_area->calibrator.threshold_misclick = threshold_misclick;
+
+ /* Move to correct screen */
+ if (monitor)
+ gtk_window_fullscreen_on_monitor (GTK_WINDOW (calib_area), monitor);
+ else
+ gtk_window_fullscreen (GTK_WINDOW (calib_area));
+
+ gtk_widget_show (GTK_WIDGET (calib_area));
+
+ return calib_area;
+}
+
+/* Finishes the calibration. Note that CalibArea
+ * needs to be destroyed with Cccalib_area_free() afterwards */
+gboolean
+cc_calib_area_finish (CcCalibArea *area)
+{
+ g_return_val_if_fail (area != NULL, FALSE);
+
+ if (area->success)
+ g_debug ("Final calibration: %f, %f, %f, %f\n",
+ area->axis.x_min,
+ area->axis.y_min,
+ area->axis.x_max,
+ area->axis.y_max);
+ else
+ g_debug ("Calibration was aborted or timed out");
+
+ return area->success;
+}
+
+void
+cc_calib_area_free (CcCalibArea *area)
+{
+ gtk_window_destroy (GTK_WINDOW (area));
+}
+
+void
+cc_calib_area_get_axis (CcCalibArea *area,
+ XYinfo *new_axis,
+ gboolean *swap_xy)
+{
+ g_return_if_fail (area != NULL);
+
+ *new_axis = area->axis;
+ *swap_xy = area->swap;
+}
+
+void
+cc_calib_area_get_padding (CcCalibArea *area,
+ XYinfo *padding)
+{
+ g_return_if_fail (area != NULL);
+
+ /* min/max values are monitor coordinates scaled to be between
+ * 0 and 1, padding starts at 0 on "the edge", and positive
+ * values grow towards the center of the rectangle.
+ */
+ padding->x_min = area->axis.x_min;
+ padding->y_min = area->axis.y_min;
+ padding->x_max = 1 - area->axis.x_max;
+ padding->y_max = 1 - area->axis.y_max;
+}
diff --git a/panels/wacom/calibrator/calibrator-gui.h b/panels/wacom/calibrator/calibrator-gui.h
new file mode 100644
index 0000000..5d6d1ae
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator-gui.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009 Tias Guns
+ * Copyright (c) 2009 Soren Hauberg
+ *
+ * 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 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.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* struct to hold min/max info of the X and Y axis */
+typedef struct
+{
+ gdouble x_min;
+ gdouble x_max;
+ gdouble y_min;
+ gdouble y_max;
+} XYinfo;
+
+#define CC_TYPE_CALIB_AREA cc_calib_area_get_type ()
+G_DECLARE_FINAL_TYPE (CcCalibArea, cc_calib_area, CC, CALIB_AREA, GtkWindow)
+
+typedef void (*FinishCallback) (CcCalibArea *area, gpointer user_data);
+
+CcCalibArea * cc_calib_area_new (GdkDisplay *display,
+ GdkMonitor *monitor,
+ GdkDevice *device,
+ FinishCallback callback,
+ gpointer user_data,
+ int threshold_doubleclick,
+ int threshold_misclick);
+
+gboolean cc_calib_area_finish (CcCalibArea *area);
+
+void cc_calib_area_free (CcCalibArea *area);
+
+void cc_calib_area_get_axis (CcCalibArea *area,
+ XYinfo *new_axis,
+ gboolean *swap_xy);
+
+void cc_calib_area_get_padding (CcCalibArea *area,
+ XYinfo *padding);
+
+G_END_DECLS
diff --git a/panels/wacom/calibrator/calibrator.c b/panels/wacom/calibrator/calibrator.c
new file mode 100644
index 0000000..4ac316e
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2009 Tias Guns
+ * Copyright (c) 2009 Soren Hauberg
+ *
+ * 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 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 <stdlib.h>
+
+#include "calibrator.h"
+
+#define SWAP(valtype,x,y) \
+ G_STMT_START { \
+ valtype t; t = (x); x = (y); y = t; \
+ } G_STMT_END
+
+/* reset clicks */
+void
+reset (struct Calib *c)
+{
+ c->num_clicks = 0;
+}
+
+/* check whether the coordinates are along the respective axis */
+static gboolean
+along_axis (struct Calib *c,
+ int xy,
+ int x0,
+ int y0)
+{
+ return ((abs(xy - x0) <= c->threshold_misclick) ||
+ (abs(xy - y0) <= c->threshold_misclick));
+}
+
+/* add a click with the given coordinates */
+gboolean
+add_click (struct Calib *c,
+ int x,
+ int y)
+{
+ g_debug ("Trying to add click (%d, %d)", x, y);
+
+ /* Double-click detection */
+ if (c->threshold_doubleclick > 0 && c->num_clicks > 0)
+ {
+ int i = c->num_clicks-1;
+ while (i >= 0)
+ {
+ if (abs(x - c->clicked_x[i]) <= c->threshold_doubleclick &&
+ abs(y - c->clicked_y[i]) <= c->threshold_doubleclick)
+ {
+ g_debug ("Detected double-click, ignoring");
+ return FALSE;
+ }
+ i--;
+ }
+ }
+
+ /* Mis-click detection */
+ if (c->threshold_misclick > 0 && c->num_clicks > 0)
+ {
+ gboolean misclick = TRUE;
+
+ if (c->num_clicks == 1)
+ {
+ /* check that along one axis of first point */
+ if (along_axis(c, x,c->clicked_x[0],c->clicked_y[0]) ||
+ along_axis(c, y,c->clicked_x[0],c->clicked_y[0]))
+ {
+ misclick = FALSE;
+ }
+ }
+ else if (c->num_clicks == 2)
+ {
+ /* check that along other axis of first point than second point */
+ if ((along_axis(c, y,c->clicked_x[0],c->clicked_y[0]) &&
+ along_axis(c, c->clicked_x[1],c->clicked_x[0],c->clicked_y[0])) ||
+ (along_axis(c, x,c->clicked_x[0],c->clicked_y[0]) &&
+ along_axis(c, c->clicked_y[1],c->clicked_x[0],c->clicked_y[0])))
+ {
+ misclick = FALSE;
+ }
+ }
+ else if (c->num_clicks == 3)
+ {
+ /* check that along both axis of second and third point */
+ if ((along_axis(c, x,c->clicked_x[1],c->clicked_y[1]) &&
+ along_axis(c, y,c->clicked_x[2],c->clicked_y[2])) ||
+ (along_axis(c, y,c->clicked_x[1],c->clicked_y[1]) &&
+ along_axis(c, x,c->clicked_x[2],c->clicked_y[2])))
+ {
+ misclick = FALSE;
+ }
+ }
+
+ if (misclick)
+ {
+ g_debug ("Detected misclick, resetting");
+ reset(c);
+ return FALSE;
+ }
+ }
+
+ g_debug ("Click (%d, %d) added", x, y);
+ c->clicked_x[c->num_clicks] = x;
+ c->clicked_y[c->num_clicks] = y;
+ c->num_clicks++;
+
+ return TRUE;
+}
+
+/* calculate and apply the calibration */
+gboolean
+finish (struct Calib *c,
+ XYinfo *new_axis,
+ gboolean *swap)
+{
+ gboolean swap_xy;
+ float scale_x;
+ float scale_y;
+ float delta_x;
+ float delta_y;
+ XYinfo axis = {-1, -1, -1, -1};
+
+ if (c->num_clicks != 4)
+ return FALSE;
+
+ /* Should x and y be swapped? If the device and output are wider
+ * towards different axes, swapping must be performed
+ *
+ * FIXME: Would be even better to know the actual output orientation,
+ * not just the direction.
+ */
+ swap_xy = (c->geometry.width < c->geometry.height);
+
+ /* Compute the scale to transform from pixel positions to [0..1]. */
+ scale_x = 1 / (float)c->geometry.width;
+ scale_y = 1 / (float)c->geometry.height;
+
+ axis.x_min = ((((c->clicked_x[UL] + c->clicked_x[LL]) / 2)) * scale_x);
+ axis.x_max = ((((c->clicked_x[UR] + c->clicked_x[LR]) / 2)) * scale_x);
+ axis.y_min = ((((c->clicked_y[UL] + c->clicked_y[UR]) / 2)) * scale_y);
+ axis.y_max = ((((c->clicked_y[LL] + c->clicked_y[LR]) / 2)) * scale_y);
+
+ /* Add/subtract the offset that comes from not having the points in the
+ * corners (using the same coordinate system they are currently in)
+ */
+ delta_x = (axis.x_max - axis.x_min) / (float)(NUM_BLOCKS - 2);
+ axis.x_min -= delta_x;
+ axis.x_max += delta_x;
+ delta_y = (axis.y_max - axis.y_min) / (float)(NUM_BLOCKS - 2);
+ axis.y_min -= delta_y;
+ axis.y_max += delta_y;
+
+ /* If x and y has to be swapped we also have to swap the parameters */
+ if (swap_xy)
+ {
+ SWAP (gdouble, axis.x_min, axis.y_min);
+ SWAP (gdouble, axis.x_max, axis.y_max);
+ }
+
+ *new_axis = axis;
+ *swap = swap_xy;
+
+ return TRUE;
+}
+
diff --git a/panels/wacom/calibrator/calibrator.css b/panels/wacom/calibrator/calibrator.css
new file mode 100644
index 0000000..462c766
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator.css
@@ -0,0 +1,47 @@
+#calibrator {
+ background-color: #000;
+}
+
+#calibrator * {
+ color: #fff;
+}
+
+#calibrator label {
+ font-size: larger;
+}
+
+#calibrator #title {
+ font-weight: bold;
+ color: #888;
+}
+
+#calibrator #error {
+ font-weight: bold;
+}
+
+#calibrator #target {
+ background-image: url('target.svg');
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+}
+
+@keyframes target-enabled-animation {
+ 0% { background-size: 0px; }
+ 90% { background-size: 120px; }
+ 100% { background-size: 100px; }
+}
+
+@keyframes target-disabled-animation {
+ 0% { background-size: 100px; }
+ 100% { background-size: 0px; }
+}
+
+#calibrator #target:not(disabled) {
+ animation: target-enabled-animation 1 ease 0.5s;
+ background-size: 100px;
+}
+
+#calibrator #target:disabled {
+ animation: target-disabled-animation 1 ease 0.2s;
+ background-size: 0px;
+}
diff --git a/panels/wacom/calibrator/calibrator.h b/panels/wacom/calibrator/calibrator.h
new file mode 100644
index 0000000..dab7a2f
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009 Tias Guns
+ * Copyright (c) 2009 Soren Hauberg
+ *
+ * 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 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.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include "calibrator-gui.h"
+
+G_BEGIN_DECLS
+
+/*
+ * Number of blocks. We partition the screen into 'num_blocks' x 'num_blocks'
+ * rectangles of equal size. We then ask the user to press points that are
+ * located at the corner closes to the center of the four blocks in the corners
+ * of the screen. The following ascii art illustrates the situation. We partition
+ * the screen into 8 blocks in each direction. We then let the user press the
+ * points marked with 'O'.
+ *
+ * +--+--+--+--+--+--+--+--+
+ * | | | | | | | | |
+ * +--O--+--+--+--+--+--O--+
+ * | | | | | | | | |
+ * +--+--+--+--+--+--+--+--+
+ * | | | | | | | | |
+ * +--+--+--+--+--+--+--+--+
+ * | | | | | | | | |
+ * +--+--+--+--+--+--+--+--+
+ * | | | | | | | | |
+ * +--+--+--+--+--+--+--+--+
+ * | | | | | | | | |
+ * +--+--+--+--+--+--+--+--+
+ * | | | | | | | | |
+ * +--O--+--+--+--+--+--O--+
+ * | | | | | | | | |
+ * +--+--+--+--+--+--+--+--+
+ */
+#define NUM_BLOCKS 8
+
+/* Names of the points */
+enum
+{
+ UL = 0, /* Upper-left */
+ UR = 1, /* Upper-right */
+ LL = 2, /* Lower-left */
+ LR = 3 /* Lower-right */
+};
+
+struct Calib
+{
+ /* Geometry of the calibration window */
+ GdkRectangle geometry;
+
+ /* nr of clicks registered */
+ int num_clicks;
+
+ /* click coordinates */
+ int clicked_x[4], clicked_y[4];
+
+ /* Threshold to keep the same point from being clicked twice.
+ * Set to zero if you don't want this check
+ */
+ int threshold_doubleclick;
+
+ /* Threshold to detect mis-clicks (clicks not along axes)
+ * A lower value forces more precise calibration
+ * Set to zero if you don't want this check
+ */
+ int threshold_misclick;
+};
+
+void reset (struct Calib *c);
+gboolean add_click (struct Calib *c,
+ int x,
+ int y);
+gboolean finish (struct Calib *c,
+ XYinfo *new_axis,
+ gboolean *swap);
+
+G_END_DECLS
diff --git a/panels/wacom/calibrator/calibrator.ui b/panels/wacom/calibrator/calibrator.ui
new file mode 100644
index 0000000..89d4865
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator.ui
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="CcCalibArea" parent="GtkWindow">
+ <property name="name">calibrator</property>
+ <property name="child">
+ <object class="GtkStack" id="stack">
+ <property name="transition_duration">0</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="row_homogeneous">1</property>
+ <property name="column_homogeneous">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">8</property>
+ <property name="row-span">8</property>
+ </layout>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="orientation">vertical</property>
+ <property name="vexpand">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="CcClock" id="clock"/>
+ </child>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="orientation">vertical</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkRevealer" id="title_revealer">
+ <property name="transition_duration">300</property>
+ <property name="child">
+ <object class="GtkLabel">
+ <property name="name">title</property>
+ <property name="label" translatable="1">Screen Calibration</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="subtitle_revealer">
+ <property name="transition_duration">300</property>
+ <property name="child">
+ <object class="GtkLabel">
+ <property name="name">subtitle</property>
+ <property name="label" translatable="1">Please tap the target markers as they appear on screen to calibrate the tablet.</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">500</property>
+ <property name="child">
+ <object class="GtkLabel">
+ <property name="name">error</property>
+ <property name="label" translatable="1">Mis-click detected, restarting…</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="target1">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="sensitive">0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ <property name="column-span">2</property>
+ <property name="row-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="target2">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="sensitive">0</property>
+ <layout>
+ <property name="column">6</property>
+ <property name="row">0</property>
+ <property name="column-span">2</property>
+ <property name="row-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="target3">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="sensitive">0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">6</property>
+ <property name="column-span">2</property>
+ <property name="row-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="target4">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="sensitive">0</property>
+ <layout>
+ <property name="column">6</property>
+ <property name="row">6</property>
+ <property name="column-span">2</property>
+ <property name="row-span">2</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="success_page">
+ <property name="pixel_size">300</property>
+ <property name="icon_name">emblem-ok-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">vertical</property>
+ <widgets>
+ <widget name="box1"/>
+ <widget name="box2"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/panels/wacom/calibrator/cc-clock.c b/panels/wacom/calibrator/cc-clock.c
new file mode 100644
index 0000000..26afd81
--- /dev/null
+++ b/panels/wacom/calibrator/cc-clock.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Joaquim Rocha <jrocha@redhat.com>
+ * Carlos Garnacho <carlosg@gnome.org>
+ */
+#include "config.h"
+#include "cc-clock.h"
+
+#include <math.h>
+
+#define CLOCK_RADIUS 50
+#define CLOCK_LINE_WIDTH 10
+#define CLOCK_LINE_PADDING 10
+#define EXTRA_SPACE 2
+
+typedef struct _CcClock CcClock;
+
+struct _CcClock
+{
+ GtkWidget parent_instance;
+ guint duration;
+ gint64 start_time;
+ gboolean running;
+};
+
+enum
+{
+ PROP_DURATION = 1,
+ N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { 0, };
+
+enum {
+ FINISHED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (CcClock, cc_clock, GTK_TYPE_WIDGET)
+
+static gint64
+cc_clock_get_time_diff (CcClock *clock)
+{
+ GdkFrameClock *frame_clock;
+ gint64 current_time;
+
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock));
+ current_time = gdk_frame_clock_get_frame_time (frame_clock);
+
+ return current_time - clock->start_time;
+}
+
+static gdouble
+cc_clock_get_angle (CcClock *clock)
+{
+ gint64 time_diff;
+
+ time_diff = cc_clock_get_time_diff (clock);
+
+ if (time_diff > clock->duration * 1000)
+ return 360;
+
+ return ((gdouble) time_diff / (clock->duration * 1000)) * 360;
+}
+
+static void
+cc_clock_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ GtkAllocation allocation;
+ cairo_t *cr;
+ gdouble angle;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ angle = cc_clock_get_angle (CC_CLOCK (widget));
+
+ cr = gtk_snapshot_append_cairo (snapshot,
+ &GRAPHENE_RECT_INIT (0, 0, allocation.width, allocation.height));
+
+ /* Draw the clock background */
+ cairo_arc (cr, allocation.width / 2, allocation.height / 2, CLOCK_RADIUS / 2, 0.0, 2.0 * M_PI);
+ cairo_set_source_rgb (cr, 0.5, 0.5, 0.5);
+ cairo_fill_preserve (cr);
+ cairo_stroke (cr);
+
+ cairo_set_line_width (cr, CLOCK_LINE_WIDTH);
+
+ cairo_arc (cr,
+ allocation.width / 2,
+ allocation.height / 2,
+ (CLOCK_RADIUS - CLOCK_LINE_WIDTH - CLOCK_LINE_PADDING) / 2,
+ 3 * M_PI_2,
+ 3 * M_PI_2 + angle * M_PI / 180.0);
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ cairo_stroke (cr);
+}
+
+static void
+cc_clock_stop (CcClock *clock)
+{
+ GdkFrameClock *frame_clock;
+
+ if (!clock->running)
+ return;
+
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock));
+
+ gdk_frame_clock_end_updating (frame_clock);
+ clock->running = FALSE;
+}
+
+static void
+on_frame_clock_update (CcClock *clock)
+{
+ gint64 time_diff;
+
+ if (!clock->running)
+ return;
+
+ time_diff = cc_clock_get_time_diff (clock);
+
+ if (time_diff > clock->duration * 1000)
+ {
+ g_signal_emit (clock, signals[FINISHED], 0);
+ cc_clock_stop (clock);
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (clock));
+}
+
+static void
+cc_clock_map (GtkWidget *widget)
+{
+ GdkFrameClock *frame_clock;
+
+ GTK_WIDGET_CLASS (cc_clock_parent_class)->map (widget);
+
+ frame_clock = gtk_widget_get_frame_clock (widget);
+ g_signal_connect_object (frame_clock, "update",
+ G_CALLBACK (on_frame_clock_update),
+ widget, G_CONNECT_SWAPPED);
+ cc_clock_reset (CC_CLOCK (widget));
+}
+
+static void
+cc_clock_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcClock *clock = CC_CLOCK (object);
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ clock->duration = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_clock_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcClock *clock = CC_CLOCK (object);
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ g_value_set_uint (value, clock->duration);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_clock_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum,
+ gint *natural,
+ gint *minimum_baseline,
+ gint *natural_baseline)
+{
+ if (minimum)
+ *minimum = CLOCK_RADIUS + EXTRA_SPACE;
+ if (natural)
+ *natural = CLOCK_RADIUS + EXTRA_SPACE;
+}
+
+static void
+cc_clock_class_init (CcClockClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = cc_clock_set_property;
+ object_class->get_property = cc_clock_get_property;
+
+ widget_class->map = cc_clock_map;
+ widget_class->snapshot = cc_clock_snapshot;
+ widget_class->measure = cc_clock_measure;
+
+ signals[FINISHED] =
+ g_signal_new ("finished",
+ CC_TYPE_CLOCK,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ props[PROP_DURATION] =
+ g_param_spec_uint ("duration",
+ "Duration",
+ "Duration",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+cc_clock_init (CcClock *clock)
+{
+}
+
+GtkWidget *
+cc_clock_new (guint duration)
+{
+ return g_object_new (CC_TYPE_CLOCK,
+ "duration", duration,
+ NULL);
+}
+
+void
+cc_clock_reset (CcClock *clock)
+{
+ GdkFrameClock *frame_clock;
+
+ if (!gtk_widget_get_mapped (GTK_WIDGET (clock)))
+ return;
+
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock));
+
+ cc_clock_stop (clock);
+
+ clock->running = TRUE;
+ clock->start_time = g_get_monotonic_time ();
+ gdk_frame_clock_begin_updating (frame_clock);
+}
+
+void
+cc_clock_set_duration (CcClock *clock,
+ guint duration)
+{
+ clock->duration = duration;
+ g_object_notify (G_OBJECT (clock), "duration");
+ cc_clock_reset (clock);
+}
+
+guint
+cc_clock_get_duration (CcClock *clock)
+{
+ return clock->duration;
+}
diff --git a/panels/wacom/calibrator/cc-clock.h b/panels/wacom/calibrator/cc-clock.h
new file mode 100644
index 0000000..9ebf024
--- /dev/null
+++ b/panels/wacom/calibrator/cc-clock.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Carlos Garnacho <carlosg@gnome.org>
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_CLOCK (cc_clock_get_type ())
+
+G_DECLARE_FINAL_TYPE (CcClock, cc_clock, CC, CLOCK, GtkWidget)
+
+GtkWidget * cc_clock_new (guint duration);
+
+void cc_clock_reset (CcClock *clock);
+
+void cc_clock_set_duration (CcClock *clock,
+ guint duration);
+guint cc_clock_get_duration (CcClock *clock);
+
+GType cc_clock_get_type (void);
+
+G_END_DECLS
diff --git a/panels/wacom/calibrator/main.c b/panels/wacom/calibrator/main.c
new file mode 100644
index 0000000..71421c7
--- /dev/null
+++ b/panels/wacom/calibrator/main.c
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2009 Tias Guns
+ * Copyright (c) 2009 Soren Hauberg
+ *
+ * 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 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 <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <dirent.h>
+#include <glib/gi18n.h>
+
+#include <X11/extensions/XInput.h>
+
+#include "calibrator-gui.h"
+#include "calibrator.h"
+
+static GMainLoop *mainloop = NULL;
+
+/**
+ * find a calibratable touchscreen device (using XInput)
+ *
+ * if pre_device is NULL, the last calibratable device is selected.
+ * retuns number of devices found,
+ * the data of the device is returned in the last 3 function parameters
+ */
+static int find_device(const char* pre_device, gboolean verbose, gboolean list_devices,
+ XID* device_id, const char** device_name, XYinfo* device_axis)
+{
+ gboolean pre_device_is_id = TRUE;
+ int found = 0;
+
+ Display* display = XOpenDisplay(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Unable to connect to X server\n");
+ exit(1);
+ }
+
+ int xi_opcode, event, error;
+ if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) {
+ fprintf(stderr, "X Input extension not available.\n");
+ exit(1);
+ }
+
+ /* verbose, get Xi version */
+ if (verbose) {
+ XExtensionVersion *version = XGetExtensionVersion(display, INAME);
+
+ if (version && (version != (XExtensionVersion*) NoSuchExtension)) {
+ printf("DEBUG: %s version is %i.%i\n",
+ INAME, version->major_version, version->minor_version);
+ XFree(version);
+ }
+ }
+
+ if (pre_device != NULL) {
+ /* check whether the pre_device is an ID (only digits) */
+ int len = strlen(pre_device);
+ int loop;
+ for (loop=0; loop<len; loop++) {
+ if (!isdigit(pre_device[loop])) {
+ pre_device_is_id = FALSE;
+ break;
+ }
+ }
+ }
+
+
+ if (verbose)
+ printf("DEBUG: Skipping virtual master devices and devices without axis valuators.\n");
+ int ndevices;
+ XDeviceInfoPtr list, slist;
+ slist=list=(XDeviceInfoPtr) XListInputDevices (display, &ndevices);
+ int i;
+ for (i=0; i<ndevices; i++, list++)
+ {
+ if (list->use == IsXKeyboard || list->use == IsXPointer) /* virtual master device */
+ continue;
+
+ /* if we are looking for a specific device */
+ if (pre_device != NULL) {
+ if ((pre_device_is_id && list->id == (XID) atoi(pre_device)) ||
+ (!pre_device_is_id && strcmp(list->name, pre_device) == 0)) {
+ /* OK, fall through */
+ } else {
+ /* skip, not this device */
+ continue;
+ }
+ }
+
+ XAnyClassPtr any = (XAnyClassPtr) (list->inputclassinfo);
+ int j;
+ for (j=0; j<list->num_classes; j++)
+ {
+
+ if (any->class == ValuatorClass)
+ {
+ XValuatorInfoPtr V = (XValuatorInfoPtr) any;
+ XAxisInfoPtr ax = (XAxisInfoPtr) V->axes;
+
+ if (V->mode != Absolute) {
+ if (verbose)
+ printf("DEBUG: Skipping device '%s' id=%i, does not report Absolute events.\n",
+ list->name, (int)list->id);
+ } else if (V->num_axes < 2 ||
+ (ax[0].min_value == -1 && ax[0].max_value == -1) ||
+ (ax[1].min_value == -1 && ax[1].max_value == -1)) {
+ if (verbose)
+ printf("DEBUG: Skipping device '%s' id=%i, does not have two calibratable axes.\n",
+ list->name, (int)list->id);
+ } else {
+ /* a calibratable device (has 2 axis valuators) */
+ found++;
+ *device_id = list->id;
+ *device_name = g_strdup(list->name);
+ device_axis->x_min = ax[0].min_value;
+ device_axis->x_max = ax[0].max_value;
+ device_axis->y_min = ax[1].min_value;
+ device_axis->y_max = ax[1].max_value;
+
+ if (list_devices)
+ printf("Device \"%s\" id=%i\n", *device_name, (int)*device_id);
+ }
+
+ }
+
+ /*
+ * Increment 'any' to point to the next item in the linked
+ * list. The length is in bytes, so 'any' must be cast to
+ * a character pointer before being incremented.
+ */
+ any = (XAnyClassPtr) ((char *) any + any->length);
+ }
+
+ }
+ XFreeDeviceList(slist);
+ XCloseDisplay(display);
+
+ return found;
+}
+
+static void usage(char* cmd, unsigned thr_misclick)
+{
+ fprintf(stderr, "Usage: %s [-h|--help] [-v|--verbose] [--list] [--device <device name or id>] [--precalib <minx> <maxx> <miny> <maxy>] [--misclick <nr of pixels>] [--output-type <auto|xorg.conf.d|hal|xinput>] [--fake]\n", cmd);
+ fprintf(stderr, "\t-h, --help: print this help message\n");
+ fprintf(stderr, "\t-v, --verbose: print debug messages during the process\n");
+ fprintf(stderr, "\t--list: list calibratable input devices and quit\n");
+ fprintf(stderr, "\t--device <device name or id>: select a specific device to calibrate\n");
+ fprintf(stderr, "\t--precalib: manually provide the current calibration setting (eg. the values in xorg.conf)\n");
+ fprintf(stderr, "\t--misclick: set the misclick threshold (0=off, default: %i pixels)\n",
+ thr_misclick);
+ fprintf(stderr, "\t--fake: emulate a fake device (for testing purposes)\n");
+}
+
+static struct Calib* CalibratorXorgPrint(const char* const device_name0, const XYinfo *axis0, const gboolean verbose0, const int thr_misclick, const int thr_doubleclick)
+{
+ struct Calib* c = (struct Calib*)calloc(1, sizeof(struct Calib));
+ c->threshold_misclick = thr_misclick;
+ c->threshold_doubleclick = thr_doubleclick;
+
+ printf("Calibrating standard Xorg driver \"%s\"\n", device_name0);
+ printf("\tcurrent calibration values: min_x=%lf, max_x=%lf and min_y=%lf, max_y=%lf\n",
+ axis0->x_min, axis0->x_max, axis0->y_min, axis0->y_max);
+ printf("\tIf these values are estimated wrong, either supply it manually with the --precalib option, or run the 'get_precalib.sh' script to automatically get it (through HAL).\n");
+
+ return c;
+}
+
+static struct Calib* main_common(int argc, char** argv)
+{
+ gboolean verbose = FALSE;
+ gboolean list_devices = FALSE;
+ gboolean fake = FALSE;
+ gboolean precalib = FALSE;
+ XYinfo pre_axis = {-1, -1, -1, -1};
+ const char* pre_device = NULL;
+ unsigned thr_misclick = 15;
+ unsigned thr_doubleclick = 7;
+
+ /* parse input */
+ if (argc > 1) {
+ int i;
+ for (i=1; i!=argc; i++) {
+ /* Display help ? */
+ if (strcmp("-h", argv[i]) == 0 ||
+ strcmp("--help", argv[i]) == 0) {
+ fprintf(stderr, "xinput_calibrator, v%s\n\n", "0.0.0");
+ usage(argv[0], thr_misclick);
+ exit(0);
+ } else
+
+ /* Verbose output ? */
+ if (strcmp("-v", argv[i]) == 0 ||
+ strcmp("--verbose", argv[i]) == 0) {
+ verbose = TRUE;
+ } else
+
+ /* Just list devices ? */
+ if (strcmp("--list", argv[i]) == 0) {
+ list_devices = TRUE;
+ } else
+
+ /* Select specific device ? */
+ if (strcmp("--device", argv[i]) == 0) {
+ if (argc > i+1)
+ pre_device = argv[++i];
+ else {
+ fprintf(stderr, "Error: --device needs a device name or id as argument; use --list to list the calibratable input devices.\n\n");
+ usage(argv[0], thr_misclick);
+ exit(1);
+ }
+ } else
+
+ /* Get pre-calibration ? */
+ if (strcmp("--precalib", argv[i]) == 0) {
+ precalib = TRUE;
+ if (argc > i+1)
+ pre_axis.x_min = atoi(argv[++i]);
+ if (argc > i+1)
+ pre_axis.x_max = atoi(argv[++i]);
+ if (argc > i+1)
+ pre_axis.y_min = atoi(argv[++i]);
+ if (argc > i+1)
+ pre_axis.y_max = atoi(argv[++i]);
+ } else
+
+ /* Get mis-click threshold ? */
+ if (strcmp("--misclick", argv[i]) == 0) {
+ if (argc > i+1)
+ thr_misclick = atoi(argv[++i]);
+ else {
+ fprintf(stderr, "Error: --misclick needs a number (the pixel threshold) as argument. Set to 0 to disable mis-click detection.\n\n");
+ usage(argv[0], thr_misclick);
+ exit(1);
+ }
+ } else
+
+ /* Fake calibratable device ? */
+ if (strcmp("--fake", argv[i]) == 0) {
+ fake = TRUE;
+ }
+
+ /* unknown option */
+ else {
+ fprintf(stderr, "Unknown option: %s\n\n", argv[i]);
+ usage(argv[0], thr_misclick);
+ exit(0);
+ }
+ }
+ }
+
+
+ /* Choose the device to calibrate */
+ XID device_id = (XID) -1;
+ const char* device_name = NULL;
+ XYinfo device_axis = {-1, -1, -1, -1};
+ if (fake) {
+ /* Fake a calibratable device */
+ device_name = "Fake_device";
+ device_axis.x_min=0;
+ device_axis.x_max=1000;
+ device_axis.y_min=0;
+ device_axis.y_max=1000;
+
+ if (verbose) {
+ printf("DEBUG: Faking device: %s\n", device_name);
+ }
+ } else {
+ /* Find the right device */
+ int nr_found = find_device(pre_device, verbose, list_devices, &device_id, &device_name, &device_axis);
+
+ if (list_devices) {
+ /* printed the list in find_device */
+ if (nr_found == 0)
+ printf("No calibratable devices found.\n");
+ exit(0);
+ }
+
+ if (nr_found == 0) {
+ if (pre_device == NULL)
+ fprintf (stderr, "Error: No calibratable devices found.\n");
+ else
+ fprintf (stderr, "Error: Device \"%s\" not found; use --list to list the calibratable input devices.\n", pre_device);
+ exit(1);
+
+ } else if (nr_found > 1) {
+ printf ("Warning: multiple calibratable devices found, calibrating last one (%s)\n\tuse --device to select another one.\n", device_name);
+ }
+
+ if (verbose) {
+ printf("DEBUG: Selected device: %s\n", device_name);
+ }
+ }
+
+ /* override min/max XY from command line ? */
+ if (precalib) {
+ if (pre_axis.x_min != -1)
+ device_axis.x_min = pre_axis.x_min;
+ if (pre_axis.x_max != -1)
+ device_axis.x_max = pre_axis.x_max;
+ if (pre_axis.y_min != -1)
+ device_axis.y_min = pre_axis.y_min;
+ if (pre_axis.y_max != -1)
+ device_axis.y_max = pre_axis.y_max;
+
+ if (verbose) {
+ printf("DEBUG: Setting precalibration: %lf, %lf, %lf, %lf\n",
+ device_axis.x_min, device_axis.x_max,
+ device_axis.y_min, device_axis.y_max);
+ }
+ }
+
+ /* lastly, presume a standard Xorg driver (evtouch, mutouch, ...) */
+ return CalibratorXorgPrint(device_name, &device_axis,
+ verbose, thr_misclick, thr_doubleclick);
+}
+
+static gboolean output_xorgconfd(const XYinfo new_axis, int swap_xy, int new_swap_xy)
+{
+ const char* sysfs_name = "!!Name_Of_TouchScreen!!";
+
+ /* xorg.conf.d snippet */
+ printf(" copy the snippet below into '/etc/X11/xorg.conf.d/99-calibration.conf'\n");
+ printf("Section \"InputClass\"\n");
+ printf(" Identifier \"calibration\"\n");
+ printf(" MatchProduct \"%s\"\n", sysfs_name);
+ printf(" Option \"MinX\" \"%lf\"\n", new_axis.x_min);
+ printf(" Option \"MaxX\" \"%lf\"\n", new_axis.x_max);
+ printf(" Option \"MinY\" \"%lf\"\n", new_axis.y_min);
+ printf(" Option \"MaxY\" \"%lf\"\n", new_axis.y_max);
+ if (swap_xy != 0)
+ printf(" Option \"SwapXY\" \"%d\" # unless it was already set to 1\n", new_swap_xy);
+ printf("EndSection\n");
+
+ return TRUE;
+}
+
+static gboolean finish_data(const XYinfo new_axis, int swap_xy)
+{
+ gboolean success = TRUE;
+
+ /* we suppose the previous 'swap_xy' value was 0 */
+ /* (unfortunately there is no way to verify this (yet)) */
+ int new_swap_xy = swap_xy;
+
+ printf("\n\n--> Making the calibration permanent <--\n");
+ success &= output_xorgconfd(new_axis, swap_xy, new_swap_xy);
+
+ return success;
+}
+
+static void
+calibration_finished_cb (CcCalibArea *area,
+ gpointer user_data)
+{
+ gboolean success;
+ XYinfo axis;
+ gboolean swap_xy;
+
+ success = cc_calib_area_finish (area);
+ if (success)
+ {
+ cc_calib_area_get_axis (area, &axis, &swap_xy);
+ success = finish_data (axis, swap_xy);
+ }
+ else
+ fprintf(stderr, "Error: unable to apply or save configuration values\n");
+
+ g_main_loop_quit (mainloop);
+}
+
+int main(int argc, char** argv)
+{
+
+ struct Calib* calibrator = main_common(argc, argv);
+ CcCalibArea *calib_area;
+
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init ();
+
+ g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+ calib_area = cc_calib_area_new (NULL,
+ NULL, /* monitor */
+ NULL, /* NULL to accept input from any device */
+ calibration_finished_cb,
+ NULL,
+ calibrator->threshold_doubleclick,
+ calibrator->threshold_misclick);
+
+ mainloop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (mainloop);
+
+ cc_calib_area_free (calib_area);
+
+ free(calibrator);
+
+ return 0;
+}
diff --git a/panels/wacom/calibrator/meson.build b/panels/wacom/calibrator/meson.build
new file mode 100644
index 0000000..f894e60
--- /dev/null
+++ b/panels/wacom/calibrator/meson.build
@@ -0,0 +1,35 @@
+calibrator_inc = include_directories('.')
+
+common_sources = files(
+ 'calibrator.c',
+ 'calibrator-gui.c',
+ 'cc-clock.c',
+)
+
+calibrator_deps = deps + [m_dep]
+
+libwacom_calibrator = static_library(
+ cappletname + '-calibrator',
+ sources: common_sources,
+ include_directories: top_inc,
+ dependencies: calibrator_deps,
+ c_args: cflags
+)
+
+libwacom_calibrator_test = static_library(
+ cappletname + '-calibrator-test',
+ sources: common_sources,
+ include_directories: top_inc,
+ dependencies: calibrator_deps,
+ c_args: test_cflags
+)
+
+sources = common_sources + wacom_gresource + files('main.c')
+
+executable(
+ 'test-calibrator',
+ sources,
+ include_directories: top_inc,
+ dependencies: calibrator_deps,
+ c_args: cflags
+)
diff --git a/panels/wacom/calibrator/target.svg b/panels/wacom/calibrator/target.svg
new file mode 100644
index 0000000..60b4cbb
--- /dev/null
+++ b/panels/wacom/calibrator/target.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="200"
+ height="200"
+ viewBox="0 0 52.916666 52.916668"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="target.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8"
+ inkscape:cx="26.277089"
+ inkscape:cy="74.824155"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ units="px"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="2160"
+ inkscape:window-height="1311"
+ inkscape:window-x="0"
+ inkscape:window-y="55"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4518" />
+ <sodipodi:guide
+ position="-7.9374999,13.229167"
+ orientation="1,0"
+ id="guide4542"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-244.08332)">
+ <path
+ style="fill:none;stroke:#ffffff;stroke-width:0.5291667px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 0,270.54165 h 52.916667 v 0"
+ id="path4520"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#ffffff;stroke-width:0.5291667px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 26.458334,244.08331 v 52.91667"
+ id="path4522"
+ inkscape:connector-curvature="0" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.5291667;stroke-opacity:1"
+ id="path4530"
+ cx="26.458332"
+ cy="270.54163"
+ rx="5.2916665"
+ ry="5.2916679" />
+ <ellipse
+ style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.00000012;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4534"
+ cx="26.458332"
+ cy="270.54163"
+ rx="15.875"
+ ry="15.875004" />
+ </g>
+</svg>