/* * 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 . * * Author: Carlos Garnacho * (based on previous work by Joaquim Rocha, Tias Guns and Soren Hauberg) */ #include "config.h" #include #include #include #include #include #include #include "calibrator.h" #include "calibrator-gui.h" #include "cc-clock.h" struct CalibArea { struct Calib calibrator; XYinfo axis; gboolean swap; gboolean success; GdkDevice *device; double X[4], Y[4]; int display_width, display_height; GtkWidget *window; GtkBuilder *builder; GtkWidget *error_revealer; GtkWidget *clock; GtkCssProvider *style_provider; FinishCallback callback; gpointer user_data; }; /* Timeout parameters */ #define MAX_TIME 15000 /* 15000 = 15 sec */ #define END_TIME 750 /* 750 = 0.75 sec */ static void set_display_size (CalibArea *calib_area, int width, int height) { int delta_x; int delta_y; calib_area->display_width = width; calib_area->display_height = height; /* Compute absolute circle centers */ delta_x = calib_area->display_width/NUM_BLOCKS; delta_y = calib_area->display_height/NUM_BLOCKS; calib_area->X[UL] = delta_x; calib_area->Y[UL] = delta_y; calib_area->X[UR] = calib_area->display_width - delta_x - 1; calib_area->Y[UR] = delta_y; calib_area->X[LL] = delta_x; calib_area->Y[LL] = calib_area->display_height - delta_y - 1; calib_area->X[LR] = calib_area->display_width - delta_x - 1; calib_area->Y[LR] = calib_area->display_height - delta_y - 1; /* reset calibration if already started */ reset (&calib_area->calibrator); } static void calib_area_notify_finish (CalibArea *area) { gtk_widget_hide (area->window); (*area->callback) (area, area->user_data); } static gboolean on_delete_event (GtkWidget *widget, GdkEvent *event, CalibArea *area) { calib_area_notify_finish (area); return TRUE; } static gboolean calib_area_finish_idle_cb (CalibArea *area) { calib_area_notify_finish (area); return FALSE; } static void set_success (CalibArea *area) { GtkWidget *stack; stack = GTK_WIDGET (gtk_builder_get_object (area->builder, "stack")); gtk_stack_set_visible_child_name (GTK_STACK (stack), "page1"); } static void set_calibration_status (CalibArea *area) { area->success = finish (&area->calibrator, &area->axis, &area->swap); if (area->success) { set_success (area); g_timeout_add (END_TIME, (GSourceFunc) calib_area_finish_idle_cb, area); } else { g_idle_add ((GSourceFunc) calib_area_finish_idle_cb, area); } } static void show_error_message (CalibArea *area) { gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), TRUE); } static void hide_error_message (CalibArea *area) { gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), FALSE); } static void set_active_target (CalibArea *area, int n_target) { GtkWidget *targets[] = { GTK_WIDGET (gtk_builder_get_object (area->builder, "target1")), GTK_WIDGET (gtk_builder_get_object (area->builder, "target2")), GTK_WIDGET (gtk_builder_get_object (area->builder, "target3")), GTK_WIDGET (gtk_builder_get_object (area->builder, "target4")), }; int i; for (i = 0; i < G_N_ELEMENTS (targets); i++) gtk_widget_set_sensitive (targets[i], i == n_target); } static void on_gesture_press (GtkGestureMultiPress *gesture, guint n_press, gdouble x, gdouble y, CalibArea *area) { gint num_clicks; gboolean success; GdkDevice *source; GdkEvent *event; if (area->success) return; event = gtk_get_current_event (); source = gdk_event_get_source_device ((GdkEvent *) event); gdk_event_free (event); /* Check matching device if a device was provided */ if (area->device && area->device != source) { g_debug ("Ignoring input from device %s", gdk_device_get_name (source)); return; } /* Handle click */ /* FIXME: reset clock */ success = add_click(&area->calibrator, (int) x, (int) y); num_clicks = area->calibrator.num_clicks; if (!success && num_clicks == 0) show_error_message (area); else hide_error_message (area); /* Are we done yet? */ if (num_clicks >= 4) { set_calibration_status (area); return; } set_active_target (area, num_clicks); } static gboolean on_key_release_event (GtkWidget *widget, GdkEventKey *event, CalibArea *area) { if (area->success || event->keyval != GDK_KEY_Escape) return GDK_EVENT_PROPAGATE; calib_area_notify_finish (area); return GDK_EVENT_STOP; } static gboolean on_focus_out_event (GtkWidget *widget, GdkEvent *event, CalibArea *area) { if (area->success) return FALSE; /* If the calibrator window loses focus, simply bail out... */ calib_area_notify_finish (area); return FALSE; } static void on_clock_finished (CcClock *clock, CalibArea *area) { set_calibration_status (area); } static void on_title_revealed (CalibArea *area) { GtkWidget *revealer; revealer = GTK_WIDGET (gtk_builder_get_object (area->builder, "subtitle_revealer")); gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE); } static gboolean on_fullscreen (GtkWindow *window, GdkEventWindowState *event, CalibArea *area) { GtkWidget *revealer; if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) == 0) return FALSE; revealer = GTK_WIDGET (gtk_builder_get_object (area->builder, "title_revealer")); g_signal_connect_swapped (revealer, "notify::child-revealed", G_CALLBACK (on_title_revealed), area); gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE); set_active_target (area, 0); return FALSE; } static void on_size_allocate (GtkWidget *widget, GtkAllocation *allocation, CalibArea *area) { set_display_size (area, allocation->width, allocation->height); } /** * Creates the windows and other objects required to do calibration * under GTK. When the window is closed (timed out, calibration finished * or user cancellation), callback will be called, where you should call * calib_area_finish(). */ CalibArea * calib_area_new (GdkScreen *screen, int n_monitor, GdkDevice *device, FinishCallback callback, gpointer user_data, int threshold_doubleclick, int threshold_misclick) { CalibArea *calib_area; GdkRectangle rect; GdkVisual *visual; GdkMonitor *monitor; #ifndef FAKE_AREA GdkWindow *window; g_autoptr(GdkCursor) cursor = NULL; #endif /* FAKE_AREA */ GtkGesture *press; g_return_val_if_fail (callback, NULL); g_type_ensure (CC_TYPE_CLOCK); calib_area = g_new0 (CalibArea, 1); calib_area->callback = callback; calib_area->user_data = user_data; calib_area->device = device; calib_area->calibrator.threshold_doubleclick = threshold_doubleclick; calib_area->calibrator.threshold_misclick = threshold_misclick; calib_area->builder = gtk_builder_new_from_resource ("/org/gnome/control-center/wacom/calibrator/calibrator.ui"); calib_area->window = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "window")); calib_area->error_revealer = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "error_revealer")); calib_area->clock = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "clock")); calib_area->style_provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (calib_area->style_provider, "/org/gnome/control-center/wacom/calibrator/calibrator.css"); gtk_style_context_add_provider_for_screen (gtk_widget_get_screen (calib_area->window), GTK_STYLE_PROVIDER (calib_area->style_provider), GTK_STYLE_PROVIDER_PRIORITY_USER); cc_clock_set_duration (CC_CLOCK (calib_area->clock), MAX_TIME); g_signal_connect (calib_area->clock, "finished", G_CALLBACK (on_clock_finished), calib_area); #ifndef FAKE_AREA /* No cursor */ gtk_widget_realize (calib_area->window); window = gtk_widget_get_window (calib_area->window); cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_BLANK_CURSOR); gdk_window_set_cursor (window, cursor); gtk_widget_set_can_focus (calib_area->window, TRUE); gtk_window_set_keep_above (GTK_WINDOW (calib_area->window), TRUE); #endif /* FAKE_AREA */ /* Move to correct screen */ if (screen == NULL) screen = gdk_screen_get_default (); monitor = gdk_display_get_monitor (gdk_screen_get_display (screen), n_monitor); gdk_monitor_get_geometry (monitor, &rect); calib_area->calibrator.geometry = rect; g_signal_connect (calib_area->window, "key-release-event", G_CALLBACK (on_key_release_event), calib_area); g_signal_connect (calib_area->window, "delete-event", G_CALLBACK (on_delete_event), calib_area); g_signal_connect (calib_area->window, "focus-out-event", G_CALLBACK(on_focus_out_event), calib_area); g_signal_connect (calib_area->window, "window-state-event", G_CALLBACK (on_fullscreen), calib_area); g_signal_connect (calib_area->window, "size-allocate", G_CALLBACK (on_size_allocate), calib_area); press = gtk_gesture_multi_press_new (calib_area->window); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_PRIMARY); g_signal_connect (press, "pressed", G_CALLBACK (on_gesture_press), calib_area); gtk_window_fullscreen_on_monitor (GTK_WINDOW (calib_area->window), screen, n_monitor); visual = gdk_screen_get_rgba_visual (screen); if (visual != NULL) gtk_widget_set_visual (GTK_WIDGET (calib_area->window), visual); gtk_widget_show (calib_area->window); return calib_area; } /* Finishes the calibration. Note that CalibArea * needs to be destroyed with calib_area_free() afterwards */ gboolean calib_area_finish (CalibArea *area) { g_return_val_if_fail (area != NULL, FALSE); if (area->success) g_debug ("Final calibration: %f, %f, %f, %f\n", area->axis.x_min, area->axis.y_min, area->axis.x_max, area->axis.y_max); else g_debug ("Calibration was aborted or timed out"); return area->success; } void calib_area_free (CalibArea *area) { g_return_if_fail (area != NULL); gtk_style_context_remove_provider_for_screen (gtk_widget_get_screen (area->window), GTK_STYLE_PROVIDER (area->style_provider)); gtk_widget_destroy (area->window); g_free (area); } void calib_area_get_display_size (CalibArea *area, gint *width, gint *height) { g_return_if_fail (area != NULL); *width = area->display_width; *height = area->display_height; } void calib_area_get_axis (CalibArea *area, XYinfo *new_axis, gboolean *swap_xy) { g_return_if_fail (area != NULL); *new_axis = area->axis; *swap_xy = area->swap; } void calib_area_get_padding (CalibArea *area, XYinfo *padding) { g_return_if_fail (area != NULL); /* min/max values are monitor coordinates scaled to be between * 0 and 1, padding starts at 0 on "the edge", and positive * values grow towards the center of the rectangle. */ padding->x_min = area->axis.x_min; padding->y_min = area->axis.y_min; padding->x_max = 1 - area->axis.x_max; padding->y_max = 1 - area->axis.y_max; }