diff options
Diffstat (limited to 'panels/wacom')
53 files changed, 9971 insertions, 0 deletions
diff --git a/panels/wacom/button-mapping.ui b/panels/wacom/button-mapping.ui new file mode 100644 index 0000000..23b2226 --- /dev/null +++ b/panels/wacom/button-mapping.ui @@ -0,0 +1,149 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkDialog" id="button-mapping-dialog"> + <property name="width_request">600</property> + <property name="height_request">450</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Map Buttons</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="default_width">600</property> + <property name="default_height">450</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="top_vbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <object class="GtkButton" id="close_button"> + <property name="label" translatable="yes">_Close</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="shortcuts_vbox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">12</property> + <property name="label" translatable="yes">Map buttons to functions</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table11"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">5</property> + <child> + <object class="GtkScrolledWindow" id="actions_swindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkListBox" id="shortcuts_list"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To edit a shortcut, choose the “Send Keystroke” action, press the keyboard shortcut button and hold down the new keys or press Backspace to clear.</property> + <property name="justify">fill</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">close_button</action-widget> + </action-widgets> + </object> +</interface> diff --git a/panels/wacom/calibrator/COPYING b/panels/wacom/calibrator/COPYING new file mode 100644 index 0000000..e0a1dc9 --- /dev/null +++ b/panels/wacom/calibrator/COPYING @@ -0,0 +1,27 @@ +Copyright (c) 2010 Tias Guns <tias@ulyssis.org> and others +See the respective files for detailed copyright information. + + +Source code: MIT/X11 License +------------ +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + diff --git a/panels/wacom/calibrator/calibrator-gui.c b/panels/wacom/calibrator/calibrator-gui.c new file mode 100644 index 0000000..012291f --- /dev/null +++ b/panels/wacom/calibrator/calibrator-gui.c @@ -0,0 +1,461 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + * (based on previous work by Joaquim Rocha, Tias Guns and Soren Hauberg) + */ + +#include "config.h" + +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib/gi18n.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include "calibrator.h" +#include "calibrator-gui.h" +#include "cc-clock.h" + +struct CalibArea +{ + struct Calib calibrator; + XYinfo axis; + gboolean swap; + gboolean success; + GdkDevice *device; + + double X[4], Y[4]; + int display_width, display_height; + + GtkWidget *window; + GtkBuilder *builder; + GtkWidget *error_revealer; + GtkWidget *clock; + GtkCssProvider *style_provider; + + FinishCallback callback; + gpointer user_data; +}; + +/* Timeout parameters */ +#define MAX_TIME 15000 /* 15000 = 15 sec */ +#define END_TIME 750 /* 750 = 0.75 sec */ + +static void +set_display_size (CalibArea *calib_area, + int width, + int height) +{ + int delta_x; + int delta_y; + + calib_area->display_width = width; + calib_area->display_height = height; + + /* Compute absolute circle centers */ + delta_x = calib_area->display_width/NUM_BLOCKS; + delta_y = calib_area->display_height/NUM_BLOCKS; + + calib_area->X[UL] = delta_x; + calib_area->Y[UL] = delta_y; + + calib_area->X[UR] = calib_area->display_width - delta_x - 1; + calib_area->Y[UR] = delta_y; + + calib_area->X[LL] = delta_x; + calib_area->Y[LL] = calib_area->display_height - delta_y - 1; + + calib_area->X[LR] = calib_area->display_width - delta_x - 1; + calib_area->Y[LR] = calib_area->display_height - delta_y - 1; + + /* reset calibration if already started */ + reset (&calib_area->calibrator); +} + +static void +calib_area_notify_finish (CalibArea *area) +{ + gtk_widget_hide (area->window); + + (*area->callback) (area, area->user_data); +} + +static gboolean +on_delete_event (GtkWidget *widget, + GdkEvent *event, + CalibArea *area) +{ + calib_area_notify_finish (area); + return TRUE; +} + +static gboolean +calib_area_finish_idle_cb (CalibArea *area) +{ + calib_area_notify_finish (area); + return FALSE; +} + +static void +set_success (CalibArea *area) +{ + GtkWidget *stack; + + stack = GTK_WIDGET (gtk_builder_get_object (area->builder, "stack")); + gtk_stack_set_visible_child_name (GTK_STACK (stack), "page1"); +} + +static void +set_calibration_status (CalibArea *area) +{ + area->success = finish (&area->calibrator, &area->axis, &area->swap); + + if (area->success) + { + set_success (area); + g_timeout_add (END_TIME, + (GSourceFunc) calib_area_finish_idle_cb, + area); + } + else + { + g_idle_add ((GSourceFunc) calib_area_finish_idle_cb, area); + } +} + +static void +show_error_message (CalibArea *area) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), TRUE); +} + +static void +hide_error_message (CalibArea *area) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), FALSE); +} + +static void +set_active_target (CalibArea *area, + int n_target) +{ + GtkWidget *targets[] = { + GTK_WIDGET (gtk_builder_get_object (area->builder, "target1")), + GTK_WIDGET (gtk_builder_get_object (area->builder, "target2")), + GTK_WIDGET (gtk_builder_get_object (area->builder, "target3")), + GTK_WIDGET (gtk_builder_get_object (area->builder, "target4")), + }; + int i; + + for (i = 0; i < G_N_ELEMENTS (targets); i++) + gtk_widget_set_sensitive (targets[i], i == n_target); +} + +static void +on_gesture_press (GtkGestureMultiPress *gesture, + guint n_press, + gdouble x, + gdouble y, + CalibArea *area) +{ + gint num_clicks; + gboolean success; + GdkDevice *source; + GdkEvent *event; + + if (area->success) + return; + + event = gtk_get_current_event (); + source = gdk_event_get_source_device ((GdkEvent *) event); + gdk_event_free (event); + + /* Check matching device if a device was provided */ + if (area->device && area->device != source) + { + g_debug ("Ignoring input from device %s", + gdk_device_get_name (source)); + return; + } + + /* Handle click */ + /* FIXME: reset clock */ + success = add_click(&area->calibrator, + (int) x, + (int) y); + + num_clicks = area->calibrator.num_clicks; + + if (!success && num_clicks == 0) + show_error_message (area); + else + hide_error_message (area); + + /* Are we done yet? */ + if (num_clicks >= 4) + { + set_calibration_status (area); + return; + } + + set_active_target (area, num_clicks); +} + +static gboolean +on_key_release_event (GtkWidget *widget, + GdkEventKey *event, + CalibArea *area) +{ + if (area->success || + event->keyval != GDK_KEY_Escape) + return GDK_EVENT_PROPAGATE; + + calib_area_notify_finish (area); + return GDK_EVENT_STOP; +} + +static gboolean +on_focus_out_event (GtkWidget *widget, + GdkEvent *event, + CalibArea *area) +{ + if (area->success) + return FALSE; + + /* If the calibrator window loses focus, simply bail out... */ + calib_area_notify_finish (area); + + return FALSE; +} + +static void +on_clock_finished (CcClock *clock, + CalibArea *area) +{ + set_calibration_status (area); +} + +static void +on_title_revealed (CalibArea *area) +{ + GtkWidget *revealer; + + revealer = GTK_WIDGET (gtk_builder_get_object (area->builder, "subtitle_revealer")); + gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE); +} + +static gboolean +on_fullscreen (GtkWindow *window, + GdkEventWindowState *event, + CalibArea *area) +{ + GtkWidget *revealer; + + if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) == 0) + return FALSE; + + revealer = GTK_WIDGET (gtk_builder_get_object (area->builder, "title_revealer")); + g_signal_connect_swapped (revealer, "notify::child-revealed", + G_CALLBACK (on_title_revealed), + area); + gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE); + + set_active_target (area, 0); + + return FALSE; +} + +static void +on_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + CalibArea *area) +{ + set_display_size (area, allocation->width, allocation->height); +} + +/** + * Creates the windows and other objects required to do calibration + * under GTK. When the window is closed (timed out, calibration finished + * or user cancellation), callback will be called, where you should call + * calib_area_finish(). + */ +CalibArea * +calib_area_new (GdkScreen *screen, + int n_monitor, + GdkDevice *device, + FinishCallback callback, + gpointer user_data, + int threshold_doubleclick, + int threshold_misclick) +{ + CalibArea *calib_area; + GdkRectangle rect; + GdkVisual *visual; + GdkMonitor *monitor; +#ifndef FAKE_AREA + GdkWindow *window; + g_autoptr(GdkCursor) cursor = NULL; +#endif /* FAKE_AREA */ + GtkGesture *press; + + g_return_val_if_fail (callback, NULL); + + g_type_ensure (CC_TYPE_CLOCK); + + calib_area = g_new0 (CalibArea, 1); + calib_area->callback = callback; + calib_area->user_data = user_data; + calib_area->device = device; + calib_area->calibrator.threshold_doubleclick = threshold_doubleclick; + calib_area->calibrator.threshold_misclick = threshold_misclick; + + calib_area->builder = gtk_builder_new_from_resource ("/org/gnome/control-center/wacom/calibrator/calibrator.ui"); + calib_area->window = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "window")); + calib_area->error_revealer = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "error_revealer")); + calib_area->clock = GTK_WIDGET (gtk_builder_get_object (calib_area->builder, "clock")); + calib_area->style_provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (calib_area->style_provider, "/org/gnome/control-center/wacom/calibrator/calibrator.css"); + gtk_style_context_add_provider_for_screen (gtk_widget_get_screen (calib_area->window), + GTK_STYLE_PROVIDER (calib_area->style_provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + + cc_clock_set_duration (CC_CLOCK (calib_area->clock), MAX_TIME); + g_signal_connect (calib_area->clock, "finished", + G_CALLBACK (on_clock_finished), calib_area); + +#ifndef FAKE_AREA + /* No cursor */ + gtk_widget_realize (calib_area->window); + window = gtk_widget_get_window (calib_area->window); + cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_BLANK_CURSOR); + gdk_window_set_cursor (window, cursor); + + gtk_widget_set_can_focus (calib_area->window, TRUE); + gtk_window_set_keep_above (GTK_WINDOW (calib_area->window), TRUE); +#endif /* FAKE_AREA */ + + /* Move to correct screen */ + if (screen == NULL) + screen = gdk_screen_get_default (); + monitor = gdk_display_get_monitor (gdk_screen_get_display (screen), n_monitor); + gdk_monitor_get_geometry (monitor, &rect); + + calib_area->calibrator.geometry = rect; + + g_signal_connect (calib_area->window, + "key-release-event", + G_CALLBACK (on_key_release_event), + calib_area); + g_signal_connect (calib_area->window, + "delete-event", + G_CALLBACK (on_delete_event), + calib_area); + g_signal_connect (calib_area->window, + "focus-out-event", + G_CALLBACK(on_focus_out_event), + calib_area); + g_signal_connect (calib_area->window, + "window-state-event", + G_CALLBACK (on_fullscreen), + calib_area); + g_signal_connect (calib_area->window, + "size-allocate", + G_CALLBACK (on_size_allocate), + calib_area); + + press = gtk_gesture_multi_press_new (calib_area->window); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_PRIMARY); + g_signal_connect (press, "pressed", + G_CALLBACK (on_gesture_press), calib_area); + + gtk_window_fullscreen_on_monitor (GTK_WINDOW (calib_area->window), screen, n_monitor); + + visual = gdk_screen_get_rgba_visual (screen); + if (visual != NULL) + gtk_widget_set_visual (GTK_WIDGET (calib_area->window), visual); + + gtk_widget_show (calib_area->window); + + return calib_area; +} + +/* Finishes the calibration. Note that CalibArea + * needs to be destroyed with calib_area_free() afterwards */ +gboolean +calib_area_finish (CalibArea *area) +{ + g_return_val_if_fail (area != NULL, FALSE); + + if (area->success) + g_debug ("Final calibration: %f, %f, %f, %f\n", + area->axis.x_min, + area->axis.y_min, + area->axis.x_max, + area->axis.y_max); + else + g_debug ("Calibration was aborted or timed out"); + + return area->success; +} + +void +calib_area_free (CalibArea *area) +{ + g_return_if_fail (area != NULL); + + gtk_style_context_remove_provider_for_screen (gtk_widget_get_screen (area->window), + GTK_STYLE_PROVIDER (area->style_provider)); + gtk_widget_destroy (area->window); + g_free (area); +} + +void +calib_area_get_display_size (CalibArea *area, gint *width, gint *height) +{ + g_return_if_fail (area != NULL); + + *width = area->display_width; + *height = area->display_height; +} + +void +calib_area_get_axis (CalibArea *area, + XYinfo *new_axis, + gboolean *swap_xy) +{ + g_return_if_fail (area != NULL); + + *new_axis = area->axis; + *swap_xy = area->swap; +} + +void +calib_area_get_padding (CalibArea *area, + XYinfo *padding) +{ + g_return_if_fail (area != NULL); + + /* min/max values are monitor coordinates scaled to be between + * 0 and 1, padding starts at 0 on "the edge", and positive + * values grow towards the center of the rectangle. + */ + padding->x_min = area->axis.x_min; + padding->y_min = area->axis.y_min; + padding->x_max = 1 - area->axis.x_max; + padding->y_max = 1 - area->axis.y_max; +} diff --git a/panels/wacom/calibrator/calibrator-gui.h b/panels/wacom/calibrator/calibrator-gui.h new file mode 100644 index 0000000..d3301c1 --- /dev/null +++ b/panels/wacom/calibrator/calibrator-gui.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* struct to hold min/max info of the X and Y axis */ +typedef struct +{ + gdouble x_min; + gdouble x_max; + gdouble y_min; + gdouble y_max; +} XYinfo; + +typedef struct CalibArea CalibArea; +typedef void (*FinishCallback) (CalibArea *area, gpointer user_data); + +CalibArea * calib_area_new (GdkScreen *screen, + int monitor, + GdkDevice *device, + FinishCallback callback, + gpointer user_data, + int threshold_doubleclick, + int threshold_misclick); + +gboolean calib_area_finish (CalibArea *area); + +void calib_area_free (CalibArea *area); + +void calib_area_get_display_size (CalibArea *area, + gint *width, + gint *height); + +void calib_area_get_axis (CalibArea *area, + XYinfo *new_axis, + gboolean *swap_xy); + +void calib_area_get_padding (CalibArea *area, + XYinfo *padding); + +G_END_DECLS diff --git a/panels/wacom/calibrator/calibrator.c b/panels/wacom/calibrator/calibrator.c new file mode 100644 index 0000000..4ac316e --- /dev/null +++ b/panels/wacom/calibrator/calibrator.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdlib.h> + +#include "calibrator.h" + +#define SWAP(valtype,x,y) \ + G_STMT_START { \ + valtype t; t = (x); x = (y); y = t; \ + } G_STMT_END + +/* reset clicks */ +void +reset (struct Calib *c) +{ + c->num_clicks = 0; +} + +/* check whether the coordinates are along the respective axis */ +static gboolean +along_axis (struct Calib *c, + int xy, + int x0, + int y0) +{ + return ((abs(xy - x0) <= c->threshold_misclick) || + (abs(xy - y0) <= c->threshold_misclick)); +} + +/* add a click with the given coordinates */ +gboolean +add_click (struct Calib *c, + int x, + int y) +{ + g_debug ("Trying to add click (%d, %d)", x, y); + + /* Double-click detection */ + if (c->threshold_doubleclick > 0 && c->num_clicks > 0) + { + int i = c->num_clicks-1; + while (i >= 0) + { + if (abs(x - c->clicked_x[i]) <= c->threshold_doubleclick && + abs(y - c->clicked_y[i]) <= c->threshold_doubleclick) + { + g_debug ("Detected double-click, ignoring"); + return FALSE; + } + i--; + } + } + + /* Mis-click detection */ + if (c->threshold_misclick > 0 && c->num_clicks > 0) + { + gboolean misclick = TRUE; + + if (c->num_clicks == 1) + { + /* check that along one axis of first point */ + if (along_axis(c, x,c->clicked_x[0],c->clicked_y[0]) || + along_axis(c, y,c->clicked_x[0],c->clicked_y[0])) + { + misclick = FALSE; + } + } + else if (c->num_clicks == 2) + { + /* check that along other axis of first point than second point */ + if ((along_axis(c, y,c->clicked_x[0],c->clicked_y[0]) && + along_axis(c, c->clicked_x[1],c->clicked_x[0],c->clicked_y[0])) || + (along_axis(c, x,c->clicked_x[0],c->clicked_y[0]) && + along_axis(c, c->clicked_y[1],c->clicked_x[0],c->clicked_y[0]))) + { + misclick = FALSE; + } + } + else if (c->num_clicks == 3) + { + /* check that along both axis of second and third point */ + if ((along_axis(c, x,c->clicked_x[1],c->clicked_y[1]) && + along_axis(c, y,c->clicked_x[2],c->clicked_y[2])) || + (along_axis(c, y,c->clicked_x[1],c->clicked_y[1]) && + along_axis(c, x,c->clicked_x[2],c->clicked_y[2]))) + { + misclick = FALSE; + } + } + + if (misclick) + { + g_debug ("Detected misclick, resetting"); + reset(c); + return FALSE; + } + } + + g_debug ("Click (%d, %d) added", x, y); + c->clicked_x[c->num_clicks] = x; + c->clicked_y[c->num_clicks] = y; + c->num_clicks++; + + return TRUE; +} + +/* calculate and apply the calibration */ +gboolean +finish (struct Calib *c, + XYinfo *new_axis, + gboolean *swap) +{ + gboolean swap_xy; + float scale_x; + float scale_y; + float delta_x; + float delta_y; + XYinfo axis = {-1, -1, -1, -1}; + + if (c->num_clicks != 4) + return FALSE; + + /* Should x and y be swapped? If the device and output are wider + * towards different axes, swapping must be performed + * + * FIXME: Would be even better to know the actual output orientation, + * not just the direction. + */ + swap_xy = (c->geometry.width < c->geometry.height); + + /* Compute the scale to transform from pixel positions to [0..1]. */ + scale_x = 1 / (float)c->geometry.width; + scale_y = 1 / (float)c->geometry.height; + + axis.x_min = ((((c->clicked_x[UL] + c->clicked_x[LL]) / 2)) * scale_x); + axis.x_max = ((((c->clicked_x[UR] + c->clicked_x[LR]) / 2)) * scale_x); + axis.y_min = ((((c->clicked_y[UL] + c->clicked_y[UR]) / 2)) * scale_y); + axis.y_max = ((((c->clicked_y[LL] + c->clicked_y[LR]) / 2)) * scale_y); + + /* Add/subtract the offset that comes from not having the points in the + * corners (using the same coordinate system they are currently in) + */ + delta_x = (axis.x_max - axis.x_min) / (float)(NUM_BLOCKS - 2); + axis.x_min -= delta_x; + axis.x_max += delta_x; + delta_y = (axis.y_max - axis.y_min) / (float)(NUM_BLOCKS - 2); + axis.y_min -= delta_y; + axis.y_max += delta_y; + + /* If x and y has to be swapped we also have to swap the parameters */ + if (swap_xy) + { + SWAP (gdouble, axis.x_min, axis.y_min); + SWAP (gdouble, axis.x_max, axis.y_max); + } + + *new_axis = axis; + *swap = swap_xy; + + return TRUE; +} + diff --git a/panels/wacom/calibrator/calibrator.css b/panels/wacom/calibrator/calibrator.css new file mode 100644 index 0000000..cf5fced --- /dev/null +++ b/panels/wacom/calibrator/calibrator.css @@ -0,0 +1,47 @@ +#calibrator { + background-color: #000; +} + +#calibrator * { + color: #fff; +} + +#calibrator label { + font-size: larger; +} + +#calibrator #title { + font-weight: bold; + color: #888; +} + +#calibrator #error { + font-weight: bold; +} + +#calibrator #target { + background-image: url('target.svg'); + background-repeat: no-repeat; + background-position: 50% 50%; +} + +@keyframes target-enabled-animation { + 0% { background-size: 0px } + 90% { background-size: 120px } + 100% { background-size: 100px } +} + +@keyframes target-disabled-animation { + 0% { background-size: 100px } + 100% { background-size: 0px } +} + +#calibrator #target:not(disabled) { + animation: target-enabled-animation 1 ease 0.5s; + background-size: 100px; +} + +#calibrator #target:disabled { + animation: target-disabled-animation 1 ease 0.2s; + background-size: 0px; +} diff --git a/panels/wacom/calibrator/calibrator.h b/panels/wacom/calibrator/calibrator.h new file mode 100644 index 0000000..dab7a2f --- /dev/null +++ b/panels/wacom/calibrator/calibrator.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <glib.h> +#include "calibrator-gui.h" + +G_BEGIN_DECLS + +/* + * Number of blocks. We partition the screen into 'num_blocks' x 'num_blocks' + * rectangles of equal size. We then ask the user to press points that are + * located at the corner closes to the center of the four blocks in the corners + * of the screen. The following ascii art illustrates the situation. We partition + * the screen into 8 blocks in each direction. We then let the user press the + * points marked with 'O'. + * + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--O--+--+--+--+--+--O--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--O--+--+--+--+--+--O--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + */ +#define NUM_BLOCKS 8 + +/* Names of the points */ +enum +{ + UL = 0, /* Upper-left */ + UR = 1, /* Upper-right */ + LL = 2, /* Lower-left */ + LR = 3 /* Lower-right */ +}; + +struct Calib +{ + /* Geometry of the calibration window */ + GdkRectangle geometry; + + /* nr of clicks registered */ + int num_clicks; + + /* click coordinates */ + int clicked_x[4], clicked_y[4]; + + /* Threshold to keep the same point from being clicked twice. + * Set to zero if you don't want this check + */ + int threshold_doubleclick; + + /* Threshold to detect mis-clicks (clicks not along axes) + * A lower value forces more precise calibration + * Set to zero if you don't want this check + */ + int threshold_misclick; +}; + +void reset (struct Calib *c); +gboolean add_click (struct Calib *c, + int x, + int y); +gboolean finish (struct Calib *c, + XYinfo *new_axis, + gboolean *swap); + +G_END_DECLS diff --git a/panels/wacom/calibrator/calibrator.ui b/panels/wacom/calibrator/calibrator.ui new file mode 100644 index 0000000..6734b76 --- /dev/null +++ b/panels/wacom/calibrator/calibrator.ui @@ -0,0 +1,203 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkWindow" id="window"> + <property name="name">calibrator</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="transition_duration">0</property> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="row_homogeneous">True</property> + <property name="column_homogeneous">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + </object> + <packing> + <property name="expand">True</property> + </packing> + </child> + <child> + <object class="CcClock" id="clock"> + <property name="visible">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box2"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkRevealer" id="title_revealer"> + <property name="visible">True</property> + <property name="transition_duration">300</property> + <child> + <object class="GtkLabel"> + <property name="name">title</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Screen Calibration</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkRevealer" id="subtitle_revealer"> + <property name="visible">True</property> + <property name="transition_duration">300</property> + <child> + <object class="GtkLabel"> + <property name="name">subtitle</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Please tap the target markers as they appear on screen to calibrate the tablet.</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkRevealer" id="error_revealer"> + <property name="visible">True</property> + <property name="transition_type">crossfade</property> + <property name="transition_duration">500</property> + <child> + <object class="GtkLabel"> + <property name="name">error</property> + <property name="visible">True</property> + <property name="label" translatable="yes">Mis-click detected, restarting…</property> + </object> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">8</property> + <property name="height">8</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="target1"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="visible">True</property> + <property name="visible_window">True</property> + <property name="sensitive">False</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">2</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="target2"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="visible">True</property> + <property name="visible_window">True</property> + <property name="sensitive">False</property> + </object> + <packing> + <property name="left_attach">6</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">2</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="target3"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="visible">True</property> + <property name="visible_window">True</property> + <property name="sensitive">False</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">6</property> + <property name="width">2</property> + <property name="height">2</property> + </packing> + </child> + <child> + <object class="GtkEventBox" id="target4"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="visible">True</property> + <property name="visible_window">True</property> + <property name="sensitive">False</property> + </object> + <packing> + <property name="left_attach">6</property> + <property name="top_attach">6</property> + <property name="width">2</property> + <property name="height">2</property> + </packing> + </child> + <child> + <object class="GtkRevealer"> + <property name="visible">True</property> + <property name="transition_type">none</property> + <property name="transition_duration">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">8</property> + <property name="height">8</property> + </packing> + </child> + </object> + <packing> + <property name="name">page0</property> + </packing> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="pixel_size">300</property> + <property name="icon_name">emblem-ok-symbolic</property> + </object> + <packing> + <property name="name">page1</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkSizeGroup"> + <property name="mode">vertical</property> + <widgets> + <widget name="box1"/> + <widget name="box2"/> + </widgets> + </object> +</interface> diff --git a/panels/wacom/calibrator/cc-clock.c b/panels/wacom/calibrator/cc-clock.c new file mode 100644 index 0000000..b39ddac --- /dev/null +++ b/panels/wacom/calibrator/cc-clock.c @@ -0,0 +1,300 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: Joaquim Rocha <jrocha@redhat.com> + * Carlos Garnacho <carlosg@gnome.org> + */ +#include "config.h" +#include "cc-clock.h" + +#include <math.h> + +#define CLOCK_RADIUS 50 +#define CLOCK_LINE_WIDTH 10 +#define CLOCK_LINE_PADDING 10 +#define EXTRA_SPACE 2 + +typedef struct _CcClock CcClock; + +struct _CcClock +{ + GtkWidget parent_instance; + guint duration; + gint64 start_time; + gboolean running; +}; + +enum +{ + PROP_DURATION = 1, + N_PROPS +}; + +static GParamSpec *props[N_PROPS] = { 0, }; + +enum { + FINISHED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0, }; + +G_DEFINE_TYPE (CcClock, cc_clock, GTK_TYPE_WIDGET) + +static gint64 +cc_clock_get_time_diff (CcClock *clock) +{ + GdkFrameClock *frame_clock; + gint64 current_time; + + frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock)); + current_time = gdk_frame_clock_get_frame_time (frame_clock); + + return current_time - clock->start_time; +} + +static gdouble +cc_clock_get_angle (CcClock *clock) +{ + gint64 time_diff; + + time_diff = cc_clock_get_time_diff (clock); + + if (time_diff > clock->duration * 1000) + return 360; + + return ((gdouble) time_diff / (clock->duration * 1000)) * 360; +} + +static gboolean +cc_clock_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkAllocation allocation; + gdouble angle; + + gtk_widget_get_allocation (widget, &allocation); + angle = cc_clock_get_angle (CC_CLOCK (widget)); + + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + + /* Draw the clock background */ + cairo_arc (cr, allocation.width / 2, allocation.height / 2, CLOCK_RADIUS / 2, 0.0, 2.0 * M_PI); + cairo_set_source_rgb (cr, 0.5, 0.5, 0.5); + cairo_fill_preserve (cr); + cairo_stroke (cr); + + cairo_set_line_width (cr, CLOCK_LINE_WIDTH); + + cairo_arc (cr, + allocation.width / 2, + allocation.height / 2, + (CLOCK_RADIUS - CLOCK_LINE_WIDTH - CLOCK_LINE_PADDING) / 2, + 3 * M_PI_2, + 3 * M_PI_2 + angle * M_PI / 180.0); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_stroke (cr); + + return TRUE; +} + +static void +cc_clock_stop (CcClock *clock) +{ + GdkFrameClock *frame_clock; + + if (!clock->running) + return; + + frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock)); + + gdk_frame_clock_end_updating (frame_clock); + clock->running = FALSE; +} + +static void +on_frame_clock_update (CcClock *clock) +{ + gint64 time_diff; + + if (!clock->running) + return; + + time_diff = cc_clock_get_time_diff (clock); + + if (time_diff > clock->duration * 1000) + { + g_signal_emit (clock, signals[FINISHED], 0); + cc_clock_stop (clock); + } + + gtk_widget_queue_draw (GTK_WIDGET (clock)); +} + +static void +cc_clock_map (GtkWidget *widget) +{ + GdkFrameClock *frame_clock; + + GTK_WIDGET_CLASS (cc_clock_parent_class)->map (widget); + + frame_clock = gtk_widget_get_frame_clock (widget); + g_signal_connect_object (frame_clock, "update", + G_CALLBACK (on_frame_clock_update), + widget, G_CONNECT_SWAPPED); + cc_clock_reset (CC_CLOCK (widget)); +} + +static void +cc_clock_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcClock *clock = CC_CLOCK (object); + + switch (prop_id) + { + case PROP_DURATION: + clock->duration = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_clock_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcClock *clock = CC_CLOCK (object); + + switch (prop_id) + { + case PROP_DURATION: + g_value_set_uint (value, clock->duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_clock_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + if (minimum) + *minimum = CLOCK_RADIUS + EXTRA_SPACE; + if (natural) + *natural = CLOCK_RADIUS + EXTRA_SPACE; +} + +static void +cc_clock_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + if (minimum) + *minimum = CLOCK_RADIUS + EXTRA_SPACE; + if (natural) + *natural = CLOCK_RADIUS + EXTRA_SPACE; +} + +static void +cc_clock_class_init (CcClockClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = cc_clock_set_property; + object_class->get_property = cc_clock_get_property; + + widget_class->map = cc_clock_map; + widget_class->draw = cc_clock_draw; + widget_class->get_preferred_width = cc_clock_get_preferred_width; + widget_class->get_preferred_height = cc_clock_get_preferred_height; + + signals[FINISHED] = + g_signal_new ("finished", + CC_TYPE_CLOCK, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + props[PROP_DURATION] = + g_param_spec_uint ("duration", + "Duration", + "Duration", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static void +cc_clock_init (CcClock *clock) +{ + gtk_widget_set_has_window (GTK_WIDGET (clock), FALSE); +} + +GtkWidget * +cc_clock_new (guint duration) +{ + return g_object_new (CC_TYPE_CLOCK, + "duration", duration, + NULL); +} + +void +cc_clock_reset (CcClock *clock) +{ + GdkFrameClock *frame_clock; + + if (!gtk_widget_get_mapped (GTK_WIDGET (clock))) + return; + + frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock)); + + cc_clock_stop (clock); + + clock->running = TRUE; + clock->start_time = g_get_monotonic_time (); + gdk_frame_clock_begin_updating (frame_clock); +} + +void +cc_clock_set_duration (CcClock *clock, + guint duration) +{ + clock->duration = duration; + g_object_notify (G_OBJECT (clock), "duration"); + cc_clock_reset (clock); +} + +guint +cc_clock_get_duration (CcClock *clock) +{ + return clock->duration; +} diff --git a/panels/wacom/calibrator/cc-clock.h b/panels/wacom/calibrator/cc-clock.h new file mode 100644 index 0000000..9ebf024 --- /dev/null +++ b/panels/wacom/calibrator/cc-clock.h @@ -0,0 +1,41 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#pragma once + +#include <glib.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_CLOCK (cc_clock_get_type ()) + +G_DECLARE_FINAL_TYPE (CcClock, cc_clock, CC, CLOCK, GtkWidget) + +GtkWidget * cc_clock_new (guint duration); + +void cc_clock_reset (CcClock *clock); + +void cc_clock_set_duration (CcClock *clock, + guint duration); +guint cc_clock_get_duration (CcClock *clock); + +GType cc_clock_get_type (void); + +G_END_DECLS diff --git a/panels/wacom/calibrator/main.c b/panels/wacom/calibrator/main.c new file mode 100644 index 0000000..1a82e87 --- /dev/null +++ b/panels/wacom/calibrator/main.c @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <dirent.h> +#include <glib/gi18n.h> + +#include <X11/extensions/XInput.h> + +#include "calibrator-gui.h" +#include "calibrator.h" + +/** + * find a calibratable touchscreen device (using XInput) + * + * if pre_device is NULL, the last calibratable device is selected. + * retuns number of devices found, + * the data of the device is returned in the last 3 function parameters + */ +static int find_device(const char* pre_device, gboolean verbose, gboolean list_devices, + XID* device_id, const char** device_name, XYinfo* device_axis) +{ + gboolean pre_device_is_id = TRUE; + int found = 0; + + Display* display = XOpenDisplay(NULL); + if (display == NULL) { + fprintf(stderr, "Unable to connect to X server\n"); + exit(1); + } + + int xi_opcode, event, error; + if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) { + fprintf(stderr, "X Input extension not available.\n"); + exit(1); + } + + /* verbose, get Xi version */ + if (verbose) { + XExtensionVersion *version = XGetExtensionVersion(display, INAME); + + if (version && (version != (XExtensionVersion*) NoSuchExtension)) { + printf("DEBUG: %s version is %i.%i\n", + INAME, version->major_version, version->minor_version); + XFree(version); + } + } + + if (pre_device != NULL) { + /* check whether the pre_device is an ID (only digits) */ + int len = strlen(pre_device); + int loop; + for (loop=0; loop<len; loop++) { + if (!isdigit(pre_device[loop])) { + pre_device_is_id = FALSE; + break; + } + } + } + + + if (verbose) + printf("DEBUG: Skipping virtual master devices and devices without axis valuators.\n"); + int ndevices; + XDeviceInfoPtr list, slist; + slist=list=(XDeviceInfoPtr) XListInputDevices (display, &ndevices); + int i; + for (i=0; i<ndevices; i++, list++) + { + if (list->use == IsXKeyboard || list->use == IsXPointer) /* virtual master device */ + continue; + + /* if we are looking for a specific device */ + if (pre_device != NULL) { + if ((pre_device_is_id && list->id == (XID) atoi(pre_device)) || + (!pre_device_is_id && strcmp(list->name, pre_device) == 0)) { + /* OK, fall through */ + } else { + /* skip, not this device */ + continue; + } + } + + XAnyClassPtr any = (XAnyClassPtr) (list->inputclassinfo); + int j; + for (j=0; j<list->num_classes; j++) + { + + if (any->class == ValuatorClass) + { + XValuatorInfoPtr V = (XValuatorInfoPtr) any; + XAxisInfoPtr ax = (XAxisInfoPtr) V->axes; + + if (V->mode != Absolute) { + if (verbose) + printf("DEBUG: Skipping device '%s' id=%i, does not report Absolute events.\n", + list->name, (int)list->id); + } else if (V->num_axes < 2 || + (ax[0].min_value == -1 && ax[0].max_value == -1) || + (ax[1].min_value == -1 && ax[1].max_value == -1)) { + if (verbose) + printf("DEBUG: Skipping device '%s' id=%i, does not have two calibratable axes.\n", + list->name, (int)list->id); + } else { + /* a calibratable device (has 2 axis valuators) */ + found++; + *device_id = list->id; + *device_name = g_strdup(list->name); + device_axis->x_min = ax[0].min_value; + device_axis->x_max = ax[0].max_value; + device_axis->y_min = ax[1].min_value; + device_axis->y_max = ax[1].max_value; + + if (list_devices) + printf("Device \"%s\" id=%i\n", *device_name, (int)*device_id); + } + + } + + /* + * Increment 'any' to point to the next item in the linked + * list. The length is in bytes, so 'any' must be cast to + * a character pointer before being incremented. + */ + any = (XAnyClassPtr) ((char *) any + any->length); + } + + } + XFreeDeviceList(slist); + XCloseDisplay(display); + + return found; +} + +static void usage(char* cmd, unsigned thr_misclick) +{ + fprintf(stderr, "Usage: %s [-h|--help] [-v|--verbose] [--list] [--device <device name or id>] [--precalib <minx> <maxx> <miny> <maxy>] [--misclick <nr of pixels>] [--output-type <auto|xorg.conf.d|hal|xinput>] [--fake]\n", cmd); + fprintf(stderr, "\t-h, --help: print this help message\n"); + fprintf(stderr, "\t-v, --verbose: print debug messages during the process\n"); + fprintf(stderr, "\t--list: list calibratable input devices and quit\n"); + fprintf(stderr, "\t--device <device name or id>: select a specific device to calibrate\n"); + fprintf(stderr, "\t--precalib: manually provide the current calibration setting (eg. the values in xorg.conf)\n"); + fprintf(stderr, "\t--misclick: set the misclick threshold (0=off, default: %i pixels)\n", + thr_misclick); + fprintf(stderr, "\t--fake: emulate a fake device (for testing purposes)\n"); +} + +static struct Calib* CalibratorXorgPrint(const char* const device_name0, const XYinfo *axis0, const gboolean verbose0, const int thr_misclick, const int thr_doubleclick) +{ + struct Calib* c = (struct Calib*)calloc(1, sizeof(struct Calib)); + c->threshold_misclick = thr_misclick; + c->threshold_doubleclick = thr_doubleclick; + + printf("Calibrating standard Xorg driver \"%s\"\n", device_name0); + printf("\tcurrent calibration values: min_x=%lf, max_x=%lf and min_y=%lf, max_y=%lf\n", + axis0->x_min, axis0->x_max, axis0->y_min, axis0->y_max); + printf("\tIf these values are estimated wrong, either supply it manually with the --precalib option, or run the 'get_precalib.sh' script to automatically get it (through HAL).\n"); + + return c; +} + +static struct Calib* main_common(int argc, char** argv) +{ + gboolean verbose = FALSE; + gboolean list_devices = FALSE; + gboolean fake = FALSE; + gboolean precalib = FALSE; + XYinfo pre_axis = {-1, -1, -1, -1}; + const char* pre_device = NULL; + unsigned thr_misclick = 15; + unsigned thr_doubleclick = 7; + + /* parse input */ + if (argc > 1) { + int i; + for (i=1; i!=argc; i++) { + /* Display help ? */ + if (strcmp("-h", argv[i]) == 0 || + strcmp("--help", argv[i]) == 0) { + fprintf(stderr, "xinput_calibrator, v%s\n\n", "0.0.0"); + usage(argv[0], thr_misclick); + exit(0); + } else + + /* Verbose output ? */ + if (strcmp("-v", argv[i]) == 0 || + strcmp("--verbose", argv[i]) == 0) { + verbose = TRUE; + } else + + /* Just list devices ? */ + if (strcmp("--list", argv[i]) == 0) { + list_devices = TRUE; + } else + + /* Select specific device ? */ + if (strcmp("--device", argv[i]) == 0) { + if (argc > i+1) + pre_device = argv[++i]; + else { + fprintf(stderr, "Error: --device needs a device name or id as argument; use --list to list the calibratable input devices.\n\n"); + usage(argv[0], thr_misclick); + exit(1); + } + } else + + /* Get pre-calibration ? */ + if (strcmp("--precalib", argv[i]) == 0) { + precalib = TRUE; + if (argc > i+1) + pre_axis.x_min = atoi(argv[++i]); + if (argc > i+1) + pre_axis.x_max = atoi(argv[++i]); + if (argc > i+1) + pre_axis.y_min = atoi(argv[++i]); + if (argc > i+1) + pre_axis.y_max = atoi(argv[++i]); + } else + + /* Get mis-click threshold ? */ + if (strcmp("--misclick", argv[i]) == 0) { + if (argc > i+1) + thr_misclick = atoi(argv[++i]); + else { + fprintf(stderr, "Error: --misclick needs a number (the pixel threshold) as argument. Set to 0 to disable mis-click detection.\n\n"); + usage(argv[0], thr_misclick); + exit(1); + } + } else + + /* Fake calibratable device ? */ + if (strcmp("--fake", argv[i]) == 0) { + fake = TRUE; + } + + /* unknown option */ + else { + fprintf(stderr, "Unknown option: %s\n\n", argv[i]); + usage(argv[0], thr_misclick); + exit(0); + } + } + } + + + /* Choose the device to calibrate */ + XID device_id = (XID) -1; + const char* device_name = NULL; + XYinfo device_axis = {-1, -1, -1, -1}; + if (fake) { + /* Fake a calibratable device */ + device_name = "Fake_device"; + device_axis.x_min=0; + device_axis.x_max=1000; + device_axis.y_min=0; + device_axis.y_max=1000; + + if (verbose) { + printf("DEBUG: Faking device: %s\n", device_name); + } + } else { + /* Find the right device */ + int nr_found = find_device(pre_device, verbose, list_devices, &device_id, &device_name, &device_axis); + + if (list_devices) { + /* printed the list in find_device */ + if (nr_found == 0) + printf("No calibratable devices found.\n"); + exit(0); + } + + if (nr_found == 0) { + if (pre_device == NULL) + fprintf (stderr, "Error: No calibratable devices found.\n"); + else + fprintf (stderr, "Error: Device \"%s\" not found; use --list to list the calibratable input devices.\n", pre_device); + exit(1); + + } else if (nr_found > 1) { + printf ("Warning: multiple calibratable devices found, calibrating last one (%s)\n\tuse --device to select another one.\n", device_name); + } + + if (verbose) { + printf("DEBUG: Selected device: %s\n", device_name); + } + } + + /* override min/max XY from command line ? */ + if (precalib) { + if (pre_axis.x_min != -1) + device_axis.x_min = pre_axis.x_min; + if (pre_axis.x_max != -1) + device_axis.x_max = pre_axis.x_max; + if (pre_axis.y_min != -1) + device_axis.y_min = pre_axis.y_min; + if (pre_axis.y_max != -1) + device_axis.y_max = pre_axis.y_max; + + if (verbose) { + printf("DEBUG: Setting precalibration: %lf, %lf, %lf, %lf\n", + device_axis.x_min, device_axis.x_max, + device_axis.y_min, device_axis.y_max); + } + } + + /* lastly, presume a standard Xorg driver (evtouch, mutouch, ...) */ + return CalibratorXorgPrint(device_name, &device_axis, + verbose, thr_misclick, thr_doubleclick); +} + +static gboolean output_xorgconfd(const XYinfo new_axis, int swap_xy, int new_swap_xy) +{ + const char* sysfs_name = "!!Name_Of_TouchScreen!!"; + + /* xorg.conf.d snippet */ + printf(" copy the snippet below into '/etc/X11/xorg.conf.d/99-calibration.conf'\n"); + printf("Section \"InputClass\"\n"); + printf(" Identifier \"calibration\"\n"); + printf(" MatchProduct \"%s\"\n", sysfs_name); + printf(" Option \"MinX\" \"%lf\"\n", new_axis.x_min); + printf(" Option \"MaxX\" \"%lf\"\n", new_axis.x_max); + printf(" Option \"MinY\" \"%lf\"\n", new_axis.y_min); + printf(" Option \"MaxY\" \"%lf\"\n", new_axis.y_max); + if (swap_xy != 0) + printf(" Option \"SwapXY\" \"%d\" # unless it was already set to 1\n", new_swap_xy); + printf("EndSection\n"); + + return TRUE; +} + +static gboolean finish_data(const XYinfo new_axis, int swap_xy) +{ + gboolean success = TRUE; + + /* we suppose the previous 'swap_xy' value was 0 */ + /* (unfortunately there is no way to verify this (yet)) */ + int new_swap_xy = swap_xy; + + printf("\n\n--> Making the calibration permanent <--\n"); + success &= output_xorgconfd(new_axis, swap_xy, new_swap_xy); + + return success; +} + +static void +calibration_finished_cb (CalibArea *area, + gpointer user_data) +{ + gboolean success; + XYinfo axis; + gboolean swap_xy; + + success = calib_area_finish (area); + if (success) + { + calib_area_get_axis (area, &axis, &swap_xy); + success = finish_data (axis, swap_xy); + } + else + fprintf(stderr, "Error: unable to apply or save configuration values\n"); + + gtk_main_quit (); +} + +int main(int argc, char** argv) +{ + + struct Calib* calibrator = main_common(argc, argv); + CalibArea *calib_area; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (&argc, &argv); + + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + + calib_area = calib_area_new (NULL, + 0, /* monitor */ + NULL, /* NULL to accept input from any device */ + calibration_finished_cb, + NULL, + calibrator->threshold_doubleclick, + calibrator->threshold_misclick); + + gtk_main (); + + calib_area_free (calib_area); + + free(calibrator); + + return 0; +} diff --git a/panels/wacom/calibrator/meson.build b/panels/wacom/calibrator/meson.build new file mode 100644 index 0000000..f894e60 --- /dev/null +++ b/panels/wacom/calibrator/meson.build @@ -0,0 +1,35 @@ +calibrator_inc = include_directories('.') + +common_sources = files( + 'calibrator.c', + 'calibrator-gui.c', + 'cc-clock.c', +) + +calibrator_deps = deps + [m_dep] + +libwacom_calibrator = static_library( + cappletname + '-calibrator', + sources: common_sources, + include_directories: top_inc, + dependencies: calibrator_deps, + c_args: cflags +) + +libwacom_calibrator_test = static_library( + cappletname + '-calibrator-test', + sources: common_sources, + include_directories: top_inc, + dependencies: calibrator_deps, + c_args: test_cflags +) + +sources = common_sources + wacom_gresource + files('main.c') + +executable( + 'test-calibrator', + sources, + include_directories: top_inc, + dependencies: calibrator_deps, + c_args: cflags +) diff --git a/panels/wacom/calibrator/target.svg b/panels/wacom/calibrator/target.svg new file mode 100644 index 0000000..60b4cbb --- /dev/null +++ b/panels/wacom/calibrator/target.svg @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="200" + height="200" + viewBox="0 0 52.916666 52.916668" + version="1.1" + id="svg8" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="target.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2.8" + inkscape:cx="26.277089" + inkscape:cy="74.824155" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="2160" + inkscape:window-height="1311" + inkscape:window-x="0" + inkscape:window-y="55" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid4518" /> + <sodipodi:guide + position="-7.9374999,13.229167" + orientation="1,0" + id="guide4542" + inkscape:locked="false" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-244.08332)"> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.5291667px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 0,270.54165 h 52.916667 v 0" + id="path4520" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.5291667px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 26.458334,244.08331 v 52.91667" + id="path4522" + inkscape:connector-curvature="0" /> + <ellipse + style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.5291667;stroke-opacity:1" + id="path4530" + cx="26.458332" + cy="270.54163" + rx="5.2916665" + ry="5.2916679" /> + <ellipse + style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.00000012;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4534" + cx="26.458332" + cy="270.54163" + rx="15.875" + ry="15.875004" /> + </g> +</svg> diff --git a/panels/wacom/cc-drawing-area.c b/panels/wacom/cc-drawing-area.c new file mode 100644 index 0000000..6c63597 --- /dev/null +++ b/panels/wacom/cc-drawing-area.c @@ -0,0 +1,214 @@ +/* + * Copyright © 2016 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> + */ + +#include "config.h" +#include <cairo/cairo.h> +#include "cc-drawing-area.h" + +typedef struct _CcDrawingArea CcDrawingArea; + +struct _CcDrawingArea { + GtkEventBox parent; + GdkDevice *current_device; + cairo_surface_t *surface; + cairo_t *cr; +}; + +G_DEFINE_TYPE (CcDrawingArea, cc_drawing_area, GTK_TYPE_EVENT_BOX) + +static void +ensure_drawing_surface (CcDrawingArea *area, + gint width, + gint height) +{ + if (!area->surface || + cairo_image_surface_get_width (area->surface) != width || + cairo_image_surface_get_height (area->surface) != height) { + cairo_surface_t *surface; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + if (area->surface) { + cairo_t *cr; + + cr = cairo_create (surface); + cairo_set_source_surface (cr, area->surface, 0, 0); + cairo_paint (cr); + + cairo_surface_destroy (area->surface); + cairo_destroy (area->cr); + cairo_destroy (cr); + } + + area->surface = surface; + area->cr = cairo_create (surface); + } +} + +static void +cc_drawing_area_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + CcDrawingArea *area = CC_DRAWING_AREA (widget); + + ensure_drawing_surface (area, allocation->width, allocation->height); + + GTK_WIDGET_CLASS (cc_drawing_area_parent_class)->size_allocate (widget, + allocation); +} + +static void +cc_drawing_area_map (GtkWidget *widget) +{ + GtkAllocation allocation; + + GTK_WIDGET_CLASS (cc_drawing_area_parent_class)->map (widget); + + gtk_widget_get_allocation (widget, &allocation); + ensure_drawing_surface (CC_DRAWING_AREA (widget), + allocation.width, allocation.height); +} + +static void +cc_drawing_area_unmap (GtkWidget *widget) +{ + CcDrawingArea *area = CC_DRAWING_AREA (widget); + + if (area->cr) { + cairo_destroy (area->cr); + area->cr = NULL; + } + + if (area->surface) { + cairo_surface_destroy (area->surface); + area->surface = NULL; + } + + GTK_WIDGET_CLASS (cc_drawing_area_parent_class)->unmap (widget); +} + +static gboolean +cc_drawing_area_draw (GtkWidget *widget, + cairo_t *cr) +{ + CcDrawingArea *area = CC_DRAWING_AREA (widget); + GtkAllocation allocation; + + GTK_WIDGET_CLASS (cc_drawing_area_parent_class)->draw (widget, cr); + + gtk_widget_get_allocation (widget, &allocation); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + cairo_set_source_surface (cr, area->surface, 0, 0); + cairo_paint (cr); + + cairo_set_source_rgb (cr, 0.6, 0.6, 0.6); + cairo_rectangle (cr, 0, 0, allocation.width, allocation.height); + cairo_stroke (cr); + + return FALSE; +} + +static gboolean +cc_drawing_area_event (GtkWidget *widget, + GdkEvent *event) +{ + CcDrawingArea *area = CC_DRAWING_AREA (widget); + GdkInputSource source; + GdkDeviceTool *tool; + GdkDevice *device; + + device = gdk_event_get_source_device (event); + + if (!device) + return GDK_EVENT_PROPAGATE; + + source = gdk_device_get_source (device); + tool = gdk_event_get_device_tool (event); + + if (source != GDK_SOURCE_PEN && source != GDK_SOURCE_ERASER) + return GDK_EVENT_PROPAGATE; + + if (area->current_device && area->current_device != device) + return GDK_EVENT_PROPAGATE; + + if (event->type == GDK_BUTTON_PRESS && + event->button.button == 1 && !area->current_device) { + area->current_device = device; + } else if (event->type == GDK_BUTTON_RELEASE && + event->button.button == 1 && area->current_device) { + cairo_new_path (area->cr); + area->current_device = NULL; + } else if (event->type == GDK_MOTION_NOTIFY && + event->motion.state & GDK_BUTTON1_MASK) { + gdouble x, y, pressure; + + gdk_event_get_coords (event, &x, &y); + gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &pressure); + + if (gdk_device_tool_get_tool_type (tool) == GDK_DEVICE_TOOL_TYPE_ERASER) { + cairo_set_line_width (area->cr, 10 * pressure); + cairo_set_operator (area->cr, CAIRO_OPERATOR_DEST_OUT); + } else { + cairo_set_line_width (area->cr, 4 * pressure); + cairo_set_operator (area->cr, CAIRO_OPERATOR_SATURATE); + } + + cairo_set_source_rgba (area->cr, 0, 0, 0, pressure); + cairo_line_to (area->cr, x, y); + cairo_stroke (area->cr); + + cairo_move_to (area->cr, x, y); + + gtk_widget_queue_draw (widget); + + return GDK_EVENT_STOP; + } + + return GDK_EVENT_PROPAGATE; +} + +static void +cc_drawing_area_class_init (CcDrawingAreaClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->size_allocate = cc_drawing_area_size_allocate; + widget_class->draw = cc_drawing_area_draw; + widget_class->event = cc_drawing_area_event; + widget_class->map = cc_drawing_area_map; + widget_class->unmap = cc_drawing_area_unmap; +} + +static void +cc_drawing_area_init (CcDrawingArea *area) +{ + gtk_event_box_set_above_child (GTK_EVENT_BOX (area), TRUE); + gtk_widget_add_events (GTK_WIDGET (area), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK); +} + +GtkWidget * +cc_drawing_area_new (void) +{ + return g_object_new (CC_TYPE_DRAWING_AREA, NULL); +} diff --git a/panels/wacom/cc-drawing-area.h b/panels/wacom/cc-drawing-area.h new file mode 100644 index 0000000..9b22a42 --- /dev/null +++ b/panels/wacom/cc-drawing-area.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2016 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 <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_DRAWING_AREA (cc_drawing_area_get_type ()) +G_DECLARE_FINAL_TYPE (CcDrawingArea, cc_drawing_area, CC, DRAWING_AREA, GtkEventBox) + +GtkWidget *cc_drawing_area_new (void); + +G_END_DECLS diff --git a/panels/wacom/cc-tablet-tool-map.c b/panels/wacom/cc-tablet-tool-map.c new file mode 100644 index 0000000..bdc51b9 --- /dev/null +++ b/panels/wacom/cc-tablet-tool-map.c @@ -0,0 +1,398 @@ +/* + * Copyright © 2016 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: Carlos Garnacho <carlosg@gnome.org> + * + */ + +#include "config.h" +#include "cc-tablet-tool-map.h" + +#define KEY_TOOL_ID "ID" +#define KEY_DEVICE_STYLI "Styli" +#define GENERIC_STYLUS "generic" + +typedef struct _CcTabletToolMap CcTabletToolMap; + +struct _CcTabletToolMap { + GObject parent_instance; + GKeyFile *tablets; + GKeyFile *tools; + GHashTable *tool_map; + GHashTable *tablet_map; + GHashTable *no_serial_tool_map; + + gchar *tablet_path; + gchar *tool_path; +}; + +G_DEFINE_TYPE (CcTabletToolMap, cc_tablet_tool_map, G_TYPE_OBJECT) + +static void +load_keyfiles (CcTabletToolMap *map) +{ + g_autoptr(GError) devices_error = NULL; + g_autoptr(GError) tools_error = NULL; + g_autofree gchar *dir = NULL; + + dir = g_build_filename (g_get_user_cache_dir (), "gnome-control-center", "wacom", NULL); + + if (g_mkdir_with_parents (dir, 0700) < 0) { + g_warning ("Could not create directory '%s', expect stylus mapping oddities: %m", dir); + return; + } + + map->tablet_path = g_build_filename (dir, "devices", NULL); + g_key_file_load_from_file (map->tablets, map->tablet_path, + G_KEY_FILE_NONE, &devices_error); + + if (devices_error && !g_error_matches (devices_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + g_warning ("Could not load tablets keyfile '%s': %s", + map->tablet_path, devices_error->message); + } + + map->tool_path = g_build_filename (dir, "tools", NULL); + g_key_file_load_from_file (map->tools, map->tool_path, + G_KEY_FILE_NONE, &tools_error); + + if (tools_error && !g_error_matches (tools_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + g_warning ("Could not load tools keyfile '%s': %s", + map->tool_path, tools_error->message); + } +} + +static void +cache_tools (CcTabletToolMap *map) +{ + g_auto(GStrv) serials = NULL; + gsize n_serials, i; + + serials = g_key_file_get_groups (map->tools, &n_serials); + + for (i = 0; i < n_serials; i++) { + g_autofree gchar *str = NULL; + gchar *end; + guint64 serial, id; + g_autoptr(GError) error = NULL; + CcWacomTool *tool; + + serial = g_ascii_strtoull (serials[i], &end, 16); + + if (*end != '\0') { + g_warning ("Invalid tool serial %s", serials[i]); + continue; + } + + str = g_key_file_get_string (map->tools, serials[i], KEY_TOOL_ID, &error); + if (str == NULL) { + g_warning ("Could not get cached ID for tool with serial %s: %s", + serials[i], error->message); + continue; + } + + id = g_ascii_strtoull (str, &end, 16); + if (*end != '\0') { + g_warning ("Invalid tool ID %s", str); + continue; + } + + tool = cc_wacom_tool_new (serial, id, NULL); + g_hash_table_insert (map->tool_map, g_strdup (serials[i]), tool); + } +} + +static void +cache_devices (CcTabletToolMap *map) +{ + gchar **ids; + gsize n_ids, i; + + ids = g_key_file_get_groups (map->tablets, &n_ids); + + for (i = 0; i < n_ids; i++) { + gchar **styli; + gsize n_styli, j; + g_autoptr(GError) error = NULL; + GList *tools = NULL; + + styli = g_key_file_get_string_list (map->tablets, ids[i], KEY_DEVICE_STYLI, &n_styli, &error); + if (styli == NULL) { + g_warning ("Could not get cached styli for with ID %s: %s", + ids[i], error->message); + continue; + } + + for (j = 0; j < n_styli; j++) { + CcWacomTool *tool; + + if (g_str_equal (styli[j], GENERIC_STYLUS)) { + /* We don't have a GsdDevice yet to create the + * serial=0 CcWacomTool, insert a NULL and defer + * to device lookups. + */ + g_hash_table_insert (map->no_serial_tool_map, + g_strdup (ids[i]), NULL); + } + + tool = g_hash_table_lookup (map->tool_map, styli[j]); + + if (tool) + tools = g_list_prepend (tools, tool); + } + + if (tools) { + g_hash_table_insert (map->tablet_map, g_strdup (ids[i]), tools); + } + + g_strfreev (styli); + } + + g_strfreev (ids); +} + +static void +cc_tablet_tool_map_finalize (GObject *object) +{ + CcTabletToolMap *map = CC_TABLET_TOOL_MAP (object); + + g_key_file_unref (map->tools); + g_key_file_unref (map->tablets); + g_hash_table_destroy (map->tool_map); + g_hash_table_destroy (map->tablet_map); + g_hash_table_destroy (map->no_serial_tool_map); + g_free (map->tablet_path); + g_free (map->tool_path); + + G_OBJECT_CLASS (cc_tablet_tool_map_parent_class)->finalize (object); +} + +static void +null_safe_unref (gpointer data) +{ + if (data != NULL) + g_object_unref (data); +} + +static void +cc_tablet_tool_map_init (CcTabletToolMap *map) +{ + map->tablets = g_key_file_new (); + map->tools = g_key_file_new (); + map->tool_map = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + map->tablet_map = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_list_free); + map->no_serial_tool_map = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) null_safe_unref); + load_keyfiles (map); + cache_tools (map); + cache_devices (map); +} + +static void +cc_tablet_tool_map_class_init (CcTabletToolMapClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_tablet_tool_map_finalize; +} + +CcTabletToolMap * +cc_tablet_tool_map_new (void) +{ + return g_object_new (CC_TYPE_TABLET_TOOL_MAP, NULL); +} + +static gchar * +get_device_key (CcWacomDevice *device) +{ + const gchar *vendor, *product; + GsdDevice *gsd_device; + + gsd_device = cc_wacom_device_get_device (device); + gsd_device_get_device_ids (gsd_device, &vendor, &product); + + return g_strdup_printf ("%s:%s", vendor, product); +} + +static gchar * +get_tool_key (guint64 serial) +{ + return g_strdup_printf ("%lx", serial); +} + +GList * +cc_tablet_tool_map_list_tools (CcTabletToolMap *map, + CcWacomDevice *device) +{ + CcWacomTool *no_serial_tool; + GList *styli; + g_autofree gchar *key = NULL; + + g_return_val_if_fail (CC_IS_TABLET_TOOL_MAP (map), NULL); + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + key = get_device_key (device); + styli = g_list_copy (g_hash_table_lookup (map->tablet_map, key)); + + if (g_hash_table_lookup_extended (map->no_serial_tool_map, key, + NULL, (gpointer) &no_serial_tool)) { + if (!no_serial_tool) { + no_serial_tool = cc_wacom_tool_new (0, 0, device); + g_hash_table_replace (map->no_serial_tool_map, + g_strdup (key), + no_serial_tool); + } + + styli = g_list_prepend (styli, no_serial_tool); + } + + return styli; +} + +CcWacomTool * +cc_tablet_tool_map_lookup_tool (CcTabletToolMap *map, + CcWacomDevice *device, + guint64 serial) +{ + CcWacomTool *tool = NULL; + g_autofree gchar *key = NULL; + + g_return_val_if_fail (CC_IS_TABLET_TOOL_MAP (map), FALSE); + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), FALSE); + + if (serial == 0) { + key = get_device_key (device); + tool = g_hash_table_lookup (map->no_serial_tool_map, key); + } else { + key = get_tool_key (serial); + tool = g_hash_table_lookup (map->tool_map, key); + } + + return tool; +} + +static void +keyfile_add_device_stylus (CcTabletToolMap *map, + const gchar *device_key, + const gchar *tool_key) +{ + g_autoptr(GArray) array = NULL; + g_auto(GStrv) styli = NULL; + gsize n_styli; + + array = g_array_new (FALSE, FALSE, sizeof (gchar *)); + styli = g_key_file_get_string_list (map->tablets, device_key, + KEY_DEVICE_STYLI, &n_styli, + NULL); + + if (styli) { + g_array_append_vals (array, styli, n_styli); + } + + g_array_append_val (array, tool_key); + g_key_file_set_string_list (map->tablets, device_key, KEY_DEVICE_STYLI, + (const gchar **) array->data, array->len); +} + +static void +keyfile_add_stylus (CcTabletToolMap *map, + const gchar *tool_key, + guint64 id) +{ + g_autofree gchar *str = NULL; + + /* Also works for IDs */ + str = get_tool_key (id); + g_key_file_set_string (map->tools, tool_key, KEY_TOOL_ID, str); +} + +void +cc_tablet_tool_map_add_relation (CcTabletToolMap *map, + CcWacomDevice *device, + CcWacomTool *tool) +{ + gboolean tablets_changed = FALSE, tools_changed = FALSE; + gboolean new_tool_without_serial = FALSE; + g_autofree gchar *tool_key = NULL; + g_autofree gchar *device_key = NULL; + guint64 serial, id; + GList *styli; + + g_return_if_fail (CC_IS_TABLET_TOOL_MAP (map)); + g_return_if_fail (CC_IS_WACOM_DEVICE (device)); + g_return_if_fail (CC_IS_WACOM_TOOL (tool)); + + serial = cc_wacom_tool_get_serial (tool); + id = cc_wacom_tool_get_id (tool); + device_key = get_device_key (device); + + if (serial == 0) { + tool_key = g_strdup (GENERIC_STYLUS); + + if (!g_hash_table_contains (map->no_serial_tool_map, device_key)) { + g_hash_table_insert (map->no_serial_tool_map, + g_strdup (device_key), + g_object_ref (tool)); + new_tool_without_serial = TRUE; + } + } else { + tool_key = get_tool_key (serial); + + if (!g_hash_table_contains (map->tool_map, tool_key)) { + keyfile_add_stylus (map, tool_key, id); + tools_changed = TRUE; + g_hash_table_insert (map->tool_map, + g_strdup (tool_key), + g_object_ref (tool)); + } + } + + styli = g_hash_table_lookup (map->tablet_map, device_key); + + if (!g_list_find (styli, tool)) { + styli = g_list_prepend (styli, tool); + g_hash_table_replace (map->tablet_map, + g_strdup (device_key), + g_list_copy (styli)); + + if (serial || new_tool_without_serial) { + tablets_changed = TRUE; + keyfile_add_device_stylus (map, device_key, tool_key); + } + } + + if (tools_changed) { + g_autoptr(GError) error = NULL; + + if (!g_key_file_save_to_file (map->tools, map->tool_path, &error)) { + g_warning ("Error saving tools keyfile: %s", + error->message); + } + } + + if (tablets_changed) { + g_autoptr(GError) error = NULL; + + if (!g_key_file_save_to_file (map->tablets, map->tablet_path, &error)) { + g_warning ("Error saving tablets keyfile: %s", + error->message); + } + } +} diff --git a/panels/wacom/cc-tablet-tool-map.h b/panels/wacom/cc-tablet-tool-map.h new file mode 100644 index 0000000..a65eb2a --- /dev/null +++ b/panels/wacom/cc-tablet-tool-map.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2016 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: Carlos Garnacho <carlosg@gnome.org> + * + */ + +#pragma once + +#include "config.h" +#include <gtk/gtk.h> +#include "cc-wacom-device.h" +#include "cc-wacom-tool.h" + +G_BEGIN_DECLS + +#define CC_TYPE_TABLET_TOOL_MAP (cc_tablet_tool_map_get_type ()) +G_DECLARE_FINAL_TYPE (CcTabletToolMap, cc_tablet_tool_map, CC, TABLET_TOOL_MAP, GObject) + +CcTabletToolMap * cc_tablet_tool_map_new (void); + +GList * cc_tablet_tool_map_list_tools (CcTabletToolMap *map, + CcWacomDevice *device); +CcWacomTool * cc_tablet_tool_map_lookup_tool (CcTabletToolMap *map, + CcWacomDevice *device, + guint64 serial); +void cc_tablet_tool_map_add_relation (CcTabletToolMap *map, + CcWacomDevice *device, + CcWacomTool *tool); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-button-row.c b/panels/wacom/cc-wacom-button-row.c new file mode 100644 index 0000000..b3ae07e --- /dev/null +++ b/panels/wacom/cc-wacom-button-row.c @@ -0,0 +1,280 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Authors: Joaquim Rocha <jrocha@redhat.com> + * + * 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/>. + */ + +#include <config.h> +#include <glib/gi18n-lib.h> + +#include "gsd-wacom-key-shortcut-button.h" +#include "cc-wacom-button-row.h" + +#define ACTION_KEY "action" +#define KEYBINDING_KEY "keybinding" + +#define WACOM_C(x) g_dpgettext2(NULL, "Wacom action-type", x) + +enum { + ACTION_NAME_COLUMN, + ACTION_TYPE_COLUMN, + ACTION_N_COLUMNS +}; + +struct _CcWacomButtonRow { + GtkListBoxRow parent_instance; + guint button; + GSettings *settings; + GtkDirectionType direction; + GtkComboBox *action_combo; + GsdWacomKeyShortcutButton *key_shortcut_button; +}; + +G_DEFINE_TYPE (CcWacomButtonRow, cc_wacom_button_row, GTK_TYPE_LIST_BOX_ROW) + +static GtkWidget * +create_actions_combo (void) +{ + GtkListStore *model; + GtkTreeIter iter; + GtkWidget *combo; + GtkCellRenderer *renderer; + gint i; + + model = gtk_list_store_new (ACTION_N_COLUMNS, G_TYPE_STRING, G_TYPE_INT); + + for (i = 0; i < G_N_ELEMENTS (action_table); i++) + { + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + ACTION_NAME_COLUMN, WACOM_C(action_table[i].action_name), + ACTION_TYPE_COLUMN, action_table[i].action_type, -1); + } + + combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (model)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, + "text", ACTION_NAME_COLUMN, NULL); + + + return combo; +} + +static void +cc_wacom_button_row_update_shortcut (CcWacomButtonRow *row, + GDesktopPadButtonAction action_type) +{ + guint keyval; + GdkModifierType mask; + g_autofree gchar *shortcut = NULL; + + if (action_type != G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING) + return; + + shortcut = g_settings_get_string (row->settings, KEYBINDING_KEY); + + if (shortcut != NULL) + { + gtk_accelerator_parse (shortcut, &keyval, &mask); + + g_object_set (row->key_shortcut_button, + "key-value", keyval, + "key-mods", mask, + NULL); + } +} + +static void +cc_wacom_button_row_update_action (CcWacomButtonRow *row, + GDesktopPadButtonAction action_type) +{ + GtkTreeIter iter; + gboolean iter_valid; + GDesktopPadButtonAction current_action_type, real_action_type; + GtkTreeModel *model; + + model = gtk_combo_box_get_model (row->action_combo); + real_action_type = action_type; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + gtk_tree_model_get (model, &iter, + ACTION_TYPE_COLUMN, ¤t_action_type, + -1); + + if (current_action_type == real_action_type) + { + gtk_combo_box_set_active_iter (row->action_combo, &iter); + break; + } + } +} + +static void +cc_wacom_button_row_update (CcWacomButtonRow *row) +{ + GDesktopPadButtonAction current_action_type; + + current_action_type = g_settings_get_enum (row->settings, ACTION_KEY); + + cc_wacom_button_row_update_shortcut (row, current_action_type); + + cc_wacom_button_row_update_action (row, current_action_type); + + gtk_widget_set_sensitive (GTK_WIDGET (row->key_shortcut_button), + current_action_type == G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING); +} + +static void +change_button_action_type (CcWacomButtonRow *row, + GDesktopPadButtonAction type) +{ + g_settings_set_enum (row->settings, ACTION_KEY, type); + gtk_widget_set_sensitive (GTK_WIDGET (row->key_shortcut_button), + type == G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING); +} + +static void +on_key_shortcut_edited (CcWacomButtonRow *row) +{ + g_autofree gchar *custom_key = NULL; + guint keyval; + GdkModifierType mask; + + change_button_action_type (row, G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING); + + g_object_get (row->key_shortcut_button, + "key-value", &keyval, + "key-mods", &mask, + NULL); + + mask &= ~GDK_LOCK_MASK; + + custom_key = gtk_accelerator_name (keyval, mask); + + g_settings_set_string (row->settings, KEYBINDING_KEY, custom_key); +} + +static void +on_key_shortcut_cleared (CcWacomButtonRow *row) +{ + change_button_action_type (row, G_DESKTOP_PAD_BUTTON_ACTION_NONE); + cc_wacom_button_row_update_action (row, G_DESKTOP_PAD_BUTTON_ACTION_NONE); +} + +static void +on_row_action_combo_box_changed (CcWacomButtonRow *row) +{ + GDesktopPadButtonAction type; + GtkTreeModel *model; + GtkListBox *list_box; + GtkTreeIter iter; + + if (!gtk_combo_box_get_active_iter (row->action_combo, &iter)) + return; + + /* Select the row where we changed the combo box (if not yet selected) */ + list_box = GTK_LIST_BOX (gtk_widget_get_parent (GTK_WIDGET (row))); + if (list_box && gtk_list_box_get_selected_row (list_box) != GTK_LIST_BOX_ROW (row)) + gtk_list_box_select_row (list_box, GTK_LIST_BOX_ROW (row)); + + model = gtk_combo_box_get_model (row->action_combo); + gtk_tree_model_get (model, &iter, ACTION_TYPE_COLUMN, &type, -1); + + change_button_action_type (row, type); +} + +static gboolean +on_key_shortcut_button_press_event (CcWacomButtonRow *row) +{ + GtkListBox *list_box; + + /* Select the row where we pressed the button (if not yet selected) */ + list_box = GTK_LIST_BOX (gtk_widget_get_parent (GTK_WIDGET (row))); + if (list_box && gtk_list_box_get_selected_row (list_box) != GTK_LIST_BOX_ROW (row)) + gtk_list_box_select_row (list_box, GTK_LIST_BOX_ROW (row)); + + return FALSE; +} + +static void +cc_wacom_button_row_class_init (CcWacomButtonRowClass *button_row_class) +{ +} + +static void +cc_wacom_button_row_init (CcWacomButtonRow *button_row) +{ +} + +GtkWidget * +cc_wacom_button_row_new (guint button, + GSettings *settings) +{ + CcWacomButtonRow *row; + GtkWidget *grid, *combo, *label, *shortcut_button; + g_autofree gchar *name = NULL; + + row = CC_WACOM_BUTTON_ROW (g_object_new (CC_WACOM_TYPE_BUTTON_ROW, NULL)); + + row->button = button; + row->settings = g_object_ref (settings); + + grid = gtk_grid_new (); + gtk_widget_show (grid); + gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE); + gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE); + + name = g_strdup_printf (_("Button %d"), button); + label = gtk_label_new (name); + g_object_set (label, "halign", GTK_ALIGN_START, NULL); + gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + gtk_widget_show (label); + + combo = create_actions_combo (); + gtk_grid_attach (GTK_GRID (grid), combo, 1, 0, 1, 1); + gtk_widget_show (combo); + row->action_combo = GTK_COMBO_BOX (combo); + g_signal_connect_object (combo, "changed", + G_CALLBACK (on_row_action_combo_box_changed), row, G_CONNECT_SWAPPED); + + shortcut_button = gsd_wacom_key_shortcut_button_new (); + g_object_set (shortcut_button, "mode", GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL, NULL); + gtk_grid_attach (GTK_GRID (grid), shortcut_button, 2, 0, 1, 1); + gtk_widget_show (shortcut_button); + row->key_shortcut_button = GSD_WACOM_KEY_SHORTCUT_BUTTON (shortcut_button); + g_signal_connect_object (shortcut_button, "key-shortcut-cleared", + G_CALLBACK (on_key_shortcut_cleared), + row, + G_CONNECT_SWAPPED); + g_signal_connect_object (shortcut_button, "key-shortcut-edited", + G_CALLBACK (on_key_shortcut_edited), + row, + G_CONNECT_SWAPPED); + g_signal_connect_object (shortcut_button, "button-press-event", + G_CALLBACK (on_key_shortcut_button_press_event), + row, + G_CONNECT_SWAPPED); + + gtk_container_add (GTK_CONTAINER (row), grid); + + cc_wacom_button_row_update (CC_WACOM_BUTTON_ROW (row)); + + return GTK_WIDGET (row); +} diff --git a/panels/wacom/cc-wacom-button-row.h b/panels/wacom/cc-wacom-button-row.h new file mode 100644 index 0000000..7a30d11 --- /dev/null +++ b/panels/wacom/cc-wacom-button-row.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Authors: Joaquim Rocha <jrocha@redhat.com> + * + * 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/>. + */ + +#pragma once + +#include <gtk/gtk.h> +#include <gdesktop-enums.h> + +G_BEGIN_DECLS + +#define CC_WACOM_TYPE_BUTTON_ROW (cc_wacom_button_row_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomButtonRow, cc_wacom_button_row, CC, WACOM_BUTTON_ROW, GtkListBoxRow) + +static struct { + GDesktopPadButtonAction action_type; + const gchar *action_name; +} action_table[] = { + { G_DESKTOP_PAD_BUTTON_ACTION_NONE, NC_("Wacom action-type", "Application defined") }, + { G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING, NC_("Wacom action-type", "Send Keystroke") }, + { G_DESKTOP_PAD_BUTTON_ACTION_SWITCH_MONITOR, NC_("Wacom action-type", "Switch Monitor") }, + { G_DESKTOP_PAD_BUTTON_ACTION_HELP, NC_("Wacom action-type", "Show On-Screen Help") } +}; + +GtkWidget * cc_wacom_button_row_new (guint button, + GSettings *settings); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-device.c b/panels/wacom/cc-wacom-device.c new file mode 100644 index 0000000..4a3f980 --- /dev/null +++ b/panels/wacom/cc-wacom-device.c @@ -0,0 +1,406 @@ +/* + * Copyright © 2016 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: Carlos Garnacho <carlosg@gnome.org> + * + */ + +#include "config.h" + +#include <string.h> +#include "cc-wacom-device.h" + +enum { + PROP_0, + PROP_DEVICE, + N_PROPS +}; + +GParamSpec *props[N_PROPS] = { 0 }; + +typedef struct _CcWacomDevice CcWacomDevice; + +struct _CcWacomDevice { + GObject parent_instance; + + GsdDevice *device; + WacomDevice *wdevice; +}; + +static void cc_wacom_device_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (CcWacomDevice, cc_wacom_device, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + cc_wacom_device_initable_iface_init)) + +WacomDeviceDatabase * +cc_wacom_device_database_get (void) +{ + static WacomDeviceDatabase *db = NULL; + + if (g_once_init_enter (&db)) { + gpointer p = libwacom_database_new (); + g_once_init_leave (&db, p); + } + + return db; +} + +static void +cc_wacom_device_init (CcWacomDevice *device) +{ +} + +static void +cc_wacom_device_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWacomDevice *device = CC_WACOM_DEVICE (object); + + switch (prop_id) { + case PROP_DEVICE: + device->device = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_wacom_device_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcWacomDevice *device = CC_WACOM_DEVICE (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_object (value, device->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_wacom_device_finalize (GObject *object) +{ + CcWacomDevice *device = CC_WACOM_DEVICE (object); + + g_clear_pointer (&device->wdevice, libwacom_destroy); + + G_OBJECT_CLASS (cc_wacom_device_parent_class)->finalize (object); +} + +static void +cc_wacom_device_class_init (CcWacomDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = cc_wacom_device_set_property; + object_class->get_property = cc_wacom_device_get_property; + object_class->finalize = cc_wacom_device_finalize; + + props[PROP_DEVICE] = + g_param_spec_object ("device", + "device", + "device", + GSD_TYPE_DEVICE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static gboolean +cc_wacom_device_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + CcWacomDevice *device = CC_WACOM_DEVICE (initable); + WacomDeviceDatabase *wacom_db; + const gchar *node_path; + + wacom_db = cc_wacom_device_database_get (); + node_path = gsd_device_get_device_file (device->device); + device->wdevice = libwacom_new_from_path (wacom_db, node_path, FALSE, NULL); + + if (!device->wdevice) { + g_set_error (error, 0, 0, "Tablet description not found"); + return FALSE; + } + + return TRUE; +} + +static void +cc_wacom_device_initable_iface_init (GInitableIface *iface) +{ + iface->init = cc_wacom_device_initable_init; +} + +CcWacomDevice * +cc_wacom_device_new (GsdDevice *device) +{ + return g_initable_new (CC_TYPE_WACOM_DEVICE, + NULL, NULL, + "device", device, + NULL); +} + +CcWacomDevice * +cc_wacom_device_new_fake (const gchar *name) +{ + CcWacomDevice *device; + WacomDevice *wacom_device; + + device = g_object_new (CC_TYPE_WACOM_DEVICE, + NULL); + + wacom_device = libwacom_new_from_name (cc_wacom_device_database_get(), + name, NULL); + if (wacom_device == NULL) + return NULL; + + device->wdevice = wacom_device; + + return device; +} + +const gchar * +cc_wacom_device_get_name (CcWacomDevice *device) +{ + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + return libwacom_get_name (device->wdevice); +} + +const gchar * +cc_wacom_device_get_icon_name (CcWacomDevice *device) +{ + WacomIntegrationFlags integration_flags; + + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + integration_flags = libwacom_get_integration_flags (device->wdevice); + + if (integration_flags & WACOM_DEVICE_INTEGRATED_SYSTEM) { + return "wacom-tablet-pc"; + } else if (integration_flags & WACOM_DEVICE_INTEGRATED_DISPLAY) { + return "wacom-tablet-cintiq"; + } else { + return "wacom-tablet"; + } +} + +gboolean +cc_wacom_device_is_reversible (CcWacomDevice *device) +{ + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), FALSE); + + return libwacom_is_reversible (device->wdevice); +} + +WacomIntegrationFlags +cc_wacom_device_get_integration_flags (CcWacomDevice *device) +{ + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), 0); + + return libwacom_get_integration_flags (device->wdevice); +} + +GsdDevice * +cc_wacom_device_get_device (CcWacomDevice *device) +{ + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + return device->device; +} + +GSettings * +cc_wacom_device_get_settings (CcWacomDevice *device) +{ + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + return gsd_device_get_settings (device->device); +} + +const gint * +cc_wacom_device_get_supported_tools (CcWacomDevice *device, + gint *n_tools) +{ + *n_tools = 0; + + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + return libwacom_get_supported_styli (device->wdevice, n_tools); +} + +static GnomeRROutput * +find_output_by_edid (GnomeRRScreen *rr_screen, + const gchar *vendor, + const gchar *product, + const gchar *serial) +{ + GnomeRROutput **rr_outputs; + GnomeRROutput *retval = NULL; + guint i; + + rr_outputs = gnome_rr_screen_list_outputs (rr_screen); + + for (i = 0; rr_outputs[i] != NULL; i++) { + g_autofree gchar *o_vendor = NULL; + g_autofree gchar *o_product = NULL; + g_autofree gchar *o_serial = NULL; + gboolean match; + + gnome_rr_output_get_ids_from_edid (rr_outputs[i], + &o_vendor, + &o_product, + &o_serial); + + g_debug ("Checking for match between '%s','%s','%s' and '%s','%s','%s'", \ + vendor, product, serial, o_vendor, o_product, o_serial); + + match = (g_strcmp0 (vendor, o_vendor) == 0) && \ + (g_strcmp0 (product, o_product) == 0) && \ + (g_strcmp0 (serial, o_serial) == 0); + + if (match) { + retval = rr_outputs[i]; + break; + } + } + + if (retval == NULL) + g_debug ("Did not find a matching output for EDID '%s,%s,%s'", + vendor, product, serial); + + return retval; +} + +static GnomeRROutput * +find_output (GnomeRRScreen *rr_screen, + CcWacomDevice *device) +{ + g_autoptr(GSettings) settings = NULL; + g_autoptr(GVariant) variant = NULL; + g_autofree const gchar **edid = NULL; + gsize n; + + settings = cc_wacom_device_get_settings (device); + variant = g_settings_get_value (settings, "output"); + edid = g_variant_get_strv (variant, &n); + + if (n != 3) { + g_critical ("Expected 'output' key to store %d values; got %"G_GSIZE_FORMAT".", 3, n); + return NULL; + } + + if (strlen (edid[0]) == 0 || strlen (edid[1]) == 0 || strlen (edid[2]) == 0) + return NULL; + + return find_output_by_edid (rr_screen, edid[0], edid[1], edid[2]); +} + +GnomeRROutput * +cc_wacom_device_get_output (CcWacomDevice *device, + GnomeRRScreen *rr_screen) +{ + GnomeRROutput *rr_output; + GnomeRRCrtc *crtc; + + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + g_return_val_if_fail (GNOME_IS_RR_SCREEN (rr_screen), NULL); + + rr_output = find_output (rr_screen, device); + if (rr_output == NULL) { + return NULL; + } + + crtc = gnome_rr_output_get_crtc (rr_output); + + if (!crtc || gnome_rr_crtc_get_current_mode (crtc) == NULL) { + g_debug ("Output is not active."); + return NULL; + } + + return rr_output; +} + +void +cc_wacom_device_set_output (CcWacomDevice *device, + GnomeRROutput *output) +{ + g_autoptr(GSettings) settings = NULL; + g_autofree gchar *vendor = NULL; + g_autofree gchar *product = NULL; + g_autofree gchar *serial = NULL; + const gchar *values[] = { "", "", "", NULL }; + + g_return_if_fail (CC_IS_WACOM_DEVICE (device)); + + vendor = product = serial = NULL; + settings = cc_wacom_device_get_settings (device); + + if (output != NULL) { + gnome_rr_output_get_ids_from_edid (output, + &vendor, + &product, + &serial); + values[0] = vendor; + values[1] = product; + values[2] = serial; + } + + g_settings_set_strv (settings, "output", values); +} + +guint +cc_wacom_device_get_num_buttons (CcWacomDevice *device) +{ + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), 0); + + return libwacom_get_num_buttons (device->wdevice); +} + +GSettings * +cc_wacom_device_get_button_settings (CcWacomDevice *device, + guint button) +{ + g_autoptr(GSettings) tablet_settings = NULL; + GSettings *settings; + g_autofree gchar *path = NULL; + g_autofree gchar *button_path = NULL; + + g_return_val_if_fail (CC_IS_WACOM_DEVICE (device), NULL); + + if (button > cc_wacom_device_get_num_buttons (device)) + return NULL; + + tablet_settings = cc_wacom_device_get_settings (device); + g_object_get (tablet_settings, "path", &path, NULL); + + button_path = g_strdup_printf ("%sbutton%c/", path, 'A' + button); + settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet.pad-button", + button_path); + + return settings; +} diff --git a/panels/wacom/cc-wacom-device.h b/panels/wacom/cc-wacom-device.h new file mode 100644 index 0000000..fae504a --- /dev/null +++ b/panels/wacom/cc-wacom-device.h @@ -0,0 +1,63 @@ +/* + * Copyright © 2016 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: Carlos Garnacho <carlosg@gnome.org> + * + */ + +#pragma once + +#include "config.h" +#include <glib-object.h> +#include <libwacom/libwacom.h> + +#include "gsd-device-manager.h" + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-rr.h> + +#define CC_TYPE_WACOM_DEVICE (cc_wacom_device_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomDevice, cc_wacom_device, CC, WACOM_DEVICE, GObject) + +WacomDeviceDatabase * + cc_wacom_device_database_get (void); + +CcWacomDevice * cc_wacom_device_new (GsdDevice *device); +CcWacomDevice * cc_wacom_device_new_fake (const gchar *name); + +const gchar * cc_wacom_device_get_name (CcWacomDevice *device); +const gchar * cc_wacom_device_get_icon_name (CcWacomDevice *device); + +gboolean cc_wacom_device_is_reversible (CcWacomDevice *device); + +WacomIntegrationFlags + cc_wacom_device_get_integration_flags (CcWacomDevice *device); + +GsdDevice * cc_wacom_device_get_device (CcWacomDevice *device); +GSettings * cc_wacom_device_get_settings (CcWacomDevice *device); + +const gint * cc_wacom_device_get_supported_tools (CcWacomDevice *device, + gint *n_tools); + +GnomeRROutput * cc_wacom_device_get_output (CcWacomDevice *device, + GnomeRRScreen *screen); +void cc_wacom_device_set_output (CcWacomDevice *wacom_device, + GnomeRROutput *monitor); + +guint cc_wacom_device_get_num_buttons (CcWacomDevice *wacom_device); + +GSettings * cc_wacom_device_get_button_settings (CcWacomDevice *device, + guint button); diff --git a/panels/wacom/cc-wacom-mapping-panel.c b/panels/wacom/cc-wacom-mapping-panel.c new file mode 100644 index 0000000..2b8b012 --- /dev/null +++ b/panels/wacom/cc-wacom-mapping-panel.c @@ -0,0 +1,338 @@ +/* + * Copyright © 2012 Wacom. + * + * 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: Jason Gerecke <killertofu@gmail.com> + * + */ + +#include <config.h> + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include <libgnome-desktop/gnome-rr.h> +#include <libgnome-desktop/gnome-rr-config.h> + +#include <string.h> + +#include "cc-wacom-device.h" +#include "cc-wacom-mapping-panel.h" + +struct _CcWacomMappingPanel +{ + GtkBox parent_instance; + + CcWacomDevice *device; + GtkWidget *label; + GtkWidget *combobox; + GtkWidget *checkbutton; + GtkWidget *aspectlabel; + GtkWidget *aspectswitch; + + GnomeRRScreen *rr_screen; +}; + +G_DEFINE_TYPE (CcWacomMappingPanel, cc_wacom_mapping_panel, GTK_TYPE_BOX) + +enum { + MONITOR_NAME_COLUMN, + MONITOR_PTR_COLUMN, + MONITOR_NUM_COLUMNS +}; + +static void combobox_changed_cb (CcWacomMappingPanel *self); +static void checkbutton_toggled_cb (CcWacomMappingPanel *self); +static void aspectswitch_toggled_cb (CcWacomMappingPanel *self); + +static void +set_combobox_sensitive (CcWacomMappingPanel *self, + gboolean sensitive) +{ + gtk_widget_set_sensitive (GTK_WIDGET(self->combobox), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET(self->label), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET(self->aspectswitch), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET(self->aspectlabel), sensitive); +} + +/* Update the display of available monitors based on the latest + * information from RandR. At the moment the chooser is just a + * a combobox crudely listing available outputs. The UI mockup + * has something more akin to the Display panel, with the ability + * to do rubber-band selection of multiple outputs (note: the + * g-s-d backend can only handle a single output at the moment) + */ +static void +update_monitor_chooser (CcWacomMappingPanel *self) +{ + g_autoptr(GtkListStore) store = NULL; + GnomeRROutput **outputs; + GSettings *settings; + GnomeRROutput *cur_output; + guint i; + + store = gtk_list_store_new (MONITOR_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER); + gtk_combo_box_set_model (GTK_COMBO_BOX(self->combobox), GTK_TREE_MODEL(store)); + + if (self->device == NULL) { + set_combobox_sensitive (self, FALSE); + return; + } + + settings = cc_wacom_device_get_settings (self->device); + cur_output = cc_wacom_device_get_output (self->device, + self->rr_screen); + + g_signal_handlers_block_by_func (G_OBJECT (self->checkbutton), checkbutton_toggled_cb, self); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(self->checkbutton), cur_output != NULL); + g_signal_handlers_unblock_by_func (G_OBJECT (self->checkbutton), checkbutton_toggled_cb, self); + + g_signal_handlers_block_by_func (G_OBJECT (self->aspectswitch), aspectswitch_toggled_cb, self); + gtk_switch_set_active (GTK_SWITCH(self->aspectswitch), g_settings_get_boolean (settings, "keep-aspect")); + g_signal_handlers_unblock_by_func (G_OBJECT (self->aspectswitch), aspectswitch_toggled_cb, self); + + if (!self->rr_screen) { + set_combobox_sensitive (self, FALSE); + return; + } + + outputs = gnome_rr_screen_list_outputs (self->rr_screen); + + for (i = 0; outputs[i] != NULL; i++) { + GnomeRROutput *output = outputs[i]; + GnomeRRCrtc *crtc = gnome_rr_output_get_crtc (output); + + /* Output is turned on? */ + if (crtc && gnome_rr_crtc_get_current_mode (crtc) != NULL) { + GtkTreeIter iter; + const gchar *name, *disp_name; + g_autofree gchar *text = NULL; + + name = gnome_rr_output_get_name (output); + disp_name = gnome_rr_output_get_display_name (output); + text = g_strdup_printf ("%s (%s)", name, disp_name); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, MONITOR_NAME_COLUMN, text, MONITOR_PTR_COLUMN, output, -1); + + if (i == 0 || output == cur_output) { + g_signal_handlers_block_by_func (G_OBJECT (self->combobox), combobox_changed_cb, self); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX(self->combobox), &iter); + g_signal_handlers_unblock_by_func (G_OBJECT (self->combobox), combobox_changed_cb, self); + } + } + } + + set_combobox_sensitive (self, cur_output != NULL); +} + +static void +update_ui (CcWacomMappingPanel *self) +{ + if (self->device == NULL) { + gtk_widget_set_sensitive (GTK_WIDGET(self->checkbutton), FALSE); + gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON(self->checkbutton), TRUE); + } else { + gboolean is_screen_tablet; + + is_screen_tablet = + cc_wacom_device_get_integration_flags (self->device) & + WACOM_DEVICE_INTEGRATED_DISPLAY; + + gtk_widget_set_sensitive (GTK_WIDGET(self->checkbutton), !is_screen_tablet); + gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON(self->checkbutton), FALSE); + } + + update_monitor_chooser (self); +} + +static void +update_mapping (CcWacomMappingPanel *self) +{ + GnomeRROutput *output = NULL; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->checkbutton))) { + GtkTreeIter iter; + GtkTreeModel *model; + char *name; + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (self->combobox)); + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->combobox), &iter)) { + g_warning ("Map to single monitor checked, but no screen selected."); + return; + } + + gtk_tree_model_get (model, &iter, MONITOR_NAME_COLUMN, &name, MONITOR_PTR_COLUMN, &output, -1); + } + + cc_wacom_device_set_output (self->device, output); +} + +void +cc_wacom_mapping_panel_set_device (CcWacomMappingPanel *self, + CcWacomDevice *device) +{ + self->device = device; + update_ui (self); +} + +static void +checkbutton_toggled_cb (CcWacomMappingPanel *self) +{ + gboolean active; + + active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->checkbutton)); + set_combobox_sensitive (self, active); + if (!active) + gtk_switch_set_active (GTK_SWITCH(self->aspectswitch), FALSE); + update_mapping (self); +} + +static void +aspectswitch_toggled_cb (CcWacomMappingPanel *self) +{ + GSettings *settings; + + settings = cc_wacom_device_get_settings (self->device); + g_settings_set_boolean (settings, + "keep-aspect", + gtk_switch_get_active (GTK_SWITCH (self->aspectswitch))); +} + +static void +combobox_changed_cb (CcWacomMappingPanel *self) +{ + update_mapping (self); +} + +static void +cc_wacom_mapping_panel_init (CcWacomMappingPanel *self) +{ + GtkWidget *vbox, *grid; + GtkCellRenderer *renderer; + g_autoptr(GError) error = NULL; + + self->rr_screen = gnome_rr_screen_new (gdk_screen_get_default (), &error); + + if (error) + g_warning ("Could not get RR screen: %s", error->message); + + g_signal_connect_object (self->rr_screen, "changed", + G_CALLBACK (update_monitor_chooser), self, G_CONNECT_SWAPPED); + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_container_add (GTK_CONTAINER (self), vbox); + gtk_container_set_border_width (GTK_CONTAINER (self), 12); + gtk_widget_set_vexpand (GTK_WIDGET (vbox), TRUE); + gtk_widget_set_hexpand (GTK_WIDGET (vbox), TRUE); + + /* Output Combobox */ + grid = gtk_grid_new(); + gtk_grid_set_row_spacing (GTK_GRID (grid), 10); + gtk_grid_set_column_spacing (GTK_GRID (grid), 10); + self->label = gtk_label_new (_("Output:")); + gtk_widget_set_halign (self->label, GTK_ALIGN_END); + self->combobox = gtk_combo_box_new (); + g_signal_connect_object (self->combobox, "changed", + G_CALLBACK (combobox_changed_cb), self, G_CONNECT_SWAPPED); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(self->combobox), renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(self->combobox), renderer, "text", 0); + gtk_grid_attach (GTK_GRID(grid), GTK_WIDGET(self->label), 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID(grid), GTK_WIDGET(self->combobox), 1, 0, 1, 1); + + /* Keep ratio switch */ + self->aspectlabel = gtk_label_new (_("Keep aspect ratio (letterbox):")); + gtk_widget_set_halign (self->aspectlabel, GTK_ALIGN_END); + self->aspectswitch = gtk_switch_new (); + gtk_widget_set_halign (self->aspectswitch, GTK_ALIGN_START); + gtk_switch_set_active (GTK_SWITCH (self->aspectswitch), FALSE); + g_signal_connect_object (self->aspectswitch, "notify::active", + G_CALLBACK (aspectswitch_toggled_cb), self, G_CONNECT_SWAPPED); + gtk_grid_attach (GTK_GRID(grid), GTK_WIDGET(self->aspectlabel), 0, 1, 1, 1); + gtk_grid_attach (GTK_GRID(grid), GTK_WIDGET(self->aspectswitch), 1, 1, 1, 1); + + /* Whole-desktop checkbox */ + self->checkbutton = gtk_check_button_new_with_label (_("Map to single monitor")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->checkbutton), FALSE); + g_signal_connect_object (self->checkbutton, "toggled", + G_CALLBACK (checkbutton_toggled_cb), self, G_CONNECT_SWAPPED); + + gtk_box_pack_start (GTK_BOX(vbox), GTK_WIDGET(self->checkbutton), + FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(vbox), GTK_WIDGET(grid), + FALSE, FALSE, 8); + + /* Update display */ + cc_wacom_mapping_panel_set_device (self, NULL); + gtk_widget_show_all(GTK_WIDGET(self)); +} + +GtkWidget * +cc_wacom_mapping_panel_new (void) +{ + CcWacomMappingPanel *panel; + + panel = CC_WACOM_MAPPING_PANEL(g_object_new (CC_TYPE_WACOM_MAPPING_PANEL, NULL)); + panel->device = NULL; + + return GTK_WIDGET(panel); +} + +static void +cc_wacom_mapping_panel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_mapping_panel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_mapping_panel_dispose (GObject *object) +{ + CcWacomMappingPanel *self = CC_WACOM_MAPPING_PANEL (object); + + g_clear_object (&self->rr_screen); + + G_OBJECT_CLASS (cc_wacom_mapping_panel_parent_class)->dispose (object); +} + +static void +cc_wacom_mapping_panel_class_init (CcWacomMappingPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_wacom_mapping_panel_get_property; + object_class->set_property = cc_wacom_mapping_panel_set_property; + object_class->dispose = cc_wacom_mapping_panel_dispose; +} diff --git a/panels/wacom/cc-wacom-mapping-panel.h b/panels/wacom/cc-wacom-mapping-panel.h new file mode 100644 index 0000000..ac9501c --- /dev/null +++ b/panels/wacom/cc-wacom-mapping-panel.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2012 Wacom. + * + * 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: Jason Gerecke <killertofu@gmail.com> + * + */ + +#pragma once + +#include <gtk/gtk.h> +#include "cc-wacom-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WACOM_MAPPING_PANEL (cc_wacom_mapping_panel_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomMappingPanel, cc_wacom_mapping_panel, CC, WACOM_MAPPING_PANEL, GtkBox) + +GtkWidget * cc_wacom_mapping_panel_new (void); + +void cc_wacom_mapping_panel_set_device (CcWacomMappingPanel *self, + CcWacomDevice *device); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-nav-button.c b/panels/wacom/cc-wacom-nav-button.c new file mode 100644 index 0000000..3239f5f --- /dev/null +++ b/panels/wacom/cc-wacom-nav-button.c @@ -0,0 +1,220 @@ +/* + * Copyright © 2011 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: Bastien Nocera <hadess@hadess.net> + * + */ + +#include <config.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "cc-wacom-nav-button.h" + +struct _CcWacomNavButton +{ + GtkBox parent_instance; + + GtkNotebook *notebook; + GtkWidget *label; + GtkWidget *prev; + GtkWidget *next; + guint page_added_id; + guint page_removed_id; + guint page_switched_id; + gboolean ignore_first_page; +}; + +G_DEFINE_TYPE (CcWacomNavButton, cc_wacom_nav_button, GTK_TYPE_BOX) + +enum { + PROP_0, + PROP_NOTEBOOK, + PROP_IGNORE_FIRST +}; + +static void +cc_wacom_nav_button_update (CcWacomNavButton *nav) +{ + int num_pages; + int current_page; + char *text; + + if (nav->notebook == NULL) { + gtk_widget_hide (GTK_WIDGET (nav)); + return; + } + + num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nav->notebook)); + if (num_pages == 0) + return; + if (nav->ignore_first_page && num_pages == 1) + return; + + if (nav->ignore_first_page) + num_pages--; + + g_assert (num_pages >= 1); + + gtk_revealer_set_reveal_child (GTK_REVEALER (gtk_widget_get_parent (GTK_WIDGET (nav))), + num_pages > 1); + + current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (nav->notebook)); + if (current_page < 0) + return; + if (nav->ignore_first_page) + current_page--; + gtk_widget_set_sensitive (nav->prev, current_page == 0 ? FALSE : TRUE); + gtk_widget_set_sensitive (nav->next, current_page + 1 == num_pages ? FALSE : TRUE); + + text = g_strdup_printf (_("%d of %d"), + current_page + 1, + num_pages); + gtk_label_set_text (GTK_LABEL (nav->label), text); +} + +static void +pages_changed (CcWacomNavButton *nav) +{ + cc_wacom_nav_button_update (nav); +} + +static void +page_switched (CcWacomNavButton *nav) +{ + cc_wacom_nav_button_update (nav); +} + +static void +next_clicked (CcWacomNavButton *nav) +{ + int current_page; + + current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (nav->notebook)); + current_page++; + gtk_notebook_set_current_page (GTK_NOTEBOOK (nav->notebook), current_page); +} + +static void +prev_clicked (CcWacomNavButton *nav) +{ + int current_page; + + current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (nav->notebook)); + current_page--; + gtk_notebook_set_current_page (GTK_NOTEBOOK (nav->notebook), current_page--); +} + +static void +cc_wacom_nav_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWacomNavButton *nav = CC_WACOM_NAV_BUTTON (object); + + switch (property_id) { + case PROP_NOTEBOOK: + if (nav->notebook) { + g_signal_handler_disconnect (nav->notebook, nav->page_added_id); + g_signal_handler_disconnect (nav->notebook, nav->page_removed_id); + g_signal_handler_disconnect (nav->notebook, nav->page_switched_id); + } + g_clear_object (&nav->notebook); + nav->notebook = g_value_dup_object (value); + nav->page_added_id = g_signal_connect_object (nav->notebook, "page-added", + G_CALLBACK (pages_changed), nav, G_CONNECT_SWAPPED); + nav->page_removed_id = g_signal_connect_object (nav->notebook, "page-removed", + G_CALLBACK (pages_changed), nav, G_CONNECT_SWAPPED); + nav->page_switched_id = g_signal_connect_object (nav->notebook, "notify::page", + G_CALLBACK (page_switched), nav, G_CONNECT_SWAPPED); + cc_wacom_nav_button_update (nav); + break; + case PROP_IGNORE_FIRST: + nav->ignore_first_page = g_value_get_boolean (value); + cc_wacom_nav_button_update (nav); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_nav_button_class_init (CcWacomNavButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = cc_wacom_nav_button_set_property; + + g_object_class_install_property (object_class, PROP_NOTEBOOK, + g_param_spec_object ("notebook", "notebook", "notebook", + GTK_TYPE_NOTEBOOK, + G_PARAM_WRITABLE)); + g_object_class_install_property (object_class, PROP_IGNORE_FIRST, + g_param_spec_boolean ("ignore-first", "ignore-first", "ignore-first", + FALSE, + G_PARAM_WRITABLE)); +} + +static void +cc_wacom_nav_button_init (CcWacomNavButton *self) +{ + GtkStyleContext *context; + GtkWidget *image, *box; + + /* Label */ + self->label = gtk_label_new (NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (self->label), "dim-label"); + gtk_box_pack_start (GTK_BOX (self), self->label, + FALSE, FALSE, 8); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + context = gtk_widget_get_style_context (GTK_WIDGET (box)); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED); + gtk_box_pack_start (GTK_BOX (self), box, + FALSE, FALSE, 0); + + /* Prev button */ + self->prev = gtk_button_new (); + image = gtk_image_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (self->prev), image); + g_signal_connect_object (G_OBJECT (self->prev), "clicked", + G_CALLBACK (prev_clicked), self, G_CONNECT_SWAPPED); + gtk_widget_set_valign (self->prev, GTK_ALIGN_CENTER); + + /* Next button */ + self->next = gtk_button_new (); + image = gtk_image_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_MENU); + gtk_container_add (GTK_CONTAINER (self->next), image); + g_signal_connect_object (G_OBJECT (self->next), "clicked", + G_CALLBACK (next_clicked), self, G_CONNECT_SWAPPED); + gtk_widget_set_valign (self->next, GTK_ALIGN_CENTER); + + gtk_box_pack_start (GTK_BOX (box), self->prev, + FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), self->next, + FALSE, FALSE, 0); + + gtk_widget_show (self->label); + gtk_widget_show_all (box); +} + +GtkWidget * +cc_wacom_nav_button_new (void) +{ + return GTK_WIDGET (g_object_new (CC_TYPE_WACOM_NAV_BUTTON, NULL)); +} diff --git a/panels/wacom/cc-wacom-nav-button.h b/panels/wacom/cc-wacom-nav-button.h new file mode 100644 index 0000000..6b60105 --- /dev/null +++ b/panels/wacom/cc-wacom-nav-button.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2011 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: Bastien Nocera <hadess@hadess.net> + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_WACOM_NAV_BUTTON (cc_wacom_nav_button_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomNavButton, cc_wacom_nav_button, CC, WACOM_NAV_BUTTON, GtkBox) + +GtkWidget * cc_wacom_nav_button_new (void); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-page.c b/panels/wacom/cc-wacom-page.c new file mode 100644 index 0000000..b85bce7 --- /dev/null +++ b/panels/wacom/cc-wacom-page.c @@ -0,0 +1,1030 @@ +/* + * Copyright © 2011 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: Peter Hutterer <peter.hutterer@redhat.com> + * Bastien Nocera <hadess@hadess.net> + * + */ + +#include <config.h> + +#ifdef FAKE_AREA +#include <gdk/gdk.h> +#endif /* FAKE_AREA */ + +#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> +#include <gdesktop-enums.h> +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/gdkwayland.h> +#endif + +#include "cc-wacom-device.h" +#include "cc-wacom-button-row.h" +#include "cc-wacom-page.h" +#include "cc-wacom-nav-button.h" +#include "cc-wacom-mapping-panel.h" +#include "cc-wacom-stylus-page.h" +#include "gsd-enums.h" +#include "calibrator-gui.h" +#include "gsd-input-helper.h" + +#include <string.h> + +#define WID(x) (GtkWidget *) gtk_builder_get_object (page->builder, x) +#define CWID(x) (GtkContainer *) gtk_builder_get_object (page->builder, x) +#define MWID(x) (GtkWidget *) gtk_builder_get_object (page->mapping_builder, x) + +#define THRESHOLD_MISCLICK 15 +#define THRESHOLD_DOUBLECLICK 7 + +enum { + MAPPING_DESCRIPTION_COLUMN, + MAPPING_TYPE_COLUMN, + MAPPING_BUTTON_COLUMN, + MAPPING_BUTTON_DIRECTION, + MAPPING_N_COLUMNS +}; + +struct _CcWacomPage +{ + GtkBox parent_instance; + + CcWacomPanel *panel; + CcWacomDevice *stylus; + GList *pads; + GtkBuilder *builder; + GtkWidget *nav; + GtkWidget *notebook; + CalibArea *area; + GSettings *wacom_settings; + + GtkSizeGroup *header_group; + + /* Button mapping */ + GtkBuilder *mapping_builder; + GtkWidget *button_map; + GtkListStore *action_store; + + /* Display mapping */ + GtkWidget *mapping; + GtkWidget *dialog; + + GCancellable *cancellable; + + /* To reach other grouped devices */ + GsdDeviceManager *manager; +}; + +G_DEFINE_TYPE (CcWacomPage, cc_wacom_page, GTK_TYPE_BOX) + +/* Button combo box storage columns */ +enum { + BUTTONNUMBER_COLUMN, + BUTTONNAME_COLUMN, + N_BUTTONCOLUMNS +}; + +/* Tablet mode combo box storage columns */ +enum { + MODENUMBER_COLUMN, + MODELABEL_COLUMN, + N_MODECOLUMNS +}; + +/* Tablet mode options - keep in sync with .ui */ +enum { + MODE_ABSOLUTE, /* stylus + eraser absolute */ + MODE_RELATIVE, /* stylus + eraser relative */ +}; + +/* Different types of layout for the tablet config */ +enum { + LAYOUT_NORMAL, /* tracking mode, button mapping */ + LAYOUT_REVERSIBLE, /* tracking mode, button mapping, left-hand orientation */ + LAYOUT_SCREEN /* button mapping, calibration, display resolution */ +}; + +static void +set_page_layout (CcWacomPage *page, + int layout); + +static int +get_layout_type (CcWacomDevice *device) +{ + int layout; + + if (cc_wacom_device_get_integration_flags (device) & + (WACOM_DEVICE_INTEGRATED_DISPLAY | WACOM_DEVICE_INTEGRATED_SYSTEM)) + layout = LAYOUT_SCREEN; + else if (cc_wacom_device_is_reversible (device)) + layout = LAYOUT_REVERSIBLE; + else + layout = LAYOUT_NORMAL; + + return layout; +} + +static void +set_calibration (CcWacomDevice *device, + const gint display_width, + const gint display_height, + gdouble *cal, + gsize ncal, + GSettings *settings) +{ + GVariant *current; /* current calibration */ + GVariant *array; /* new calibration */ + g_autofree GVariant **tmp = NULL; + gsize nvalues; + gint i; + + current = g_settings_get_value (settings, "area"); + g_variant_get_fixed_array (current, &nvalues, sizeof (gdouble)); + if ((ncal != 4) || (nvalues != 4)) { + g_warning("Unable to set device calibration property. Got %"G_GSIZE_FORMAT" items to put in %"G_GSIZE_FORMAT" slots; expected %d items.\n", ncal, nvalues, 4); + return; + } + + tmp = g_malloc (nvalues * sizeof (GVariant*)); + for (i = 0; i < ncal; i++) + tmp[i] = g_variant_new_double (cal[i]); + + array = g_variant_new_array (G_VARIANT_TYPE_DOUBLE, tmp, nvalues); + g_settings_set_value (settings, "area", array); + + g_debug ("Setting area to %f, %f, %f, %f (left/right/top/bottom) (last used resolution: %d x %d)", + cal[0], cal[1], cal[2], cal[3], + display_width, display_height); +} + +static void +finish_calibration (CalibArea *area, + gpointer user_data) +{ + CcWacomPage *page = (CcWacomPage *) user_data; + XYinfo axis; + gdouble cal[4]; + gint display_width, display_height; + + if (calib_area_finish (area)) { + calib_area_get_padding (area, &axis); + cal[0] = axis.x_min; + cal[1] = axis.x_max; + cal[2] = axis.y_min; + cal[3] = axis.y_max; + + calib_area_get_display_size (area, &display_width, &display_height); + + set_calibration (page->stylus, + display_width, + display_height, + cal, 4, page->wacom_settings); + } else { + /* Reset the old values */ + GVariant *old_calibration; + + old_calibration = g_object_get_data (G_OBJECT (page), "old-calibration"); + g_settings_set_value (page->wacom_settings, "area", old_calibration); + g_object_set_data (G_OBJECT (page), "old-calibration", NULL); + } + + calib_area_free (area); + page->area = NULL; + gtk_widget_set_sensitive (WID ("button-calibrate"), TRUE); +} + +static GdkDevice * +cc_wacom_page_get_gdk_device (CcWacomPage *page) +{ + GsdDevice *gsd_device; + GdkDevice *gdk_device = NULL; + GdkDisplay *display; + GdkSeat *seat; + g_autoptr(GList) slaves = NULL; + GList *l; + + gsd_device = cc_wacom_device_get_device (page->stylus); + g_return_val_if_fail (GSD_IS_DEVICE (gsd_device), NULL); + + display = gtk_widget_get_display (GTK_WIDGET (page)); + seat = gdk_display_get_default_seat (display); + slaves = gdk_seat_get_slaves (seat, GDK_SEAT_CAPABILITY_TABLET_STYLUS); + + for (l = slaves; l && !gdk_device; l = l->next) { + g_autofree gchar *device_node = NULL; + + if (gdk_device_get_source (l->data) != GDK_SOURCE_PEN) + continue; + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (display)) + device_node = xdevice_get_device_node (gdk_x11_device_get_id (l->data)); +#endif +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (display)) + device_node = g_strdup (gdk_wayland_device_get_node_path (l->data)); +#endif + + if (g_strcmp0 (device_node, gsd_device_get_device_file (gsd_device)) == 0) + gdk_device = l->data; + } + + return gdk_device; +} + +static gboolean +run_calibration (CcWacomPage *page, + GVariant *old_calibration, + gdouble *cal, + GdkMonitor *monitor) +{ + GdkDisplay *display = gdk_monitor_get_display (monitor); + gint i, n_monitor = 0; + + g_assert (page->area == NULL); + + for (i = 0; i < gdk_display_get_n_monitors (display); i++) { + if (monitor == gdk_display_get_monitor (display, i)) { + n_monitor = i; + break; + } + } + + page->area = calib_area_new (NULL, + n_monitor, + cc_wacom_page_get_gdk_device (page), + finish_calibration, + page, + THRESHOLD_MISCLICK, + THRESHOLD_DOUBLECLICK); + + g_object_set_data_full (G_OBJECT (page), + "old-calibration", + old_calibration, + (GDestroyNotify) g_variant_unref); + + return FALSE; +} + +static void +calibrate (CcWacomPage *page) +{ + int i; + GVariant *old_calibration, *array; + g_autofree GVariant **tmp = NULL; + g_autofree gdouble *calibration = NULL; + gsize ncal; + GdkMonitor *monitor; + GdkScreen *screen; + g_autoptr(GnomeRRScreen) rr_screen = NULL; + GnomeRROutput *output; + g_autoptr(GError) error = NULL; + gint x, y; + + screen = gdk_screen_get_default (); + rr_screen = gnome_rr_screen_new (screen, &error); + if (error) { + g_warning ("Could not connect to display manager: %s", error->message); + return; + } + + output = cc_wacom_device_get_output (page->stylus, rr_screen); + gnome_rr_output_get_position (output, &x, &y); + monitor = gdk_display_get_monitor_at_point (gdk_screen_get_display (screen), x, y); + + if (!monitor) { + /* The display the tablet should be mapped to could not be located. + * This shouldn't happen if the EDID data is good... + */ + g_critical("Output associated with the tablet is not connected. Unable to calibrate."); + return; + } + + old_calibration = g_settings_get_value (page->wacom_settings, "area"); + g_variant_get_fixed_array (old_calibration, &ncal, sizeof (gdouble)); + + if (ncal != 4) { + g_warning("Device calibration property has wrong length. Got %"G_GSIZE_FORMAT" items; expected %d.\n", ncal, 4); + return; + } + + calibration = g_new0 (gdouble, ncal); + + /* Reset the current values, to avoid old calibrations + * from interfering with the calibration */ + tmp = g_malloc (ncal * sizeof (GVariant*)); + for (i = 0; i < ncal; i++) { + calibration[i] = 0.0; + tmp[i] = g_variant_new_double (calibration[i]); + } + + array = g_variant_new_array (G_VARIANT_TYPE_DOUBLE, tmp, ncal); + g_settings_set_value (page->wacom_settings, "area", array); + + run_calibration (page, old_calibration, calibration, monitor); + gtk_widget_set_sensitive (WID ("button-calibrate"), FALSE); +} + +static void +calibrate_button_clicked_cb (CcWacomPage *page) +{ + calibrate (page); +} + +/* This avoids us crashing when a newer version of + * gnome-control-center has been used, and we load up an + * old one, as the action type if unknown to the old g-c-c */ +static gboolean +action_type_is_valid (GDesktopPadButtonAction action) +{ + if (action >= G_N_ELEMENTS (action_table)) + return FALSE; + return TRUE; +} + +static void +create_row_from_button (GtkWidget *list_box, + guint button, + GSettings *settings) +{ + GtkWidget *row; + + row = cc_wacom_button_row_new (button, settings); + gtk_container_add (GTK_CONTAINER (list_box), row); + gtk_widget_show (row); +} + +static void +setup_button_mapping (CcWacomPage *page) +{ + GDesktopPadButtonAction action; + CcWacomDevice *pad; + GtkWidget *list_box; + guint i, n_buttons; + GSettings *settings; + + list_box = MWID ("shortcuts_list"); + pad = page->pads->data; + n_buttons = cc_wacom_device_get_num_buttons (pad); + + for (i = 0; i < n_buttons; i++) { + settings = cc_wacom_device_get_button_settings (pad, i); + if (!settings) + continue; + + action = g_settings_get_enum (settings, "action"); + if (!action_type_is_valid (action)) + continue; + + create_row_from_button (list_box, i, settings); + } +} + +static void +button_mapping_dialog_closed (CcWacomPage *page) +{ + gtk_widget_destroy (MWID ("button-mapping-dialog")); + g_clear_object (&page->mapping_builder); +} + +static void +show_button_mapping_dialog (CcWacomPage *page) +{ + GtkWidget *toplevel; + g_autoptr(GError) error = NULL; + GtkWidget *dialog; + + g_assert (page->mapping_builder == NULL); + page->mapping_builder = gtk_builder_new (); + gtk_builder_add_from_resource (page->mapping_builder, + "/org/gnome/control-center/wacom/button-mapping.ui", + &error); + + if (error != NULL) { + g_warning ("Error loading UI file: %s", error->message); + g_clear_object (&page->mapping_builder); + return; + } + + setup_button_mapping (page); + + dialog = MWID ("button-mapping-dialog"); + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (page)); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (toplevel)); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + g_signal_connect_object (dialog, "response", + G_CALLBACK (button_mapping_dialog_closed), page, G_CONNECT_SWAPPED); + + gtk_widget_show (dialog); + + page->button_map = dialog; + g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &page->button_map); +} + +static void +set_osd_visibility_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(GError) error = NULL; + GVariant *result; + CcWacomPage *page; + + page = CC_WACOM_PAGE (data); + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + + if (result == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_printerr ("Error setting OSD's visibility: %s\n", error->message); + show_button_mapping_dialog (page); + } else { + return; + } + } +} + +static void +set_osd_visibility (CcWacomPage *page) +{ + GDBusProxy *proxy; + GsdDevice *gsd_device; + const gchar *device_path; + + proxy = cc_wacom_panel_get_gsd_wacom_bus_proxy (page->panel); + + /* Pick the first device, the OSD may change later between them */ + gsd_device = cc_wacom_device_get_device (page->pads->data); + + device_path = gsd_device_get_device_file (gsd_device); + + if (proxy == NULL) { + show_button_mapping_dialog (page); + return; + } + + g_dbus_proxy_call (proxy, + "Show", + g_variant_new ("(ob)", device_path, TRUE), + G_DBUS_CALL_FLAGS_NONE, + -1, + page->cancellable, + set_osd_visibility_cb, + page); +} + +static void +map_buttons_button_clicked_cb (CcWacomPage *page) +{ + set_osd_visibility (page); +} + +static void +display_mapping_dialog_closed (CcWacomPage *page) +{ + int layout; + + gtk_widget_destroy (page->dialog); + page->dialog = NULL; + page->mapping = NULL; + layout = get_layout_type (page->stylus); + set_page_layout (page, layout); +} + +static void +display_mapping_button_clicked_cb (CcWacomPage *page) +{ + g_assert (page->mapping == NULL); + + page->dialog = gtk_dialog_new_with_buttons (_("Display Mapping"), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Close"), + GTK_RESPONSE_ACCEPT, + NULL); + page->mapping = cc_wacom_mapping_panel_new (); + cc_wacom_mapping_panel_set_device (CC_WACOM_MAPPING_PANEL (page->mapping), + page->stylus); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (page->dialog))), + page->mapping); + g_signal_connect_object (page->dialog, "response", + G_CALLBACK (display_mapping_dialog_closed), page, G_CONNECT_SWAPPED); + gtk_widget_show_all (page->dialog); + + g_object_add_weak_pointer (G_OBJECT (page->mapping), (gpointer *) &page->dialog); +} + +static void +tabletmode_changed_cb (CcWacomPage *page) +{ + GtkListStore *liststore; + GtkTreeIter iter; + gint mode; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (WID ("combo-tabletmode")), &iter)) + return; + + liststore = GTK_LIST_STORE (WID ("liststore-tabletmode")); + gtk_tree_model_get (GTK_TREE_MODEL (liststore), &iter, + MODENUMBER_COLUMN, &mode, + -1); + + g_settings_set_enum (page->wacom_settings, "mapping", mode); +} + +static void +left_handed_toggled_cb (CcWacomPage *page) +{ + gboolean left_handed; + + left_handed = gtk_switch_get_active (GTK_SWITCH (WID ("switch-left-handed"))); + g_settings_set_boolean (page->wacom_settings, "left-handed", left_handed); +} + +static void +set_left_handed_from_gsettings (CcWacomPage *page) +{ + gboolean left_handed; + + left_handed = g_settings_get_boolean (page->wacom_settings, "left-handed"); + gtk_switch_set_active (GTK_SWITCH (WID ("switch-left-handed")), left_handed); +} + +static void +set_mode_from_gsettings (GtkComboBox *combo, + CcWacomPage *page) +{ + GDesktopTabletMapping mapping; + + mapping = g_settings_get_enum (page->wacom_settings, "mapping"); + + /* this must be kept in sync with the .ui file */ + gtk_combo_box_set_active (combo, mapping); +} + +static void +update_display_decoupled_sensitivity (CcWacomPage *page, + gboolean active) +{ + if (get_layout_type (page->stylus) != LAYOUT_SCREEN) + return; + + gtk_widget_set_sensitive (WID ("label-trackingmode"), active); + gtk_widget_set_sensitive (WID ("combo-tabletmode"), active); + gtk_widget_set_sensitive (WID ("display-mapping-button-2"), active); + + gtk_widget_set_sensitive (WID ("button-calibrate"), !active); +} + +static void +set_display_decoupled_from_gsettings (GtkSwitch *sw, + CcWacomPage *page) +{ + g_auto(GStrv) output = g_settings_get_strv (page->wacom_settings, "output"); + gboolean active = g_strcmp0 (output[0], "") != 0; + + gtk_switch_set_active (sw, active); + update_display_decoupled_sensitivity (page, active); +} + +static void +combobox_text_cellrenderer (GtkComboBox *combo, int name_column) +{ + GtkCellRenderer *renderer; + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, + "text", BUTTONNAME_COLUMN, NULL); +} + +static gboolean +display_clicked_cb (CcWacomPage *page) +{ + cc_wacom_panel_switch_to_panel (page->panel, "display"); + return TRUE; +} + +static gboolean +mouse_clicked_cb (CcWacomPage *page) +{ + cc_wacom_panel_switch_to_panel (page->panel, "mouse"); + return TRUE; +} + +static void +decouple_display_toggled_cb (CcWacomPage *page) +{ + gboolean active = gtk_switch_get_active (GTK_SWITCH (WID ("switch-decouple-display"))); + + update_display_decoupled_sensitivity (page, active); + + if (!active) { + cc_wacom_device_set_output (page->stylus, NULL); + } else { + GdkScreen *screen; + GnomeRRScreen *rr_screen; + GnomeRROutput **outputs, *picked = NULL; + g_autoptr(GError) error = NULL; + int i; + + screen = gtk_widget_get_screen (GTK_WIDGET (WID ("switch-decouple-display"))); + rr_screen = gnome_rr_screen_new (screen, &error); + if (rr_screen == NULL) { + g_warning ("Could not connect to display manager: %s", error->message); + return; + } + + outputs = gnome_rr_screen_list_outputs (rr_screen); + + /* Pick *some* output here. decoupled mode can only jump across + * monitors, not map to the full span of those. We prefer the + * builtin display, falling back to the first output found if + * there's none. + */ + for (i = 0; outputs[i] != NULL; i++) { + if (gnome_rr_output_is_builtin_display (outputs[i])) + picked = outputs[i]; + } + + if (!picked) + picked = outputs[0]; + + cc_wacom_device_set_output (page->stylus, picked); + } +} + +/* Boilerplate code goes below */ + +static void +cc_wacom_page_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_page_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_page_dispose (GObject *object) +{ + CcWacomPage *self = CC_WACOM_PAGE (object); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_pointer (&self->area, calib_area_free); + g_clear_pointer (&self->button_map, gtk_widget_destroy); + g_clear_pointer (&self->dialog, gtk_widget_destroy); + g_clear_object (&self->builder); + g_clear_object (&self->header_group); + g_list_free_full (self->pads, g_object_unref); + self->pads = NULL; + + self->panel = NULL; + + G_OBJECT_CLASS (cc_wacom_page_parent_class)->dispose (object); +} + +static void +cc_wacom_page_class_init (CcWacomPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_wacom_page_get_property; + object_class->set_property = cc_wacom_page_set_property; + object_class->dispose = cc_wacom_page_dispose; +} + +static void +remove_link_padding (GtkWidget *widget) +{ + g_autoptr(GtkCssProvider) provider = NULL; + + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), + ".link { padding: 0px; }", -1, NULL); + gtk_style_context_add_provider (gtk_widget_get_style_context (widget), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +} + +static void +cc_wacom_page_init (CcWacomPage *page) +{ + g_autoptr(GError) error = NULL; + GtkComboBox *combo; + GtkWidget *box; + char *objects[] = { + "main-grid", + "liststore-tabletmode", + "liststore-buttons", + "adjustment-tip-feel", + "adjustment-eraser-feel", + NULL + }; + + page->builder = gtk_builder_new (); + + gtk_builder_add_objects_from_resource (page->builder, + "/org/gnome/control-center/wacom/gnome-wacom-properties.ui", + objects, + &error); + if (error != NULL) { + g_warning ("Error loading UI file: %s", error->message); + return; + } + + box = WID ("main-grid"); + gtk_container_add (GTK_CONTAINER (page), box); + gtk_widget_set_vexpand (GTK_WIDGET (box), TRUE); + + g_signal_connect_object (WID ("button-calibrate"), "clicked", + G_CALLBACK (calibrate_button_clicked_cb), page, G_CONNECT_SWAPPED); + g_signal_connect_object (WID ("map-buttons-button"), "clicked", + G_CALLBACK (map_buttons_button_clicked_cb), page, G_CONNECT_SWAPPED); + + combo = GTK_COMBO_BOX (WID ("combo-tabletmode")); + combobox_text_cellrenderer (combo, MODELABEL_COLUMN); + g_signal_connect_object (combo, "changed", + G_CALLBACK (tabletmode_changed_cb), page, G_CONNECT_SWAPPED); + + g_signal_connect_object (WID ("switch-left-handed"), "notify::active", + G_CALLBACK (left_handed_toggled_cb), page, G_CONNECT_SWAPPED); + + g_signal_connect_object (WID ("display-link"), "activate-link", + G_CALLBACK (display_clicked_cb), page, G_CONNECT_SWAPPED); + remove_link_padding (WID ("display-link")); + + g_signal_connect_object (WID ("mouse-link"), "activate-link", + G_CALLBACK (mouse_clicked_cb), page, G_CONNECT_SWAPPED); + remove_link_padding (WID ("mouse-link")); + + g_signal_connect_object (WID ("display-mapping-button"), "clicked", + G_CALLBACK (display_mapping_button_clicked_cb), page, G_CONNECT_SWAPPED); + g_signal_connect_object (WID ("display-mapping-button-2"), "clicked", + G_CALLBACK (display_mapping_button_clicked_cb), page, G_CONNECT_SWAPPED); + g_signal_connect_object (WID ("switch-decouple-display"), "notify::active", + G_CALLBACK (decouple_display_toggled_cb), page, G_CONNECT_SWAPPED); + + page->nav = cc_wacom_nav_button_new (); + gtk_widget_set_halign (page->nav, GTK_ALIGN_END); + gtk_widget_set_margin_start (page->nav, 10); + gtk_widget_show (page->nav); + gtk_container_add (CWID ("navigation-placeholder"), page->nav); + + page->cancellable = g_cancellable_new (); +} + +static void +set_icon_name (CcWacomPage *page, + const char *widget_name, + const char *icon_name) +{ + g_autofree gchar *resource = NULL; + + resource = g_strdup_printf ("/org/gnome/control-center/wacom/%s.svg", icon_name); + gtk_image_set_from_resource (GTK_IMAGE (WID (widget_name)), resource); +} + +static void +remove_left_handed (CcWacomPage *page) +{ + gtk_widget_destroy (WID ("label-left-handed")); + gtk_widget_destroy (WID ("switch-left-handed")); +} + +static void +remove_display_link (CcWacomPage *page) +{ + gtk_widget_destroy (WID ("display-link")); +} + +static void +remove_mouse_link (CcWacomPage *page) +{ + gtk_widget_destroy (WID ("mouse-link")); +} + +static void +remove_decouple_options (CcWacomPage *page) +{ + gtk_widget_destroy (WID ("label-decouple-display")); + gtk_widget_destroy (WID ("switch-decouple-display")); + gtk_widget_destroy (WID ("display-mapping-button-2")); +} + +static gboolean +has_monitor (CcWacomPage *page) +{ + WacomIntegrationFlags integration_flags; + + integration_flags = cc_wacom_device_get_integration_flags (page->stylus); + + return ((integration_flags & + (WACOM_DEVICE_INTEGRATED_DISPLAY | WACOM_DEVICE_INTEGRATED_SYSTEM)) != 0); +} + +static void +set_page_layout (CcWacomPage *page, + int layout) +{ + WacomIntegrationFlags integration_flags; + + integration_flags = cc_wacom_device_get_integration_flags (page->stylus); + + if ((integration_flags & + (WACOM_DEVICE_INTEGRATED_DISPLAY | WACOM_DEVICE_INTEGRATED_SYSTEM)) != 0) { + /* FIXME: Check we've got a puck, or a corresponding touchpad device */ + remove_mouse_link (page); + } + + switch (layout) { + case LAYOUT_NORMAL: + remove_left_handed (page); + remove_display_link (page); + remove_decouple_options (page); + break; + case LAYOUT_REVERSIBLE: + remove_display_link (page); + remove_decouple_options (page); + break; + case LAYOUT_SCREEN: + remove_left_handed (page); + + gtk_widget_destroy (WID ("display-mapping-button")); + + gtk_widget_show (WID ("button-calibrate")); + gtk_widget_set_sensitive (WID ("button-calibrate"), + has_monitor (page)); + + gtk_container_child_set (CWID ("main-controls-grid"), + WID ("label-trackingmode"), + "top_attach", 5, NULL); + gtk_container_child_set (CWID ("main-controls-grid"), + WID ("combo-tabletmode"), + "top_attach", 5, NULL); + break; + default: + g_assert_not_reached (); + } +} + +static void +update_pad_availability (CcWacomPage *page) +{ + gtk_widget_set_visible (WID ("map-buttons-button"), page->pads != NULL); +} + +static void +check_add_pad (CcWacomPage *page, + GsdDevice *gsd_device) +{ + g_autoptr(CcWacomDevice) wacom_device = NULL; + + if ((gsd_device_get_device_type (gsd_device) & GSD_DEVICE_TYPE_PAD) == 0) + return; + + if (!gsd_device_shares_group (cc_wacom_device_get_device (page->stylus), + gsd_device)) + return; + + wacom_device = cc_wacom_device_new (gsd_device); + if (!wacom_device) + return; + + page->pads = g_list_prepend (page->pads, g_steal_pointer (&wacom_device)); + update_pad_availability (page); +} + +static void +check_remove_pad (CcWacomPage *page, + GsdDevice *gsd_device) +{ + GList *l; + + if ((gsd_device_get_device_type (gsd_device) & GSD_DEVICE_TYPE_PAD) == 0) + return; + + for (l = page->pads; l; l = l->next) { + CcWacomDevice *wacom_device = l->data; + if (cc_wacom_device_get_device (wacom_device) == gsd_device) { + page->pads = g_list_delete_link (page->pads, l); + g_object_unref (wacom_device); + } + } + + update_pad_availability (page); +} + +GtkWidget * +cc_wacom_page_new (CcWacomPanel *panel, + CcWacomDevice *stylus) +{ + g_autoptr (GList) pads = NULL; + CcWacomPage *page; + GList *l; + + g_return_val_if_fail (CC_IS_WACOM_DEVICE (stylus), NULL); + + page = g_object_new (CC_TYPE_WACOM_PAGE, NULL); + + page->panel = panel; + page->stylus = stylus; + + set_page_layout (page, get_layout_type (stylus)); + + /* FIXME move this to construct */ + page->wacom_settings = cc_wacom_device_get_settings (stylus); + set_mode_from_gsettings (GTK_COMBO_BOX (WID ("combo-tabletmode")), page); + if (get_layout_type (page->stylus) == LAYOUT_SCREEN) + set_display_decoupled_from_gsettings (GTK_SWITCH (WID ("switch-decouple-display")), page); + + /* Tablet name */ + gtk_label_set_text (GTK_LABEL (WID ("label-tabletmodel")), cc_wacom_device_get_name (stylus)); + + /* Left-handedness */ + if (cc_wacom_device_is_reversible (stylus)) + set_left_handed_from_gsettings (page); + + /* Tablet icon */ + set_icon_name (page, "image-tablet", cc_wacom_device_get_icon_name (stylus)); + + /* Listen to changes in related/paired pads */ + page->manager = gsd_device_manager_get (); + g_signal_connect_object (G_OBJECT (page->manager), "device-added", + G_CALLBACK (check_add_pad), page, + G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (page->manager), "device-removed", + G_CALLBACK (check_remove_pad), page, + G_CONNECT_SWAPPED); + + pads = gsd_device_manager_list_devices (page->manager, GSD_DEVICE_TYPE_PAD); + for (l = pads; l ; l = l->next) + check_add_pad (page, l->data); + + return GTK_WIDGET (page); +} + +void +cc_wacom_page_set_navigation (CcWacomPage *page, + GtkNotebook *notebook, + gboolean ignore_first_page) +{ + g_return_if_fail (CC_IS_WACOM_PAGE (page)); + + g_object_set (G_OBJECT (page->nav), + "notebook", notebook, + "ignore-first", ignore_first_page, + NULL); +} + +void +cc_wacom_page_calibrate (CcWacomPage *page) +{ + g_return_if_fail (CC_IS_WACOM_PAGE (page)); + + calibrate (page); +} + +gboolean +cc_wacom_page_can_calibrate (CcWacomPage *page) +{ + g_return_val_if_fail (CC_IS_WACOM_PAGE (page), + FALSE); + + return has_monitor (page); +} diff --git a/panels/wacom/cc-wacom-page.h b/panels/wacom/cc-wacom-page.h new file mode 100644 index 0000000..56b32ac --- /dev/null +++ b/panels/wacom/cc-wacom-page.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2011 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: Peter Hutterer <peter.hutterer@redhat.com> + * Bastien Nocera <hadess@hadess.net> + */ + +#pragma once + +#include <gtk/gtk.h> +#include "cc-wacom-panel.h" +#include "cc-wacom-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WACOM_PAGE (cc_wacom_page_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomPage, cc_wacom_page, CC, WACOM_PAGE, GtkBox) + +GtkWidget * cc_wacom_page_new (CcWacomPanel *panel, + CcWacomDevice *stylus); + +void cc_wacom_page_set_navigation (CcWacomPage *page, + GtkNotebook *notebook, + gboolean ignore_first_page); + +void cc_wacom_page_calibrate (CcWacomPage *page); + +gboolean cc_wacom_page_can_calibrate (CcWacomPage *page); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-panel.c b/panels/wacom/cc-wacom-panel.c new file mode 100644 index 0000000..17404c1 --- /dev/null +++ b/panels/wacom/cc-wacom-panel.c @@ -0,0 +1,767 @@ +/* + * Copyright © 2011 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: Peter Hutterer <peter.hutterer@redhat.com> + * Bastien Nocera <hadess@hadess.net> + * + */ + +#include <config.h> + +#include <string.h> +#include <gtk/gtk.h> +#include <glib/gi18n-lib.h> + +#include "shell/cc-application.h" +#include "shell/cc-debug.h" +#include "cc-wacom-panel.h" +#include "cc-wacom-page.h" +#include "cc-wacom-stylus-page.h" +#include "cc-wacom-resources.h" +#include "cc-drawing-area.h" +#include "cc-tablet-tool-map.h" +#include "gsd-device-manager.h" + +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/gdkwayland.h> +#endif + +#define WID(x) (GtkWidget *) gtk_builder_get_object (self->builder, x) + +struct _CcWacomPanel +{ + CcPanel parent_instance; + + GtkBuilder *builder; + GtkWidget *stack; + GtkWidget *switcher; + GtkWidget *tablet_notebook; + GtkWidget *stylus_notebook; + GtkWidget *test_popover; + GtkWidget *test_draw_area; + GtkWidget *test_button; + GHashTable *devices; /* key=GsdDevice, value=CcWacomDevice */ + GHashTable *pages; /* key=CcWacomDevice, value=GtkWidget */ + GHashTable *stylus_pages; /* key=CcWacomTool, value=GtkWidget */ + + CcTabletToolMap *tablet_tool_map; + + /* DBus */ + GDBusProxy *proxy; +}; + +CC_PANEL_REGISTER (CcWacomPanel, cc_wacom_panel) + +typedef struct { + const char *name; + CcWacomDevice *stylus; + CcWacomDevice *pad; +} Tablet; + +enum { + WACOM_PAGE = -1, + PLUG_IN_PAGE = 0, +}; + +enum { + PROP_0, + PROP_PARAMETERS +}; + +/* Static init function */ +static void +update_visibility (GsdDeviceManager *manager, + GsdDevice *device, + gpointer user_data) +{ + CcApplication *application; + g_autoptr(GList) devices = NULL; + guint i; + + devices = gsd_device_manager_list_devices (manager, GSD_DEVICE_TYPE_TABLET); + i = g_list_length (devices); + + /* Set the new visibility */ + application = CC_APPLICATION (g_application_get_default ()); + cc_shell_model_set_panel_visibility (cc_application_get_model (application), + "wacom", + i > 0 ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH); + + g_debug ("Wacom panel visible: %s", i > 0 ? "yes" : "no"); +} + +void +cc_wacom_panel_static_init_func (void) +{ + GsdDeviceManager *manager; + + manager = gsd_device_manager_get (); + g_signal_connect (G_OBJECT (manager), "device-added", + G_CALLBACK (update_visibility), NULL); + g_signal_connect (G_OBJECT (manager), "device-removed", + G_CALLBACK (update_visibility), NULL); + update_visibility (manager, NULL, NULL); +} + +static CcWacomDevice * +lookup_wacom_device (CcWacomPanel *self, + const gchar *name) +{ + GHashTableIter iter; + CcWacomDevice *wacom_device; + + g_hash_table_iter_init (&iter, self->devices); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &wacom_device)) { + if (g_strcmp0 (cc_wacom_device_get_name (wacom_device), name) == 0) + return wacom_device; + } + + return NULL; +} + +static CcWacomPage * +set_device_page (CcWacomPanel *self, const gchar *device_name) +{ + CcWacomPage *page; + CcWacomDevice *wacom_device; + gint current; + + if (device_name == NULL) + return NULL; + + wacom_device = lookup_wacom_device (self, device_name); + if (!wacom_device) { + g_warning ("Failed to find device '%s', supplied in the command line.", device_name); + return NULL; + } + + page = g_hash_table_lookup (self->pages, wacom_device); + current = gtk_notebook_page_num (GTK_NOTEBOOK (self->tablet_notebook), GTK_WIDGET (page)); + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->tablet_notebook), current); + + return page; +} + +static void +run_operation_from_params (CcWacomPanel *self, GVariant *parameters) +{ + g_autoptr(GVariant) v = NULL; + g_autoptr(GVariant) v2 = NULL; + CcWacomPage *page; + const gchar *operation = NULL; + const gchar *device_name = NULL; + gint n_params; + + n_params = g_variant_n_children (parameters); + + g_variant_get_child (parameters, n_params - 1, "v", &v); + device_name = g_variant_get_string (v, NULL); + + if (!g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) { + g_warning ("Wrong type for the second argument GVariant, expected 's' but got '%s'", + g_variant_get_type_string (v)); + return; + } + + switch (n_params) { + case 3: + page = set_device_page (self, device_name); + if (page == NULL) + return; + + g_variant_get_child (parameters, 1, "v", &v2); + + if (!g_variant_is_of_type (v2, G_VARIANT_TYPE_STRING)) { + g_warning ("Wrong type for the operation name argument. A string is expected."); + break; + } + + operation = g_variant_get_string (v2, NULL); + if (g_strcmp0 (operation, "run-calibration") == 0) { + if (cc_wacom_page_can_calibrate (page)) + cc_wacom_page_calibrate (page); + else + g_warning ("The device %s cannot be calibrated.", device_name); + } else { + g_warning ("Ignoring unrecognized operation '%s'", operation); + } + case 2: + set_device_page (self, device_name); + break; + case 1: + g_assert_not_reached (); + default: + g_warning ("Unexpected number of parameters found: %d. Request ignored.", n_params); + } +} + +/* Boilerplate code goes below */ + +static void +cc_wacom_panel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_panel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWacomPanel *self; + self = CC_WACOM_PANEL (object); + + switch (property_id) + { + case PROP_PARAMETERS: { + GVariant *parameters; + + parameters = g_value_get_variant (value); + if (parameters == NULL || g_variant_n_children (parameters) <= 1) + return; + + run_operation_from_params (self, parameters); + + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_panel_dispose (GObject *object) +{ + CcWacomPanel *self = CC_WACOM_PANEL (object); + + g_clear_object (&self->builder); + + g_clear_pointer (&self->devices, g_hash_table_unref); + g_clear_object (&self->proxy); + g_clear_pointer (&self->pages, g_hash_table_unref); + g_clear_pointer (&self->stylus_pages, g_hash_table_unref); + + G_OBJECT_CLASS (cc_wacom_panel_parent_class)->dispose (object); +} + +static void +check_remove_stylus_pages (CcWacomPanel *self) +{ + GHashTableIter iter; + CcWacomDevice *device; + CcWacomTool *tool; + GtkWidget *page; + GList *tools; + g_autoptr(GList) total = NULL; + + /* First. Iterate known devices and get the tools */ + g_hash_table_iter_init (&iter, self->devices); + while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &device)) { + tools = cc_tablet_tool_map_list_tools (self->tablet_tool_map, device); + total = g_list_concat (total, tools); + } + + /* Second. Iterate through stylus pages and remove the ones whose + * tool is no longer in the list. + */ + g_hash_table_iter_init (&iter, self->stylus_pages); + while (g_hash_table_iter_next (&iter, (gpointer*) &tool, (gpointer*) &page)) { + if (g_list_find (total, tool)) + continue; + gtk_widget_destroy (page); + g_hash_table_iter_remove (&iter); + } +} + +static gboolean +add_stylus (CcWacomPanel *self, + CcWacomTool *tool) +{ + GtkWidget *page; + + if (g_hash_table_lookup (self->stylus_pages, tool)) + return FALSE; + + page = cc_wacom_stylus_page_new (tool); + cc_wacom_stylus_page_set_navigation (CC_WACOM_STYLUS_PAGE (page), + GTK_NOTEBOOK (self->stylus_notebook)); + gtk_widget_show (page); + gtk_notebook_append_page (GTK_NOTEBOOK (self->stylus_notebook), page, NULL); + g_hash_table_insert (self->stylus_pages, tool, page); + + if (gtk_notebook_get_current_page (GTK_NOTEBOOK (self->stylus_notebook)) == 0) + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->stylus_notebook), 1); + + return TRUE; +} + +static void +update_test_button (CcWacomPanel *self) +{ + if (!self->test_button) + return; + + if (g_hash_table_size (self->devices) == 0) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->test_button), FALSE); + gtk_widget_set_sensitive (self->test_button, FALSE); + } else { + gtk_widget_set_sensitive (self->test_button, TRUE); + } +} + +static void +update_current_tool (CcWacomPanel *panel, + GdkDevice *device, + GdkDeviceTool *tool) +{ + GsdDeviceManager *device_manager; + CcWacomDevice *wacom_device; + CcWacomTool *stylus; + GsdDevice *gsd_device; + guint64 serial, id; + gboolean added; + + if (!tool) + return; + + /* Work our way to the CcWacomDevice */ + device_manager = gsd_device_manager_get (); + gsd_device = gsd_device_manager_lookup_gdk_device (device_manager, + device); + if (!gsd_device) + return; + + wacom_device = g_hash_table_lookup (panel->devices, gsd_device); + if (!wacom_device) + return; + + /* Check whether we already know this tool, nothing to do then */ + serial = gdk_device_tool_get_serial (tool); + + /* The wacom driver sends serial-less tools with a serial of + * 1, libinput uses 0. No device exists with serial 1, let's reset + * it here so everything else works as expected. + */ + if (serial == 1) + serial = 0; + + stylus = cc_tablet_tool_map_lookup_tool (panel->tablet_tool_map, + wacom_device, serial); + + if (!stylus) { + id = gdk_device_tool_get_hardware_id (tool); + + /* The wacom driver sends a hw id of 0x2 for stylus and 0xa + * for eraser for devices that don't have a true HW id. + * Reset those to 0 so we can use the same code-paths + * libinput uses. + * The touch ID is 0x3, let's ignore that because we don't + * have a touch tool and it only happens when the wacom + * driver handles the touch device. + */ + if (id == 0x2 || id == 0xa) + id = 0; + else if (id == 0x3) + return; + + stylus = cc_wacom_tool_new (serial, id, wacom_device); + if (!stylus) + return; + } + + added = add_stylus (panel, stylus); + + if (added) { + if (panel->stylus_notebook == + gtk_stack_get_visible_child (GTK_STACK (panel->stack))) { + GtkWidget *widget; + gint page; + + widget = g_hash_table_lookup (panel->stylus_pages, stylus); + page = gtk_notebook_page_num (GTK_NOTEBOOK (panel->stylus_notebook), widget); + gtk_notebook_set_current_page (GTK_NOTEBOOK (panel->stylus_notebook), page); + } else { + gtk_container_child_set (GTK_CONTAINER (panel->stack), + panel->stylus_notebook, + "needs-attention", TRUE, + NULL); + } + } + + cc_tablet_tool_map_add_relation (panel->tablet_tool_map, + wacom_device, stylus); +} + +static gboolean +on_shell_event_cb (CcWacomPanel *panel, + GdkEvent *event) +{ + if (event->type == GDK_MOTION_NOTIFY) { + update_current_tool (panel, + gdk_event_get_source_device (event), + gdk_event_get_device_tool (event)); + } + + return GDK_EVENT_PROPAGATE; +} + +static void +cc_wacom_panel_constructed (GObject *object) +{ + CcWacomPanel *self = CC_WACOM_PANEL (object); + GtkWidget *button; + CcShell *shell; + + G_OBJECT_CLASS (cc_wacom_panel_parent_class)->constructed (object); + + /* Add test area button to shell header. */ + shell = cc_panel_get_shell (CC_PANEL (self)); + + button = gtk_toggle_button_new_with_mnemonic (_("Test Your _Settings")); + gtk_style_context_add_class (gtk_widget_get_style_context (button), + "text-button"); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_widget_set_visible (button, TRUE); + + cc_shell_embed_widget_in_header (shell, button, GTK_POS_RIGHT); + + self->test_popover = gtk_popover_new (button); + gtk_container_set_border_width (GTK_CONTAINER (self->test_popover), 6); + + self->test_draw_area = cc_drawing_area_new (); + gtk_widget_set_size_request (self->test_draw_area, 400, 300); + gtk_container_add (GTK_CONTAINER (self->test_popover), + self->test_draw_area); + gtk_widget_show (self->test_draw_area); + + g_object_bind_property (button, "active", + self->test_popover, "visible", + G_BINDING_BIDIRECTIONAL); + + g_signal_connect_object (shell, "event", + G_CALLBACK (on_shell_event_cb), self, G_CONNECT_SWAPPED); + + self->test_button = button; + update_test_button (self); +} + +static const char * +cc_wacom_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/wacom"; +} + +static GtkWidget * +cc_wacom_panel_get_title_widget (CcPanel *panel) +{ + CcWacomPanel *self = CC_WACOM_PANEL (panel); + + return self->switcher; +} + +static void +cc_wacom_panel_class_init (CcWacomPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + + object_class->get_property = cc_wacom_panel_get_property; + object_class->set_property = cc_wacom_panel_set_property; + object_class->dispose = cc_wacom_panel_dispose; + object_class->constructed = cc_wacom_panel_constructed; + + panel_class->get_help_uri = cc_wacom_panel_get_help_uri; + panel_class->get_title_widget = cc_wacom_panel_get_title_widget; + + g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); +} + +static void +update_current_page (CcWacomPanel *self) +{ + int num_pages; + + num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->tablet_notebook)); + if (num_pages > 1) + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->tablet_notebook), 1); + + update_test_button (self); +} + +static void +add_known_device (CcWacomPanel *self, + GsdDevice *gsd_device) +{ + CcWacomDevice *device; + GsdDeviceType device_type; + g_autoptr(GList) tools = NULL; + GtkWidget *page; + GList *l; + + device_type = gsd_device_get_device_type (gsd_device); + + if ((device_type & GSD_DEVICE_TYPE_TABLET) == 0) + return; + + if ((device_type & + (GSD_DEVICE_TYPE_PAD | + GSD_DEVICE_TYPE_TOUCHSCREEN | + GSD_DEVICE_TYPE_TOUCHPAD)) != 0) { + return; + } + + device = cc_wacom_device_new (gsd_device); + if (!device) + return; + + g_hash_table_insert (self->devices, gsd_device, device); + + tools = cc_tablet_tool_map_list_tools (self->tablet_tool_map, device); + + for (l = tools; l != NULL; l = l->next) { + add_stylus (self, l->data); + } + + page = cc_wacom_page_new (self, device); + cc_wacom_page_set_navigation (CC_WACOM_PAGE (page), GTK_NOTEBOOK (self->tablet_notebook), TRUE); + gtk_widget_show (page); + gtk_notebook_append_page (GTK_NOTEBOOK (self->tablet_notebook), page, NULL); + g_hash_table_insert (self->pages, device, page); +} + +static void +device_removed_cb (CcWacomPanel *self, + GsdDevice *gsd_device) +{ + CcWacomDevice *device; + GtkWidget *page; + + device = g_hash_table_lookup (self->devices, gsd_device); + if (!device) + return; + + page = g_hash_table_lookup (self->pages, device); + if (page) { + g_hash_table_remove (self->pages, device); + gtk_widget_destroy (page); + } + + g_hash_table_remove (self->devices, gsd_device); + check_remove_stylus_pages (self); + update_current_page (self); +} + +static void +device_added_cb (CcWacomPanel *self, + GsdDevice *device) +{ + add_known_device (self, device); + update_current_page (self); +} + +static gboolean +link_activated (CcWacomPanel *self) +{ + cc_wacom_panel_switch_to_panel (self, "bluetooth"); + return TRUE; +} + +void +cc_wacom_panel_switch_to_panel (CcWacomPanel *self, + const char *panel) +{ + CcShell *shell; + g_autoptr(GError) error = NULL; + + g_return_if_fail (self); + + shell = cc_panel_get_shell (CC_PANEL (self)); + if (!cc_shell_set_active_panel_from_id (shell, panel, NULL, &error)) + g_warning ("Failed to activate '%s' panel: %s", panel, error->message); +} + +static void +got_osd_proxy_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(GError) error = NULL; + CcWacomPanel *self; + + self = CC_WACOM_PANEL (data); + self->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (self->proxy == NULL) { + g_printerr ("Error creating proxy: %s\n", error->message); + return; + } +} + +static void +enbiggen_label (GtkLabel *label) +{ + const char *str; + g_autofree char *new_str = NULL; + + str = gtk_label_get_text (label); + new_str = g_strdup_printf ("<big>%s</big>", str); + gtk_label_set_markup (label, new_str); +} + +static void +on_stack_visible_child_notify_cb (CcWacomPanel *panel) +{ + GtkWidget *child; + + child = gtk_stack_get_visible_child (GTK_STACK (panel->stack)); + + if (child == panel->stylus_notebook) { + gtk_container_child_set (GTK_CONTAINER (panel->stack), + panel->stylus_notebook, + "needs-attention", FALSE, + NULL); + } +} + +static void +cc_wacom_panel_init (CcWacomPanel *self) +{ + GtkWidget *widget; + GsdDeviceManager *device_manager; + g_autoptr(GList) devices = NULL; + GList *l; + g_autoptr(GError) error = NULL; + char *objects[] = { + "main-box", + "no-stylus-page", + NULL + }; + + g_resources_register (cc_wacom_get_resource ()); + + self->builder = gtk_builder_new (); + + gtk_builder_add_objects_from_resource (self->builder, + "/org/gnome/control-center/wacom/gnome-wacom-properties.ui", + objects, + &error); + gtk_builder_add_objects_from_resource (self->builder, + "/org/gnome/control-center/wacom/wacom-stylus-page.ui", + objects, + &error); + if (error != NULL) { + g_warning ("Error loading UI file: %s", error->message); + return; + } + + self->tablet_tool_map = cc_tablet_tool_map_new (); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.Shell", + "/org/gnome/Shell/Wacom", + "org.gnome.Shell.Wacom.PadOsd", + cc_panel_get_cancellable (CC_PANEL (self)), + got_osd_proxy_cb, + self); + + /* Stack + Switcher */ + self->stack = gtk_stack_new (); + g_object_set (G_OBJECT (self->stack), + "margin-top", 30, + "margin-end", 30, + "margin-start", 30, + "margin-bottom", 30, + NULL); + + g_signal_connect_object (self->stack, "notify::visible-child", + G_CALLBACK (on_stack_visible_child_notify_cb), self, G_CONNECT_SWAPPED); + + self->switcher = gtk_stack_switcher_new (); + gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER (self->switcher), + GTK_STACK (self->stack)); + gtk_widget_show (self->switcher); + + gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->stack)); + gtk_widget_show (self->stack); + + self->tablet_notebook = gtk_notebook_new (); + gtk_widget_show (self->tablet_notebook); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->tablet_notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (self->tablet_notebook), FALSE); + gtk_widget_set_vexpand (self->tablet_notebook, TRUE); + + self->stylus_notebook = gtk_notebook_new (); + gtk_widget_show (self->stylus_notebook); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->stylus_notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (self->stylus_notebook), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (self->stylus_notebook), 0); + gtk_widget_set_vexpand (self->stylus_notebook, TRUE); + + gtk_stack_add_titled (GTK_STACK (self->stack), + self->stylus_notebook, "stylus", + _("Stylus")); + gtk_stack_add_titled (GTK_STACK (self->stack), + self->tablet_notebook, "tablet", + _("Tablet")); + + /* No styli page */ + widget = WID ("no-stylus-page"); + enbiggen_label (GTK_LABEL (WID ("no-stylus-label1"))); + gtk_notebook_append_page (GTK_NOTEBOOK (self->stylus_notebook), widget, NULL); + + /* No tablets page */ + widget = WID ("main-box"); + enbiggen_label (GTK_LABEL (WID ("advice-label1"))); + gtk_notebook_append_page (GTK_NOTEBOOK (self->tablet_notebook), widget, NULL); + + g_signal_connect_object (WID ("linkbutton"), "activate-link", + G_CALLBACK (link_activated), self, G_CONNECT_SWAPPED); + + self->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); + self->pages = g_hash_table_new (NULL, NULL); + self->stylus_pages = g_hash_table_new (NULL, NULL); + + device_manager = gsd_device_manager_get (); + g_signal_connect_object (device_manager, "device-added", + G_CALLBACK (device_added_cb), self, G_CONNECT_SWAPPED); + g_signal_connect_object (device_manager, "device-removed", + G_CALLBACK (device_removed_cb), self, G_CONNECT_SWAPPED); + + devices = gsd_device_manager_list_devices (device_manager, + GSD_DEVICE_TYPE_TABLET); + for (l = devices; l ; l = l->next) + add_known_device (self, l->data); + + update_current_page (self); +} + +GDBusProxy * +cc_wacom_panel_get_gsd_wacom_bus_proxy (CcWacomPanel *self) +{ + g_return_val_if_fail (CC_IS_WACOM_PANEL (self), NULL); + + return self->proxy; +} diff --git a/panels/wacom/cc-wacom-panel.h b/panels/wacom/cc-wacom-panel.h new file mode 100644 index 0000000..55bf1f9 --- /dev/null +++ b/panels/wacom/cc-wacom-panel.h @@ -0,0 +1,40 @@ +/* + * Copyright © 2011 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: Peter Hutterer <peter.hutterer@redhat.com> + * Bastien Nocera <hadess@hadess.net> + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_WACOM_PANEL (cc_wacom_panel_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomPanel, cc_wacom_panel, CC, WACOM_PANEL, CcPanel) + +void cc_wacom_panel_static_init_func (void); + +void cc_wacom_panel_switch_to_panel (CcWacomPanel *self, + const char *panel); + +void cc_wacom_panel_set_osd_visibility (CcWacomPanel *self, + guint32 device_id); + +GDBusProxy * cc_wacom_panel_get_gsd_wacom_bus_proxy (CcWacomPanel *self); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-stylus-page.c b/panels/wacom/cc-wacom-stylus-page.c new file mode 100644 index 0000000..2fc6c5a --- /dev/null +++ b/panels/wacom/cc-wacom-stylus-page.c @@ -0,0 +1,503 @@ +/* + * Copyright © 2011 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: Peter Hutterer <peter.hutterer@redhat.com> + * Bastien Nocera <hadess@hadess.net> + * + */ + +#include <config.h> + +#include <glib/gi18n.h> +#include "cc-wacom-stylus-page.h" +#include "cc-wacom-nav-button.h" +#include <gtk/gtk.h> +#include <gdesktop-enums.h> + +#include <string.h> + +#define WID(x) (GtkWidget *) gtk_builder_get_object (page->builder, x) +#define CWID(x) (GtkContainer *) gtk_builder_get_object (page->builder, x) + +struct _CcWacomStylusPage +{ + GtkBox parent_instance; + + CcWacomTool *stylus; + GtkBuilder *builder; + GtkWidget *nav; + GSettings *stylus_settings; +}; + +G_DEFINE_TYPE (CcWacomStylusPage, cc_wacom_stylus_page, GTK_TYPE_BOX) + +/* Button combo box storage columns */ +enum { + BUTTONNUMBER_COLUMN, + BUTTONNAME_COLUMN, + N_BUTTONCOLUMNS +}; + +/* GSettings stores pressurecurve as 4 values like the driver. We map slider + * scale to these values given the array below. These settings were taken from + * wacomcpl, where they've been around for years. + */ +#define N_PRESSURE_CURVES 7 +static const gint32 PRESSURE_CURVES[N_PRESSURE_CURVES][4] = { + { 0, 75, 25, 100 }, /* soft */ + { 0, 50, 50, 100 }, + { 0, 25, 75, 100 }, + { 0, 0, 100, 100 }, /* neutral */ + { 25, 0, 100, 75 }, + { 50, 0, 100, 50 }, + { 75, 0, 100, 25 } /* firm */ +}; + +static void +set_pressurecurve (GtkRange *range, GSettings *settings, const gchar *key) +{ + gint slider_val = gtk_range_get_value (range); + GVariant *values[4], + *array; + int i; + + for (i = 0; i < G_N_ELEMENTS (values); i++) + values[i] = g_variant_new_int32 (PRESSURE_CURVES[slider_val][i]); + + array = g_variant_new_array (G_VARIANT_TYPE_INT32, values, G_N_ELEMENTS (values)); + + g_settings_set_value (settings, key, array); +} + +static void +tip_feel_value_changed_cb (CcWacomStylusPage *page) +{ + set_pressurecurve (GTK_RANGE (WID ("scale-tip-feel")), page->stylus_settings, "pressure-curve"); +} + +static void +eraser_feel_value_changed_cb (CcWacomStylusPage *page) +{ + set_pressurecurve (GTK_RANGE (WID ("scale-eraser-feel")), page->stylus_settings, "eraser-pressure-curve"); +} + +static void +set_feel_from_gsettings (GtkAdjustment *adjustment, GSettings *settings, const gchar *key) +{ + GVariant *variant; + const gint32 *values; + gsize nvalues; + int i; + + variant = g_settings_get_value (settings, key); + values = g_variant_get_fixed_array (variant, &nvalues, sizeof (gint32)); + + if (nvalues != 4) { + g_warning ("Invalid pressure curve format, expected 4 values (got %"G_GSIZE_FORMAT")", nvalues); + return; + } + + for (i = 0; i < N_PRESSURE_CURVES; i++) { + if (memcmp (PRESSURE_CURVES[i], values, sizeof (gint32) * 4) == 0) { + gtk_adjustment_set_value (adjustment, i); + break; + } + } +} + +static void +set_button_mapping_from_gsettings (GtkComboBox *combo, GSettings* settings, const gchar *key) +{ + GDesktopStylusButtonAction action; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean valid; + + action = g_settings_get_enum (settings, key); + model = gtk_combo_box_get_model (combo); + valid = gtk_tree_model_get_iter_first (model, &iter); + + while (valid) { + gint button; + + gtk_tree_model_get (model, &iter, + BUTTONNUMBER_COLUMN, &button, + -1); + + /* Currently button values match logical X buttons. If we + * introduce things like double-click, this code must + * change. Recommendation: use negative buttons numbers for + * special ones. + */ + + if (button == action) { + gtk_combo_box_set_active_iter (combo, &iter); + break; + } + + valid = gtk_tree_model_iter_next (model, &iter); + } +} + +static void +button_changed_cb (CcWacomStylusPage *page) +{ + GtkTreeIter iter; + GtkListStore *liststore; + gint mapping_b2, + mapping_b3, + mapping_b4; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (WID ("combo-bottombutton")), &iter)) + return; + + liststore = GTK_LIST_STORE (WID ("liststore-buttons")); + gtk_tree_model_get (GTK_TREE_MODEL (liststore), &iter, + BUTTONNUMBER_COLUMN, &mapping_b2, + -1); + + if (cc_wacom_tool_get_num_buttons (page->stylus) > 1) { + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (WID ("combo-topbutton")), &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (liststore), &iter, + BUTTONNUMBER_COLUMN, &mapping_b3, + -1); + } else { + mapping_b3 = 0; + } + + if (cc_wacom_tool_get_num_buttons (page->stylus) > 2) { + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (WID ("combo-thirdbutton")), &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (liststore), &iter, + BUTTONNUMBER_COLUMN, &mapping_b4, + -1); + } else { + mapping_b4 = 0; + } + + g_settings_set_enum (page->stylus_settings, "button-action", mapping_b2); + g_settings_set_enum (page->stylus_settings, "secondary-button-action", mapping_b3); + g_settings_set_enum (page->stylus_settings, "tertiary-button-action", mapping_b4); +} + +static void +combobox_text_cellrenderer (GtkComboBox *combo, int name_column) +{ + GtkCellRenderer *renderer; + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, + "text", BUTTONNAME_COLUMN, NULL); +} + +/* Boilerplate code goes below */ + +static void +cc_wacom_stylus_page_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_stylus_page_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +cc_wacom_stylus_page_dispose (GObject *object) +{ + CcWacomStylusPage *page = CC_WACOM_STYLUS_PAGE (object); + + g_clear_object (&page->builder); + + G_OBJECT_CLASS (cc_wacom_stylus_page_parent_class)->dispose (object); +} + +static void +cc_wacom_stylus_page_class_init (CcWacomStylusPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_wacom_stylus_page_get_property; + object_class->set_property = cc_wacom_stylus_page_set_property; + object_class->dispose = cc_wacom_stylus_page_dispose; +} + +static void +add_marks (GtkScale *scale) +{ + gint i; + + for (i = 0; i < N_PRESSURE_CURVES; i++) + gtk_scale_add_mark (scale, i, GTK_POS_BOTTOM, NULL); +} + +static void +cc_wacom_stylus_page_init (CcWacomStylusPage *page) +{ + g_autoptr(GError) error = NULL; + GtkComboBox *combo; + GtkWidget *box; + char *objects[] = { + "stylus-grid", + "liststore-buttons", + "adjustment-tip-feel", + "adjustment-eraser-feel", + NULL + }; + + page->builder = gtk_builder_new (); + + gtk_builder_add_objects_from_resource (page->builder, + "/org/gnome/control-center/wacom/wacom-stylus-page.ui", + objects, + &error); + if (error != NULL) { + g_warning ("Error loading UI file: %s", error->message); + return; + } + + box = WID ("stylus-grid"); + gtk_container_add (GTK_CONTAINER (page), box); + gtk_widget_set_vexpand (GTK_WIDGET (box), TRUE); + + add_marks (GTK_SCALE (WID ("scale-tip-feel"))); + add_marks (GTK_SCALE (WID ("scale-eraser-feel"))); + + g_signal_connect_object (WID ("scale-tip-feel"), "value-changed", + G_CALLBACK (tip_feel_value_changed_cb), page, G_CONNECT_SWAPPED); + g_signal_connect_object (WID ("scale-eraser-feel"), "value-changed", + G_CALLBACK (eraser_feel_value_changed_cb), page, G_CONNECT_SWAPPED); + + combo = GTK_COMBO_BOX (WID ("combo-topbutton")); + combobox_text_cellrenderer (combo, BUTTONNAME_COLUMN); + g_signal_connect_object (combo, "changed", + G_CALLBACK (button_changed_cb), page, G_CONNECT_SWAPPED); + + combo = GTK_COMBO_BOX (WID ("combo-bottombutton")); + combobox_text_cellrenderer (combo, BUTTONNAME_COLUMN); + g_signal_connect_object (combo, "changed", + G_CALLBACK (button_changed_cb), page, G_CONNECT_SWAPPED); + + combo = GTK_COMBO_BOX (WID ("combo-thirdbutton")); + combobox_text_cellrenderer (combo, BUTTONNAME_COLUMN); + g_signal_connect_object (G_OBJECT (combo), "changed", + G_CALLBACK (button_changed_cb), page, G_CONNECT_SWAPPED); + + page->nav = cc_wacom_nav_button_new (); + gtk_widget_set_halign (page->nav, GTK_ALIGN_END); + gtk_widget_set_margin_start (page->nav, 10); + gtk_widget_show (page->nav); + gtk_container_add (CWID ("navigation-placeholder"), page->nav); +} + +static void +set_icon_name (CcWacomStylusPage *page, + const char *widget_name, + const char *icon_name) +{ + g_autofree gchar *resource = NULL; + + resource = g_strdup_printf ("/org/gnome/control-center/wacom/%s.svg", icon_name); + gtk_image_set_from_resource (GTK_IMAGE (WID (widget_name)), resource); +} + +/* Different types of layout for the stylus config */ +enum { + LAYOUT_NORMAL, /* eraser, 2 buttons, tip */ + LAYOUT_INKING, /* tip */ + LAYOUT_AIRBRUSH, /* eraser, 1 button, tip */ + LAYOUT_GENERIC_2_BUTTONS_NO_ERASER, /* 2 buttons, tip, no eraser */ + LAYOUT_3DPEN, /* 3 buttons, tip, no eraser */ + LAYOUT_OTHER +}; + +static void +remove_buttons (CcWacomStylusPage *page, int n) +{ + if (n < 3) { + gtk_widget_destroy (WID ("combo-thirdbutton")); + gtk_widget_destroy (WID ("label-third-button")); + } + if (n < 2) { + gtk_widget_destroy (WID ("combo-topbutton")); + gtk_widget_destroy (WID ("label-top-button")); + gtk_label_set_text (GTK_LABEL (WID ("label-lower-button")), _("Button")); + } + if (n < 1) { + gtk_widget_destroy (WID ("combo-bottombutton")); + gtk_widget_destroy (WID ("label-lower-button")); + } +} + +static void +remove_eraser (CcWacomStylusPage *page) +{ + gtk_widget_destroy (WID ("eraser-box")); + gtk_widget_destroy (WID ("label-eraser-feel")); +} + +static void +update_stylus_ui (CcWacomStylusPage *page, + int layout) +{ + switch (layout) { + case LAYOUT_NORMAL: + remove_buttons (page, 2); + break; + case LAYOUT_INKING: + remove_buttons (page, 0); + remove_eraser (page); + gtk_container_child_set (CWID ("stylus-controls-grid"), + WID ("label-tip-feel"), + "top_attach", 0, NULL); + gtk_container_child_set (CWID ("stylus-controls-grid"), + WID ("box-tip-feel"), + "top_attach", 0, NULL); + break; + case LAYOUT_AIRBRUSH: + remove_buttons (page, 1); + gtk_container_child_set (CWID ("stylus-controls-grid"), + WID ("label-lower-button"), + "top_attach", 1, NULL); + gtk_container_child_set (CWID ("stylus-controls-grid"), + WID ("combo-bottombutton"), + "top_attach", 1, NULL); + gtk_container_child_set (CWID ("stylus-controls-grid"), + WID ("label-tip-feel"), + "top_attach", 2, NULL); + gtk_container_child_set (CWID ("stylus-controls-grid"), + WID ("box-tip-feel"), + "top_attach", 2, NULL); + break; + case LAYOUT_GENERIC_2_BUTTONS_NO_ERASER: + remove_buttons (page, 2); + remove_eraser (page); + break; + case LAYOUT_3DPEN: + remove_buttons (page, 3); + remove_eraser (page); + break; + case LAYOUT_OTHER: + /* We already warn about it in cc_wacom_stylus_page_new () */ + break; + } +} + +GtkWidget * +cc_wacom_stylus_page_new (CcWacomTool *stylus) +{ + CcWacomStylusPage *page; + guint num_buttons; + int layout; + gboolean has_eraser; + + g_return_val_if_fail (CC_IS_WACOM_TOOL (stylus), NULL); + + page = g_object_new (CC_TYPE_WACOM_STYLUS_PAGE, NULL); + + page->stylus = stylus; + + /* Icon */ + set_icon_name (page, "image-stylus", cc_wacom_tool_get_icon_name (stylus)); + + /* Settings */ + page->stylus_settings = cc_wacom_tool_get_settings (stylus); + has_eraser = cc_wacom_tool_get_has_eraser (stylus); + + /* Stylus name */ + gtk_label_set_text (GTK_LABEL (WID ("label-stylus")), cc_wacom_tool_get_name (stylus)); + + num_buttons = cc_wacom_tool_get_num_buttons (stylus); + if (num_buttons == 0 && !has_eraser) + layout = LAYOUT_INKING; + else if (num_buttons == 2 && has_eraser) + layout = LAYOUT_NORMAL; + else if (num_buttons == 1 && has_eraser) + layout = LAYOUT_AIRBRUSH; + else if (num_buttons == 2 && !has_eraser) + layout = LAYOUT_GENERIC_2_BUTTONS_NO_ERASER; + else if (num_buttons == 3 && !has_eraser) + layout = LAYOUT_3DPEN; + else { + layout = LAYOUT_OTHER; + remove_buttons (page, num_buttons); + + /* Gray out eraser if not available */ + gtk_widget_set_sensitive (WID ("eraser-box"), has_eraser); + gtk_widget_set_sensitive (WID ("label-eraser-feel"), has_eraser); + + g_warning ("The layout of this page is not known, %d buttons, %s eraser", + num_buttons, has_eraser ? "with" : "without"); + } + + update_stylus_ui (page, layout); + + if (num_buttons >= 3) + set_button_mapping_from_gsettings (GTK_COMBO_BOX (WID ("combo-thirdbutton")), + page->stylus_settings, "tertiary-button-action"); + if (num_buttons >= 2) + set_button_mapping_from_gsettings (GTK_COMBO_BOX (WID ("combo-topbutton")), + page->stylus_settings, "secondary-button-action"); + if (num_buttons >= 1) + set_button_mapping_from_gsettings (GTK_COMBO_BOX (WID ("combo-bottombutton")), + page->stylus_settings, "button-action"); + set_feel_from_gsettings (GTK_ADJUSTMENT (WID ("adjustment-tip-feel")), + page->stylus_settings, "pressure-curve"); + + if (has_eraser) + set_feel_from_gsettings (GTK_ADJUSTMENT (WID ("adjustment-eraser-feel")), + page->stylus_settings, "eraser-pressure-curve"); + + return GTK_WIDGET (page); +} + +CcWacomTool * +cc_wacom_stylus_page_get_tool (CcWacomStylusPage *page) +{ + return page->stylus; +} + +void +cc_wacom_stylus_page_set_navigation (CcWacomStylusPage *page, + GtkNotebook *notebook) +{ + g_return_if_fail (CC_IS_WACOM_STYLUS_PAGE (page)); + + g_object_set (G_OBJECT (page->nav), + "notebook", notebook, + "ignore-first", TRUE, + NULL); +} diff --git a/panels/wacom/cc-wacom-stylus-page.h b/panels/wacom/cc-wacom-stylus-page.h new file mode 100644 index 0000000..23810d9 --- /dev/null +++ b/panels/wacom/cc-wacom-stylus-page.h @@ -0,0 +1,38 @@ +/* + * Copyright © 2011 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: Peter Hutterer <peter.hutterer@redhat.com> + * Bastien Nocera <hadess@hadess.net> + */ + +#pragma once + +#include <gtk/gtk.h> +#include "cc-wacom-tool.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WACOM_STYLUS_PAGE (cc_wacom_stylus_page_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomStylusPage, cc_wacom_stylus_page, CC, WACOM_STYLUS_PAGE, GtkBox) + +GtkWidget * cc_wacom_stylus_page_new (CcWacomTool *stylus); + +CcWacomTool * cc_wacom_stylus_page_get_tool (CcWacomStylusPage *page); + +void cc_wacom_stylus_page_set_navigation (CcWacomStylusPage *page, + GtkNotebook *notebook); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-tool.c b/panels/wacom/cc-wacom-tool.c new file mode 100644 index 0000000..d54de61 --- /dev/null +++ b/panels/wacom/cc-wacom-tool.c @@ -0,0 +1,309 @@ +/* + * Copyright © 2016 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: Carlos Garnacho <carlosg@gnome.org> + * + */ + +#include "config.h" + +#include "cc-wacom-tool.h" + +enum { + PROP_0, + PROP_SERIAL, + PROP_ID, + PROP_DEVICE, + N_PROPS +}; + +static GParamSpec *props[N_PROPS] = { 0 }; + +typedef struct _CcWacomTool CcWacomTool; + +struct _CcWacomTool { + GObject parent_instance; + guint64 serial; + guint64 id; + + CcWacomDevice *device; /* Only set for tools with no serial */ + + GSettings *settings; + const WacomStylus *wstylus; +}; + +static void cc_wacom_tool_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (CcWacomTool, cc_wacom_tool, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + cc_wacom_tool_initable_iface_init)) + +static void +cc_wacom_tool_init (CcWacomTool *tool) +{ +} + +static void +cc_wacom_tool_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWacomTool *tool = CC_WACOM_TOOL (object); + + switch (prop_id) { + case PROP_SERIAL: + tool->serial = g_value_get_uint64 (value); + break; + case PROP_ID: + tool->id = g_value_get_uint64 (value); + break; + case PROP_DEVICE: + tool->device = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_wacom_tool_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcWacomTool *tool = CC_WACOM_TOOL (object); + + switch (prop_id) { + case PROP_SERIAL: + g_value_set_uint64 (value, tool->serial); + break; + case PROP_ID: + g_value_set_uint64 (value, tool->id); + break; + case PROP_DEVICE: + g_value_set_object (value, tool->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_wacom_tool_finalize (GObject *object) +{ + CcWacomTool *tool = CC_WACOM_TOOL (object); + + g_clear_object (&tool->settings); + + G_OBJECT_CLASS (cc_wacom_tool_parent_class)->finalize (object); +} + +static void +cc_wacom_tool_class_init (CcWacomToolClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = cc_wacom_tool_set_property; + object_class->get_property = cc_wacom_tool_get_property; + object_class->finalize = cc_wacom_tool_finalize; + + props[PROP_SERIAL] = + g_param_spec_uint64 ("serial", + "serial", + "serial", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + props[PROP_ID] = + g_param_spec_uint64 ("id", + "id", + "id", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + props[PROP_DEVICE] = + g_param_spec_object ("device", + "device", + "device", + CC_TYPE_WACOM_DEVICE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static gboolean +cc_wacom_tool_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + CcWacomTool *tool = CC_WACOM_TOOL (initable); + WacomDeviceDatabase *wacom_db; + gchar *path; + + wacom_db = cc_wacom_device_database_get (); + + if (tool->id == 0 && tool->device) { + const gint *ids; + gint n_supported; + + ids = cc_wacom_device_get_supported_tools (tool->device, &n_supported); + if (n_supported > 0) + tool->id = ids[0]; + } + + if (tool->id == 0) + tool->wstylus = libwacom_stylus_get_for_id (wacom_db, 0xfffff); + else + tool->wstylus = libwacom_stylus_get_for_id (wacom_db, tool->id); + + if (!tool->wstylus) { + g_set_error (error, 0, 0, "Stylus description not found"); + return FALSE; + } + + if (tool->serial == 0) { + const gchar *vendor, *product; + GsdDevice *gsd_device; + + gsd_device = cc_wacom_device_get_device (tool->device); + gsd_device_get_device_ids (gsd_device, &vendor, &product); + path = g_strdup_printf ("/org/gnome/desktop/peripherals/stylus/default-%s:%s/", + vendor, product); + } else { + path = g_strdup_printf ("/org/gnome/desktop/peripherals/stylus/%lx/", tool->serial); + } + + tool->settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet.stylus", + path); + g_free (path); + + return TRUE; +} + +static void +cc_wacom_tool_initable_iface_init (GInitableIface *iface) +{ + iface->init = cc_wacom_tool_initable_init; +} + +CcWacomTool * +cc_wacom_tool_new (guint64 serial, + guint64 id, + CcWacomDevice *device) +{ + g_return_val_if_fail (serial != 0 || CC_IS_WACOM_DEVICE (device), NULL); + + return g_initable_new (CC_TYPE_WACOM_TOOL, + NULL, NULL, + "serial", serial, + "id", id, + "device", device, + NULL); +} + +guint64 +cc_wacom_tool_get_serial (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), 0); + + return tool->serial; +} + +guint64 +cc_wacom_tool_get_id (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), 0); + + return tool->id; +} + +const gchar * +cc_wacom_tool_get_name (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), NULL); + + return libwacom_stylus_get_name (tool->wstylus); +} + +static const char * +get_icon_name_from_type (const WacomStylus *wstylus) +{ + WacomStylusType type = libwacom_stylus_get_type (wstylus); + + switch (type) { + case WSTYLUS_INKING: + case WSTYLUS_STROKE: + /* The stroke pen is the same as the inking pen with + * a different nib */ + return "wacom-stylus-inking"; + case WSTYLUS_AIRBRUSH: + return "wacom-stylus-airbrush"; + case WSTYLUS_MARKER: + return "wacom-stylus-art-pen"; + case WSTYLUS_CLASSIC: + return "wacom-stylus-classic"; +#ifdef HAVE_WACOM_3D_STYLUS + case WSTYLUS_3D: + return "wacom-stylus-3btn-no-eraser"; +#endif + default: + if (!libwacom_stylus_has_eraser (wstylus)) { + if (libwacom_stylus_get_num_buttons (wstylus) >= 3) + return "wacom-stylus-3btn-no-eraser"; + else + return "wacom-stylus-no-eraser"; + } + else { + if (libwacom_stylus_get_num_buttons (wstylus) >= 3) + return "wacom-stylus-3btn"; + else + return "wacom-stylus"; + } + } +} + +const gchar * +cc_wacom_tool_get_icon_name (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), NULL); + + return get_icon_name_from_type (tool->wstylus); +} + +GSettings * +cc_wacom_tool_get_settings (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), NULL); + + return tool->settings; +} + +guint +cc_wacom_tool_get_num_buttons (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), 0); + + return libwacom_stylus_get_num_buttons (tool->wstylus); +} + +gboolean +cc_wacom_tool_get_has_eraser (CcWacomTool *tool) +{ + g_return_val_if_fail (CC_IS_WACOM_TOOL (tool), FALSE); + + return libwacom_stylus_has_eraser (tool->wstylus); +} diff --git a/panels/wacom/cc-wacom-tool.h b/panels/wacom/cc-wacom-tool.h new file mode 100644 index 0000000..f988e1f --- /dev/null +++ b/panels/wacom/cc-wacom-tool.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2016 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: Carlos Garnacho <carlosg@gnome.org> + * + */ + +#pragma once + +#include "config.h" +#include "gsd-device-manager.h" +#include "cc-wacom-device.h" +#include <glib.h> + +#define CC_TYPE_WACOM_TOOL (cc_wacom_tool_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomTool, cc_wacom_tool, CC, WACOM_TOOL, GObject) + +CcWacomTool * cc_wacom_tool_new (guint64 serial, + guint64 id, + CcWacomDevice *device); + +guint64 cc_wacom_tool_get_serial (CcWacomTool *tool); +guint64 cc_wacom_tool_get_id (CcWacomTool *tool); + +const gchar * cc_wacom_tool_get_name (CcWacomTool *tool); +const gchar * cc_wacom_tool_get_icon_name (CcWacomTool *tool); + +GSettings * cc_wacom_tool_get_settings (CcWacomTool *tool); + +guint cc_wacom_tool_get_num_buttons (CcWacomTool *tool); +gboolean cc_wacom_tool_get_has_eraser (CcWacomTool *tool); diff --git a/panels/wacom/gnome-wacom-panel.desktop.in.in b/panels/wacom/gnome-wacom-panel.desktop.in.in new file mode 100644 index 0000000..c8358ee --- /dev/null +++ b/panels/wacom/gnome-wacom-panel.desktop.in.in @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Wacom Tablet +Comment=Set button mappings and adjust stylus sensitivity for graphics tablets +Exec=gnome-control-center wacom +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=input-tablet +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel;X-GNOME-DevicesSettings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=wacom +X-GNOME-Bugzilla-Version=@VERSION@ +# Translators: Search terms to find the Wacom Tablet panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Tablet;Wacom;Stylus;Eraser;Mouse; diff --git a/panels/wacom/gnome-wacom-properties.ui b/panels/wacom/gnome-wacom-properties.ui new file mode 100644 index 0000000..8679bd2 --- /dev/null +++ b/panels/wacom/gnome-wacom-properties.ui @@ -0,0 +1,465 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkListStore" id="liststore-tabletmode"> + <columns> + <!-- column-name tabletmode --> + <column type="guint"/> + <!-- column-name tabletmode-label --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0">0</col> + <col id="1" translatable="yes">Tablet (absolute)</col> + </row> + <row> + <col id="0">1</col> + <col id="1" translatable="yes">Touchpad (relative)</col> + </row> + </data> + </object> + <object class="GtkDialog" id="wacom_properties_dialog"> + <property name="can_focus">False</property> + <property name="vexpand">True</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Tablet Preferences</property> + <property name="resizable">False</property> + <property name="default_width">675</property> + <property name="default_height">460</property> + <property name="icon_name">input-tablet</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="helpbutton1"> + <property name="label" translatable="yes">_Help</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="closebutton1"> + <property name="label" translatable="yes">_Close</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="main-notebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="vexpand">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkGrid" id="main-box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">False</property> + <property name="valign">end</property> + <property name="vexpand">True</property> + <property name="pixel_size">96</property> + <property name="icon_name">input-tablet-symbolic</property> + <property name="icon_size">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="advice-label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes">No tablet detected</property> + <property name="justify">center</property> + <property name="yalign">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="advice-label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="vexpand">True</property> + <property name="label" translatable="yes">Please plug in or turn on your Wacom tablet</property> + <property name="justify">center</property> + <property name="yalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">3</property> + <property name="height">3</property> + </packing> + </child> + <child> + <object class="GtkLinkButton" id="linkbutton"> + <property name="label" translatable="yes">Bluetooth Settings</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">end</property> + <property name="valign">end</property> + <property name="vexpand">True</property> + <property name="relief">none</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">2</property> + </packing> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Plugin</property> + </object> + <packing> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="main-grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">10</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel" id="label-tabletmodel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Wacom Tablet</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + </packing> + </child> + <child> + <object class="GtkImage" id="image-tablet"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="pixbuf">wacom-tablet.svg</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="main-controls-grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">16</property> + <property name="orientation">vertical</property> + <property name="row_spacing">10</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel" id="label-trackingmode"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Tracking Mode</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="combo-tabletmode"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="model">liststore-tabletmode</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-left-handed"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Left-Handed Orientation</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="switch-left-handed"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">start</property> + <property name="valign">center</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="tablet-buttons-box"> + <property name="visible">True</property> + <property name="halign">start</property> + <property name="spacing">10</property> + <child> + <object class="GtkButton" id="display-mapping-button"> + <property name="label" translatable="yes">Map to Monitor…</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="map-buttons-button"> + <property name="label" translatable="yes">Map Buttons…</property> + <property name="use_action_appearance">False</property> + <property name="visible">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button-calibrate"> + <property name="label" translatable="yes">Calibrate…</property> + <property name="use_action_appearance">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLinkButton" id="mouse-link"> + <property name="label" translatable="yes">Adjust mouse settings</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + <property name="valign">start</property> + <property name="relief">none</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLinkButton" id="display-link"> + <property name="label" translatable="yes">Adjust display resolution</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + <property name="valign">start</property> + <property name="relief">none</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-decouple-display"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Decouple Display</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="switch-decouple-display"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="active">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkButton" id="display-mapping-button-2"> + <property name="label" translatable="yes">Map to Monitor…</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">6</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkRevealer" id="navigation-placeholder"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="transition_type">crossfade</property> + <property name="transition_duration">100</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Wacom</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">helpbutton1</action-widget> + <action-widget response="0">closebutton1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/panels/wacom/gsd-enums.h b/panels/wacom/gsd-enums.h new file mode 100644 index 0000000..9cfa48f --- /dev/null +++ b/panels/wacom/gsd-enums.h @@ -0,0 +1,7 @@ +/* We copy gsd-wacom-device from gnome-settings-daemon. + * It include "gsd-enums.h" because the include directory + * is known. As gnome-settings-daemon's pkg-config file + * prefixes this, we need a little help to avoid this + * one line difference */ + +#include <gnome-settings-daemon/gsd-enums.h> diff --git a/panels/wacom/gsd-wacom-key-shortcut-button.c b/panels/wacom/gsd-wacom-key-shortcut-button.c new file mode 100644 index 0000000..fcc5313 --- /dev/null +++ b/panels/wacom/gsd-wacom-key-shortcut-button.c @@ -0,0 +1,571 @@ +/* + * gsd-wacom-key-shortcut-button.c + * + * Copyright © 2013 Red Hat, Inc. + * + * Author: Joaquim Rocha <jrocha@redhat.com> + * + * 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/>. + */ + +#include "config.h" +#include <glib/gi18n-lib.h> + +#include "gsd-wacom-key-shortcut-button.h" + +/** + * SECTION:gsd-wacom-key-shortcut-button + * @short_description: A button which captures and displays a keyboard shortcut + * @title: GsdWacomKeyShortcutButton + * + * GsdWacomKeyShortcutButton is a button which, when clicked, captures a keyboard + * shortcut and displays it. + * It works in a similar way to #GtkCellRendererAccel but, being a #GtkWidget, + * can be added to e.g. containers. + */ + +#define DEFAULT_CANCEL_KEY GDK_KEY_Escape +#define DEFAULT_CLEAR_KEY GDK_KEY_BackSpace + +enum { + KEY_SHORTCUT_EDITED, + KEY_SHORTCUT_CLEARED, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_SHORTCUT_KEY_VAL, + PROP_SHORTCUT_KEY_MODS, + PROP_SHORTCUT_MODE, + PROP_SHORTCUT_CANCEL_KEY, + PROP_SHORTCUT_CLEAR_KEY, + N_PROPERTIES +}; + +struct _GsdWacomKeyShortcutButton +{ + GtkButton parent_instance; + + gboolean editing_mode; + + GdkSeat *grab_seat; + + guint keyval; + guint keycode; + GdkModifierType mods; + + /* Temporary shortcut info used for allowing + * modifier-only shortcuts */ + guint tmp_shortcut_keyval; + GdkModifierType tmp_shortcut_mods; + guint32 tmp_shortcut_time; + + GsdWacomKeyShortcutButtonMode mode; + + guint cancel_keyval; + guint clear_keyval; +}; + +G_DEFINE_TYPE (GsdWacomKeyShortcutButton, gsd_wacom_key_shortcut_button, GTK_TYPE_BUTTON); + +static guint signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +static void gsd_wacom_key_shortcut_button_changed (GsdWacomKeyShortcutButton *self); + +static void +gsd_wacom_key_shortcut_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GsdWacomKeyShortcutButton *self = GSD_WACOM_KEY_SHORTCUT_BUTTON (object); + gboolean changed = FALSE; + + switch (property_id) + { + case PROP_SHORTCUT_KEY_VAL: + self->keyval = g_value_get_uint (value); + changed = TRUE; + break; + + case PROP_SHORTCUT_KEY_MODS: + self->mods = g_value_get_uint (value); + changed = TRUE; + break; + + case PROP_SHORTCUT_MODE: + self->mode = g_value_get_enum (value); + break; + + case PROP_SHORTCUT_CANCEL_KEY: + self->cancel_keyval = g_value_get_uint (value); + break; + + case PROP_SHORTCUT_CLEAR_KEY: + self->clear_keyval = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + if (changed) + gsd_wacom_key_shortcut_button_changed (self); +} + +static void +gsd_wacom_key_shortcut_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GsdWacomKeyShortcutButton *self = GSD_WACOM_KEY_SHORTCUT_BUTTON (object); + + switch (property_id) + { + case PROP_SHORTCUT_KEY_VAL: + g_value_set_uint (value, self->keyval); + break; + + case PROP_SHORTCUT_KEY_MODS: + g_value_set_uint (value, self->mods); + break; + + case PROP_SHORTCUT_MODE: + g_value_set_enum (value, self->mode); + break; + + case PROP_SHORTCUT_CANCEL_KEY: + g_value_set_uint (value, self->cancel_keyval); + break; + + case PROP_SHORTCUT_CLEAR_KEY: + g_value_set_uint (value, self->clear_keyval); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gsd_wacom_key_shortcut_set_editing_mode (GsdWacomKeyShortcutButton *self, + GdkEvent *event) +{ + GdkWindow *window; + GdkSeat *seat; + + self->editing_mode = TRUE; + gsd_wacom_key_shortcut_button_changed (self); + + window = gtk_widget_get_window (GTK_WIDGET (self)); + + g_return_if_fail (window != NULL); + + seat = gdk_event_get_seat (event); + + if (gdk_seat_grab (seat, window, GDK_SEAT_CAPABILITY_ALL, + FALSE, NULL, event, NULL, NULL) != GDK_GRAB_SUCCESS) + return; + + gtk_widget_grab_focus (GTK_WIDGET (self)); + + self->grab_seat = seat; +} + +static void +gsd_wacom_key_shortcut_remove_editing_mode (GsdWacomKeyShortcutButton *self) +{ + self->editing_mode = FALSE; + + self->editing_mode = FALSE; + + if (self->grab_seat) + { + gdk_seat_ungrab (self->grab_seat); + self->grab_seat = NULL; + } + + self->tmp_shortcut_keyval = 0; + self->tmp_shortcut_mods = 0; + self->tmp_shortcut_time = 0; +} + +static void +gsd_wacom_key_shortcut_button_changed (GsdWacomKeyShortcutButton *self) +{ + g_autofree gchar *text = NULL; + + if (self->editing_mode) + { + gtk_button_set_label (GTK_BUTTON (self), _("New shortcut…")); + + gtk_widget_set_state_flags (GTK_WIDGET (self), + GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT, + FALSE); + + return; + } + + if (self->keyval == 0 && self->mods == 0) + { + gtk_button_set_label (GTK_BUTTON (self), ""); + return; + } + + text = gtk_accelerator_get_label (self->keyval, self->mods); + gtk_button_set_label (GTK_BUTTON (self), text); +} + +static void +gsd_wacom_key_shortcut_button_activate (GtkButton *self) +{ + gsd_wacom_key_shortcut_set_editing_mode (GSD_WACOM_KEY_SHORTCUT_BUTTON (self), NULL); + + GTK_BUTTON_CLASS (gsd_wacom_key_shortcut_button_parent_class)->activate (self); +} + +static void +gsd_wacom_key_shortcut_button_init (GsdWacomKeyShortcutButton *self) +{ + gtk_button_set_relief (GTK_BUTTON (self), GTK_RELIEF_NONE); + + self->cancel_keyval = DEFAULT_CANCEL_KEY; + self->clear_keyval = DEFAULT_CLEAR_KEY; +} + +static void +key_shortcut_finished_editing (GsdWacomKeyShortcutButton *self, + guint32 time) +{ + gdk_seat_ungrab (self->grab_seat); + self->grab_seat = NULL; + + self->editing_mode = FALSE; + + gsd_wacom_key_shortcut_remove_editing_mode (self); + + gsd_wacom_key_shortcut_button_changed (self); +} + +static gboolean +gsd_wacom_key_shortcut_button_key_release (GtkWidget *widget, + GdkEventKey *event) +{ + GsdWacomKeyShortcutButton *self = GSD_WACOM_KEY_SHORTCUT_BUTTON (widget); + + if (self->tmp_shortcut_keyval == 0) + { + GTK_WIDGET_CLASS (gsd_wacom_key_shortcut_button_parent_class)->key_release_event (widget, event); + + return FALSE; + } + + self->keyval = self->tmp_shortcut_keyval; + self->mods = self->tmp_shortcut_mods; + + key_shortcut_finished_editing (self, self->tmp_shortcut_time); + + g_signal_emit (self, signals[KEY_SHORTCUT_EDITED], 0); + + return TRUE; +} + +static gboolean +gsd_wacom_key_shortcut_button_key_press (GtkWidget *widget, + GdkEventKey *event) +{ + /* This code is based on the gtk_cell_renderer_accel_start_editing */ + GsdWacomKeyShortcutButton *self = GSD_WACOM_KEY_SHORTCUT_BUTTON (widget); + GdkModifierType mods = 0; + guint shortcut_keyval; + guint keyval; + gboolean edited; + gboolean cleared; + + /* GTK and OTHER modes don't allow modifier keyvals */ + if (event->is_modifier && self->mode != GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL) + return TRUE; + + if (!self->editing_mode) + { + GTK_WIDGET_CLASS (gsd_wacom_key_shortcut_button_parent_class)->key_press_event (widget, event); + + return FALSE; + } + + edited = FALSE; + cleared = FALSE; + + mods = event->state; + + keyval = event->keyval; + if (keyval == GDK_KEY_Sys_Req && + (mods & GDK_MOD1_MASK) != 0) + { + /* HACK: we don't want to use SysRq as a keybinding (but we do + * want Alt+Print), so we avoid translation from Alt+Print to SysRq + */ + keyval = GDK_KEY_Print; + } + + shortcut_keyval = gdk_keyval_to_lower (keyval); + + if (shortcut_keyval == GDK_KEY_ISO_Left_Tab) + shortcut_keyval = GDK_KEY_Tab; + + mods &= gtk_accelerator_get_default_mod_mask (); + + /* Put shift back if it changed the case of the key, not otherwise. + */ + if (shortcut_keyval != keyval) + mods |= GDK_SHIFT_MASK; + + if (mods == 0) + { + if (keyval == self->cancel_keyval) + { + /* cancel the edition */ + goto out; + } + else if (keyval == self->clear_keyval) + { + /* clear the current shortcut */ + cleared = TRUE; + goto out; + } + } + + self->tmp_shortcut_keyval = 0; + self->tmp_shortcut_mods = 0; + self->tmp_shortcut_time = 0; + + if (event->is_modifier) + { + /* when the user presses a non-modifier key, it readily assigns the + * shortcut but since we also support modifiers-only shortcuts, we + * cannot assign the shortcut right when the user presses a modifier + * key because the user might assign e.g. Alt, Alt+Ctrl, Alt+Ctrl+Shift, etc. + * So, we keep track of the pressed shortcut's (keyval, mods and time) if + * it is a modifier shortcut and assign them when a key-release happens */ + self->tmp_shortcut_keyval = shortcut_keyval; + self->tmp_shortcut_mods = mods; + self->tmp_shortcut_time = event->time; + + return TRUE; + } + + edited = TRUE; + + out: + + if (edited) + { + self->keyval = shortcut_keyval; + self->mods = mods; + } + + if (cleared) + { + self->keyval = 0; + self->mods = 0; + } + + key_shortcut_finished_editing (GSD_WACOM_KEY_SHORTCUT_BUTTON (widget), event->time); + + if (edited) + g_signal_emit (self, signals[KEY_SHORTCUT_EDITED], 0); + else if (cleared) + g_signal_emit (self, signals[KEY_SHORTCUT_CLEARED], 0); + + return TRUE; +} + +static gboolean +gsd_wacom_key_shortcut_button_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GsdWacomKeyShortcutButton *self; + + self = GSD_WACOM_KEY_SHORTCUT_BUTTON (widget); + + if (self->editing_mode) + return TRUE; + + gsd_wacom_key_shortcut_set_editing_mode (self, NULL); + + GTK_WIDGET_CLASS (gsd_wacom_key_shortcut_button_parent_class)->button_press_event (widget, + event); + + return TRUE; +} + +static void +gsd_wacom_key_shortcut_button_unrealize (GtkWidget *widget) +{ + GsdWacomKeyShortcutButton *self; + + self = GSD_WACOM_KEY_SHORTCUT_BUTTON (widget); + + gsd_wacom_key_shortcut_remove_editing_mode (self); + + GTK_WIDGET_CLASS (gsd_wacom_key_shortcut_button_parent_class)->unrealize (widget); +} + +static void +gsd_wacom_key_shortcut_button_class_init (GsdWacomKeyShortcutButtonClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); + + gobject_class->set_property = gsd_wacom_key_shortcut_button_set_property; + gobject_class->get_property = gsd_wacom_key_shortcut_button_get_property; + + obj_properties[PROP_SHORTCUT_KEY_VAL] = + g_param_spec_uint ("key-value", + "The key value", + "The key value of the shortcut currently set", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SHORTCUT_KEY_MODS] = + g_param_spec_uint ("key-mods", + "The key modifiers", + "The key modifiers of the shortcut currently set", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SHORTCUT_CANCEL_KEY] = + g_param_spec_uint ("cancel-key", + "The cancel key", + "The key which cancels the edition of the shortcut", + 0, + G_MAXUINT, + DEFAULT_CANCEL_KEY, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_SHORTCUT_CLEAR_KEY] = + g_param_spec_uint ("clear-key", + "The clear key", + "The key which clears the currently set shortcut", + 0, + G_MAXUINT, + DEFAULT_CLEAR_KEY, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * GsdWacomKeyShortcutButton:mode: + * + * Determines which type of keys are allowed in the captured shortcuts. + * %GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL is the same as + * %GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER but allows shortcuts composed of + * only modifier keys. + */ + obj_properties[PROP_SHORTCUT_MODE] = + g_param_spec_enum ("mode", + "The shortcut mode", + "The mode with which the shortcuts are captured", + GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON_MODE, + GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + N_PROPERTIES, + obj_properties); + + widget_class->key_press_event = gsd_wacom_key_shortcut_button_key_press; + widget_class->button_press_event = gsd_wacom_key_shortcut_button_button_press; + widget_class->key_release_event = gsd_wacom_key_shortcut_button_key_release; + widget_class->unrealize = gsd_wacom_key_shortcut_button_unrealize; + + button_class->activate = gsd_wacom_key_shortcut_button_activate; + + /** + * GsdWacomKeyShortcutButton::key-shortcut-edited: + * @keyshortcutbutton: the #GsdWacomKeyShortcutButton + * + * Emitted when the key shortcut of the @keyshortcutbutton is edited. + * + * The new shortcut can be retrieved by using the #GsdWacomKeyShortcutButton:key-value + * and #GsdWacomKeyShortcutButton:key-mods properties. + */ + signals[KEY_SHORTCUT_EDITED] = g_signal_new ("key-shortcut-edited", + GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * GsdWacomKeyShortcutButton::key-shortcut-cleared: + * @keyshortcutbutton: the #GsdWacomKeyShortcutButton + * + * Emitted when the key shortcut of the @keyshortcutbutton is cleared. + */ + signals[KEY_SHORTCUT_CLEARED] = g_signal_new ("key-shortcut-cleared", + GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/** + * gsd_wacom_key_shortcut_button_new: + * + * Creates a new #GsdWacomKeyShortcutButton. + * + * Returns: a new #GsdWacomKeyShortcutButton object. + * + * Since: 3.10 + */ +GtkWidget * +gsd_wacom_key_shortcut_button_new (void) +{ + return g_object_new (GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON, NULL); +} + +GType +gsd_wacom_key_shortcut_button_mode_type (void) +{ + static GType enum_type_id = 0; + if (G_UNLIKELY (!enum_type_id)) + { + static const GEnumValue values[] = + { + { GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER, "OTHER", "other" }, + { GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL, "ALL", "all" }, + { 0, NULL, NULL } + }; + enum_type_id = g_enum_register_static ("GsdWacomKeyShortcutButtonMode", values); + } + return enum_type_id; +} diff --git a/panels/wacom/gsd-wacom-key-shortcut-button.h b/panels/wacom/gsd-wacom-key-shortcut-button.h new file mode 100644 index 0000000..f4d5525 --- /dev/null +++ b/panels/wacom/gsd-wacom-key-shortcut-button.h @@ -0,0 +1,40 @@ +/* + * gsd-wacom-key-shortcut-button.h + * + * Copyright © 2013 Red Hat, Inc. + * + * Author: Joaquim Rocha <jrocha@redhat.com> + * + * 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/>. + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON (gsd_wacom_key_shortcut_button_get_type ()) +G_DECLARE_FINAL_TYPE (GsdWacomKeyShortcutButton, gsd_wacom_key_shortcut_button, GSD, WACOM_KEY_SHORTCUT_BUTTON, GtkButton) + +GType gsd_wacom_key_shortcut_button_mode_type (void) G_GNUC_CONST; +#define GSD_WACOM_TYPE_KEY_SHORTCUT_BUTTON_MODE (gsd_wacom_key_shortcut_button_mode_type ()) + +typedef enum +{ + GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_OTHER, + GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL +} GsdWacomKeyShortcutButtonMode; + +GtkWidget * gsd_wacom_key_shortcut_button_new (void); diff --git a/panels/wacom/meson.build b/panels/wacom/meson.build new file mode 100644 index 0000000..5f62220 --- /dev/null +++ b/panels/wacom/meson.build @@ -0,0 +1,108 @@ +deps = wacom_deps + [ + gnome_desktop_dep, + gnome_settings_dep, + x11_dep, + xi_dep +] + +cflags += ['-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)] + +test_cflags = cflags + ['-DFAKE_AREA'] + +wacom_gresource = gnome.compile_resources( + 'cc-' + cappletname + '-resources', + cappletname + '.gresource.xml', + source_dir : '.', + c_name : 'cc_' + cappletname, + dependencies : resource_data, + export : true +) + +subdir('calibrator') + +panels_list += cappletname +desktop = 'gnome-@0@-panel.desktop'.format(cappletname) + +desktop_in = configure_file( + input : desktop + '.in.in', + output : desktop + '.in', + configuration : desktop_conf +) + +i18n.merge_file( + desktop, + type : 'desktop', + input : desktop_in, + output : desktop, + po_dir : po_dir, + install : true, + install_dir : control_center_desktopdir +) + +common_sources = files( + 'cc-tablet-tool-map.c', + 'cc-wacom-button-row.c', + 'cc-wacom-device.c', + 'cc-wacom-mapping-panel.c', + 'cc-wacom-nav-button.c', + 'cc-wacom-page.c', + 'cc-wacom-stylus-page.c', + 'cc-wacom-tool.c', + 'gsd-wacom-key-shortcut-button.c' +) + +resource_data = files( + 'calibrator/calibrator.ui', + 'calibrator/calibrator.css', + 'calibrator/target.svg', + 'button-mapping.ui', + 'gnome-wacom-properties.ui', + 'wacom-stylus-3btn.svg', + 'wacom-stylus-3btn-no-eraser.svg', + 'wacom-stylus-airbrush.svg', + 'wacom-stylus-art-pen.svg', + 'wacom-stylus-classic.svg', + 'wacom-stylus-inking.svg', + 'wacom-stylus-no-eraser.svg', + 'wacom-stylus-page.ui', + 'wacom-stylus.svg', + 'wacom-tablet-cintiq.svg', + 'wacom-tablet-pc.svg', + 'wacom-tablet.svg' +) + +common_sources += wacom_gresource + +sources = common_sources + files( + 'cc-' + cappletname + '-panel.c', + 'cc-drawing-area.c' +) + +deps += libdevice_dep + +incs = [ + top_inc, + calibrator_inc +] + +panels_libs += static_library( + cappletname + '-properties', + sources : sources, + include_directories : incs, + dependencies : deps, + c_args : cflags, + link_with : [ libwacom_calibrator ] +) + +name = 'test-wacom' + +sources = common_sources + files(name + '.c') + +executable( + name, + sources, + include_directories : incs, + dependencies : deps, + c_args : test_cflags, + link_with : [ libwacom_calibrator_test ] +) diff --git a/panels/wacom/test-wacom.c b/panels/wacom/test-wacom.c new file mode 100644 index 0000000..a8d4f69 --- /dev/null +++ b/panels/wacom/test-wacom.c @@ -0,0 +1,160 @@ + +#include "config.h" + +#include <glib/gi18n.h> + +#include "cc-wacom-page.h" + +#define FIXED_WIDTH 675 + +void +cc_wacom_panel_switch_to_panel (CcWacomPanel *self, const char *panel) +{ + g_message ("Should launch %s preferences here", panel); +} + +GDBusProxy * +cc_wacom_panel_get_gsd_wacom_bus_proxy (CcWacomPanel *self) +{ + g_message ("Should get the g-s-d wacom dbus proxy here"); + + return NULL; +} + +static void +add_page (GList *devices, + GtkWidget *notebook) +{ + GtkWidget *widget; + CcWacomDevice *stylus = NULL; + GList *l; + + if (devices == NULL) + return; + + for (l = devices; l ; l = l->next) { + stylus = l->data; + } + g_list_free (devices); + + widget = cc_wacom_page_new (NULL, stylus); + cc_wacom_page_set_navigation (CC_WACOM_PAGE (widget), GTK_NOTEBOOK (notebook), FALSE); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), widget, NULL); + gtk_widget_show (widget); +} + +static gboolean +delete_event_cb (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + gtk_main_quit (); + + return FALSE; +} + +static GList * +create_fake_cintiq (void) +{ + CcWacomDevice *device; + GList *devices; + + device = cc_wacom_device_new_fake ("Wacom Cintiq 21UX2"); + devices = g_list_prepend (NULL, device); + + return devices; +} + +static GList * +create_fake_bt (void) +{ + CcWacomDevice *device; + GList *devices; + + device = cc_wacom_device_new_fake ("Wacom Graphire Wireless"); + devices = g_list_prepend (NULL, device); + + return devices; +} + +static GList * +create_fake_x201 (void) +{ + CcWacomDevice *device; + GList *devices; + + device = cc_wacom_device_new_fake ("Wacom Serial Tablet WACf004"); + devices = g_list_prepend (NULL, device); + + return devices; +} + +static GList * +create_fake_intuos4 (void) +{ + CcWacomDevice *device; + GList *devices; + + device = cc_wacom_device_new_fake ("Wacom Intuos4 6x9"); + devices = g_list_prepend (NULL, device); + + return devices; +} + +static GList * +create_fake_h610pro (void) +{ + CcWacomDevice *device; + GList *devices; + + device = cc_wacom_device_new_fake ("Huion H610 Pro"); + devices = g_list_prepend (NULL, device); + + return devices; +} + +int main (int argc, char **argv) +{ + GtkWidget *window, *notebook; + GList *devices; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_resizable (GTK_WINDOW (window), FALSE); + gtk_window_set_default_size (GTK_WINDOW (window), FIXED_WIDTH, -1); + g_signal_connect (G_OBJECT (window), "delete-event", + G_CALLBACK (delete_event_cb), NULL); + notebook = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE); + gtk_widget_set_vexpand (notebook, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (notebook), 24); + gtk_container_add (GTK_CONTAINER (window), notebook); + gtk_widget_show (notebook); + + devices = create_fake_intuos4 (); + add_page (devices, notebook); + + devices = create_fake_cintiq (); + add_page (devices, notebook); + + devices = create_fake_bt (); + add_page (devices, notebook); + + devices = create_fake_x201 (); + add_page (devices, notebook); + + devices = create_fake_h610pro (); + add_page (devices, notebook); + + gtk_widget_show (window); + + gtk_main (); + + return 0; +} diff --git a/panels/wacom/wacom-stylus-3btn-no-eraser.svg b/panels/wacom/wacom-stylus-3btn-no-eraser.svg new file mode 100644 index 0000000..60642d7 --- /dev/null +++ b/panels/wacom/wacom-stylus-3btn-no-eraser.svg @@ -0,0 +1,132 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.91 r13725" + width="148" + height="192" + sodipodi:docname="wacom-stylus-3btn-no-eraser.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="960" + inkscape:window-height="1014" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="2.4748737" + inkscape:cx="176.8759" + inkscape:cy="81.687362" + inkscape:window-x="1920" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g10631" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="translate(592.43375,-287.62088)" + id="g10545"> + <path + sodipodi:nodetypes="cscscccccccccccccscscc" + inkscape:connector-curvature="0" + id="rect10526" + transform="translate(-928.4063,-95.84375)" + d="m 344.125,384.88832 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 L 340.53125,516 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 1.53125,0 0,2.15625 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 0,-2.1875 1.53125,0 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 l 0,-127.51793 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10541" + width="8.75" + height="22" + x="-579.65631" + y="385.90625" + rx="3.25" + ry="3.25" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10543" + width="8.75" + height="12.25" + x="-579.65631" + y="410.90625" + rx="3.25" + ry="3.2500002" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10544" + width="8.75" + height="8.75" + x="-579.65631" + y="429.15625" + rx="3.25" + ry="3.2500002" /> + </g> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,413.06786 35.28033,0 0,-89.17947 82.46967,0" + id="path3342" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,430.06786 48.28033,0 0,-64.17947 69.46967,0" + id="path3344" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,448.31786 61.28033,0 0,-40.43396 56.46967,0" + id="path3344" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -575.4063,492.06786 78.53033,0 0,-43.17947 43.46967,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + </g> +</svg> diff --git a/panels/wacom/wacom-stylus-3btn.svg b/panels/wacom/wacom-stylus-3btn.svg new file mode 100644 index 0000000..2f3db9a --- /dev/null +++ b/panels/wacom/wacom-stylus-3btn.svg @@ -0,0 +1,138 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.48.2 r9819" + width="148" + height="192" + sodipodi:docname="wacom-stylus-3btn.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1280" + inkscape:window-height="742" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="7" + inkscape:cx="93.888956" + inkscape:cy="186.10424" + inkscape:window-x="0" + inkscape:window-y="26" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="translate(592.43375,-287.62088)" + id="g10545"> + <path + sodipodi:nodetypes="sscsscscccccccccccccscsscsss" + inkscape:connector-curvature="0" + id="rect10526" + transform="translate(-928.4063,-95.84375)" + d="m 349.69531,384.96463 c -2.3083,0 -3.2326,1.49535 -3.69531,4.51323 L 345.53125,396 344.125,396 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 L 340.53125,516 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 1.53125,0 0,2.15625 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 0,-2.1875 1.53125,0 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 l 0,-116.40625 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 l -1.40625,0 L 360,389.47786 c -0.23272,-2.85711 -1.26201,-4.51323 -3.69531,-4.51323 z" + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10541" + width="8.75" + height="22" + x="-579.65631" + y="385.90625" + rx="3.25" + ry="3.25" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10543" + width="8.75" + height="12.25" + x="-579.65631" + y="410.90625" + rx="3.25" + ry="3.2500002" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10544" + width="8.75" + height="8.75" + x="-579.65631" + y="429.15625" + rx="3.25" + ry="3.2500002" /> + </g> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -568.1563,309.03125 32.25,0 0,14.85714 82.5,0" + id="path10556" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,413.06786 35.28033,0 0,-52.14286 82.46967,0" + id="path10552" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,430.06786 48.28033,0 0,-27.14286 69.46967,0" + id="path86913" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,448.31786 61.28033,0 0,-3.39286 56.46967,0" + id="path3344" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -575.4063,492.06786 78.53033,0 0,-5.14286 43.46967,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + </g> +</svg> diff --git a/panels/wacom/wacom-stylus-airbrush.svg b/panels/wacom/wacom-stylus-airbrush.svg new file mode 100644 index 0000000..7d35a51 --- /dev/null +++ b/panels/wacom/wacom-stylus-airbrush.svg @@ -0,0 +1,94 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.48.2 r9819" + width="148" + height="192" + sodipodi:docname="wacom-stylus-airbrush.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1037" + inkscape:window-height="741" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="7" + inkscape:cx="111.67742" + inkscape:cy="-1.9993707" + inkscape:window-x="241" + inkscape:window-y="26" + inkscape:window-maximized="0" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 384.35014,508.88388 8.18019,0 0,-68.25798 82.46967,0" + id="path10552" + inkscape:connector-curvature="0" + transform="translate(-928.4063,-79.84375)" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path10556" + d="m -553.62983,305.002 16.78159,0 0,18.8605 83.44194,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -557.78929,491.98468 42.82049,0 0,-88.95343 61.5625,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + </g> + <path + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 36.76199,1.22591 c -1.43276,0 -2.59423,1.88644 -2.59423,4.21347 0,0.06 -0.002,0.11387 0,0.17315 l -0.9224,0 -9.22393,88.19416 c 0.9224,9.23499 -17.8868963,29.69978 -17.8868963,29.69978 -4.0295528,5.27125 -3.9083456,22.05579 3.5898163,27.55716 l 12.4523,8.31149 5.07316,15.23774 0.5765,0 c 0.4809,6.59867 3.7818,16.16123 5.30375,16.16123 2.11004,0 4.72727,-11.12955 4.72727,-19.16261 0,-0.1448 0.002,-0.28896 0,-0.43293 0,0 5.38613,-9.2445 8.12859,-20.17264 6.283146,-2.81141 5.782898,-13.64962 0.49916,-16.4982 l -0.0094,-3.91974 c 0.83292,-0.84067 1.75859,-2.00027 1.75859,-3.53531 0,-1.88597 -0.81769,-3.49443 -1.98891,-4.21347 L 45.09235,108.09211 40.62451,5.61253 l -1.26829,0 c 0.002,-0.0591 0,-0.11322 0,-0.17315 0,-2.32703 -1.16148,-4.21347 -2.59423,-4.21347 z" + id="path5306" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssccccccccssccccsccccss" /> +</svg> diff --git a/panels/wacom/wacom-stylus-art-pen.svg b/panels/wacom/wacom-stylus-art-pen.svg new file mode 100644 index 0000000..e1b5b83 --- /dev/null +++ b/panels/wacom/wacom-stylus-art-pen.svg @@ -0,0 +1,127 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.48.2 r9819" + width="148" + height="192" + sodipodi:docname="wacom-stylus-art-pen.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347"> + <inkscape:path-effect + effect="spiro" + id="path-effect31574" + is_visible="true" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1037" + inkscape:window-height="741" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="4" + inkscape:cx="84.005601" + inkscape:cy="140.80529" + inkscape:window-x="241" + inkscape:window-y="26" + inkscape:window-maximized="0" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="translate(592.43375,-287.62088)" + id="g10545"> + <path + sodipodi:nodetypes="sscsscsccccccccccccccscsscsss" + inkscape:connector-curvature="0" + id="rect10526" + transform="translate(-928.4063,-95.84375)" + d="m 351.16741,385.06603 c -2.25232,0 -3.61834,1.53072 -4.16741,4.08946 L 345.53125,396 344.125,396 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 L 340.53125,516 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 1.625,6.6639 2.20868,0 -1.40241,1.58029 c 0,0 3.53964,7.3799 5.83774,7.46829 l 1.42852,-6.9e-4 0.0512,7.31672 3.97602,-2.37893 -0.0292,-4.98639 1.38503,0.0197 c 2.82843,0 5.42789,-7.64858 5.42789,-7.64858 l -1.5299,-1.37038 2.20868,0 1.65625,-6.72641 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 l 0,-116.40625 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 l -1.40625,0 L 359,389.15549 c -0.53643,-2.39779 -1.88996,-4.08946 -4.16741,-4.08946 z" + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10541" + width="9.0059462" + height="35.214661" + x="-579.93378" + y="385.90622" + rx="3.25" + ry="3.2499998" /> + </g> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 357.25,492.91161 35.28033,0 0,-52.04275 82.46967,0" + id="path10552" + inkscape:connector-curvature="0" + transform="translate(-928.4063,-79.84375)" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path10556" + d="m -568.1563,309.03125 32.25,0 0,14.94921 82.5,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -577.26246,492.06786 41.38649,0 0,-48.24593 82.46967,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path86913" + d="m -571.1563,430.06786 48.10785,0 0,-27.00932 69.64215,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <path + style="opacity:0.5;color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 12.427494,118.46089 8.909363,0" + id="path31572" + inkscape:path-effect="#path-effect31574" + inkscape:original-d="m 12.427494,118.46089 8.909363,0" + inkscape:connector-curvature="0" /> +</svg> diff --git a/panels/wacom/wacom-stylus-classic.svg b/panels/wacom/wacom-stylus-classic.svg new file mode 100644 index 0000000..d07906e --- /dev/null +++ b/panels/wacom/wacom-stylus-classic.svg @@ -0,0 +1,103 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.48.2 r9819" + width="148" + height="192" + sodipodi:docname="wacom-stylus-classic.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347"> + <inkscape:path-effect + effect="spiro" + id="path-effect14408" + is_visible="true" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1037" + inkscape:window-height="741" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="6" + inkscape:cx="72.723556" + inkscape:cy="29.510722" + inkscape:window-x="241" + inkscape:window-y="26" + inkscape:window-maximized="0" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)" /> + <path + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 9.320334,143.05408 c 0.5048552,10.77062 -1.425426,24.64726 -1.425426,24.64726 l 1.559872,1.05225 c 0,0 1.362729,5.49117 1.850845,8.2077 0.488109,2.71653 3.679579,7.5712 3.679579,7.5712 l 1.545431,1e-5 -8e-6,3.89851 c -0.03586,1.30926 1.983788,1.38546 1.983788,9e-5 l 8e-6,-3.89851 1.451666,0 c 0,0 3.191479,-4.85469 3.679602,-7.57122 0.488109,-2.71652 1.850829,-8.20768 1.850829,-8.20768 l 1.559865,-1.05226 c 0,0 -1.786488,-11.863 -1.545414,-21.63127 0.02477,-1.0038 2.597475,-2.07462 2.650333,-3.20226 0.270987,-5.78108 0.707625,-18.64287 1.089793,-26.63126 0.05442,-1.13744 -1.422603,-2.29772 -1.371031,-3.48097 0.344388,-7.90158 0.611194,-10.82762 0.611194,-20.817562 0,-18.010758 -2.769382,-60.275057 -4.161591,-77.215202 -0.04797,-0.583659 -1.395782,-0.703789 -1.431722,-1.120751 -0.194665,-2.258415 -1.008414,-7.2278662 -1.084445,-7.5212464 -0.488463,-1.884831 -2.23225,-3.5368788 -4.278453,-3.4701457 -0.03684,-0.00219 -0.07529,0.00133 -0.11193,0 -2.047734,-0.068768 -3.789705,1.5842322 -4.278452,3.470141 -0.07358,0.2839198 -0.882492,5.1712001 -1.068191,7.3053231 -0.04236,0.486815 -1.400479,0.674009 -1.457914,1.373508 -1.3931925,16.967387 -4.1585528,59.180639 -4.158534,77.178329 2.3e-5,21.992366 2.3554437,40.345416 2.860306,51.116016 z" + id="path14406" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccsccccccsccssssssssccssscs" /> + <path + style="opacity:0.2;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" + d="m 28.911333,120.55337 27.646447,0 0,-61.96967 82.46967,0" + id="path10552" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path10556" + d="m 24.27745,6.54709 32.25,0 0,15 82.5,0" + style="opacity:0.2;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" /> + <path + style="opacity:0.2;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" + d="m 17.02745,189.5837 64.53033,0 0,-48 57.46967,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path86913" + d="m 28.02745,138.52564 41.53033,0 0,-37.94194 69.46967,0" + style="opacity:0.2;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" /> +</svg> diff --git a/panels/wacom/wacom-stylus-inking.svg b/panels/wacom/wacom-stylus-inking.svg new file mode 100644 index 0000000..aca5ca4 --- /dev/null +++ b/panels/wacom/wacom-stylus-inking.svg @@ -0,0 +1,87 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.48.2 r9819" + width="148" + height="192" + sodipodi:docname="wacom-stylus-inking.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347"> + <inkscape:path-effect + effect="spiro" + id="path-effect14408" + is_visible="true" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1280" + inkscape:window-height="742" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="7" + inkscape:cx="100.36114" + inkscape:cy="19.260911" + inkscape:window-x="0" + inkscape:window-y="26" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 353.5,569.875 38.0625,0 0,-166.14286 83.4375,0" + id="path10552" + inkscape:connector-curvature="0" + transform="translate(-928.4063,-79.84375)" + sodipodi:nodetypes="cccc" /> + </g> + <path + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 9.320334,142.86781 c 0.5052639,10.77933 -1.425426,24.83353 -1.425426,24.83353 l 1.559872,1.05225 c 0,0 1.362729,5.49117 1.850845,8.2077 0.488109,2.71653 3.679579,7.5712 3.679579,7.5712 l 1.545431,1e-5 -8e-6,3.89851 c -0.03586,1.30926 1.983788,1.38546 1.983788,9e-5 l 8e-6,-3.89851 1.451666,0 c 0,0 3.191479,-4.85469 3.679602,-7.57122 0.488109,-2.71652 1.850829,-8.20768 1.850829,-8.20768 l 1.559865,-1.05226 c 0,0 -1.930689,-14.0542 -1.425411,-24.83354 C 26.136241,132.08855 28.49126,113.93051 28.49126,91.938108 28.491252,69.945706 24.362076,8.8089377 23.653056,6.0730443 22.956873,3.3867824 20.471645,1.0322717 17.55539,1.12738 c -0.0525,-0.00312 -0.107307,0.0019 -0.159523,0 C 14.477429,1.029372 11.994773,3.3852354 11.298202,6.0730375 10.589174,8.8089377 6.460005,69.945698 6.460028,91.938064 c 2.3e-5,21.992366 2.355035,40.150416 2.860306,50.929746 z" + id="path14406" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccsccccccsccsssccscs" /> +</svg> diff --git a/panels/wacom/wacom-stylus-no-eraser.svg b/panels/wacom/wacom-stylus-no-eraser.svg new file mode 100644 index 0000000..cca7d34 --- /dev/null +++ b/panels/wacom/wacom-stylus-no-eraser.svg @@ -0,0 +1,118 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.91 r13725" + width="148" + height="192" + sodipodi:docname="wacom-stylus-no-eraser.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="960" + inkscape:window-height="1014" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="2.4748737" + inkscape:cx="176.8759" + inkscape:cy="81.687362" + inkscape:window-x="1920" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g10631" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="translate(592.43375,-287.62088)" + id="g10545"> + <path + sodipodi:nodetypes="cscscccccccccccccscscc" + inkscape:connector-curvature="0" + id="rect10526" + transform="translate(-928.4063,-95.84375)" + d="m 344.125,384.88832 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 L 340.53125,516 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 1.53125,0 0,2.15625 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 0,-2.1875 1.53125,0 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 l 0,-127.51793 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10541" + width="8.75" + height="22" + x="-579.65631" + y="385.90625" + rx="3.25" + ry="3.25" /> + <rect + ry="3.2500002" + rx="3.25" + y="410.90625" + x="-579.65631" + height="12.25" + width="8.75" + id="rect10543" + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + sodipodi:nodetypes="cccc" + transform="translate(-928.4063,-79.84375)" + inkscape:connector-curvature="0" + id="path3342" + d="m 357.25,492.91161 35.28033,0 0,-89.17947 82.46967,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -575.4063,492.06786 65.53033,0 0,-85.17947 56.46967,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -571.1563,430.06786 48.28033,0 0,-64.17947 69.46967,0" + id="path3344" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + </g> +</svg> diff --git a/panels/wacom/wacom-stylus-page.ui b/panels/wacom/wacom-stylus-page.ui new file mode 100644 index 0000000..133f3c8 --- /dev/null +++ b/panels/wacom/wacom-stylus-page.ui @@ -0,0 +1,417 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.20.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkAdjustment" id="adjustment-eraser-feel"> + <property name="upper">6</property> + <property name="step_increment">1</property> + <property name="page_increment">3</property> + </object> + <object class="GtkAdjustment" id="adjustment-tip-feel"> + <property name="upper">6</property> + <property name="step_increment">1</property> + <property name="page_increment">3</property> + </object> + <object class="GtkListStore" id="liststore-buttons"> + <columns> + <!-- column-name button --> + <column type="gint"/> + <!-- column-name button-label --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0">0</col> + <col id="1" translatable="yes">Default</col> + </row> + <row> + <col id="0">1</col> + <col id="1" translatable="yes">Middle Mouse Button Click</col> + </row> + <row> + <col id="0">2</col> + <col id="1" translatable="yes">Right Mouse Button Click</col> + </row> + <row> + <col id="0">3</col> + <col id="1" translatable="yes">Back</col> + </row> + <row> + <col id="0">4</col> + <col id="1" translatable="yes">Forward</col> + </row> + </data> + </object> + <object class="GtkNotebook" id="stylus-notebook"> + <property name="name">stylus-notebook</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkBox" id="no-stylus-page"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">end</property> + <property name="vexpand">True</property> + <property name="pixel_size">96</property> + <property name="icon_name">input-tablet-symbolic</property> + <property name="icon_size">0</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="no-stylus-label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">No stylus found</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="vexpand">True</property> + <property name="label" translatable="yes">Please move your stylus to the proximity of the tablet to configure it</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <property name="width_chars">30</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + <child> + <object class="GtkGrid" id="stylus-grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="row_spacing">10</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel" id="label-stylus"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Stylus</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + </packing> + </child> + <child> + <object class="GtkImage" id="image-stylus"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">start</property> + <property name="pixbuf">wacom-stylus.svg</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkGrid" id="stylus-controls-grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">16</property> + <property name="margin_top">12</property> + <property name="hexpand">True</property> + <property name="orientation">vertical</property> + <property name="row_spacing">6</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel" id="label-eraser-feel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Eraser Pressure Feel</property> + <property name="justify">right</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="eraser-box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel" id="label-eraser-soft"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Soft</property> + <attributes> + <attribute name="scale" value="0.82999999999999996"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale" id="scale-eraser-feel"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="valign">center</property> + <property name="adjustment">adjustment-eraser-feel</property> + <property name="digits">0</property> + <property name="draw_value">False</property> + <property name="round_digits">0</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-eraser-firm"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Firm</property> + <attributes> + <attribute name="scale" value="0.82999999999999996"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-top-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Top Button</property> + <property name="justify">right</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="combo-topbutton"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="model">liststore-buttons</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-lower-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Lower Button</property> + <property name="justify">right</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="combo-bottombutton"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="model">liststore-buttons</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-third-button"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Lowest Button</property> + <property name="justify">right</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="combo-thirdbutton"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="model">liststore-buttons</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-tip-feel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Tip Pressure Feel</property> + <property name="justify">right</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box-tip-feel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel" id="label-tip-soft"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Soft</property> + <attributes> + <attribute name="scale" value="0.82999999999999996"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScale" id="scale-tip-feel"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="adjustment">adjustment-tip-feel</property> + <property name="digits">0</property> + <property name="draw_value">False</property> + <property name="round_digits">0</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label-tip-firm"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Firm</property> + <attributes> + <attribute name="scale" value="0.82999999999999996"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkRevealer" id="navigation-placeholder"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="transition_type">crossfade</property> + <property name="transition_duration">100</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> +</interface> diff --git a/panels/wacom/wacom-stylus.svg b/panels/wacom/wacom-stylus.svg new file mode 100644 index 0000000..63dfa6e --- /dev/null +++ b/panels/wacom/wacom-stylus.svg @@ -0,0 +1,124 @@ +<?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" + id="svg86343" + version="1.1" + inkscape:version="0.48.2 r9819" + width="148" + height="192" + sodipodi:docname="wacom-stylus.svg"> + <metadata + id="metadata86349"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86347" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1280" + inkscape:window-height="742" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="7" + inkscape:cx="93.888956" + inkscape:cy="186.10424" + inkscape:window-x="0" + inkscape:window-y="26" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="translate(592.43375,-287.62088)" + id="g10545"> + <path + sodipodi:nodetypes="sscsscscccccccccccccscsscsss" + inkscape:connector-curvature="0" + id="rect10526" + transform="translate(-928.4063,-95.84375)" + d="m 349.69531,384.96463 c -2.3083,0 -3.2326,1.49535 -3.69531,4.51323 L 345.53125,396 344.125,396 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 L 340.53125,516 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 1.53125,0 0,2.15625 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 0,-2.1875 1.53125,0 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 l 0,-116.40625 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 l -1.40625,0 L 360,389.47786 c -0.23272,-2.85711 -1.26201,-4.51323 -3.69531,-4.51323 z" + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <rect + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10541" + width="8.75" + height="22" + x="-579.65631" + y="385.90625" + rx="3.25" + ry="3.25" /> + <rect + ry="3.2500002" + rx="3.25" + y="410.90625" + x="-579.65631" + height="12.25" + width="8.75" + id="rect10543" + style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> + <g + style="opacity:0.2;display:inline" + id="g10631" + transform="translate(592.43375,-302.48416)"> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 357.25,492.91161 35.28033,0 0,-52.14286 82.46967,0" + id="path10552" + inkscape:connector-curvature="0" + transform="translate(-928.4063,-79.84375)" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path10556" + d="m -568.1563,309.03125 32.25,0 0,14.85714 82.5,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -575.4063,492.06786 39.53033,0 0,-48.14286 82.46967,0" + id="path10629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path86913" + d="m -571.1563,430.06786 48.28033,0 0,-27.14286 69.46967,0" + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> +</svg> diff --git a/panels/wacom/wacom-tablet-cintiq.svg b/panels/wacom/wacom-tablet-cintiq.svg new file mode 100644 index 0000000..e9ae5c3 --- /dev/null +++ b/panels/wacom/wacom-tablet-cintiq.svg @@ -0,0 +1,83 @@ +<?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" + id="svg86858" + version="1.1" + inkscape:version="0.48.1 r9760" + width="148" + height="95" + sodipodi:docname="wacom-tablet-cintiq.svg"> + <metadata + id="metadata86864"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86862" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1199" + inkscape:window-height="807" + id="namedview86860" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:zoom="1" + inkscape:cx="-123.95096" + inkscape:cy="-15.993957" + inkscape:window-x="2858" + inkscape:window-y="395" + inkscape:window-maximized="0" + inkscape:current-layer="svg86858" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true"> + <inkscape:grid + type="xygrid" + id="grid86892" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline;enable-background:new" + transform="matrix(0.68936116,0,0,0.68936115,465.30952,-75.491812)" + id="g4353-3"> + <path + inkscape:connector-curvature="0" + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1.45061844px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 191,253 0,122 30.45602,0 0,10 20.32523,0 0,-10 65.25887,0 0,10 20.4159,0 0,-10 31.81597,0 0,-122 z" + transform="translate(-860.90625,-139.8605)" + id="rect22528" + sodipodi:nodetypes="ccccccccccccc" /> + <rect + style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1.45061835000000006;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect4133-4" + width="126.20383" + height="79.784019" + x="-649.60083" + y="130.54378" /> + </g> +</svg> diff --git a/panels/wacom/wacom-tablet-pc.svg b/panels/wacom/wacom-tablet-pc.svg new file mode 100644 index 0000000..091b9b1 --- /dev/null +++ b/panels/wacom/wacom-tablet-pc.svg @@ -0,0 +1,77 @@ +<?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" + id="svg86858" + version="1.1" + inkscape:version="0.48.1 r9760" + width="148" + height="95" + sodipodi:docname="wacom-tablet-pc.svg"> + <metadata + id="metadata86864"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86862" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1199" + inkscape:window-height="807" + id="namedview86860" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:zoom="1" + inkscape:cx="218.7114" + inkscape:cy="-21.332194" + inkscape:window-x="1873" + inkscape:window-y="310" + inkscape:window-maximized="0" + inkscape:current-layer="svg86858" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true"> + <inkscape:grid + type="xygrid" + id="grid86892" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <path + style="color:#000000;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:0.99999994000000003px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 17.536356,5.4999999 0,59.7141961 -12.9738561,25.238058 115.6736201,0 -14.68849,-26.666627 -0.012,-58.2856271 z" + id="rect22528" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + <rect + y="14.5" + x="28.499998" + height="42" + width="66" + id="rect4133-4" + style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:0.99999994000000003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> +</svg> diff --git a/panels/wacom/wacom-tablet.svg b/panels/wacom/wacom-tablet.svg new file mode 100644 index 0000000..35e5a4b --- /dev/null +++ b/panels/wacom/wacom-tablet.svg @@ -0,0 +1,81 @@ +<?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" + id="svg86858" + version="1.1" + inkscape:version="0.48.1 r9760" + width="148" + height="95" + sodipodi:docname="wacom-tablet.svg"> + <metadata + id="metadata86864"> + <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 /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs86862" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1199" + inkscape:window-height="807" + id="namedview86860" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:zoom="1.4142136" + inkscape:cx="42.197473" + inkscape:cy="89.570991" + inkscape:window-x="1873" + inkscape:window-y="310" + inkscape:window-maximized="0" + inkscape:current-layer="g4353-0"> + <inkscape:grid + type="xygrid" + id="grid86892" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="matrix(0.59415025,0,0,0.67531282,405.44733,-74.059104)" + id="g4353-0"> + <path + style="fill:#d3d7cf;fill-opacity:1;stroke:#babdb6;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 4.2812501,3.375 3.53125,85.5 C 31.076436,90.740667 83.579166,92.281216 115,85.5 L 114.25,3.375 z" + transform="matrix(1.683076,0,0,1.4807952,-682.39865,109.66637)" + id="path3568-6" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + <rect + style="color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1.57869899;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect10889" + width="138.6996" + height="82.952255" + x="-651.99988" + y="129.88136" /> + </g> +</svg> diff --git a/panels/wacom/wacom.gresource.xml b/panels/wacom/wacom.gresource.xml new file mode 100644 index 0000000..846835f --- /dev/null +++ b/panels/wacom/wacom.gresource.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/wacom"> + <file preprocess="xml-stripblanks">gnome-wacom-properties.ui</file> + <file preprocess="xml-stripblanks">wacom-stylus-page.ui</file> + <file preprocess="xml-stripblanks">button-mapping.ui</file> + <file preprocess="xml-stripblanks">calibrator/calibrator.ui</file> + <file>calibrator/calibrator.css</file> + <file>calibrator/target.svg</file> + <file>wacom-tablet.svg</file> + <file>wacom-stylus.svg</file> + <file>wacom-stylus-3btn-no-eraser.svg</file> + <file>wacom-stylus-3btn.svg</file> + <file>wacom-stylus-no-eraser.svg</file> + <file>wacom-stylus-airbrush.svg</file> + <file>wacom-stylus-inking.svg</file> + <file>wacom-stylus-art-pen.svg</file> + <file>wacom-stylus-classic.svg</file> + <file>wacom-tablet-cintiq.svg</file> + <file>wacom-tablet-pc.svg</file> + </gresource> +</gresources> |