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.c461
-rw-r--r--panels/wacom/calibrator/calibrator-gui.h65
-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.ui203
-rw-r--r--panels/wacom/calibrator/cc-clock.c300
-rw-r--r--panels/wacom/calibrator/cc-clock.h41
-rw-r--r--panels/wacom/calibrator/main.c418
-rw-r--r--panels/wacom/calibrator/meson.build35
-rw-r--r--panels/wacom/calibrator/target.svg93
12 files changed, 1972 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..012291f
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator-gui.c
@@ -0,0 +1,461 @@
+/*
+ * 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/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "calibrator.h"
+#include "calibrator-gui.h"
+#include "cc-clock.h"
+
+struct CalibArea
+{
+ struct Calib calibrator;
+ XYinfo axis;
+ gboolean swap;
+ gboolean success;
+ GdkDevice *device;
+
+ double X[4], Y[4];
+ int display_width, display_height;
+
+ GtkWidget *window;
+ GtkBuilder *builder;
+ GtkWidget *error_revealer;
+ GtkWidget *clock;
+ GtkCssProvider *style_provider;
+
+ FinishCallback callback;
+ gpointer user_data;
+};
+
+/* Timeout parameters */
+#define MAX_TIME 15000 /* 15000 = 15 sec */
+#define END_TIME 750 /* 750 = 0.75 sec */
+
+static void
+set_display_size (CalibArea *calib_area,
+ int width,
+ int height)
+{
+ int delta_x;
+ int delta_y;
+
+ calib_area->display_width = width;
+ calib_area->display_height = height;
+
+ /* Compute absolute circle centers */
+ delta_x = calib_area->display_width/NUM_BLOCKS;
+ delta_y = calib_area->display_height/NUM_BLOCKS;
+
+ calib_area->X[UL] = delta_x;
+ calib_area->Y[UL] = delta_y;
+
+ calib_area->X[UR] = calib_area->display_width - delta_x - 1;
+ calib_area->Y[UR] = delta_y;
+
+ calib_area->X[LL] = delta_x;
+ calib_area->Y[LL] = calib_area->display_height - delta_y - 1;
+
+ calib_area->X[LR] = calib_area->display_width - delta_x - 1;
+ calib_area->Y[LR] = calib_area->display_height - delta_y - 1;
+
+ /* reset calibration if already started */
+ reset (&calib_area->calibrator);
+}
+
+static void
+calib_area_notify_finish (CalibArea *area)
+{
+ gtk_widget_hide (area->window);
+
+ (*area->callback) (area, area->user_data);
+}
+
+static gboolean
+on_delete_event (GtkWidget *widget,
+ GdkEvent *event,
+ CalibArea *area)
+{
+ calib_area_notify_finish (area);
+ return TRUE;
+}
+
+static gboolean
+calib_area_finish_idle_cb (CalibArea *area)
+{
+ calib_area_notify_finish (area);
+ return FALSE;
+}
+
+static void
+set_success (CalibArea *area)
+{
+ GtkWidget *stack;
+
+ stack = GTK_WIDGET (gtk_builder_get_object (area->builder, "stack"));
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), "page1");
+}
+
+static void
+set_calibration_status (CalibArea *area)
+{
+ area->success = finish (&area->calibrator, &area->axis, &area->swap);
+
+ if (area->success)
+ {
+ set_success (area);
+ g_timeout_add (END_TIME,
+ (GSourceFunc) calib_area_finish_idle_cb,
+ area);
+ }
+ else
+ {
+ g_idle_add ((GSourceFunc) calib_area_finish_idle_cb, area);
+ }
+}
+
+static void
+show_error_message (CalibArea *area)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), TRUE);
+}
+
+static void
+hide_error_message (CalibArea *area)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), FALSE);
+}
+
+static void
+set_active_target (CalibArea *area,
+ int n_target)
+{
+ GtkWidget *targets[] = {
+ GTK_WIDGET (gtk_builder_get_object (area->builder, "target1")),
+ GTK_WIDGET (gtk_builder_get_object (area->builder, "target2")),
+ GTK_WIDGET (gtk_builder_get_object (area->builder, "target3")),
+ GTK_WIDGET (gtk_builder_get_object (area->builder, "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 (GtkGestureMultiPress *gesture,
+ guint n_press,
+ gdouble x,
+ gdouble y,
+ CalibArea *area)
+{
+ gint num_clicks;
+ gboolean success;
+ GdkDevice *source;
+ GdkEvent *event;
+
+ if (area->success)
+ return;
+
+ event = gtk_get_current_event ();
+ source = gdk_event_get_source_device ((GdkEvent *) event);
+ gdk_event_free (event);
+
+ /* 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_event (GtkWidget *widget,
+ GdkEventKey *event,
+ CalibArea *area)
+{
+ if (area->success ||
+ event->keyval != GDK_KEY_Escape)
+ return GDK_EVENT_PROPAGATE;
+
+ calib_area_notify_finish (area);
+ return GDK_EVENT_STOP;
+}
+
+static gboolean
+on_focus_out_event (GtkWidget *widget,
+ GdkEvent *event,
+ CalibArea *area)
+{
+ if (area->success)
+ return FALSE;
+
+ /* If the calibrator window loses focus, simply bail out... */
+ calib_area_notify_finish (area);
+
+ return FALSE;
+}
+
+static void
+on_clock_finished (CcClock *clock,
+ CalibArea *area)
+{
+ set_calibration_status (area);
+}
+
+static void
+on_title_revealed (CalibArea *area)
+{
+ GtkWidget *revealer;
+
+ revealer = GTK_WIDGET (gtk_builder_get_object (area->builder, "subtitle_revealer"));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE);
+}
+
+static gboolean
+on_fullscreen (GtkWindow *window,
+ GdkEventWindowState *event,
+ CalibArea *area)
+{
+ GtkWidget *revealer;
+
+ if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) == 0)
+ return FALSE;
+
+ revealer = GTK_WIDGET (gtk_builder_get_object (area->builder, "title_revealer"));
+ g_signal_connect_swapped (revealer, "notify::child-revealed",
+ G_CALLBACK (on_title_revealed),
+ area);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE);
+
+ set_active_target (area, 0);
+
+ return FALSE;
+}
+
+static void
+on_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ CalibArea *area)
+{
+ set_display_size (area, allocation->width, allocation->height);
+}
+
+/**
+ * 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
+ * calib_area_finish().
+ */
+CalibArea *
+calib_area_new (GdkScreen *screen,
+ int n_monitor,
+ GdkDevice *device,
+ FinishCallback callback,
+ gpointer user_data,
+ int threshold_doubleclick,
+ int threshold_misclick)
+{
+ CalibArea *calib_area;
+ GdkRectangle rect;
+ GdkVisual *visual;
+ GdkMonitor *monitor;
+#ifndef FAKE_AREA
+ GdkWindow *window;
+ g_autoptr(GdkCursor) cursor = NULL;
+#endif /* FAKE_AREA */
+ GtkGesture *press;
+
+ g_return_val_if_fail (callback, NULL);
+
+ g_type_ensure (CC_TYPE_CLOCK);
+
+ calib_area = g_new0 (CalibArea, 1);
+ 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;
+
+ calib_area->builder = gtk_builder_new_from_resource ("/org/gnome/control-center/wacom/calibrator/calibrator.ui");
+ calib_area->window = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "window"));
+ calib_area->error_revealer = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "error_revealer"));
+ calib_area->clock = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "clock"));
+ 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_screen (gtk_widget_get_screen (calib_area->window),
+ 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 (calib_area->window);
+ window = gtk_widget_get_window (calib_area->window);
+ cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_BLANK_CURSOR);
+ gdk_window_set_cursor (window, cursor);
+
+ gtk_widget_set_can_focus (calib_area->window, TRUE);
+ gtk_window_set_keep_above (GTK_WINDOW (calib_area->window), TRUE);
+#endif /* FAKE_AREA */
+
+ /* Move to correct screen */
+ if (screen == NULL)
+ screen = gdk_screen_get_default ();
+ monitor = gdk_display_get_monitor (gdk_screen_get_display (screen), n_monitor);
+ gdk_monitor_get_geometry (monitor, &rect);
+
+ calib_area->calibrator.geometry = rect;
+
+ g_signal_connect (calib_area->window,
+ "key-release-event",
+ G_CALLBACK (on_key_release_event),
+ calib_area);
+ g_signal_connect (calib_area->window,
+ "delete-event",
+ G_CALLBACK (on_delete_event),
+ calib_area);
+ g_signal_connect (calib_area->window,
+ "focus-out-event",
+ G_CALLBACK(on_focus_out_event),
+ calib_area);
+ g_signal_connect (calib_area->window,
+ "window-state-event",
+ G_CALLBACK (on_fullscreen),
+ calib_area);
+ g_signal_connect (calib_area->window,
+ "size-allocate",
+ G_CALLBACK (on_size_allocate),
+ calib_area);
+
+ press = gtk_gesture_multi_press_new (calib_area->window);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_PRIMARY);
+ g_signal_connect (press, "pressed",
+ G_CALLBACK (on_gesture_press), calib_area);
+
+ gtk_window_fullscreen_on_monitor (GTK_WINDOW (calib_area->window), screen, n_monitor);
+
+ visual = gdk_screen_get_rgba_visual (screen);
+ if (visual != NULL)
+ gtk_widget_set_visual (GTK_WIDGET (calib_area->window), visual);
+
+ gtk_widget_show (calib_area->window);
+
+ return calib_area;
+}
+
+/* Finishes the calibration. Note that CalibArea
+ * needs to be destroyed with calib_area_free() afterwards */
+gboolean
+calib_area_finish (CalibArea *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
+calib_area_free (CalibArea *area)
+{
+ g_return_if_fail (area != NULL);
+
+ gtk_style_context_remove_provider_for_screen (gtk_widget_get_screen (area->window),
+ GTK_STYLE_PROVIDER (area->style_provider));
+ gtk_widget_destroy (area->window);
+ g_free (area);
+}
+
+void
+calib_area_get_display_size (CalibArea *area, gint *width, gint *height)
+{
+ g_return_if_fail (area != NULL);
+
+ *width = area->display_width;
+ *height = area->display_height;
+}
+
+void
+calib_area_get_axis (CalibArea *area,
+ XYinfo *new_axis,
+ gboolean *swap_xy)
+{
+ g_return_if_fail (area != NULL);
+
+ *new_axis = area->axis;
+ *swap_xy = area->swap;
+}
+
+void
+calib_area_get_padding (CalibArea *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..d3301c1
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator-gui.h
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+typedef struct CalibArea CalibArea;
+typedef void (*FinishCallback) (CalibArea *area, gpointer user_data);
+
+CalibArea * calib_area_new (GdkScreen *screen,
+ int monitor,
+ GdkDevice *device,
+ FinishCallback callback,
+ gpointer user_data,
+ int threshold_doubleclick,
+ int threshold_misclick);
+
+gboolean calib_area_finish (CalibArea *area);
+
+void calib_area_free (CalibArea *area);
+
+void calib_area_get_display_size (CalibArea *area,
+ gint *width,
+ gint *height);
+
+void calib_area_get_axis (CalibArea *area,
+ XYinfo *new_axis,
+ gboolean *swap_xy);
+
+void calib_area_get_padding (CalibArea *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..cf5fced
--- /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..6734b76
--- /dev/null
+++ b/panels/wacom/calibrator/calibrator.ui
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkWindow" id="window">
+ <property name="name">calibrator</property>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="transition_duration">0</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="row_homogeneous">True</property>
+ <property name="column_homogeneous">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="CcClock" id="clock">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRevealer" id="title_revealer">
+ <property name="visible">True</property>
+ <property name="transition_duration">300</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="name">title</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Screen Calibration</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="subtitle_revealer">
+ <property name="visible">True</property>
+ <property name="transition_duration">300</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="name">subtitle</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Please tap the target markers as they appear on screen to calibrate the tablet.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="error_revealer">
+ <property name="visible">True</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">500</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="name">error</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Mis-click detected, restarting…</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">8</property>
+ <property name="height">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="target1">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="visible_window">True</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="target2">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="visible_window">True</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">6</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="target3">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="visible_window">True</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ <property name="width">2</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="target4">
+ <property name="name">target</property>
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="visible_window">True</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">6</property>
+ <property name="top_attach">6</property>
+ <property name="width">2</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer">
+ <property name="visible">True</property>
+ <property name="transition_type">none</property>
+ <property name="transition_duration">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">8</property>
+ <property name="height">8</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">page0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="pixel_size">300</property>
+ <property name="icon_name">emblem-ok-symbolic</property>
+ </object>
+ <packing>
+ <property name="name">page1</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <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..b39ddac
--- /dev/null
+++ b/panels/wacom/calibrator/cc-clock.c
@@ -0,0 +1,300 @@
+/*
+ * 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 gboolean
+cc_clock_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkAllocation allocation;
+ gdouble angle;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ angle = cc_clock_get_angle (CC_CLOCK (widget));
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+ /* 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);
+
+ return TRUE;
+}
+
+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_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ if (minimum)
+ *minimum = CLOCK_RADIUS + EXTRA_SPACE;
+ if (natural)
+ *natural = CLOCK_RADIUS + EXTRA_SPACE;
+}
+
+static void
+cc_clock_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ 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->draw = cc_clock_draw;
+ widget_class->get_preferred_width = cc_clock_get_preferred_width;
+ widget_class->get_preferred_height = cc_clock_get_preferred_height;
+
+ 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)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (clock), FALSE);
+}
+
+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..1a82e87
--- /dev/null
+++ b/panels/wacom/calibrator/main.c
@@ -0,0 +1,418 @@
+/*
+ * 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"
+
+/**
+ * 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 (CalibArea *area,
+ gpointer user_data)
+{
+ gboolean success;
+ XYinfo axis;
+ gboolean swap_xy;
+
+ success = calib_area_finish (area);
+ if (success)
+ {
+ 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");
+
+ gtk_main_quit ();
+}
+
+int main(int argc, char** argv)
+{
+
+ struct Calib* calibrator = main_common(argc, argv);
+ CalibArea *calib_area;
+
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+ calib_area = calib_area_new (NULL,
+ 0, /* monitor */
+ NULL, /* NULL to accept input from any device */
+ calibration_finished_cb,
+ NULL,
+ calibrator->threshold_doubleclick,
+ calibrator->threshold_misclick);
+
+ gtk_main ();
+
+ 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>