diff --git a/configure.ac b/configure.ac index 1d7ac0deb..d8cf527c0 100644 --- a/configure.ac +++ b/configure.ac @@ -401,6 +401,7 @@ panels/user-accounts/data/gnome-user-accounts-panel.desktop.in panels/user-accounts/data/faces/Makefile panels/user-accounts/data/icons/Makefile panels/wacom/Makefile +panels/wacom/calibrator/Makefile panels/wacom/gnome-wacom-panel.desktop.in po/Makefile.in shell/Makefile diff --git a/panels/wacom/Makefile.am b/panels/wacom/Makefile.am index ba4ec6943..6d61c1a47 100644 --- a/panels/wacom/Makefile.am +++ b/panels/wacom/Makefile.am @@ -1,9 +1,12 @@ # This is used in PANEL_CFLAGS cappletname = wacom +SUBDIRS = calibrator + INCLUDES = \ $(PANEL_CFLAGS) \ $(WACOM_PANEL_CFLAGS) \ + -I$(srcdir)/calibrator \ -DGNOMELOCALEDIR="\"$(datadir)/locale\"" \ -DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \ -DGNOMECC_UI_DIR="\"$(uidir)\"" \ @@ -27,7 +30,7 @@ libwacom_properties_la_SOURCES = \ cc-wacom-nav-button.c \ cc-wacom-nav-button.h -libwacom_properties_la_LIBADD = $(PANEL_LIBS) $(WACOM_PANEL_LIBS) +libwacom_properties_la_LIBADD = $(PANEL_LIBS) $(WACOM_PANEL_LIBS) $(builddir)/calibrator/libwacom-calibrator.la libwacom_properties_la_LDFLAGS = $(PANEL_LDFLAGS) noinst_PROGRAMS = test-wacom @@ -44,7 +47,7 @@ test_wacom_SOURCES = \ gsd-input-helper.h test_wacom_CPPFLAGS = $(INCLUDES) -test_wacom_LDADD = $(PANEL_LIBS) $(WACOM_PANEL_LIBS) +test_wacom_LDADD = $(PANEL_LIBS) $(WACOM_PANEL_LIBS) $(builddir)/calibrator/libwacom-calibrator.la @INTLTOOL_DESKTOP_RULE@ diff --git a/panels/wacom/calibrator/Makefile.am b/panels/wacom/calibrator/Makefile.am new file mode 100644 index 000000000..cf223c4b0 --- /dev/null +++ b/panels/wacom/calibrator/Makefile.am @@ -0,0 +1,38 @@ +# This is used in PANEL_CFLAGS +cappletname = wacom + +INCLUDES = \ + $(PANEL_CFLAGS) \ + $(WACOM_PANEL_CFLAGS) \ + -DGNOMELOCALEDIR="\"$(datadir)/locale\"" \ + -DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \ + -DGNOMECC_UI_DIR="\"$(uidir)\"" \ + -DPIXMAP_DIR=\""$(datadir)/gnome-control-center/pixmaps"\" \ + $(NULL) + + +noinst_LTLIBRARIES = libwacom-calibrator.la + +libwacom_calibrator_la_SOURCES = \ + calibrator.c \ + calibrator.h \ + gui_gtk.c \ + gui_gtk.h + +libwacom_calibrator_la_LIBADD = $(PANEL_LIBS) $(WACOM_PANEL_LIBS) +libwacom_calibrator_la_LDFLAGS = $(PANEL_LDFLAGS) + +noinst_PROGRAMS = test-calibrator + +test_calibrator_SOURCES = \ + main.c \ + main.h \ + calibrator.c \ + calibrator.h \ + gui_gtk.c \ + gui_gtk.h + +test_calibrator_CPPFLAGS = $(INCLUDES) +test_calibrator_LDADD = $(PANEL_LIBS) $(WACOM_PANEL_LIBS) + +-include $(top_srcdir)/git.mk diff --git a/panels/wacom/calibrator/calibrator.c b/panels/wacom/calibrator/calibrator.c new file mode 100644 index 000000000..213d09854 --- /dev/null +++ b/panels/wacom/calibrator/calibrator.c @@ -0,0 +1,177 @@ +/* + * 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 + +#include "calibrator.h" + +#define SWAP(x,y) do { int t; t=(x); x=(y); y=t; } while (0) + +/* reset clicks */ +void +reset (struct Calib *c) +{ + c->num_clicks = 0; +} + +/* add a click with the given coordinates */ +bool +add_click (struct Calib *c, + int x, + int 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) + { + return false; + } + i--; + } + } + + /* Mis-click detection */ + if (c->threshold_misclick > 0 && c->num_clicks > 0) + { + bool 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) + { + reset(c); + return false; + } + } + + c->clicked_x[c->num_clicks] = x; + c->clicked_y[c->num_clicks] = y; + c->num_clicks++; + + return true; +} + +/* check whether the coordinates are along the respective axis */ +bool +along_axis (struct Calib *c, + int xy, + int x0, + int y0) +{ + return ((abs(xy - x0) <= c->threshold_misclick) || + (abs(xy - y0) <= c->threshold_misclick)); +} + +/* calculate and apply the calibration */ +bool +finish (struct Calib *c, + int width, + int height, + XYinfo *new_axys, + bool *swap) +{ + bool swap_xy; + float scale_x; + float scale_y; + int delta_x; + int delta_y; + XYinfo axys = {-1, -1, -1, -1}; + + if (c->num_clicks != 4) + return false; + + /* Should x and y be swapped? */ + swap_xy = (abs (c->clicked_x [UL] - c->clicked_x [UR]) < abs (c->clicked_y [UL] - c->clicked_y [UR])); + if (swap_xy) + { + SWAP(c->clicked_x[LL], c->clicked_x[UR]); + SWAP(c->clicked_y[LL], c->clicked_y[UR]); + } + + /* Compute min/max coordinates. */ + /* These are scaled using the values of old_axys */ + scale_x = (c->old_axys.x_max - c->old_axys.x_min)/(float)width; + axys.x_min = ((c->clicked_x[UL] + c->clicked_x[LL]) * scale_x/2) + c->old_axys.x_min; + axys.x_max = ((c->clicked_x[UR] + c->clicked_x[LR]) * scale_x/2) + c->old_axys.x_min; + scale_y = (c->old_axys.y_max - c->old_axys.y_min)/(float)height; + axys.y_min = ((c->clicked_y[UL] + c->clicked_y[UR]) * scale_y/2) + c->old_axys.y_min; + axys.y_max = ((c->clicked_y[LL] + c->clicked_y[LR]) * scale_y/2) + c->old_axys.y_min; + + /* 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 = (axys.x_max - axys.x_min) / (float)(NUM_BLOCKS - 2); + axys.x_min -= delta_x; + axys.x_max += delta_x; + delta_y = (axys.y_max - axys.y_min) / (float)(NUM_BLOCKS - 2); + axys.y_min -= delta_y; + axys.y_max += delta_y; + + /* If x and y has to be swapped we also have to swap the parameters */ + if (swap_xy) + { + SWAP(axys.x_min, axys.y_max); + SWAP(axys.y_min, axys.x_max); + } + + *new_axys = axys; + *swap = swap_xy; + + return true; +} + diff --git a/panels/wacom/calibrator/calibrator.h b/panels/wacom/calibrator/calibrator.h new file mode 100644 index 000000000..98bc71eed --- /dev/null +++ b/panels/wacom/calibrator/calibrator.h @@ -0,0 +1,119 @@ +/* + * 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. + */ + +#ifndef _calibrator_h +#define _calibrator_h + +/* + * 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 to hold min/max info of the X and Y axis */ +typedef struct +{ + int x_min; + int x_max; + int y_min; + int y_max; +} XYinfo; + +typedef enum +{ + false = 0, + true = 1 +} bool; + +struct Calib +{ + /* original axys values */ + XYinfo old_axys; + + /* 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; + + /* manually specified geometry string */ + const char* geometry; +}; + +void reset (struct Calib *c); +bool add_click (struct Calib *c, + int x, + int y); +bool along_axis (struct Calib *c, + int xy, + int x0, + int y0); +bool finish (struct Calib *c, + int width, + int height, + XYinfo *new_axys, + bool *swap); + +#endif /* _calibrator_h */ diff --git a/panels/wacom/calibrator/gui_gtk.c b/panels/wacom/calibrator/gui_gtk.c new file mode 100644 index 000000000..2a3e970ef --- /dev/null +++ b/panels/wacom/calibrator/gui_gtk.c @@ -0,0 +1,411 @@ +/* + * 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 +#include +#include +#include + +#include "calibrator.h" +#include "gui_gtk.h" + +#define MAXIMUM(x,y) ((x) > (y) ? (x) : (y)) + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + +/* Timeout parameters */ +const int time_step = 100; /* in milliseconds */ +const int max_time = 15000; /* 5000 = 5 sec */ + +/* Clock appereance */ +const int cross_lines = 25; +const int cross_circle = 4; +const int clock_radius = 50; +const int clock_line_width = 10; + +/* Text printed on screen */ +const int font_size = 16; +#define HELP_LINES (sizeof help_text / sizeof help_text[0]) +const char *help_text[] = { + "Touchscreen Calibration", + "Press the point, use a stylus to increase precision.", + "", + "(To abort, press any key or wait)" +}; + +struct CalibArea* +CalibrationArea_(struct Calib *c) +{ + struct CalibArea *calib_area; + const char *geo = c->geometry; + + calib_area = (struct CalibArea*)calloc(1, sizeof(struct CalibArea)); + calib_area->calibrator = c; + calib_area->drawing_area = gtk_drawing_area_new(); + + /* Listen for mouse events */ + gtk_widget_add_events(calib_area->drawing_area, GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK); + gtk_widget_set_can_focus(calib_area->drawing_area, TRUE); + + /* Connect callbacks */ + g_signal_connect(calib_area->drawing_area, "expose-event", G_CALLBACK(on_expose_event), calib_area); + g_signal_connect(calib_area->drawing_area, "draw", G_CALLBACK(draw), calib_area); + g_signal_connect(calib_area->drawing_area, "button-press-event", G_CALLBACK(on_button_press_event), calib_area); + g_signal_connect(calib_area->drawing_area, "key-press-event", G_CALLBACK(on_key_press_event), calib_area); + + /* parse geometry string */ + if (geo != NULL) + { + int gw,gh; + int res = sscanf(geo,"%dx%d",&gw,&gh); + if (res != 2) + geo = NULL; + else + set_display_size(calib_area, gw, gh );\ + } + if (geo == NULL) + { + GtkAllocation allocation; + gtk_widget_get_allocation(calib_area->drawing_area, &allocation); + set_display_size(calib_area, allocation.width, allocation.height); + } + + /* Setup timer for animation */ + g_timeout_add(time_step, (GSourceFunc)on_timer_signal, calib_area); + + return calib_area; +} + +void +set_display_size(struct 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); +} + +void +resize_display(struct CalibArea *calib_area) +{ + /* check that screensize did not change (if no manually specified geometry) */ + GtkAllocation allocation; + gtk_widget_get_allocation(calib_area->drawing_area, &allocation); + if (calib_area->calibrator->geometry == NULL && + (calib_area->display_width != allocation.width || + calib_area->display_height != allocation.height )) + { + set_display_size(calib_area, allocation.width, allocation.height); + } +} + +bool +on_expose_event(GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + struct CalibArea *calib_area = (struct CalibArea*)data; + GdkWindow *window = gtk_widget_get_window(calib_area->drawing_area); + + if (window) + { + cairo_t *cr = gdk_cairo_create(window); + cairo_save(cr); + cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height); + cairo_clip(cr); + draw(widget, cr, data); + cairo_restore(cr); + } + return true; +} + +void +draw(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + struct CalibArea *calib_area = (struct CalibArea*)data; + int i; + double text_height; + double text_width; + double x; + double y; + cairo_text_extents_t extent; + + resize_display(calib_area); + + /* Print the text */ + cairo_set_font_size(cr, font_size); + text_height = -1; + text_width = -1; + for (i = 0; i != HELP_LINES; i++) + { + cairo_text_extents(cr, help_text[i], &extent); + text_width = MAXIMUM(text_width, extent.width); + text_height = MAXIMUM(text_height, extent.height); + } + text_height += 2; + + x = (calib_area->display_width - text_width) / 2; + y = (calib_area->display_height - text_height) / 2 - 60; + cairo_set_line_width(cr, 2); + cairo_rectangle(cr, x - 10, y - (HELP_LINES*text_height) - 10, + text_width + 20, (HELP_LINES*text_height) + 20); + + /* Print help lines */ + y -= 3; + for (i = HELP_LINES-1; i != -1; i--) + { + cairo_text_extents(cr, help_text[i], &extent); + cairo_move_to(cr, x + (text_width-extent.width)/2, y); + cairo_show_text(cr, help_text[i]); + y -= text_height; + } + cairo_stroke(cr); + + /* Draw the points */ + for (i = 0; i <= calib_area->calibrator->num_clicks; i++) + { + /* set color: already clicked or not */ + if (i < calib_area->calibrator->num_clicks) + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + else + cairo_set_source_rgb(cr, 0.8, 0.0, 0.0); + + cairo_set_line_width(cr, 1); + cairo_move_to(cr, calib_area->X[i] - cross_lines, calib_area->Y[i]); + cairo_rel_line_to(cr, cross_lines*2, 0); + cairo_move_to(cr, calib_area->X[i], calib_area->Y[i] - cross_lines); + cairo_rel_line_to(cr, 0, cross_lines*2); + cairo_stroke(cr); + + cairo_arc(cr, calib_area->X[i], calib_area->Y[i], cross_circle, 0.0, 2.0 * M_PI); + cairo_stroke(cr); + } + + /* Draw the clock background */ + cairo_arc(cr, calib_area->display_width/2, calib_area->display_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, calib_area->display_width/2, calib_area->display_height/2, (clock_radius - clock_line_width)/2, + 3/2.0*M_PI, (3/2.0*M_PI) + ((double)calib_area->time_elapsed/(double)max_time) * 2*M_PI); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_stroke(cr); + + + /* Draw the message (if any) */ + if (calib_area->message != NULL) + { + /* Frame the message */ + cairo_set_font_size(cr, font_size); + cairo_text_extents(cr, calib_area->message, &extent); + text_width = extent.width; + text_height = extent.height; + + x = (calib_area->display_width - text_width) / 2; + y = (calib_area->display_height - text_height + clock_radius) / 2 + 60; + cairo_set_line_width(cr, 2); + cairo_rectangle(cr, x - 10, y - text_height - 10, + text_width + 20, text_height + 25); + + /* Print the message */ + cairo_move_to(cr, x, y); + cairo_show_text(cr, calib_area->message); + cairo_stroke(cr); + } +} + +void +redraw(struct CalibArea *calib_area) +{ + GdkWindow *win = gtk_widget_get_window(calib_area->drawing_area); + if (win) + { + GdkRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = calib_area->display_width; + rect.height = calib_area->display_height; + gdk_window_invalidate_rect(win, &rect, false); + } +} + +bool +on_timer_signal(struct CalibArea *calib_area) +{ + GdkWindow *win; + GtkWidget *parent = gtk_widget_get_parent(calib_area->drawing_area); + + calib_area->time_elapsed += time_step; + if (calib_area->time_elapsed > max_time || parent == NULL) + { + if (parent) + gtk_widget_destroy(parent); + return false; + } + + /* Update clock */ + win = gtk_widget_get_window(calib_area->drawing_area); + if (win) + { + GdkRectangle rect; + rect.x = calib_area->display_width/2 - clock_radius - clock_line_width; + rect.y = calib_area->display_height/2 - clock_radius - clock_line_width; + rect.width = 2 * clock_radius + 1 + 2 * clock_line_width; + rect.height = 2 * clock_radius + 1 + 2 * clock_line_width; + gdk_window_invalidate_rect(win, &rect, false); + } + + return true; +} + +bool +on_button_press_event(GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + struct CalibArea *calib_area = (struct CalibArea*)data; + bool success; + + /* Handle click */ + calib_area->time_elapsed = 0; + success = add_click(calib_area->calibrator, (int)event->x_root, (int)event->y_root); + + if (!success && calib_area->calibrator->num_clicks == 0) + draw_message(calib_area, "Mis-click detected, restarting..."); + else + draw_message(calib_area, NULL); + + /* Are we done yet? */ + if (calib_area->calibrator->num_clicks >= 4) + { + GtkWidget *parent = gtk_widget_get_parent(calib_area->drawing_area); + if (parent) + gtk_widget_destroy(parent); + return true; + } + + /* Force a redraw */ + redraw(calib_area); + + return true; +} + +void +draw_message(struct CalibArea *calib_area, + const char *msg) +{ + calib_area->message = msg; +} + +bool +on_key_press_event(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + struct CalibArea *calib_area = (struct CalibArea*)data; + GtkWidget *parent = gtk_widget_get_parent(calib_area->drawing_area); + if (parent) + gtk_widget_destroy(parent); + return true; +} + +/** + * Creates the windows and other objects required to do calibration + * under GTK and then starts the main loop. When the main loop exits, + * the calibration will be calculated (if possible) and this function + * will then return ('true' if successful, 'false' otherwise). + */ +bool +run_gui(struct Calib *c, + XYinfo *new_axys, + bool *swap) +{ + bool success; + struct CalibArea *calib_area = CalibrationArea_(c); + + printf("Current calibration: %d, %d, %d, %d\n", + c->old_axys.x_min, + c->old_axys.y_min, + c->old_axys.x_max, + c->old_axys.y_max); + + GdkScreen *screen = gdk_screen_get_default(); + GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + GdkRectangle rect; + /*int num_monitors = screen->get_n_monitors(); TODO, multiple monitors?*/ + + g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(gtk_main_quit), NULL); + + gdk_screen_get_monitor_geometry(screen, 0, &rect); + + /* when no window manager: explicitely take size of full screen */ + gtk_window_move(GTK_WINDOW(win), rect.x, rect.y); + gtk_window_set_default_size(GTK_WINDOW(win), rect.width, rect.height); + + /* in case of window manager: set as full screen to hide window decorations */ + gtk_window_fullscreen(GTK_WINDOW(win)); + + gtk_container_add(GTK_CONTAINER(win), calib_area->drawing_area); + gtk_widget_show_all(win); + + printf("gtk_main entered!\n"); + gtk_main(); + printf("gtk_main returned!\n"); + + success = finish(calib_area->calibrator, calib_area->display_width, calib_area->display_height, new_axys, swap); + + printf("Final calibration: %d, %d, %d, %d\n", + new_axys->x_min, + new_axys->y_min, + new_axys->x_max, + new_axys->y_max); + + return success; +} + diff --git a/panels/wacom/calibrator/gui_gtk.h b/panels/wacom/calibrator/gui_gtk.h new file mode 100644 index 000000000..b376ff1d7 --- /dev/null +++ b/panels/wacom/calibrator/gui_gtk.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef _gui_gtk_h +#define _gui_gtk_h + +#include + +#include "calibrator.h" + +struct CalibArea +{ + struct Calib* calibrator; + double X[4], Y[4]; + int display_width, display_height; + int time_elapsed; + + const char* message; + + GtkWidget *drawing_area; +}; + +struct CalibArea* CalibrationArea_ (struct Calib *c); +void set_display_size (struct CalibArea *calib_area, + int width, + int height); +void resize_display (struct CalibArea *calib_area); +bool on_expose_event (GtkWidget *widget, + GdkEventExpose *event, + gpointer data); +void draw (GtkWidget *widget, + cairo_t *cr, + gpointer data); +void redraw (struct CalibArea *calib_area); +bool on_timer_signal (struct CalibArea *calib_area); +bool on_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +void draw_message (struct CalibArea *calib_area, + const char *msg); +bool on_key_press_event (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +bool run_gui (struct Calib *c, + XYinfo *new_axys, + bool *swap); + +#endif /* _gui_gtk_h */ diff --git a/panels/wacom/calibrator/main.c b/panels/wacom/calibrator/main.c new file mode 100644 index 000000000..85dc9e7a8 --- /dev/null +++ b/panels/wacom/calibrator/main.c @@ -0,0 +1,398 @@ +/* + * 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 +#include +#include +#include + +#include + +#include "gui_gtk.h" +#include "main.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 + */ +int find_device(const char* pre_device, bool verbose, bool list_devices, + XID* device_id, const char** device_name, XYinfo* device_axys) +{ + bool 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; loopuse == 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; jnum_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 = my_strdup(list->name); + device_axys->x_min = ax[0].min_value; + device_axys->x_max = ax[0].max_value; + device_axys->y_min = ax[1].min_value; + device_axys->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 ] [--precalib ] [--misclick ] [--output-type ] [--fake] [--geometry x]\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 : 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"); + fprintf(stderr, "\t--geometry: manually provide the geometry (width and height) for the calibration window\n"); +} + +struct Calib* main_common(int argc, char** argv) +{ + bool verbose = false; + bool list_devices = false; + bool fake = false; + bool precalib = false; + XYinfo pre_axys = {-1, -1, -1, -1}; + const char* pre_device = NULL; + const char* geometry = 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_calibratior, 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_axys.x_min = atoi(argv[++i]); + if (argc > i+1) + pre_axys.x_max = atoi(argv[++i]); + if (argc > i+1) + pre_axys.y_min = atoi(argv[++i]); + if (argc > i+1) + pre_axys.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 + + /* specify window geometry? */ + if (strcmp("--geometry", argv[i]) == 0) { + geometry = argv[++i]; + /* sscanf(argv[++i],"%dx%d",&win_width,&win_height); */ + } 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_axys = {-1, -1, -1, -1}; + if (fake) { + /* Fake a calibratable device */ + device_name = "Fake_device"; + device_axys.x_min=0; + device_axys.x_max=1000; + device_axys.y_min=0; + device_axys.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_axys); + + 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_axys.x_min != -1) + device_axys.x_min = pre_axys.x_min; + if (pre_axys.x_max != -1) + device_axys.x_max = pre_axys.x_max; + if (pre_axys.y_min != -1) + device_axys.y_min = pre_axys.y_min; + if (pre_axys.y_max != -1) + device_axys.y_max = pre_axys.y_max; + + if (verbose) { + printf("DEBUG: Setting precalibration: %i, %i, %i, %i\n", + device_axys.x_min, device_axys.x_max, + device_axys.y_min, device_axys.y_max); + } + } + + /* lastly, presume a standard Xorg driver (evtouch, mutouch, ...) */ + return CalibratorXorgPrint(device_name, &device_axys, + verbose, thr_misclick, thr_doubleclick, geometry); +} + +struct Calib* CalibratorXorgPrint(const char* const device_name0, const XYinfo *axys0, const bool verbose0, const int thr_misclick, const int thr_doubleclick, const char* geometry) +{ + struct Calib* c = (struct Calib*)calloc(1, sizeof(struct Calib)); + c->old_axys = *axys0; + c->threshold_misclick = thr_misclick; + c->threshold_doubleclick = thr_doubleclick; + c->geometry = geometry; + + printf("Calibrating standard Xorg driver \"%s\"\n", device_name0); + printf("\tcurrent calibration values: min_x=%d, max_x=%d and min_y=%d, max_y=%d\n", + c->old_axys.x_min, c->old_axys.x_max, c->old_axys.y_min, c->old_axys.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; +} + +bool finish_data(struct Calib* c, const XYinfo new_axys, int swap_xy) +{ + bool 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(c, new_axys, swap_xy, new_swap_xy); + + return success; +} + +bool output_xorgconfd(struct Calib* c, const XYinfo new_axys, 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\" \"%d\"\n", new_axys.x_min); + printf(" Option \"MaxX\" \"%d\"\n", new_axys.x_max); + printf(" Option \"MinY\" \"%d\"\n", new_axys.y_min); + printf(" Option \"MaxY\" \"%d\"\n", new_axys.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; +} + +int main(int argc, char** argv) +{ + int success = 0; + XYinfo axys; + bool swap_xy; + + struct Calib* calibrator = main_common(argc, argv); + + /* GTK setup */ + gtk_init(&argc, &argv); + + success = run_gui(calibrator, &axys, &swap_xy); + if (success) + success = finish_data(calibrator, axys, swap_xy); + + if (!success) { + /* TODO, in GUI ? */ + fprintf(stderr, "Error: unable to apply or save configuration values\n"); + } + + free(calibrator); + return success; +} diff --git a/panels/wacom/calibrator/main.h b/panels/wacom/calibrator/main.h new file mode 100644 index 000000000..fa2107355 --- /dev/null +++ b/panels/wacom/calibrator/main.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef _main_h +#define _main_h + +#include "calibrator.h" + + +/* strdup: non-ansi */ +char* my_strdup(const char* s); +char* my_strdup(const char* s) { + size_t len = strlen(s) + 1; + void* p = malloc(len); + + if (p == NULL) + return NULL; + + return (char*) memcpy(p, s, len); +} + +int find_device(const char*, bool, bool, XID*, const char**, XYinfo*); + +static void usage(char* cmd, unsigned thr_misclick); + +struct Calib* main_common(int argc, char** argv); + +struct Calib* CalibratorXorgPrint(const char* const device_name, const XYinfo *axys, + const bool verbose, const int thr_misclick, const int thr_doubleclick, + const char* geometry); + +bool finish_data(struct Calib*, const XYinfo new_axys, int swap_xy); +bool output_xorgconfd(struct Calib*, const XYinfo new_axys, int swap_xy, int new_swap_xy); + +int main(int argc, char** argv); + +#endif