diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:45:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:45:20 +0000 |
commit | ae1c76ff830d146d41e88d6fba724c0a54bce868 (patch) | |
tree | 3c354bec95af07be35fc71a4b738268496f1a1c4 /panels/wacom/calibrator | |
parent | Initial commit. (diff) | |
download | gnome-control-center-ae1c76ff830d146d41e88d6fba724c0a54bce868.tar.xz gnome-control-center-ae1c76ff830d146d41e88d6fba724c0a54bce868.zip |
Adding upstream version 1:43.6.upstream/1%43.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | panels/wacom/calibrator/COPYING | 27 | ||||
-rw-r--r-- | panels/wacom/calibrator/calibrator-gui.c | 429 | ||||
-rw-r--r-- | panels/wacom/calibrator/calibrator-gui.h | 63 | ||||
-rw-r--r-- | panels/wacom/calibrator/calibrator.c | 183 | ||||
-rw-r--r-- | panels/wacom/calibrator/calibrator.css | 47 | ||||
-rw-r--r-- | panels/wacom/calibrator/calibrator.h | 99 | ||||
-rw-r--r-- | panels/wacom/calibrator/calibrator.ui | 146 | ||||
-rw-r--r-- | panels/wacom/calibrator/cc-clock.c | 289 | ||||
-rw-r--r-- | panels/wacom/calibrator/cc-clock.h | 41 | ||||
-rw-r--r-- | panels/wacom/calibrator/main.c | 421 | ||||
-rw-r--r-- | panels/wacom/calibrator/meson.build | 35 | ||||
-rw-r--r-- | panels/wacom/calibrator/target.svg | 93 |
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> |