diff options
Diffstat (limited to 'panels/wacom')
56 files changed, 8358 insertions, 0 deletions
diff --git a/panels/wacom/button-mapping.ui b/panels/wacom/button-mapping.ui new file mode 100644 index 0000000..401aa12 --- /dev/null +++ b/panels/wacom/button-mapping.ui @@ -0,0 +1,78 @@ +<?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="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> + <child> + <object class="GtkBox" id="top_vbox"> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkBox" id="shortcuts_vbox"> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="xalign">0</property> + <property name="margin-top">12</property> + <property name="label" translatable="yes">Map buttons to functions</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkScrolledWindow" id="actions_swindow"> + <property name="hscrollbar_policy">never</property> + <child> + <object class="GtkListBox" id="shortcuts_list"> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="hbox1"> + <property name="margin-top">5</property> + <property name="margin-bottom">5</property> + <property name="margin-start">5</property> + <property name="margin-end">5</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="label12"> + <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> + <property name="hexpand">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="close_button"> + <property name="label" translatable="yes">_Close</property> + <property name="use_underline">True</property> + </object> + </child> + </object> + </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..2d5d7ec --- /dev/null +++ b/panels/wacom/calibrator/calibrator-gui.c @@ -0,0 +1,429 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + * (based on previous work by Joaquim Rocha, Tias Guns and Soren Hauberg) + */ + +#include "config.h" + +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib/gi18n.h> +#include <gdk/x11/gdkx.h> +#include <gtk/gtk.h> + +#include "calibrator.h" +#include "calibrator-gui.h" +#include "cc-clock.h" + +struct _CcCalibArea +{ + GtkWindow parent_instance; + + struct Calib calibrator; + XYinfo axis; + gboolean swap; + gboolean success; + GdkDevice *device; + + GtkWidget *error_revealer; + GtkWidget *title_revealer; + GtkWidget *subtitle_revealer; + GtkWidget *clock; + GtkWidget *target1, *target2, *target3, *target4; + GtkWidget *stack; + GtkWidget *success_page; + GtkCssProvider *style_provider; + + FinishCallback callback; + gpointer user_data; +}; + +G_DEFINE_TYPE (CcCalibArea, cc_calib_area, GTK_TYPE_WINDOW) + +/* Timeout parameters */ +#define MAX_TIME 15000 /* 15000 = 15 sec */ +#define END_TIME 750 /* 750 = 0.75 sec */ + +static void +cc_calib_area_notify_finish (CcCalibArea *area) +{ + gtk_widget_hide (GTK_WIDGET (area)); + + (*area->callback) (area, area->user_data); +} + +static gboolean +on_close_request (GtkWidget *widget, + CcCalibArea *area) +{ + cc_calib_area_notify_finish (area); + return GDK_EVENT_PROPAGATE; +} + +static gboolean +cc_calib_area_finish_idle_cb (CcCalibArea *area) +{ + cc_calib_area_notify_finish (area); + return FALSE; +} + +static void +set_success (CcCalibArea *area) +{ + gtk_stack_set_visible_child (GTK_STACK (area->stack), area->success_page); +} + +static void +set_calibration_status (CcCalibArea *area) +{ + area->success = finish (&area->calibrator, &area->axis, &area->swap); + + if (area->success) + { + set_success (area); + g_timeout_add (END_TIME, + (GSourceFunc) cc_calib_area_finish_idle_cb, + area); + } + else + { + g_idle_add ((GSourceFunc) cc_calib_area_finish_idle_cb, area); + } +} + +static void +show_error_message (CcCalibArea *area) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), TRUE); +} + +static void +hide_error_message (CcCalibArea *area) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), FALSE); +} + +static void +set_active_target (CcCalibArea *area, + int n_target) +{ + GtkWidget *targets[] = { + area->target1, + area->target2, + area->target3, + area->target4, + }; + int i; + + for (i = 0; i < G_N_ELEMENTS (targets); i++) + gtk_widget_set_sensitive (targets[i], i == n_target); +} + +static void +on_gesture_press (GtkGestureClick *gesture, + guint n_press, + gdouble x, + gdouble y, + CcCalibArea *area) +{ + gint num_clicks; + gboolean success; + GdkDevice *source; + + if (area->success) + return; + + source = gtk_gesture_get_device (GTK_GESTURE (gesture)); + + if (gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN) + return; + + /* Check matching device if a device was provided */ + if (area->device && area->device != source) + { + g_debug ("Ignoring input from device %s", + gdk_device_get_name (source)); + return; + } + + /* Handle click */ + /* FIXME: reset clock */ + success = add_click(&area->calibrator, + (int) x, + (int) y); + + num_clicks = area->calibrator.num_clicks; + + if (!success && num_clicks == 0) + show_error_message (area); + else + hide_error_message (area); + + /* Are we done yet? */ + if (num_clicks >= 4) + { + set_calibration_status (area); + return; + } + + set_active_target (area, num_clicks); +} + +static gboolean +on_key_release (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + CcCalibArea *area) +{ + if (area->success || keyval != GDK_KEY_Escape) + return GDK_EVENT_PROPAGATE; + + cc_calib_area_notify_finish (area); + return GDK_EVENT_STOP; +} + +static void +on_clock_finished (CcClock *clock, + CcCalibArea *area) +{ + set_calibration_status (area); +} + +static void +on_title_revealed (CcCalibArea *area) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (area->subtitle_revealer), TRUE); +} + +static void +on_fullscreen (GtkWindow *window, + GParamSpec *pspec, + CcCalibArea *area) +{ + if (!gtk_window_is_fullscreen (window)) + return; + + g_signal_connect_swapped (area->title_revealer, + "notify::child-revealed", + G_CALLBACK (on_title_revealed), + area); + gtk_revealer_set_reveal_child (GTK_REVEALER (area->title_revealer), TRUE); + + set_active_target (area, 0); +} + +static void +cc_calib_area_finalize (GObject *object) +{ + CcCalibArea *area = CC_CALIB_AREA (object); + + gtk_style_context_remove_provider_for_display (gtk_widget_get_display (GTK_WIDGET (area)), + GTK_STYLE_PROVIDER (area->style_provider)); + + G_OBJECT_CLASS (cc_calib_area_parent_class)->finalize (object); +} + +static void +cc_calib_area_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + CcCalibArea *calib_area = CC_CALIB_AREA (widget); + + if (calib_area->calibrator.geometry.width != width || + calib_area->calibrator.geometry.height != height) + { + calib_area->calibrator.geometry.width = width; + calib_area->calibrator.geometry.height = height; + + /* reset calibration if already started */ + reset (&calib_area->calibrator); + set_active_target (calib_area, 0); + } + + GTK_WIDGET_CLASS (cc_calib_area_parent_class)->size_allocate (widget, + width, + height, + baseline); +} + +static void +cc_calib_area_class_init (CcCalibAreaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_calib_area_finalize; + + widget_class->size_allocate = cc_calib_area_size_allocate; + + g_type_ensure (CC_TYPE_CLOCK); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/calibrator/calibrator.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, error_revealer); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, title_revealer); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, subtitle_revealer); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, clock); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target1); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target2); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target3); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target4); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, stack); + gtk_widget_class_bind_template_child (widget_class, CcCalibArea, success_page); +} + +static void +cc_calib_area_init (CcCalibArea *calib_area) +{ + GtkGesture *click; + GtkEventController *key; + + gtk_widget_init_template (GTK_WIDGET (calib_area)); + + calib_area->style_provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (calib_area->style_provider, "/org/gnome/control-center/wacom/calibrator/calibrator.css"); + gtk_style_context_add_provider_for_display (gtk_widget_get_display (GTK_WIDGET (calib_area)), + GTK_STYLE_PROVIDER (calib_area->style_provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + + cc_clock_set_duration (CC_CLOCK (calib_area->clock), MAX_TIME); + g_signal_connect (calib_area->clock, "finished", + G_CALLBACK (on_clock_finished), calib_area); + +#ifndef FAKE_AREA + /* No cursor */ + gtk_widget_realize (GTK_WIDGET (calib_area)); + gtk_widget_set_cursor_from_name (GTK_WIDGET (calib_area), "blank"); + + gtk_widget_set_can_focus (GTK_WIDGET (calib_area), TRUE); +#endif /* FAKE_AREA */ + + g_signal_connect (calib_area, + "close-request", + G_CALLBACK (on_close_request), + calib_area); + g_signal_connect (calib_area, + "notify::fullscreened", + G_CALLBACK (on_fullscreen), + calib_area); + + click = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), GDK_BUTTON_PRIMARY); + g_signal_connect (click, "pressed", + G_CALLBACK (on_gesture_press), calib_area); + gtk_widget_add_controller (GTK_WIDGET (calib_area), + GTK_EVENT_CONTROLLER (click)); + + key = gtk_event_controller_key_new (); + g_signal_connect (key, "key-released", + G_CALLBACK (on_key_release), calib_area); + gtk_widget_add_controller (GTK_WIDGET (calib_area), key); +} + +/** + * Creates the windows and other objects required to do calibration + * under GTK. When the window is closed (timed out, calibration finished + * or user cancellation), callback will be called, where you should call + * cc_calib_area_finish(). + */ +CcCalibArea * +cc_calib_area_new (GdkDisplay *display, + GdkMonitor *monitor, + GdkDevice *device, + FinishCallback callback, + gpointer user_data, + int threshold_doubleclick, + int threshold_misclick) +{ + CcCalibArea *calib_area; + + g_return_val_if_fail (callback, NULL); + + calib_area = g_object_new (CC_TYPE_CALIB_AREA, NULL); + calib_area->callback = callback; + calib_area->user_data = user_data; + calib_area->device = device; + calib_area->calibrator.threshold_doubleclick = threshold_doubleclick; + calib_area->calibrator.threshold_misclick = threshold_misclick; + + /* Move to correct screen */ + if (monitor) + gtk_window_fullscreen_on_monitor (GTK_WINDOW (calib_area), monitor); + else + gtk_window_fullscreen (GTK_WINDOW (calib_area)); + + gtk_widget_show (GTK_WIDGET (calib_area)); + + return calib_area; +} + +/* Finishes the calibration. Note that CalibArea + * needs to be destroyed with Cccalib_area_free() afterwards */ +gboolean +cc_calib_area_finish (CcCalibArea *area) +{ + g_return_val_if_fail (area != NULL, FALSE); + + if (area->success) + g_debug ("Final calibration: %f, %f, %f, %f\n", + area->axis.x_min, + area->axis.y_min, + area->axis.x_max, + area->axis.y_max); + else + g_debug ("Calibration was aborted or timed out"); + + return area->success; +} + +void +cc_calib_area_free (CcCalibArea *area) +{ + gtk_window_destroy (GTK_WINDOW (area)); +} + +void +cc_calib_area_get_axis (CcCalibArea *area, + XYinfo *new_axis, + gboolean *swap_xy) +{ + g_return_if_fail (area != NULL); + + *new_axis = area->axis; + *swap_xy = area->swap; +} + +void +cc_calib_area_get_padding (CcCalibArea *area, + XYinfo *padding) +{ + g_return_if_fail (area != NULL); + + /* min/max values are monitor coordinates scaled to be between + * 0 and 1, padding starts at 0 on "the edge", and positive + * values grow towards the center of the rectangle. + */ + padding->x_min = area->axis.x_min; + padding->y_min = area->axis.y_min; + padding->x_max = 1 - area->axis.x_max; + padding->y_max = 1 - area->axis.y_max; +} diff --git a/panels/wacom/calibrator/calibrator-gui.h b/panels/wacom/calibrator/calibrator-gui.h new file mode 100644 index 0000000..5d6d1ae --- /dev/null +++ b/panels/wacom/calibrator/calibrator-gui.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* struct to hold min/max info of the X and Y axis */ +typedef struct +{ + gdouble x_min; + gdouble x_max; + gdouble y_min; + gdouble y_max; +} XYinfo; + +#define CC_TYPE_CALIB_AREA cc_calib_area_get_type () +G_DECLARE_FINAL_TYPE (CcCalibArea, cc_calib_area, CC, CALIB_AREA, GtkWindow) + +typedef void (*FinishCallback) (CcCalibArea *area, gpointer user_data); + +CcCalibArea * cc_calib_area_new (GdkDisplay *display, + GdkMonitor *monitor, + GdkDevice *device, + FinishCallback callback, + gpointer user_data, + int threshold_doubleclick, + int threshold_misclick); + +gboolean cc_calib_area_finish (CcCalibArea *area); + +void cc_calib_area_free (CcCalibArea *area); + +void cc_calib_area_get_axis (CcCalibArea *area, + XYinfo *new_axis, + gboolean *swap_xy); + +void cc_calib_area_get_padding (CcCalibArea *area, + XYinfo *padding); + +G_END_DECLS diff --git a/panels/wacom/calibrator/calibrator.c b/panels/wacom/calibrator/calibrator.c new file mode 100644 index 0000000..4ac316e --- /dev/null +++ b/panels/wacom/calibrator/calibrator.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdlib.h> + +#include "calibrator.h" + +#define SWAP(valtype,x,y) \ + G_STMT_START { \ + valtype t; t = (x); x = (y); y = t; \ + } G_STMT_END + +/* reset clicks */ +void +reset (struct Calib *c) +{ + c->num_clicks = 0; +} + +/* check whether the coordinates are along the respective axis */ +static gboolean +along_axis (struct Calib *c, + int xy, + int x0, + int y0) +{ + return ((abs(xy - x0) <= c->threshold_misclick) || + (abs(xy - y0) <= c->threshold_misclick)); +} + +/* add a click with the given coordinates */ +gboolean +add_click (struct Calib *c, + int x, + int y) +{ + g_debug ("Trying to add click (%d, %d)", x, y); + + /* Double-click detection */ + if (c->threshold_doubleclick > 0 && c->num_clicks > 0) + { + int i = c->num_clicks-1; + while (i >= 0) + { + if (abs(x - c->clicked_x[i]) <= c->threshold_doubleclick && + abs(y - c->clicked_y[i]) <= c->threshold_doubleclick) + { + g_debug ("Detected double-click, ignoring"); + return FALSE; + } + i--; + } + } + + /* Mis-click detection */ + if (c->threshold_misclick > 0 && c->num_clicks > 0) + { + gboolean misclick = TRUE; + + if (c->num_clicks == 1) + { + /* check that along one axis of first point */ + if (along_axis(c, x,c->clicked_x[0],c->clicked_y[0]) || + along_axis(c, y,c->clicked_x[0],c->clicked_y[0])) + { + misclick = FALSE; + } + } + else if (c->num_clicks == 2) + { + /* check that along other axis of first point than second point */ + if ((along_axis(c, y,c->clicked_x[0],c->clicked_y[0]) && + along_axis(c, c->clicked_x[1],c->clicked_x[0],c->clicked_y[0])) || + (along_axis(c, x,c->clicked_x[0],c->clicked_y[0]) && + along_axis(c, c->clicked_y[1],c->clicked_x[0],c->clicked_y[0]))) + { + misclick = FALSE; + } + } + else if (c->num_clicks == 3) + { + /* check that along both axis of second and third point */ + if ((along_axis(c, x,c->clicked_x[1],c->clicked_y[1]) && + along_axis(c, y,c->clicked_x[2],c->clicked_y[2])) || + (along_axis(c, y,c->clicked_x[1],c->clicked_y[1]) && + along_axis(c, x,c->clicked_x[2],c->clicked_y[2]))) + { + misclick = FALSE; + } + } + + if (misclick) + { + g_debug ("Detected misclick, resetting"); + reset(c); + return FALSE; + } + } + + g_debug ("Click (%d, %d) added", x, y); + c->clicked_x[c->num_clicks] = x; + c->clicked_y[c->num_clicks] = y; + c->num_clicks++; + + return TRUE; +} + +/* calculate and apply the calibration */ +gboolean +finish (struct Calib *c, + XYinfo *new_axis, + gboolean *swap) +{ + gboolean swap_xy; + float scale_x; + float scale_y; + float delta_x; + float delta_y; + XYinfo axis = {-1, -1, -1, -1}; + + if (c->num_clicks != 4) + return FALSE; + + /* Should x and y be swapped? If the device and output are wider + * towards different axes, swapping must be performed + * + * FIXME: Would be even better to know the actual output orientation, + * not just the direction. + */ + swap_xy = (c->geometry.width < c->geometry.height); + + /* Compute the scale to transform from pixel positions to [0..1]. */ + scale_x = 1 / (float)c->geometry.width; + scale_y = 1 / (float)c->geometry.height; + + axis.x_min = ((((c->clicked_x[UL] + c->clicked_x[LL]) / 2)) * scale_x); + axis.x_max = ((((c->clicked_x[UR] + c->clicked_x[LR]) / 2)) * scale_x); + axis.y_min = ((((c->clicked_y[UL] + c->clicked_y[UR]) / 2)) * scale_y); + axis.y_max = ((((c->clicked_y[LL] + c->clicked_y[LR]) / 2)) * scale_y); + + /* Add/subtract the offset that comes from not having the points in the + * corners (using the same coordinate system they are currently in) + */ + delta_x = (axis.x_max - axis.x_min) / (float)(NUM_BLOCKS - 2); + axis.x_min -= delta_x; + axis.x_max += delta_x; + delta_y = (axis.y_max - axis.y_min) / (float)(NUM_BLOCKS - 2); + axis.y_min -= delta_y; + axis.y_max += delta_y; + + /* If x and y has to be swapped we also have to swap the parameters */ + if (swap_xy) + { + SWAP (gdouble, axis.x_min, axis.y_min); + SWAP (gdouble, axis.x_max, axis.y_max); + } + + *new_axis = axis; + *swap = swap_xy; + + return TRUE; +} + diff --git a/panels/wacom/calibrator/calibrator.css b/panels/wacom/calibrator/calibrator.css new file mode 100644 index 0000000..462c766 --- /dev/null +++ b/panels/wacom/calibrator/calibrator.css @@ -0,0 +1,47 @@ +#calibrator { + background-color: #000; +} + +#calibrator * { + color: #fff; +} + +#calibrator label { + font-size: larger; +} + +#calibrator #title { + font-weight: bold; + color: #888; +} + +#calibrator #error { + font-weight: bold; +} + +#calibrator #target { + background-image: url('target.svg'); + background-repeat: no-repeat; + background-position: 50% 50%; +} + +@keyframes target-enabled-animation { + 0% { background-size: 0px; } + 90% { background-size: 120px; } + 100% { background-size: 100px; } +} + +@keyframes target-disabled-animation { + 0% { background-size: 100px; } + 100% { background-size: 0px; } +} + +#calibrator #target:not(disabled) { + animation: target-enabled-animation 1 ease 0.5s; + background-size: 100px; +} + +#calibrator #target:disabled { + animation: target-disabled-animation 1 ease 0.2s; + background-size: 0px; +} diff --git a/panels/wacom/calibrator/calibrator.h b/panels/wacom/calibrator/calibrator.h new file mode 100644 index 0000000..dab7a2f --- /dev/null +++ b/panels/wacom/calibrator/calibrator.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <glib.h> +#include "calibrator-gui.h" + +G_BEGIN_DECLS + +/* + * Number of blocks. We partition the screen into 'num_blocks' x 'num_blocks' + * rectangles of equal size. We then ask the user to press points that are + * located at the corner closes to the center of the four blocks in the corners + * of the screen. The following ascii art illustrates the situation. We partition + * the screen into 8 blocks in each direction. We then let the user press the + * points marked with 'O'. + * + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--O--+--+--+--+--+--O--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + * | | | | | | | | | + * +--O--+--+--+--+--+--O--+ + * | | | | | | | | | + * +--+--+--+--+--+--+--+--+ + */ +#define NUM_BLOCKS 8 + +/* Names of the points */ +enum +{ + UL = 0, /* Upper-left */ + UR = 1, /* Upper-right */ + LL = 2, /* Lower-left */ + LR = 3 /* Lower-right */ +}; + +struct Calib +{ + /* Geometry of the calibration window */ + GdkRectangle geometry; + + /* nr of clicks registered */ + int num_clicks; + + /* click coordinates */ + int clicked_x[4], clicked_y[4]; + + /* Threshold to keep the same point from being clicked twice. + * Set to zero if you don't want this check + */ + int threshold_doubleclick; + + /* Threshold to detect mis-clicks (clicks not along axes) + * A lower value forces more precise calibration + * Set to zero if you don't want this check + */ + int threshold_misclick; +}; + +void reset (struct Calib *c); +gboolean add_click (struct Calib *c, + int x, + int y); +gboolean finish (struct Calib *c, + XYinfo *new_axis, + gboolean *swap); + +G_END_DECLS diff --git a/panels/wacom/calibrator/calibrator.ui b/panels/wacom/calibrator/calibrator.ui new file mode 100644 index 0000000..89d4865 --- /dev/null +++ b/panels/wacom/calibrator/calibrator.ui @@ -0,0 +1,146 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcCalibArea" parent="GtkWindow"> + <property name="name">calibrator</property> + <property name="child"> + <object class="GtkStack" id="stack"> + <property name="transition_duration">0</property> + <child> + <object class="GtkGrid"> + <property name="row_homogeneous">1</property> + <property name="column_homogeneous">1</property> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + <property name="column-span">8</property> + <property name="row-span">8</property> + </layout> + <child> + <object class="GtkBox" id="box1"> + <property name="orientation">vertical</property> + <property name="vexpand">1</property> + </object> + </child> + <child> + <object class="CcClock" id="clock"/> + </child> + <child> + <object class="GtkBox" id="box2"> + <property name="orientation">vertical</property> + <property name="vexpand">1</property> + <child> + <object class="GtkRevealer" id="title_revealer"> + <property name="transition_duration">300</property> + <property name="child"> + <object class="GtkLabel"> + <property name="name">title</property> + <property name="label" translatable="1">Screen Calibration</property> + </object> + </property> + </object> + </child> + <child> + <object class="GtkRevealer" id="subtitle_revealer"> + <property name="transition_duration">300</property> + <property name="child"> + <object class="GtkLabel"> + <property name="name">subtitle</property> + <property name="label" translatable="1">Please tap the target markers as they appear on screen to calibrate the tablet.</property> + </object> + </property> + </object> + </child> + <child> + <object class="GtkRevealer" id="error_revealer"> + <property name="transition_type">crossfade</property> + <property name="transition_duration">500</property> + <property name="child"> + <object class="GtkLabel"> + <property name="name">error</property> + <property name="label" translatable="1">Mis-click detected, restarting…</property> + </object> + </property> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="GtkImage" id="target1"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="sensitive">0</property> + <layout> + <property name="column">0</property> + <property name="row">0</property> + <property name="column-span">2</property> + <property name="row-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkImage" id="target2"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="sensitive">0</property> + <layout> + <property name="column">6</property> + <property name="row">0</property> + <property name="column-span">2</property> + <property name="row-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkImage" id="target3"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="sensitive">0</property> + <layout> + <property name="column">0</property> + <property name="row">6</property> + <property name="column-span">2</property> + <property name="row-span">2</property> + </layout> + </object> + </child> + <child> + <object class="GtkImage" id="target4"> + <property name="name">target</property> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="sensitive">0</property> + <layout> + <property name="column">6</property> + <property name="row">6</property> + <property name="column-span">2</property> + <property name="row-span">2</property> + </layout> + </object> + </child> + </object> + </child> + <child> + <object class="GtkImage" id="success_page"> + <property name="pixel_size">300</property> + <property name="icon_name">emblem-ok-symbolic</property> + </object> + </child> + </object> + </property> + </template> + <object class="GtkSizeGroup"> + <property name="mode">vertical</property> + <widgets> + <widget name="box1"/> + <widget name="box2"/> + </widgets> + </object> +</interface> diff --git a/panels/wacom/calibrator/cc-clock.c b/panels/wacom/calibrator/cc-clock.c new file mode 100644 index 0000000..26afd81 --- /dev/null +++ b/panels/wacom/calibrator/cc-clock.c @@ -0,0 +1,289 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: Joaquim Rocha <jrocha@redhat.com> + * Carlos Garnacho <carlosg@gnome.org> + */ +#include "config.h" +#include "cc-clock.h" + +#include <math.h> + +#define CLOCK_RADIUS 50 +#define CLOCK_LINE_WIDTH 10 +#define CLOCK_LINE_PADDING 10 +#define EXTRA_SPACE 2 + +typedef struct _CcClock CcClock; + +struct _CcClock +{ + GtkWidget parent_instance; + guint duration; + gint64 start_time; + gboolean running; +}; + +enum +{ + PROP_DURATION = 1, + N_PROPS +}; + +static GParamSpec *props[N_PROPS] = { 0, }; + +enum { + FINISHED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0, }; + +G_DEFINE_TYPE (CcClock, cc_clock, GTK_TYPE_WIDGET) + +static gint64 +cc_clock_get_time_diff (CcClock *clock) +{ + GdkFrameClock *frame_clock; + gint64 current_time; + + frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock)); + current_time = gdk_frame_clock_get_frame_time (frame_clock); + + return current_time - clock->start_time; +} + +static gdouble +cc_clock_get_angle (CcClock *clock) +{ + gint64 time_diff; + + time_diff = cc_clock_get_time_diff (clock); + + if (time_diff > clock->duration * 1000) + return 360; + + return ((gdouble) time_diff / (clock->duration * 1000)) * 360; +} + +static void +cc_clock_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkAllocation allocation; + cairo_t *cr; + gdouble angle; + + gtk_widget_get_allocation (widget, &allocation); + angle = cc_clock_get_angle (CC_CLOCK (widget)); + + cr = gtk_snapshot_append_cairo (snapshot, + &GRAPHENE_RECT_INIT (0, 0, allocation.width, allocation.height)); + + /* Draw the clock background */ + cairo_arc (cr, allocation.width / 2, allocation.height / 2, CLOCK_RADIUS / 2, 0.0, 2.0 * M_PI); + cairo_set_source_rgb (cr, 0.5, 0.5, 0.5); + cairo_fill_preserve (cr); + cairo_stroke (cr); + + cairo_set_line_width (cr, CLOCK_LINE_WIDTH); + + cairo_arc (cr, + allocation.width / 2, + allocation.height / 2, + (CLOCK_RADIUS - CLOCK_LINE_WIDTH - CLOCK_LINE_PADDING) / 2, + 3 * M_PI_2, + 3 * M_PI_2 + angle * M_PI / 180.0); + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_stroke (cr); +} + +static void +cc_clock_stop (CcClock *clock) +{ + GdkFrameClock *frame_clock; + + if (!clock->running) + return; + + frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock)); + + gdk_frame_clock_end_updating (frame_clock); + clock->running = FALSE; +} + +static void +on_frame_clock_update (CcClock *clock) +{ + gint64 time_diff; + + if (!clock->running) + return; + + time_diff = cc_clock_get_time_diff (clock); + + if (time_diff > clock->duration * 1000) + { + g_signal_emit (clock, signals[FINISHED], 0); + cc_clock_stop (clock); + } + + gtk_widget_queue_draw (GTK_WIDGET (clock)); +} + +static void +cc_clock_map (GtkWidget *widget) +{ + GdkFrameClock *frame_clock; + + GTK_WIDGET_CLASS (cc_clock_parent_class)->map (widget); + + frame_clock = gtk_widget_get_frame_clock (widget); + g_signal_connect_object (frame_clock, "update", + G_CALLBACK (on_frame_clock_update), + widget, G_CONNECT_SWAPPED); + cc_clock_reset (CC_CLOCK (widget)); +} + +static void +cc_clock_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcClock *clock = CC_CLOCK (object); + + switch (prop_id) + { + case PROP_DURATION: + clock->duration = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_clock_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcClock *clock = CC_CLOCK (object); + + switch (prop_id) + { + case PROP_DURATION: + g_value_set_uint (value, clock->duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_clock_measure (GtkWidget *widget, + GtkOrientation orientation, + gint for_size, + gint *minimum, + gint *natural, + gint *minimum_baseline, + gint *natural_baseline) +{ + if (minimum) + *minimum = CLOCK_RADIUS + EXTRA_SPACE; + if (natural) + *natural = CLOCK_RADIUS + EXTRA_SPACE; +} + +static void +cc_clock_class_init (CcClockClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = cc_clock_set_property; + object_class->get_property = cc_clock_get_property; + + widget_class->map = cc_clock_map; + widget_class->snapshot = cc_clock_snapshot; + widget_class->measure = cc_clock_measure; + + signals[FINISHED] = + g_signal_new ("finished", + CC_TYPE_CLOCK, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + props[PROP_DURATION] = + g_param_spec_uint ("duration", + "Duration", + "Duration", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static void +cc_clock_init (CcClock *clock) +{ +} + +GtkWidget * +cc_clock_new (guint duration) +{ + return g_object_new (CC_TYPE_CLOCK, + "duration", duration, + NULL); +} + +void +cc_clock_reset (CcClock *clock) +{ + GdkFrameClock *frame_clock; + + if (!gtk_widget_get_mapped (GTK_WIDGET (clock))) + return; + + frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock)); + + cc_clock_stop (clock); + + clock->running = TRUE; + clock->start_time = g_get_monotonic_time (); + gdk_frame_clock_begin_updating (frame_clock); +} + +void +cc_clock_set_duration (CcClock *clock, + guint duration) +{ + clock->duration = duration; + g_object_notify (G_OBJECT (clock), "duration"); + cc_clock_reset (clock); +} + +guint +cc_clock_get_duration (CcClock *clock) +{ + return clock->duration; +} diff --git a/panels/wacom/calibrator/cc-clock.h b/panels/wacom/calibrator/cc-clock.h new file mode 100644 index 0000000..9ebf024 --- /dev/null +++ b/panels/wacom/calibrator/cc-clock.h @@ -0,0 +1,41 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#pragma once + +#include <glib.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_CLOCK (cc_clock_get_type ()) + +G_DECLARE_FINAL_TYPE (CcClock, cc_clock, CC, CLOCK, GtkWidget) + +GtkWidget * cc_clock_new (guint duration); + +void cc_clock_reset (CcClock *clock); + +void cc_clock_set_duration (CcClock *clock, + guint duration); +guint cc_clock_get_duration (CcClock *clock); + +GType cc_clock_get_type (void); + +G_END_DECLS diff --git a/panels/wacom/calibrator/main.c b/panels/wacom/calibrator/main.c new file mode 100644 index 0000000..71421c7 --- /dev/null +++ b/panels/wacom/calibrator/main.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2009 Tias Guns + * Copyright (c) 2009 Soren Hauberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <dirent.h> +#include <glib/gi18n.h> + +#include <X11/extensions/XInput.h> + +#include "calibrator-gui.h" +#include "calibrator.h" + +static GMainLoop *mainloop = NULL; + +/** + * find a calibratable touchscreen device (using XInput) + * + * if pre_device is NULL, the last calibratable device is selected. + * retuns number of devices found, + * the data of the device is returned in the last 3 function parameters + */ +static int find_device(const char* pre_device, gboolean verbose, gboolean list_devices, + XID* device_id, const char** device_name, XYinfo* device_axis) +{ + gboolean pre_device_is_id = TRUE; + int found = 0; + + Display* display = XOpenDisplay(NULL); + if (display == NULL) { + fprintf(stderr, "Unable to connect to X server\n"); + exit(1); + } + + int xi_opcode, event, error; + if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) { + fprintf(stderr, "X Input extension not available.\n"); + exit(1); + } + + /* verbose, get Xi version */ + if (verbose) { + XExtensionVersion *version = XGetExtensionVersion(display, INAME); + + if (version && (version != (XExtensionVersion*) NoSuchExtension)) { + printf("DEBUG: %s version is %i.%i\n", + INAME, version->major_version, version->minor_version); + XFree(version); + } + } + + if (pre_device != NULL) { + /* check whether the pre_device is an ID (only digits) */ + int len = strlen(pre_device); + int loop; + for (loop=0; loop<len; loop++) { + if (!isdigit(pre_device[loop])) { + pre_device_is_id = FALSE; + break; + } + } + } + + + if (verbose) + printf("DEBUG: Skipping virtual master devices and devices without axis valuators.\n"); + int ndevices; + XDeviceInfoPtr list, slist; + slist=list=(XDeviceInfoPtr) XListInputDevices (display, &ndevices); + int i; + for (i=0; i<ndevices; i++, list++) + { + if (list->use == IsXKeyboard || list->use == IsXPointer) /* virtual master device */ + continue; + + /* if we are looking for a specific device */ + if (pre_device != NULL) { + if ((pre_device_is_id && list->id == (XID) atoi(pre_device)) || + (!pre_device_is_id && strcmp(list->name, pre_device) == 0)) { + /* OK, fall through */ + } else { + /* skip, not this device */ + continue; + } + } + + XAnyClassPtr any = (XAnyClassPtr) (list->inputclassinfo); + int j; + for (j=0; j<list->num_classes; j++) + { + + if (any->class == ValuatorClass) + { + XValuatorInfoPtr V = (XValuatorInfoPtr) any; + XAxisInfoPtr ax = (XAxisInfoPtr) V->axes; + + if (V->mode != Absolute) { + if (verbose) + printf("DEBUG: Skipping device '%s' id=%i, does not report Absolute events.\n", + list->name, (int)list->id); + } else if (V->num_axes < 2 || + (ax[0].min_value == -1 && ax[0].max_value == -1) || + (ax[1].min_value == -1 && ax[1].max_value == -1)) { + if (verbose) + printf("DEBUG: Skipping device '%s' id=%i, does not have two calibratable axes.\n", + list->name, (int)list->id); + } else { + /* a calibratable device (has 2 axis valuators) */ + found++; + *device_id = list->id; + *device_name = g_strdup(list->name); + device_axis->x_min = ax[0].min_value; + device_axis->x_max = ax[0].max_value; + device_axis->y_min = ax[1].min_value; + device_axis->y_max = ax[1].max_value; + + if (list_devices) + printf("Device \"%s\" id=%i\n", *device_name, (int)*device_id); + } + + } + + /* + * Increment 'any' to point to the next item in the linked + * list. The length is in bytes, so 'any' must be cast to + * a character pointer before being incremented. + */ + any = (XAnyClassPtr) ((char *) any + any->length); + } + + } + XFreeDeviceList(slist); + XCloseDisplay(display); + + return found; +} + +static void usage(char* cmd, unsigned thr_misclick) +{ + fprintf(stderr, "Usage: %s [-h|--help] [-v|--verbose] [--list] [--device <device name or id>] [--precalib <minx> <maxx> <miny> <maxy>] [--misclick <nr of pixels>] [--output-type <auto|xorg.conf.d|hal|xinput>] [--fake]\n", cmd); + fprintf(stderr, "\t-h, --help: print this help message\n"); + fprintf(stderr, "\t-v, --verbose: print debug messages during the process\n"); + fprintf(stderr, "\t--list: list calibratable input devices and quit\n"); + fprintf(stderr, "\t--device <device name or id>: select a specific device to calibrate\n"); + fprintf(stderr, "\t--precalib: manually provide the current calibration setting (eg. the values in xorg.conf)\n"); + fprintf(stderr, "\t--misclick: set the misclick threshold (0=off, default: %i pixels)\n", + thr_misclick); + fprintf(stderr, "\t--fake: emulate a fake device (for testing purposes)\n"); +} + +static struct Calib* CalibratorXorgPrint(const char* const device_name0, const XYinfo *axis0, const gboolean verbose0, const int thr_misclick, const int thr_doubleclick) +{ + struct Calib* c = (struct Calib*)calloc(1, sizeof(struct Calib)); + c->threshold_misclick = thr_misclick; + c->threshold_doubleclick = thr_doubleclick; + + printf("Calibrating standard Xorg driver \"%s\"\n", device_name0); + printf("\tcurrent calibration values: min_x=%lf, max_x=%lf and min_y=%lf, max_y=%lf\n", + axis0->x_min, axis0->x_max, axis0->y_min, axis0->y_max); + printf("\tIf these values are estimated wrong, either supply it manually with the --precalib option, or run the 'get_precalib.sh' script to automatically get it (through HAL).\n"); + + return c; +} + +static struct Calib* main_common(int argc, char** argv) +{ + gboolean verbose = FALSE; + gboolean list_devices = FALSE; + gboolean fake = FALSE; + gboolean precalib = FALSE; + XYinfo pre_axis = {-1, -1, -1, -1}; + const char* pre_device = NULL; + unsigned thr_misclick = 15; + unsigned thr_doubleclick = 7; + + /* parse input */ + if (argc > 1) { + int i; + for (i=1; i!=argc; i++) { + /* Display help ? */ + if (strcmp("-h", argv[i]) == 0 || + strcmp("--help", argv[i]) == 0) { + fprintf(stderr, "xinput_calibrator, v%s\n\n", "0.0.0"); + usage(argv[0], thr_misclick); + exit(0); + } else + + /* Verbose output ? */ + if (strcmp("-v", argv[i]) == 0 || + strcmp("--verbose", argv[i]) == 0) { + verbose = TRUE; + } else + + /* Just list devices ? */ + if (strcmp("--list", argv[i]) == 0) { + list_devices = TRUE; + } else + + /* Select specific device ? */ + if (strcmp("--device", argv[i]) == 0) { + if (argc > i+1) + pre_device = argv[++i]; + else { + fprintf(stderr, "Error: --device needs a device name or id as argument; use --list to list the calibratable input devices.\n\n"); + usage(argv[0], thr_misclick); + exit(1); + } + } else + + /* Get pre-calibration ? */ + if (strcmp("--precalib", argv[i]) == 0) { + precalib = TRUE; + if (argc > i+1) + pre_axis.x_min = atoi(argv[++i]); + if (argc > i+1) + pre_axis.x_max = atoi(argv[++i]); + if (argc > i+1) + pre_axis.y_min = atoi(argv[++i]); + if (argc > i+1) + pre_axis.y_max = atoi(argv[++i]); + } else + + /* Get mis-click threshold ? */ + if (strcmp("--misclick", argv[i]) == 0) { + if (argc > i+1) + thr_misclick = atoi(argv[++i]); + else { + fprintf(stderr, "Error: --misclick needs a number (the pixel threshold) as argument. Set to 0 to disable mis-click detection.\n\n"); + usage(argv[0], thr_misclick); + exit(1); + } + } else + + /* Fake calibratable device ? */ + if (strcmp("--fake", argv[i]) == 0) { + fake = TRUE; + } + + /* unknown option */ + else { + fprintf(stderr, "Unknown option: %s\n\n", argv[i]); + usage(argv[0], thr_misclick); + exit(0); + } + } + } + + + /* Choose the device to calibrate */ + XID device_id = (XID) -1; + const char* device_name = NULL; + XYinfo device_axis = {-1, -1, -1, -1}; + if (fake) { + /* Fake a calibratable device */ + device_name = "Fake_device"; + device_axis.x_min=0; + device_axis.x_max=1000; + device_axis.y_min=0; + device_axis.y_max=1000; + + if (verbose) { + printf("DEBUG: Faking device: %s\n", device_name); + } + } else { + /* Find the right device */ + int nr_found = find_device(pre_device, verbose, list_devices, &device_id, &device_name, &device_axis); + + if (list_devices) { + /* printed the list in find_device */ + if (nr_found == 0) + printf("No calibratable devices found.\n"); + exit(0); + } + + if (nr_found == 0) { + if (pre_device == NULL) + fprintf (stderr, "Error: No calibratable devices found.\n"); + else + fprintf (stderr, "Error: Device \"%s\" not found; use --list to list the calibratable input devices.\n", pre_device); + exit(1); + + } else if (nr_found > 1) { + printf ("Warning: multiple calibratable devices found, calibrating last one (%s)\n\tuse --device to select another one.\n", device_name); + } + + if (verbose) { + printf("DEBUG: Selected device: %s\n", device_name); + } + } + + /* override min/max XY from command line ? */ + if (precalib) { + if (pre_axis.x_min != -1) + device_axis.x_min = pre_axis.x_min; + if (pre_axis.x_max != -1) + device_axis.x_max = pre_axis.x_max; + if (pre_axis.y_min != -1) + device_axis.y_min = pre_axis.y_min; + if (pre_axis.y_max != -1) + device_axis.y_max = pre_axis.y_max; + + if (verbose) { + printf("DEBUG: Setting precalibration: %lf, %lf, %lf, %lf\n", + device_axis.x_min, device_axis.x_max, + device_axis.y_min, device_axis.y_max); + } + } + + /* lastly, presume a standard Xorg driver (evtouch, mutouch, ...) */ + return CalibratorXorgPrint(device_name, &device_axis, + verbose, thr_misclick, thr_doubleclick); +} + +static gboolean output_xorgconfd(const XYinfo new_axis, int swap_xy, int new_swap_xy) +{ + const char* sysfs_name = "!!Name_Of_TouchScreen!!"; + + /* xorg.conf.d snippet */ + printf(" copy the snippet below into '/etc/X11/xorg.conf.d/99-calibration.conf'\n"); + printf("Section \"InputClass\"\n"); + printf(" Identifier \"calibration\"\n"); + printf(" MatchProduct \"%s\"\n", sysfs_name); + printf(" Option \"MinX\" \"%lf\"\n", new_axis.x_min); + printf(" Option \"MaxX\" \"%lf\"\n", new_axis.x_max); + printf(" Option \"MinY\" \"%lf\"\n", new_axis.y_min); + printf(" Option \"MaxY\" \"%lf\"\n", new_axis.y_max); + if (swap_xy != 0) + printf(" Option \"SwapXY\" \"%d\" # unless it was already set to 1\n", new_swap_xy); + printf("EndSection\n"); + + return TRUE; +} + +static gboolean finish_data(const XYinfo new_axis, int swap_xy) +{ + gboolean success = TRUE; + + /* we suppose the previous 'swap_xy' value was 0 */ + /* (unfortunately there is no way to verify this (yet)) */ + int new_swap_xy = swap_xy; + + printf("\n\n--> Making the calibration permanent <--\n"); + success &= output_xorgconfd(new_axis, swap_xy, new_swap_xy); + + return success; +} + +static void +calibration_finished_cb (CcCalibArea *area, + gpointer user_data) +{ + gboolean success; + XYinfo axis; + gboolean swap_xy; + + success = cc_calib_area_finish (area); + if (success) + { + cc_calib_area_get_axis (area, &axis, &swap_xy); + success = finish_data (axis, swap_xy); + } + else + fprintf(stderr, "Error: unable to apply or save configuration values\n"); + + g_main_loop_quit (mainloop); +} + +int main(int argc, char** argv) +{ + + struct Calib* calibrator = main_common(argc, argv); + CcCalibArea *calib_area; + + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (); + + g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + + calib_area = cc_calib_area_new (NULL, + NULL, /* monitor */ + NULL, /* NULL to accept input from any device */ + calibration_finished_cb, + NULL, + calibrator->threshold_doubleclick, + calibrator->threshold_misclick); + + mainloop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (mainloop); + + cc_calib_area_free (calib_area); + + free(calibrator); + + return 0; +} diff --git a/panels/wacom/calibrator/meson.build b/panels/wacom/calibrator/meson.build new file mode 100644 index 0000000..f894e60 --- /dev/null +++ b/panels/wacom/calibrator/meson.build @@ -0,0 +1,35 @@ +calibrator_inc = include_directories('.') + +common_sources = files( + 'calibrator.c', + 'calibrator-gui.c', + 'cc-clock.c', +) + +calibrator_deps = deps + [m_dep] + +libwacom_calibrator = static_library( + cappletname + '-calibrator', + sources: common_sources, + include_directories: top_inc, + dependencies: calibrator_deps, + c_args: cflags +) + +libwacom_calibrator_test = static_library( + cappletname + '-calibrator-test', + sources: common_sources, + include_directories: top_inc, + dependencies: calibrator_deps, + c_args: test_cflags +) + +sources = common_sources + wacom_gresource + files('main.c') + +executable( + 'test-calibrator', + sources, + include_directories: top_inc, + dependencies: calibrator_deps, + c_args: cflags +) diff --git a/panels/wacom/calibrator/target.svg b/panels/wacom/calibrator/target.svg new file mode 100644 index 0000000..60b4cbb --- /dev/null +++ b/panels/wacom/calibrator/target.svg @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="200" + height="200" + viewBox="0 0 52.916666 52.916668" + version="1.1" + id="svg8" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="target.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2.8" + inkscape:cx="26.277089" + inkscape:cy="74.824155" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="2160" + inkscape:window-height="1311" + inkscape:window-x="0" + inkscape:window-y="55" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid4518" /> + <sodipodi:guide + position="-7.9374999,13.229167" + orientation="1,0" + id="guide4542" + inkscape:locked="false" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-244.08332)"> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.5291667px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 0,270.54165 h 52.916667 v 0" + id="path4520" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.5291667px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 26.458334,244.08331 v 52.91667" + id="path4522" + inkscape:connector-curvature="0" /> + <ellipse + style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.5291667;stroke-opacity:1" + id="path4530" + cx="26.458332" + cy="270.54163" + rx="5.2916665" + ry="5.2916679" /> + <ellipse + style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.00000012;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4534" + cx="26.458332" + cy="270.54163" + rx="15.875" + ry="15.875004" /> + </g> +</svg> diff --git a/panels/wacom/cc-drawing-area.c b/panels/wacom/cc-drawing-area.c new file mode 100644 index 0000000..3f570ad --- /dev/null +++ b/panels/wacom/cc-drawing-area.c @@ -0,0 +1,182 @@ +/* + * 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 { + GtkDrawingArea parent; + GtkGesture *stylus_gesture; + cairo_surface_t *surface; + cairo_t *cr; +}; + +G_DEFINE_TYPE (CcDrawingArea, cc_drawing_area, GTK_TYPE_DRAWING_AREA) + +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_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 void +draw_cb (GtkDrawingArea *drawing_area, + cairo_t *cr, + gint width, + gint height, + gpointer user_data) +{ + CcDrawingArea *area = CC_DRAWING_AREA (drawing_area); + + ensure_drawing_surface (area, width, height); + + 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, width, height); + cairo_stroke (cr); +} + +static void +stylus_down_cb (GtkGestureStylus *gesture, + double x, + double y, + CcDrawingArea *area) +{ + cairo_new_path (area->cr); +} + +static void +stylus_motion_cb (GtkGestureStylus *gesture, + double x, + double y, + CcDrawingArea *area) +{ + GdkDeviceTool *tool; + gdouble pressure; + + tool = gtk_gesture_stylus_get_device_tool (gesture); + gtk_gesture_stylus_get_axis (gesture, + 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 (GTK_WIDGET (area)); +} + +static void +cc_drawing_area_class_init (CcDrawingAreaClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->map = cc_drawing_area_map; + widget_class->unmap = cc_drawing_area_unmap; +} + +static void +cc_drawing_area_init (CcDrawingArea *area) +{ + gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (area), draw_cb, NULL, NULL); + area->stylus_gesture = gtk_gesture_stylus_new (); + g_signal_connect (area->stylus_gesture, "down", + G_CALLBACK (stylus_down_cb), area); + g_signal_connect (area->stylus_gesture, "motion", + G_CALLBACK (stylus_motion_cb), area); + gtk_widget_add_controller (GTK_WIDGET (area), + GTK_EVENT_CONTROLLER (area->stylus_gesture)); +} + +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..1d3d6ba --- /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, GtkDrawingArea) + +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..3c8536b --- /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_list_box_row_set_child (GTK_LIST_BOX_ROW (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..695b85b --- /dev/null +++ b/panels/wacom/cc-wacom-device.c @@ -0,0 +1,437 @@ +/* + * 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" + +#include <glib/gi18n.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; + WacomError *wacom_error; + const gchar *node_path; + + wacom_db = cc_wacom_device_database_get (); + node_path = gsd_device_get_device_file (device->device); + wacom_error = libwacom_error_new (); + device->wdevice = libwacom_new_from_path (wacom_db, node_path, FALSE, wacom_error); + + if (!device->wdevice) { + g_debug ("libwacom_new_from_path() failed: %s", libwacom_error_get_message (wacom_error)); + libwacom_error_free (&wacom_error); + g_set_error (error, 0, 0, "Tablet description not found"); + return FALSE; + } + libwacom_error_free (&wacom_error); + + 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; + WacomError *wacom_error; + + device = g_object_new (CC_TYPE_WACOM_DEVICE, + NULL); + + wacom_error = libwacom_error_new (); + wacom_device = libwacom_new_from_name (cc_wacom_device_database_get(), + name, wacom_error); + if (wacom_device == NULL) { + g_debug ("libwacom_new_fake() failed: %s", libwacom_error_get_message (wacom_error)); + libwacom_error_free (&wacom_error); + return NULL; + } + libwacom_error_free (&wacom_error); + + 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_RR_IS_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; +} + +const gchar * +cc_wacom_device_get_description (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 _("Tablet mounted on laptop panel"); + } else if (integration_flags & WACOM_DEVICE_INTEGRATED_DISPLAY) { + return _("Tablet mounted on external display"); + } else { + return _("External tablet device"); + } +} diff --git a/panels/wacom/cc-wacom-device.h b/panels/wacom/cc-wacom-device.h new file mode 100644 index 0000000..63d38fb --- /dev/null +++ b/panels/wacom/cc-wacom-device.h @@ -0,0 +1,66 @@ +/* + * 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 <gnome-rr/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); + +const gchar * cc_wacom_device_get_description (CcWacomDevice *device); + diff --git a/panels/wacom/cc-wacom-ekr-page.c b/panels/wacom/cc-wacom-ekr-page.c new file mode 100644 index 0000000..fb2f3e3 --- /dev/null +++ b/panels/wacom/cc-wacom-ekr-page.c @@ -0,0 +1,195 @@ +/* + * Copyright © 2022 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-ekr-page.h" +#include "cc-wacom-panel.h" + +struct _CcWacomEkrPage +{ + GtkBox parent_instance; + GtkWidget *ekr_section; + GtkWidget *ekr_icon; + GtkWidget *ekr_map_buttons; + CcWacomPanel *panel; + CcWacomDevice *device; +}; + +enum { + PROP_0, + PROP_PANEL, + PROP_DEVICE, + N_PROPS, +}; + +static GParamSpec *props[N_PROPS] = { 0, }; + +G_DEFINE_TYPE (CcWacomEkrPage, cc_wacom_ekr_page, GTK_TYPE_BOX) + +static void +set_osd_visibility_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) result = NULL; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + + if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Error invoking pad button mapping OSK: %s\n", error->message); +} + +static void +set_osd_visibility (CcWacomEkrPage *page) +{ + GDBusProxy *proxy; + GsdDevice *gsd_device; + const gchar *device_path; + + proxy = cc_wacom_panel_get_gsd_wacom_bus_proxy (page->panel); + + if (proxy == NULL) { + g_warning ("Wacom D-Bus interface is not available"); + return; + } + + gsd_device = cc_wacom_device_get_device (page->device); + device_path = gsd_device_get_device_file (gsd_device); + + g_dbus_proxy_call (proxy, + "Show", + g_variant_new ("(ob)", device_path, TRUE), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + set_osd_visibility_cb, + page); +} + +static void +on_map_buttons_activated (CcWacomEkrPage *self) +{ + set_osd_visibility (self); +} + +static void +cc_wacom_ekr_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcWacomEkrPage *page = CC_WACOM_EKR_PAGE (object); + + switch (prop_id) { + case PROP_PANEL: + g_value_set_object (value, page->panel); + break; + case PROP_DEVICE: + g_value_set_object (value, page->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_wacom_ekr_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcWacomEkrPage *page = CC_WACOM_EKR_PAGE (object); + + switch (prop_id) { + case PROP_PANEL: + page->panel = g_value_get_object (value); + break; + case PROP_DEVICE: + page->device = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_wacom_ekr_page_constructed (GObject *object) +{ + CcWacomEkrPage *page = CC_WACOM_EKR_PAGE (object); + + G_OBJECT_CLASS (cc_wacom_ekr_page_parent_class)->constructed (object); + + adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (page->ekr_section), + cc_wacom_device_get_name (page->device)); +} + +static void +cc_wacom_ekr_page_class_init (CcWacomEkrPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_wacom_ekr_page_get_property; + object_class->set_property = cc_wacom_ekr_page_set_property; + object_class->constructed = cc_wacom_ekr_page_constructed; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/cc-wacom-ekr-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWacomEkrPage, ekr_section); + gtk_widget_class_bind_template_child (widget_class, CcWacomEkrPage, ekr_icon); + gtk_widget_class_bind_template_child (widget_class, CcWacomEkrPage, ekr_map_buttons); + + gtk_widget_class_bind_template_callback (widget_class, on_map_buttons_activated); + + props[PROP_PANEL] = g_param_spec_object ("panel", + "panel", + "panel", + CC_TYPE_WACOM_PANEL, + 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 void +cc_wacom_ekr_page_init (CcWacomEkrPage *page) +{ + gtk_widget_init_template (GTK_WIDGET (page)); +} + +GtkWidget * +cc_wacom_ekr_page_new (CcWacomPanel *panel, + CcWacomDevice *ekr) +{ + return g_object_new (CC_TYPE_WACOM_EKR_PAGE, + "panel", panel, + "device", ekr, + NULL); +} diff --git a/panels/wacom/cc-wacom-ekr-page.h b/panels/wacom/cc-wacom-ekr-page.h new file mode 100644 index 0000000..f8944a1 --- /dev/null +++ b/panels/wacom/cc-wacom-ekr-page.h @@ -0,0 +1,34 @@ +/* + * Copyright © 2022 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 <gtk/gtk.h> +#include "cc-wacom-panel.h" +#include "cc-wacom-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_WACOM_EKR_PAGE (cc_wacom_ekr_page_get_type ()) +G_DECLARE_FINAL_TYPE (CcWacomEkrPage, cc_wacom_ekr_page, CC, WACOM_EKR_PAGE, GtkBox) + +GtkWidget * cc_wacom_ekr_page_new (CcWacomPanel *panel, + CcWacomDevice *stylus); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-ekr-page.ui b/panels/wacom/cc-wacom-ekr-page.ui new file mode 100644 index 0000000..1d1e403 --- /dev/null +++ b/panels/wacom/cc-wacom-ekr-page.ui @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcWacomEkrPage" parent="GtkBox"> + <property name="orientation">vertical</property> + <property name="spacing">24</property> + <child> + <object class="AdwPreferencesGroup" id="ekr_section"> + <property name="description" translatable="yes" + comments="translators: this is a drawing tablet pad, i.e. a collection of buttons and knobs">External pad device</property> + <property name="header-suffix"> + <object class="GtkPicture" id="ekr_icon"> + <property name="halign">end</property> + <property name="valign">start</property> + <property name="file">resource:///org/gnome/control-center/wacom/wacom-tablet.svg</property> + </object> + </property> + <child> + <object class="AdwActionRow" id="ekr_map_buttons"> + <property name="title" translatable="yes">Map Buttons</property> + <property name="activatable">True</property> + <signal name="activated" handler="on_map_buttons_activated" object="CcWacomEkrPage" swapped="yes" /> + <child type="suffix"> + <object class="GtkImage"> + <property name="icon-name">go-next-symbolic</property> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/wacom/cc-wacom-page.c b/panels/wacom/cc-wacom-page.c new file mode 100644 index 0000000..696d71d --- /dev/null +++ b/panels/wacom/cc-wacom-page.c @@ -0,0 +1,865 @@ +/* + * 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/x11/gdkx.h> +#endif +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/wayland/gdkwayland.h> +#endif + +#include "cc-wacom-device.h" +#include "cc-wacom-button-row.h" +#include "cc-wacom-page.h" +#include "cc-wacom-stylus-page.h" +#include "gsd-enums.h" +#include "calibrator-gui.h" +#include "gsd-input-helper.h" + +#include <string.h> + +#define MWID(x) (GtkWidget *) gtk_builder_get_object (page->mapping_builder, x) + +#define THRESHOLD_MISCLICK 15 +#define THRESHOLD_DOUBLECLICK 7 + +struct _CcWacomPage +{ + GtkBox parent_instance; + + CcWacomPanel *panel; + CcWacomDevice *stylus; + GList *pads; + CcCalibArea *area; + GSettings *wacom_settings; + + GtkWidget *tablet_section; + GtkWidget *tablet_icon; + GtkWidget *tablet_display; + GtkWidget *tablet_calibrate; + GtkWidget *tablet_map_buttons; + GtkWidget *tablet_mode; + GtkWidget *tablet_mode_switch; + GtkWidget *tablet_left_handed; + GtkWidget *tablet_left_handed_switch; + GtkWidget *tablet_aspect_ratio; + GtkWidget *tablet_aspect_ratio_switch; + GtkWidget *display_section; + + GnomeRRScreen *rr_screen; + + /* Button mapping */ + GtkBuilder *mapping_builder; + GtkWindow *button_map; + GtkListStore *action_store; + + GCancellable *cancellable; + + /* To reach other grouped devices */ + GsdDeviceManager *manager; +}; + +G_DEFINE_TYPE (CcWacomPage, cc_wacom_page, GTK_TYPE_BOX) + +/* 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 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, + 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)", + cal[0], cal[1], cal[2], cal[3]); +} + +static void +finish_calibration (CcCalibArea *area, + gpointer user_data) +{ + CcWacomPage *page = (CcWacomPage *) user_data; + XYinfo axis; + gdouble cal[4]; + + if (cc_calib_area_finish (area)) { + cc_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; + + set_calibration (page->stylus, + 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); + } + + cc_calib_area_free (area); + page->area = NULL; + gtk_widget_set_sensitive (page->tablet_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_devices (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) +{ + g_assert (page->area == NULL); + + page->area = cc_calib_area_new (NULL, + 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 GdkMonitor * +find_monitor_at_point (GdkDisplay *display, + gint x, + gint y) +{ + GListModel *monitors; + int i; + + monitors = gdk_display_get_monitors (display); + + for (i = 0; i < g_list_model_get_n_items (monitors); i++) { + g_autoptr(GdkMonitor) m = g_list_model_get_item (monitors, i); + GdkRectangle geometry; + + gdk_monitor_get_geometry (m, &geometry); + if (gdk_rectangle_contains_point (&geometry, x, y)) + return g_steal_pointer (&m); + } + + return NULL; +} + +static void +calibrate (CcWacomPage *page) +{ + int i; + GVariant *old_calibration, *array; + g_autofree GVariant **tmp = NULL; + g_autofree gdouble *calibration = NULL; + gsize ncal; + GdkDisplay *display; + g_autoptr(GdkMonitor) monitor = NULL; + g_autoptr(GnomeRRScreen) rr_screen = NULL; + GnomeRROutput *output; + g_autoptr(GError) error = NULL; + GDBusProxy *input_mapping_proxy; + gint x, y; + + display = gdk_display_get_default (); + rr_screen = gnome_rr_screen_new (display, &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); + input_mapping_proxy = cc_wacom_panel_get_input_mapping_bus_proxy (page->panel); + + if (output) { + gnome_rr_output_get_position (output, &x, &y); + monitor = find_monitor_at_point (display, x, y); + } else if (input_mapping_proxy) { + GsdDevice *gsd_device; + GVariant *mapping; + + gsd_device = cc_wacom_device_get_device (page->stylus); + + if (gsd_device) { + mapping = g_dbus_proxy_call_sync (input_mapping_proxy, + "GetDeviceMapping", + g_variant_new ("(o)", gsd_device_get_device_file (gsd_device)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL); + if (mapping) { + gint x, y, width, height; + + g_variant_get (mapping, "((iiii))", &x, &y, &width, &height); + monitor = find_monitor_at_point (display, 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. Calibration may appear in wrong monitor."); + } + + 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 (page->tablet_calibrate, FALSE); +} + +static void +on_calibrate_activated (CcWacomPage *self) +{ + calibrate (self); +} + +/* 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) +{ + gtk_list_box_append (GTK_LIST_BOX (list_box), + cc_wacom_button_row_new (button, settings)); +} + +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_window_destroy (GTK_WINDOW (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 (gtk_widget_get_native (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 = GTK_WINDOW (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 +on_map_buttons_activated (CcWacomPage *self) +{ + set_osd_visibility (self); +} + +static void +on_display_selected (GtkWidget *widget, + GParamSpec *pspec, + CcWacomPage *page) +{ + GListModel *list; + g_autoptr (GObject) obj = NULL; + GVariant *variant; + gint idx; + + list = adw_combo_row_get_model (ADW_COMBO_ROW (widget)); + idx = adw_combo_row_get_selected (ADW_COMBO_ROW (widget)); + obj = g_list_model_get_item (list, idx); + + variant = g_object_get_data (obj, "value-output"); + + if (variant) + g_settings_set_value (page->wacom_settings, "output", g_variant_ref (variant)); + else + g_settings_reset (page->wacom_settings, "output"); + + gtk_widget_set_sensitive (page->tablet_calibrate, variant == NULL); +} + +/* 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, cc_calib_area_free); + g_clear_pointer (&self->button_map, gtk_window_destroy); + g_list_free_full (self->pads, g_object_unref); + g_clear_object (&self->rr_screen); + 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); + GtkWidgetClass *widget_class = GTK_WIDGET_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; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/cc-wacom-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_section); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_icon); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_display); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_calibrate); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_map_buttons); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_mode); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_mode_switch); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_left_handed); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_left_handed_switch); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_aspect_ratio); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, tablet_aspect_ratio_switch); + gtk_widget_class_bind_template_child (widget_class, CcWacomPage, display_section); + + gtk_widget_class_bind_template_callback (widget_class, on_map_buttons_activated); + gtk_widget_class_bind_template_callback (widget_class, on_calibrate_activated); + gtk_widget_class_bind_template_callback (widget_class, on_display_selected); +} + +static void +update_displays_model (CcWacomPage *page) +{ + g_autoptr (GtkStringList) list = NULL; + GnomeRROutput **outputs, *cur_output; + int i, idx = 0, cur = -1, automatic_item = -1; + g_autoptr (GObject) obj = NULL; + GVariant *variant; + + outputs = gnome_rr_screen_list_outputs (page->rr_screen); + list = gtk_string_list_new (NULL); + cur_output = cc_wacom_device_get_output (page->stylus, + page->rr_screen); + + for (i = 0; outputs[i] != NULL; i++) { + GnomeRROutput *output = outputs[i]; + GnomeRRCrtc *crtc = gnome_rr_output_get_crtc (output); + g_autofree gchar *text = NULL; + g_autofree gchar *vendor = NULL; + g_autofree gchar *product = NULL; + g_autofree gchar *serial = NULL; + const gchar *name, *disp_name; + + /* Output is turned on? */ + if (!crtc || gnome_rr_crtc_get_current_mode (crtc) == NULL) + continue; + + if (output == cur_output) + cur = idx; + + 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); + + gnome_rr_output_get_ids_from_edid (output, + &vendor, + &product, + &serial); + variant = g_variant_new_strv ((const gchar *[]) { vendor, product, serial }, 3); + + gtk_string_list_append (list, text); + obj = g_list_model_get_item (G_LIST_MODEL (list), idx); + g_object_set_data_full (G_OBJECT (obj), "value-output", + variant, (GDestroyNotify) g_variant_unref); + idx++; + } + + /* All displays item */ + gtk_string_list_append (list, _("All Displays")); + variant = g_variant_new_strv ((const gchar *[]) { "", "", "" }, 3); + obj = g_list_model_get_item (G_LIST_MODEL (list), idx); + g_object_set_data_full (G_OBJECT (obj), "value-output", + variant, (GDestroyNotify) g_variant_unref); + if (cur_output == NULL) + cur = idx; + + /* "Automatic" item */ + if (get_layout_type (page->stylus) == LAYOUT_SCREEN) { + g_autoptr (GVariant) user_value = NULL; + + idx++; + gtk_string_list_append (list, _("Automatic")); + automatic_item = idx; + + user_value = g_settings_get_user_value (page->wacom_settings, "output"); + if (!user_value) + cur = idx; + } + + g_signal_handlers_block_by_func (page->tablet_display, on_display_selected, page); + adw_combo_row_set_model (ADW_COMBO_ROW (page->tablet_display), G_LIST_MODEL (list)); + adw_combo_row_set_selected (ADW_COMBO_ROW (page->tablet_display), cur); + g_signal_handlers_unblock_by_func (page->tablet_display, on_display_selected, page); + + gtk_widget_set_sensitive (page->tablet_calibrate, cur == automatic_item); +} + +static void +cc_wacom_page_init (CcWacomPage *page) +{ + g_autoptr (GError) error = NULL; + + gtk_widget_init_template (GTK_WIDGET (page)); + page->rr_screen = gnome_rr_screen_new (gdk_display_get_default (), &error); + + if (error) + g_warning ("Could not get RR screen: %s", error->message); + + g_signal_connect_object (page->rr_screen, "changed", + G_CALLBACK (update_displays_model), + page, G_CONNECT_SWAPPED); +} + +static void +set_icon_name (CcWacomPage *page, + GtkWidget *widget, + const char *icon_name) +{ + g_autofree gchar *resource = NULL; + + resource = g_strdup_printf ("/org/gnome/control-center/wacom/%s.svg", icon_name); + gtk_picture_set_resource (GTK_PICTURE (widget), resource); +} + +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 +update_pad_availability (CcWacomPage *page) +{ + gtk_widget_set_visible (page->tablet_map_buttons, 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); +} + +static GVariant * +tablet_mode_bind_set (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + gboolean setting; + + setting = g_value_get_boolean (value); + + return g_variant_new_string (setting ? "absolute" : "relative"); +} + +static gboolean +tablet_mode_bind_get (GValue *value, + GVariant *variant, + gpointer user_data) +{ + g_value_set_boolean (value, + g_strcmp0 (g_variant_get_string (variant, NULL), + "absolute") == 0); + return TRUE; +} + +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; + + gtk_widget_set_visible (page->tablet_left_handed, + get_layout_type (stylus) == LAYOUT_REVERSIBLE); + gtk_widget_set_visible (page->tablet_calibrate, + get_layout_type (stylus) == LAYOUT_SCREEN); + + /* FIXME move this to construct */ + page->wacom_settings = cc_wacom_device_get_settings (stylus); + + /* Tablet name */ + adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (page->tablet_section), + cc_wacom_device_get_name (stylus)); + adw_preferences_group_set_description (ADW_PREFERENCES_GROUP (page->tablet_section), + cc_wacom_device_get_description (stylus)); + + g_settings_bind_with_mapping (page->wacom_settings, "mapping", + page->tablet_mode_switch, "active", + G_SETTINGS_BIND_DEFAULT, + tablet_mode_bind_get, + tablet_mode_bind_set, + NULL, NULL); + g_settings_bind_with_mapping (page->wacom_settings, "mapping", + page->display_section, "sensitive", + G_SETTINGS_BIND_DEFAULT, + tablet_mode_bind_get, + tablet_mode_bind_set, + NULL, NULL); + g_settings_bind (page->wacom_settings, "left-handed", + page->tablet_left_handed_switch, "active", + G_SETTINGS_BIND_DEFAULT); + g_settings_bind (page->wacom_settings, "keep-aspect", + page->tablet_aspect_ratio_switch, "active", + G_SETTINGS_BIND_DEFAULT); + + /* Tablet icon */ + set_icon_name (page, page->tablet_icon, 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); + + update_pad_availability (page); + update_displays_model (page); + + return GTK_WIDGET (page); +} + +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..76a16ec --- /dev/null +++ b/panels/wacom/cc-wacom-page.h @@ -0,0 +1,39 @@ +/* + * 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_calibrate (CcWacomPage *page); + +gboolean cc_wacom_page_can_calibrate (CcWacomPage *page); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-page.ui b/panels/wacom/cc-wacom-page.ui new file mode 100644 index 0000000..327e5a9 --- /dev/null +++ b/panels/wacom/cc-wacom-page.ui @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcWacomPage" parent="GtkBox"> + <property name="orientation">vertical</property> + <property name="spacing">24</property> + <child> + <object class="AdwPreferencesGroup" id="tablet_section"> + <property name="header-suffix"> + <object class="GtkPicture" id="tablet_icon"> + <property name="halign">end</property> + <property name="valign">start</property> + </object> + </property> + <child> + <object class="AdwActionRow" id="tablet_mode"> + <property name="title" translatable="yes">Tablet Mode</property> + <property name="subtitle" translatable="yes">Use absolute positioning for the pen</property> + <property name="activatable_widget">tablet_mode_switch</property> + <child> + <object class="GtkSwitch" id="tablet_mode_switch"> + <property name="valign">center</property> + </object> + </child> + </object> + </child> + <child> + <object class="AdwActionRow" id="tablet_left_handed"> + <property name="title" translatable="yes">Left Hand Orientation</property> + <property name="subtitle" translatable="yes">Tablet and Express Keys™ are rotated for left hand use</property> + <property name="activatable_widget">tablet_left_handed_switch</property> + <child> + <object class="GtkSwitch" id="tablet_left_handed_switch"> + <property name="valign">center</property> + </object> + </child> + </object> + </child> + <child> + <object class="AdwActionRow" id="tablet_map_buttons"> + <property name="title" translatable="yes">Map Buttons</property> + <property name="activatable">True</property> + <signal name="activated" handler="on_map_buttons_activated" object="CcWacomPage" swapped="yes" /> + <child type="suffix"> + <object class="GtkImage"> + <property name="icon-name">go-next-symbolic</property> + </object> + </child> + </object> + </child> + </object> + </child> + + <child> + <object class="AdwPreferencesGroup" id="display_section"> + <child> + <object class="AdwComboRow" id="tablet_display"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Map to Monitor</property> + <signal name="notify::selected-item" handler="on_display_selected" swapped="no"/> + </object> + </child> + <child> + <object class="AdwActionRow" id="tablet_aspect_ratio"> + <property name="title" translatable="yes">Keep Aspect Ratio</property> + <property name="subtitle" translatable="yes">Only use a portion of the tablet surface to keep monitor aspect ratio</property> + <property name="activatable_widget">tablet_aspect_ratio_switch</property> + <child> + <object class="GtkSwitch" id="tablet_aspect_ratio_switch"> + <property name="valign">center</property> + </object> + </child> + </object> + </child> + <child> + <object class="AdwActionRow" id="tablet_calibrate"> + <property name="title" translatable="yes">Calibrate</property> + <property name="activatable">True</property> + <signal name="activated" handler="on_calibrate_activated" object="CcWacomPage" swapped="yes" /> + <child type="suffix"> + <object class="GtkImage"> + <property name="icon-name">go-next-symbolic</property> + </object> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/wacom/cc-wacom-panel.c b/panels/wacom/cc-wacom-panel.c new file mode 100644 index 0000000..36aca06 --- /dev/null +++ b/panels/wacom/cc-wacom-panel.c @@ -0,0 +1,753 @@ +/* + * 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-ekr-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/wayland/gdkwayland.h> +#endif + +#define EKR_VENDOR "056a" +#define EKR_PRODUCT "0331" + +struct _CcWacomPanel +{ + CcPanel parent_instance; + + GtkWidget *test_popover; + GtkWidget *test_draw_area; + GtkWidget *test_button; + GtkWidget *scrollable; + GtkWidget *tablets; + GtkWidget *styli; + GtkWidget *initial_state_stack; + GtkWidget *panel_view; + GtkWidget *panel_empty_state; + GHashTable *devices; /* key=GsdDevice, value=CcWacomDevice */ + GHashTable *pages; /* key=CcWacomDevice, value=GtkWidget */ + GHashTable *stylus_pages; /* key=CcWacomTool, value=GtkWidget */ + guint mock_stylus_id; + + CcTabletToolMap *tablet_tool_map; + + GtkAdjustment *vadjustment; + GtkGesture *stylus_gesture; + + GtkWidget *highlighted_widget; + + /* DBus */ + GDBusProxy *proxy; + GDBusProxy *input_mapping_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 void +highlight_widget (CcWacomPanel *self, GtkWidget *widget) +{ + double y; + + if (self->highlighted_widget == widget) + return; + + gtk_widget_translate_coordinates (widget, + self->scrollable, + 0, 0, + NULL, &y); + gtk_adjustment_set_value (self->vadjustment, y); + self->highlighted_widget = widget; +} + +static CcWacomPage * +update_highlighted_device (CcWacomPanel *self, const gchar *device_name) +{ + CcWacomPage *page; + CcWacomDevice *wacom_device; + + 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); + highlight_widget (self, GTK_WIDGET (page)); + + 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 = update_highlighted_device (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: + update_highlighted_device (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); + CcShell *shell; + + shell = cc_panel_get_shell (CC_PANEL (self)); + if (shell) { + gtk_widget_remove_controller (GTK_WIDGET (shell), + GTK_EVENT_CONTROLLER (self->stylus_gesture)); + } + + + g_clear_pointer (&self->devices, g_hash_table_unref); + g_clear_object (&self->proxy); + g_clear_object (&self->input_mapping_proxy); + g_clear_pointer (&self->pages, g_hash_table_unref); + g_clear_pointer (&self->stylus_pages, g_hash_table_unref); + g_clear_handle_id (&self->mock_stylus_id, g_source_remove); + + 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_box_remove (GTK_BOX (self->styli), 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); + gtk_box_append (GTK_BOX (self->styli), page); + g_hash_table_insert (self->stylus_pages, tool, page); + + return TRUE; +} + +static void +update_test_button (CcWacomPanel *self) +{ + if (!self->test_button) + return; + + if (g_hash_table_size (self->devices) == 0) { + gtk_popover_popdown (GTK_POPOVER (self->test_popover)); + gtk_widget_set_sensitive (self->test_button, FALSE); + } else { + gtk_widget_set_sensitive (self->test_button, TRUE); + } +} + +static void +update_initial_state (CcWacomPanel *self) +{ + gtk_stack_set_visible_child (GTK_STACK (self->initial_state_stack), + g_hash_table_size (self->devices) == 0 ? + self->panel_empty_state : + self->panel_view); +} + +static void +update_highlighted_stylus (CcWacomPanel *panel, + CcWacomTool *stylus) +{ + GtkWidget *widget; + + widget = g_hash_table_lookup (panel->stylus_pages, stylus); + highlight_widget (panel, widget); +} + +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; + + 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; + } + + add_stylus (panel, stylus); + + update_highlighted_stylus (panel, stylus); + + cc_tablet_tool_map_add_relation (panel->tablet_tool_map, + wacom_device, stylus); +} + +static void +on_stylus_proximity_cb (GtkGestureStylus *gesture, + double x, + double y, + CcWacomPanel *panel) +{ + GdkDevice *device; + GdkDeviceTool *tool; + + device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (gesture)); + tool = gtk_gesture_stylus_get_device_tool (gesture); + update_current_tool (panel, device, tool); +} + +static gboolean +show_mock_stylus_cb (gpointer user_data) +{ + CcWacomPanel *panel = user_data; + GList *device_list; + CcWacomDevice *wacom_device; + CcWacomTool *stylus; + + panel->mock_stylus_id = 0; + + device_list = g_hash_table_get_values (panel->devices); + if (device_list == NULL) { + g_warning ("Could not create fake stylus event because could not find tablet device"); + return G_SOURCE_REMOVE; + } + + wacom_device = device_list->data; + g_list_free (device_list); + + stylus = cc_wacom_tool_new (0, 0, wacom_device); + add_stylus (panel, stylus); + update_highlighted_stylus (panel, stylus); + cc_tablet_tool_map_add_relation (panel->tablet_tool_map, + wacom_device, stylus); + + return G_SOURCE_REMOVE; +} + +static void +cc_wacom_panel_constructed (GObject *object) +{ + CcWacomPanel *self = CC_WACOM_PANEL (object); + 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)); + + self->stylus_gesture = gtk_gesture_stylus_new (); + g_signal_connect (self->stylus_gesture, "proximity", + G_CALLBACK (on_stylus_proximity_cb), self); + gtk_widget_add_controller (GTK_WIDGET (shell), + GTK_EVENT_CONTROLLER (self->stylus_gesture)); + + if (g_getenv ("UMOCKDEV_DIR") != NULL) + self->mock_stylus_id = g_idle_add (show_mock_stylus_cb, self); +} + +static const char * +cc_wacom_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/wacom"; +} + +static void +cc_wacom_panel_class_init (CcWacomPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_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; + + g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); + + g_type_ensure (CC_TYPE_DRAWING_AREA); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/cc-wacom-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, scrollable); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, test_button); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, test_popover); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, test_draw_area); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, tablets); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, styli); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, initial_state_stack); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, panel_empty_state); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, panel_view); + gtk_widget_class_bind_template_child (widget_class, CcWacomPanel, vadjustment); +} + +static void +add_known_device (CcWacomPanel *self, + GsdDevice *gsd_device) +{ + CcWacomDevice *device; + GsdDeviceType device_type; + g_autoptr(GList) tools = NULL; + GtkWidget *page; + gboolean is_ekr = FALSE; + 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_TOUCHSCREEN | + GSD_DEVICE_TYPE_TOUCHPAD)) != 0) { + return; + } + + if ((device_type & GSD_DEVICE_TYPE_PAD) != 0) { + const char *vendor, *product; + + gsd_device_get_device_ids (gsd_device, &vendor, &product); + is_ekr = (g_strcmp0 (vendor, EKR_VENDOR) == 0 && + g_strcmp0 (product, EKR_PRODUCT) == 0); + + /* Express key remote is an special case, as it is an + * external pad device, we want to distinctly show it + * in the list. Other pads are mounted on a tablet, which + * get their own entries. + */ + if (!is_ekr) + 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); + } + + if (is_ekr) + page = cc_wacom_ekr_page_new (self, device); + else + page = cc_wacom_page_new (self, device); + + gtk_box_append (GTK_BOX (self->tablets), page); + 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_box_remove (GTK_BOX (self->tablets), page); + } + + g_hash_table_remove (self->devices, gsd_device); + check_remove_stylus_pages (self); + update_test_button (self); + update_initial_state (self); +} + +static void +device_added_cb (CcWacomPanel *self, + GsdDevice *device) +{ + add_known_device (self, device); + update_test_button (self); + update_initial_state (self); +} + +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 +got_input_mapping_proxy_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(GError) error = NULL; + CcWacomPanel *self; + + self = CC_WACOM_PANEL (data); + self->input_mapping_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (self->input_mapping_proxy == NULL) { + g_printerr ("Error creating input mapping proxy: %s\n", error->message); + return; + } +} + +static void +cc_wacom_panel_init (CcWacomPanel *self) +{ + GsdDeviceManager *device_manager; + g_autoptr(GList) devices = NULL; + GList *l; + g_autoptr(GError) error = NULL; + + g_resources_register (cc_wacom_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (self)); + + 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); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.gnome.Shell", + "/org/gnome/Mutter/InputMapping", + "org.gnome.Mutter.InputMapping", + cc_panel_get_cancellable (CC_PANEL (self)), + got_input_mapping_proxy_cb, + self); + + 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_test_button (self); + update_initial_state (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; +} + +GDBusProxy * +cc_wacom_panel_get_input_mapping_bus_proxy (CcWacomPanel *self) +{ + g_return_val_if_fail (CC_IS_WACOM_PANEL (self), NULL); + + return self->input_mapping_proxy; +} diff --git a/panels/wacom/cc-wacom-panel.h b/panels/wacom/cc-wacom-panel.h new file mode 100644 index 0000000..68f46aa --- /dev/null +++ b/panels/wacom/cc-wacom-panel.h @@ -0,0 +1,42 @@ +/* + * 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); + +GDBusProxy * cc_wacom_panel_get_input_mapping_bus_proxy (CcWacomPanel *self); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-panel.ui b/panels/wacom/cc-wacom-panel.ui new file mode 100644 index 0000000..77e7e16 --- /dev/null +++ b/panels/wacom/cc-wacom-panel.ui @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcWacomPanel" parent="CcPanel"> + + <child type="titlebar-end"> + <object class="GtkMenuButton" id="test_button"> + <property name="use_underline">True</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Test Your _Settings</property> + <property name="popover">test_popover</property> + <style> + <class name="text-button"/> + </style> + </object> + </child> + + <child type="content"> + <object class="GtkStack" id="initial_state_stack"> + <property name="transition_duration">0</property> + <child> + <object class="GtkScrolledWindow" id="panel_view"> + <property name="hscrollbar-policy">never</property> + <property name="vadjustment">vadjustment</property> + <child> + <object class="AdwClamp" id="scrollable"> + <property name="margin_top">32</property> + <property name="margin_bottom">32</property> + <property name="margin_start">12</property> + <property name="margin_end">12</property> + + <child> + <object class="GtkBox"> + <property name="spacing">48</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="tablets"> + <property name="orientation">vertical</property> + <property name="spacing">48</property> + </object> + </child> + <child> + <object class="GtkBox" id="styli"> + <property name="orientation">vertical</property> + <property name="spacing">48</property> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="AdwStatusPage" id="panel_empty_state"> + <property name="icon-name">input-tablet-symbolic</property> + <property name="title" translatable="yes">No tablet detected</property> + <property name="description" translatable="yes">Please plug in or turn on your Wacom tablet.</property> + </object> + </child> + </object> + </child> + </template> + + <object class="GtkAdjustment" id="vadjustment" /> + + <!-- Test Popover --> + <object class="GtkPopover" id="test_popover"> + <style> + <class name="menu" /> + </style> + <child> + <object class="GtkBox"> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="spacing">6</property> + <property name="orientation">vertical</property> + <child> + <object class="CcDrawingArea" id="test_draw_area"> + <property name="width-request">400</property> + <property name="height-request">300</property> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/panels/wacom/cc-wacom-stylus-page.c b/panels/wacom/cc-wacom-stylus-page.c new file mode 100644 index 0000000..01819ad --- /dev/null +++ b/panels/wacom/cc-wacom-stylus-page.c @@ -0,0 +1,274 @@ +/* + * 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 <adwaita.h> +#include <glib/gi18n.h> +#include "cc-wacom-stylus-page.h" +#include <gtk/gtk.h> +#include <gdesktop-enums.h> + +#include <string.h> + +struct _CcWacomStylusPage +{ + GtkBox parent_instance; + + GtkWidget *stylus_section; + GtkWidget *stylus_icon; + GtkWidget *stylus_button1_action; + GtkWidget *stylus_button2_action; + GtkWidget *stylus_button3_action; + GtkWidget *stylus_eraser_pressure; + GtkWidget *stylus_tip_pressure_scale; + GtkWidget *stylus_eraser_pressure_scale; + GtkAdjustment *stylus_tip_pressure_adjustment; + GtkAdjustment *stylus_eraser_pressure_adjustment; + CcWacomTool *stylus; + GSettings *stylus_settings; +}; + +G_DEFINE_TYPE (CcWacomStylusPage, cc_wacom_stylus_page, GTK_TYPE_BOX) + +/* 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 +on_tip_pressure_value_changed (CcWacomStylusPage *page) +{ + set_pressurecurve (GTK_RANGE (page->stylus_tip_pressure_scale), page->stylus_settings, "pressure-curve"); +} + +static void +on_eraser_pressure_value_changed (CcWacomStylusPage *page) +{ + set_pressurecurve (GTK_RANGE (page->stylus_eraser_pressure_scale), 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 +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 +on_stylus_action_selected (GtkWidget *widget, + GParamSpec *pspec, + CcWacomStylusPage *page) +{ + gint idx; + + idx = adw_combo_row_get_selected (ADW_COMBO_ROW (widget)); + + if (widget == page->stylus_button1_action) + g_settings_set_enum (page->stylus_settings, "button-action", idx); + else if (widget == page->stylus_button2_action) + g_settings_set_enum (page->stylus_settings, "secondary-button-action", idx); + else if (widget == page->stylus_button3_action) + g_settings_set_enum (page->stylus_settings, "tertiary-button-action", idx); +} + +static void +cc_wacom_stylus_page_class_init (CcWacomStylusPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_wacom_stylus_page_get_property; + object_class->set_property = cc_wacom_stylus_page_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/cc-wacom-stylus-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_section); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_icon); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_button1_action); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_button2_action); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_button3_action); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_eraser_pressure); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_tip_pressure_scale); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_eraser_pressure_scale); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_tip_pressure_adjustment); + gtk_widget_class_bind_template_child (widget_class, CcWacomStylusPage, stylus_eraser_pressure_adjustment); + + gtk_widget_class_bind_template_callback (widget_class, on_stylus_action_selected); + gtk_widget_class_bind_template_callback (widget_class, on_tip_pressure_value_changed); + gtk_widget_class_bind_template_callback (widget_class, on_eraser_pressure_value_changed); +} + +static void +add_marks (GtkScale *scale) +{ +#if 0 + gint i; + + for (i = 0; i < N_PRESSURE_CURVES; i++) + gtk_scale_add_mark (scale, i, GTK_POS_BOTTOM, NULL); +#endif +} + +static void +cc_wacom_stylus_page_init (CcWacomStylusPage *page) +{ + gtk_widget_init_template (GTK_WIDGET (page)); + + add_marks (GTK_SCALE (page->stylus_tip_pressure_scale)); + add_marks (GTK_SCALE (page->stylus_eraser_pressure_scale)); +} + +static void +set_icon_name (CcWacomStylusPage *page, + const char *icon_name) +{ + g_autofree gchar *resource = NULL; + + resource = g_strdup_printf ("/org/gnome/control-center/wacom/%s.svg", icon_name); + gtk_picture_set_resource (GTK_PICTURE (page->stylus_icon), resource); +} + +GtkWidget * +cc_wacom_stylus_page_new (CcWacomTool *stylus) +{ + CcWacomStylusPage *page; + guint num_buttons; + 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; + + /* Stylus name */ + adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (page->stylus_section), + cc_wacom_tool_get_name (stylus)); + adw_preferences_group_set_description (ADW_PREFERENCES_GROUP (page->stylus_section), + cc_wacom_tool_get_description (stylus)); + + /* Icon */ + set_icon_name (page, 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); + + num_buttons = cc_wacom_tool_get_num_buttons (stylus); + gtk_widget_set_visible (page->stylus_button3_action, + num_buttons >= 3); + gtk_widget_set_visible (page->stylus_button2_action, + num_buttons >= 2); + gtk_widget_set_visible (page->stylus_button1_action, + num_buttons >= 1); + gtk_widget_set_visible (page->stylus_eraser_pressure, + has_eraser); + + adw_combo_row_set_selected (ADW_COMBO_ROW (page->stylus_button1_action), + g_settings_get_enum (page->stylus_settings, "button-action")); + adw_combo_row_set_selected (ADW_COMBO_ROW (page->stylus_button2_action), + g_settings_get_enum (page->stylus_settings, "secondary-button-action")); + adw_combo_row_set_selected (ADW_COMBO_ROW (page->stylus_button3_action), + g_settings_get_enum (page->stylus_settings, "tertiary-button-action")); + + set_feel_from_gsettings (page->stylus_tip_pressure_adjustment, + page->stylus_settings, "pressure-curve"); + set_feel_from_gsettings (page->stylus_eraser_pressure_adjustment, + page->stylus_settings, "eraser-pressure-curve"); + + return GTK_WIDGET (page); +} + +CcWacomTool * +cc_wacom_stylus_page_get_tool (CcWacomStylusPage *page) +{ + return page->stylus; +} diff --git a/panels/wacom/cc-wacom-stylus-page.h b/panels/wacom/cc-wacom-stylus-page.h new file mode 100644 index 0000000..51ad7d3 --- /dev/null +++ b/panels/wacom/cc-wacom-stylus-page.h @@ -0,0 +1,35 @@ +/* + * 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); + +G_END_DECLS diff --git a/panels/wacom/cc-wacom-stylus-page.ui b/panels/wacom/cc-wacom-stylus-page.ui new file mode 100644 index 0000000..a7850f7 --- /dev/null +++ b/panels/wacom/cc-wacom-stylus-page.ui @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcWacomStylusPage" parent="GtkBox"> + <property name="orientation">vertical</property> + <property name="spacing">24</property> + <child> + <object class="AdwPreferencesGroup" id="stylus_section"> + <property name="header-suffix"> + <object class="GtkPicture" id="stylus_icon"> + <property name="halign">end</property> + <property name="valign">start</property> + </object> + </property> + <child> + <object class="AdwActionRow" id="stylus_tip_pressure"> + <property name="title" translatable="yes">Tip Pressure Feel</property> + <child> + <object class="GtkBox" id="stylus_tip_pressure_box"> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Soft</property> + <style> + <class name="caption"/> + </style> + </object> + </child> + <child> + <object class="GtkScale" id="stylus_tip_pressure_scale"> + <property name="adjustment">stylus_tip_pressure_adjustment</property> + <property name="draw_value">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <signal name="value-changed" handler="on_tip_pressure_value_changed" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Stylus tip pressure</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Firm</property> + <style> + <class name="caption"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + <child> + <object class="AdwComboRow" id="stylus_button1_action"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Button 1</property> + <property name="model">button_model</property> + <signal name="notify::selected-item" handler="on_stylus_action_selected" swapped="no"/> + </object> + </child> + <child> + <object class="AdwComboRow" id="stylus_button2_action"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Button 2</property> + <property name="model">button_model</property> + <signal name="notify::selected-item" handler="on_stylus_action_selected" swapped="no"/> + </object> + </child> + <child> + <object class="AdwComboRow" id="stylus_button3_action"> + <property name="width_request">100</property> + <property name="title" translatable="yes" context="display setting">Button 3</property> + <property name="model">button_model</property> + <signal name="notify::selected-item" handler="on_stylus_action_selected" swapped="no"/> + </object> + </child> + <child> + <object class="AdwActionRow" id="stylus_eraser_pressure"> + <property name="title" translatable="yes">Eraser Pressure Feel</property> + <child> + <object class="GtkBox" id="stylus_eraser_pressure_box"> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Soft</property> + <style> + <class name="caption"/> + </style> + </object> + </child> + <child> + <object class="GtkScale" id="stylus_eraser_pressure_scale"> + <property name="adjustment">stylus_eraser_pressure_adjustment</property> + <property name="draw_value">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <signal name="value-changed" handler="on_eraser_pressure_value_changed" swapped="yes"/> + <accessibility> + <property name="label" translatable="yes">Eraser pressure</property> + </accessibility> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label" translatable="yes">Firm</property> + <style> + <class name="caption"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkAdjustment" id="stylus_tip_pressure_adjustment"> + <property name="upper">6</property> + <property name="step_increment">1</property> + <property name="page_increment">3</property> + </object> + <object class="GtkAdjustment" id="stylus_eraser_pressure_adjustment"> + <property name="upper">6</property> + <property name="step_increment">1</property> + <property name="page_increment">3</property> + </object> + <object class="GtkSizeGroup" id="sizegroup_pressure"> + <widgets> + <widget name="stylus_tip_pressure_box" /> + <widget name="stylus_eraser_pressure_box" /> + </widgets> + </object> + <object class="GtkStringList" id="button_model"> + <items> + <item translatable="yes">Default</item> + <item translatable="yes">Middle Mouse Button Click</item> + <item translatable="yes">Right Mouse Button Click</item> + <item translatable="yes">Back</item> + <item translatable="yes">Forward</item> + </items> + </object> +</interface> diff --git a/panels/wacom/cc-wacom-tool.c b/panels/wacom/cc-wacom-tool.c new file mode 100644 index 0000000..5633f5b --- /dev/null +++ b/panels/wacom/cc-wacom-tool.c @@ -0,0 +1,330 @@ +/* + * 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" + +#include <glib/gi18n.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_is_eraser (tool->wstylus); +} + +const gchar * +cc_wacom_tool_get_description (CcWacomTool *tool) +{ + WacomAxisTypeFlags axes; + + axes = libwacom_stylus_get_axes (tool->wstylus); + + if ((~axes & (WACOM_AXIS_TYPE_TILT | WACOM_AXIS_TYPE_PRESSURE | WACOM_AXIS_TYPE_SLIDER)) == 0) + return _("Airbrush stylus with pressure, tilt, and integrated slider"); + else if ((~axes & (WACOM_AXIS_TYPE_TILT | WACOM_AXIS_TYPE_PRESSURE | WACOM_AXIS_TYPE_ROTATION_Z)) == 0) + return _("Airbrush stylus with pressure, tilt, and rotation"); + else if ((~axes & (WACOM_AXIS_TYPE_TILT | WACOM_AXIS_TYPE_PRESSURE)) == 0) + return _("Standard stylus with pressure and tilt"); + else if ((~axes & WACOM_AXIS_TYPE_PRESSURE) == 0) + return _("Standard stylus with pressure"); + + return NULL; +} diff --git a/panels/wacom/cc-wacom-tool.h b/panels/wacom/cc-wacom-tool.h new file mode 100644 index 0000000..a07659f --- /dev/null +++ b/panels/wacom/cc-wacom-tool.h @@ -0,0 +1,46 @@ +/* + * 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); + +const gchar * cc_wacom_tool_get_description (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..87cebd7 --- /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=org.gnome.Settings-wacom-symbolic +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/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..aa15b0f --- /dev/null +++ b/panels/wacom/gsd-wacom-key-shortcut-button.c @@ -0,0 +1,535 @@ +/* + * 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; + + 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) +{ + self->editing_mode = TRUE; + gsd_wacom_key_shortcut_button_changed (self); + gtk_widget_grab_focus (GTK_WIDGET (self)); +} + +static void +gsd_wacom_key_shortcut_remove_editing_mode (GsdWacomKeyShortcutButton *self) +{ + self->editing_mode = FALSE; + 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 +key_shortcut_finished_editing (GsdWacomKeyShortcutButton *self, + guint32 time) +{ + 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_released_cb (GtkEventController *controller, + guint keyval, + guint keycode, + GdkModifierType state, + GsdWacomKeyShortcutButton *self) +{ + if (self->tmp_shortcut_keyval == 0) + 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_pressed_cb (GtkEventController *controller, + guint keyval, + guint keycode, + GdkModifierType state, + GsdWacomKeyShortcutButton *self) +{ + /* This code is based on the gtk_cell_renderer_accel_start_editing */ + GdkModifierType mods = 0; + GdkEvent *event; + guint shortcut_keyval; + gboolean edited; + gboolean cleared; + + event = gtk_event_controller_get_current_event (controller); + + /* GTK and OTHER modes don't allow modifier keyvals */ + if (gdk_key_event_is_modifier (event) && self->mode != GSD_WACOM_KEY_SHORTCUT_BUTTON_MODE_ALL) + return TRUE; + + if (!self->editing_mode) + return FALSE; + + edited = FALSE; + cleared = FALSE; + + mods = state; + + if (keyval == GDK_KEY_Sys_Req && (mods & GDK_ALT_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 (gdk_key_event_is_modifier (event)) + { + /* 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 = gtk_event_controller_get_current_event_time (controller); + + 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 (self, gtk_event_controller_get_current_event_time (controller)); + + 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 void +gsd_wacom_key_shortcut_button_button_pressed_cb (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + GsdWacomKeyShortcutButton *self) +{ + if (!self->editing_mode) + gsd_wacom_key_shortcut_set_editing_mode (self, NULL); +} + +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->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); +} + +static void +gsd_wacom_key_shortcut_button_init (GsdWacomKeyShortcutButton *self) +{ + GtkEventController *controller; + GtkGesture *gesture; + + self->cancel_keyval = DEFAULT_CANCEL_KEY; + self->clear_keyval = DEFAULT_CLEAR_KEY; + + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "key-pressed", G_CALLBACK (gsd_wacom_key_shortcut_button_key_pressed_cb), self); + g_signal_connect (controller, "key-released", G_CALLBACK (gsd_wacom_key_shortcut_button_key_released_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + gesture = gtk_gesture_click_new (); + g_signal_connect (gesture, "pressed", G_CALLBACK (gsd_wacom_key_shortcut_button_button_pressed_cb), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); +} + +/** + * 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/icons/meson.build b/panels/wacom/icons/meson.build new file mode 100644 index 0000000..7437a1d --- /dev/null +++ b/panels/wacom/icons/meson.build @@ -0,0 +1,4 @@ +install_data( + 'scalable/org.gnome.Settings-wacom-symbolic.svg', + install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps') +) diff --git a/panels/wacom/icons/scalable/org.gnome.Settings-wacom-symbolic.svg b/panels/wacom/icons/scalable/org.gnome.Settings-wacom-symbolic.svg new file mode 100644 index 0000000..bc66c02 --- /dev/null +++ b/panels/wacom/icons/scalable/org.gnome.Settings-wacom-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 8 0 v 1.5 c 0 0.132812 -0.054688 0.261719 -0.144531 0.355469 c -0.09375 0.09375 -0.21875 0.144531 -0.347657 0.144531 h -3.011718 c -0.535156 0 -1.03125 0.289062 -1.296875 0.75 c -0.265625 0.464844 -0.265625 1.035156 0 1.5 c 0.265625 0.460938 0.761719 0.75 1.296875 0.75 h 3.007812 c 0.179688 0 0.339844 0.09375 0.429688 0.25 c 0.042968 0.074219 0.066406 0.164062 0.066406 0.25 v 0.5 h -5 c -1.644531 0 -3 1.355469 -3 3 v 4 c 0 1.644531 1.355469 3 3 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -4 c 0 -0.867188 -0.378906 -1.648438 -0.972656 -2.199219 l -1.410156 1.402344 c 0.234374 0.179687 0.382812 0.460937 0.382812 0.796875 v 3 c 0 0.554688 -0.445312 1 -1 1 h -10 c -0.554688 0 -1 -0.445312 -1 -1 v -3 c 0 -0.570312 0.5 -1 1 -1 h 5.191406 l 1.992188 -2 h -1.183594 v -0.5 c 0 -0.257812 -0.066406 -0.515625 -0.199219 -0.75 c -0.265625 -0.460938 -0.761719 -0.75 -1.296875 -0.75 h -3.007812 c -0.179688 0 -0.339844 -0.09375 -0.429688 -0.25 c -0.089844 -0.152344 -0.089844 -0.347656 0 -0.5 c 0.089844 -0.15625 0.25 -0.25 0.429688 -0.25 h 3.011718 c 0.398438 0 0.777344 -0.160156 1.054688 -0.441406 c 0.28125 -0.28125 0.4375 -0.660156 0.4375 -1.058594 v -1.5 z m 7 3 c -0.265625 0 -0.519531 0.105469 -0.703125 0.292969 h -0.003906 l -6.292969 6.316406 v 1.390625 h 1.390625 l 6.316406 -6.292969 l -0.003906 -0.003906 c 0.191406 -0.183594 0.296875 -0.4375 0.296875 -0.703125 c 0 -0.550781 -0.449219 -1 -1 -1 z m 0 0" fill="#2e3436"/> +</svg> diff --git a/panels/wacom/meson.build b/panels/wacom/meson.build new file mode 100644 index 0000000..f65a3db --- /dev/null +++ b/panels/wacom/meson.build @@ -0,0 +1,106 @@ +deps = common_deps + wacom_deps + [ + gnome_rr_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( + 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-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', + '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.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', + 'cc-wacom-ekr-page.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 ] +) + +subdir('icons') diff --git a/panels/wacom/test-wacom.c b/panels/wacom/test-wacom.c new file mode 100644 index 0000000..5e4cf7e --- /dev/null +++ b/panels/wacom/test-wacom.c @@ -0,0 +1,155 @@ + +#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; +} + +GDBusProxy * +cc_wacom_panel_get_input_mapping_bus_proxy (CcWacomPanel *self) +{ + g_message ("Should get the mutter input mapping 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); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), widget, NULL); + gtk_widget_show (widget); +} + +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 (); + + window = gtk_window_new (); + gtk_window_set_resizable (GTK_WINDOW (window), FALSE); + gtk_window_set_default_size (GTK_WINDOW (window), FIXED_WIDTH, -1); + 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_window_set_child (GTK_WINDOW (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); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, TRUE); + + return 0; +} diff --git a/panels/wacom/wacom-panel-scenario-tester.py b/panels/wacom/wacom-panel-scenario-tester.py new file mode 100755 index 0000000..731d780 --- /dev/null +++ b/panels/wacom/wacom-panel-scenario-tester.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 Red Hat Inc. +# +# Author: Bastien Nocera <hadess@hadess.net> +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import gi +import subprocess +import sys + +gi.require_version('UMockdev', '1.0') +from gi.repository import UMockdev + +def setup_devices(testbed): + dev = testbed.add_device('hid', + '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.2/0003:046D:C52B.0009/0003:046D:4101.000A', + None, + [], []) + + parent = dev + testbed.add_device('input', + '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.2/0003:046D:C52B.0009/0003:046D:4101.000A/input/input3', + parent, + ['name', 'Wacom Cintiq 24HD Pad'], + ['DEVNAME', 'input/event3', + 'ID_INPUT', '1', + 'ID_INPUT_TABLET', '1', + 'ID_INPUT_TABLET_PAD', '1', + 'ID_VENDOR_ID', '0x56a', + 'ID_MODEL_ID', '0x0f4', + 'ID_INPUT_WIDTH_MM', '50', + 'ID_INPUT_HEIGHT_MM', '40', + 'PRODUCT', '3/56a/f4/100', + 'LIBINPUT_DEVICE_GROUP', '3/56a/f4:usb-0000:00:14.0-5']) + testbed.add_device('input', + '/devices/pci0000:00/0000:00:14.0/usb3/3-10/3-10:1.2/0003:046D:C52B.0009/0003:046D:4101.000A/input/input4', + parent, + ['name', 'Wacom Cintiq 24HD Pen'], + ['DEVNAME', 'input/event4', + 'ID_INPUT', '1', + 'ID_INPUT_TABLET', '1', + 'ID_VENDOR_ID', '0x56a', + 'ID_MODEL_ID', '0x0f4', + 'ID_INPUT_WIDTH_MM', '50', + 'ID_INPUT_HEIGHT_MM', '40', + 'PRODUCT', '3/56a/f4/100', + 'LIBINPUT_DEVICE_GROUP', '3/56a/f4:usb-0000:00:14.0-5']) + +def wrap_call(testbed): + os.environ['GSETTINGS_BACKEND'] = 'memory' + os.environ['UMOCKDEV_DIR'] = testbed.get_root_dir() + + wrapper = os.environ.get('WRAPPER') + args = ['gnome-control-center', '-v', 'wacom'] + if wrapper == 'gdb': + args = ['gdb', '-ex', 'r', '-ex', 'bt full', '--args'] + args + elif wrapper: + args = wrapper.split(' ') + args + + print(os.environ) + + p = subprocess.Popen(args, env=os.environ) + p.wait() + +if __name__ == '__main__': + if 'umockdev' not in os.environ.get('LD_PRELOAD', ''): + os.execvp('umockdev-wrapper', ['umockdev-wrapper'] + sys.argv) + + # Start mock udev + testbed = UMockdev.Testbed.new() + setup_devices(testbed) + wrap_call(testbed) 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..b52c208 --- /dev/null +++ b/panels/wacom/wacom-stylus-3btn-no-eraser.svg @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="190.35817" + height="31.02216" + sodipodi:docname="wacom-stylus-3btn-no-eraser.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.9143497" + inkscape:cx="237.93981" + inkscape:cy="17.23823" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="77.933462" + originy="-80.812147" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="rotate(90,-56.008515,534.91126)" + 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 V 516 L 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 h 1.53125 v 2.15625 l 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 v -2.1875 h 1.53125 l 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 V 388.48207 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10541" + width="8.75" + height="22" + x="-579.65631" + y="385.90625" + rx="3.25" + ry="3.25" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10543" + width="8.75" + height="12.25" + x="-579.65631" + y="410.90625" + rx="3.25" + ry="3.2500002" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10544" + width="8.75" + height="8.75" + x="-579.65631" + y="429.15625" + rx="3.25" + ry="3.2500002" /> + </g> + <g + style="display:inline;opacity:0.2" + id="g10631" + transform="translate(670.36721,-383.29631)" /> +</svg> diff --git a/panels/wacom/wacom-stylus-3btn.svg b/panels/wacom/wacom-stylus-3btn.svg new file mode 100644 index 0000000..99a72fb --- /dev/null +++ b/panels/wacom/wacom-stylus-3btn.svg @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="190.28186" + height="31.02216" + sodipodi:docname="wacom-stylus-3btn.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.8154258" + inkscape:cx="167.72924" + inkscape:cy="14.872544" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="77.895312" + originy="-80.850297" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="rotate(90,-56.008515,534.91126)" + 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 H 344.125 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 V 516 L 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 h 1.53125 v 2.15625 l 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 v -2.1875 h 1.53125 l 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 V 399.59375 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 h -1.40625 L 360,389.47786 c -0.23272,-2.85711 -1.26201,-4.51323 -3.69531,-4.51323 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10541" + width="8.75" + height="22" + x="-579.65631" + y="385.90625" + rx="3.25" + ry="3.25" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10543" + width="8.75" + height="12.25" + x="-579.65631" + y="410.90625" + rx="3.25" + ry="3.2500002" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10544" + width="8.75" + height="8.75" + x="-579.65631" + y="429.15625" + rx="3.25" + ry="3.2500002" /> + </g> +</svg> diff --git a/panels/wacom/wacom-stylus-airbrush.svg b/panels/wacom/wacom-stylus-airbrush.svg new file mode 100644 index 0000000..763fc7b --- /dev/null +++ b/panels/wacom/wacom-stylus-airbrush.svg @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="190.54817" + height="48.084461" + sodipodi:docname="wacom-stylus-airbrush.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.566601" + inkscape:cx="202.66806" + inkscape:cy="10.213194" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="68.241145" + originy="-71.955672" /> + </sodipodi:namedview> + <path + 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" + d="m 190.04818,14.311086 c 0,1.43276 -1.88644,2.59423 -4.21348,2.59423 -0.06,0 -0.11387,0.002 -0.17315,0 v 0.9224 L 97.4674,27.051651 c -9.23499,-0.9224 -29.69978,17.886897 -29.69978,17.886897 -5.27125,4.02955 -22.05579,3.90834 -27.55716,-3.58982 l -8.31149,-12.4523 -15.23774,-5.073157 v -0.5765 C 10.06256,22.765871 0.5,19.464966 0.5,17.943016 c 0,-2.11004 11.12955,-4.72727 19.16261,-4.72727 0.1448,0 0.28896,-0.002 0.43293,0 0,0 9.2445,-5.3861297 20.17264,-8.1285897 2.81141,-6.28314 13.64962,-5.78289004 16.4982,-0.49916 l 3.91974,0.009 c 0.84067,-0.83292 2.00027,-1.75859 3.53531,-1.75859 1.88597,0 3.49443,0.81769 4.21347,1.98891 l 14.74708,1.15341 102.47958,4.4678397 v 1.26829 c 0.0591,-0.002 0.11321,0 0.17315,0 2.32703,0 4.21347,1.16148 4.21347,2.59423 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..bb0be38 --- /dev/null +++ b/panels/wacom/wacom-stylus-art-pen.svg @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="188.59064" + height="31.02191" + sodipodi:docname="wacom-stylus-art-pen.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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" + lpeversion="0" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.670234" + inkscape:cx="146.38667" + inkscape:cy="34.725672" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="77.459407" + originy="-79.696633" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="rotate(90,-56.803429,534.11634)" + 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 H 344.125 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 V 516 L 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 1.625,6.6639 h 2.20868 l -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 h 2.20868 l 1.65625,-6.72641 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 V 399.59375 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 h -1.40625 L 359,389.15549 c -0.53643,-2.39779 -1.88996,-4.08946 -4.16741,-4.08946 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + id="rect10541" + width="9.0059462" + height="35.214661" + x="-579.93378" + y="385.90622" + rx="3.25" + ry="3.2499998" /> + </g> +</svg> diff --git a/panels/wacom/wacom-stylus-classic.svg b/panels/wacom/wacom-stylus-classic.svg new file mode 100644 index 0000000..4a82793 --- /dev/null +++ b/panels/wacom/wacom-stylus-classic.svg @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="187.83311" + height="23.792789" + sodipodi:docname="wacom-stylus-classic.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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" + lpeversion="0" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.3696365" + inkscape:cx="152.96029" + inkscape:cy="14.602414" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="76.060233" + originy="-84.128949" /> + </sodipodi:namedview> + <g + style="display:inline;opacity:0.2" + id="g10631" + transform="translate(668.49398,-386.61311)" /> + <path + 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" + d="M 46.887667,3.360306 C 36.117047,3.865161 22.240407,1.93488 22.240407,1.93488 l -1.05225,1.559872 c 0,0 -5.49117,1.362729 -8.2077,1.850845 -2.71653,0.488109 -7.5711996,3.679579 -7.5711996,3.679579 l -1e-5,1.545431 -3.89851,-8e-6 c -1.30925997,-0.03586 -1.38545997,1.983788 -9e-5,1.983788 l 3.89851,8e-6 v 1.451666 c 0,0 4.8546896,3.19148 7.5712196,3.6796 2.71652,0.48811 8.20768,1.85083 8.20768,1.85083 l 1.05226,1.55987 c 0,0 11.863,-1.78649 21.63127,-1.54542 1.0038,0.0248 2.07462,2.59748 3.20226,2.65033 5.78108,0.27099 18.64287,0.70763 26.63126,1.0898 1.13744,0.0544 2.29772,-1.42261 3.48097,-1.37103 7.90158,0.34438 10.82762,0.61119 20.817562,0.61119 18.010761,0 60.275061,-2.76938 77.215201,-4.16159 0.58366,-0.048 0.70379,-1.39578 1.12075,-1.43172 2.25841,-0.19467 7.22786,-1.00842 7.52125,-1.084447 1.88483,-0.488463 3.53687,-2.23225 3.47014,-4.278453 0.002,-0.03684 -0.001,-0.07529 0,-0.11193 0.0688,-2.047734 -1.58423,-3.789705 -3.47014,-4.278452 -0.28392,-0.07358 -5.1712,-0.882492 -7.30532,-1.068191 C 176.0687,6.074088 175.88151,4.715969 175.18201,4.658534 158.21463,3.265341 116.00137,0.499981 98.003683,0.5 76.011317,0.500023 57.658267,2.855443 46.887667,3.360306 Z" + id="path14406" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sccsccccccsccssssssssccssscs" /> +</svg> diff --git a/panels/wacom/wacom-stylus-inking.svg b/panels/wacom/wacom-stylus-inking.svg new file mode 100644 index 0000000..aa67724 --- /dev/null +++ b/panels/wacom/wacom-stylus-inking.svg @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="189.31738" + height="23.031231" + sodipodi:docname="wacom-stylus-inking.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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" + lpeversion="0" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.2481956" + inkscape:cx="90.93126" + inkscape:cy="28.841633" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="77.183018" + originy="-83.767469" /> + </sodipodi:namedview> + <path + 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" + d="M 47.073937,3.360306 C 36.294607,3.86557 22.240407,1.93488 22.240407,1.93488 l -1.05225,1.559872 c 0,0 -5.49117,1.362729 -8.2077,1.850845 -2.71653,0.488109 -7.5711996,3.679579 -7.5711996,3.679579 l -1e-5,1.545431 -3.89851,-8e-6 c -1.30925997,-0.03586 -1.38545997,1.983788 -9e-5,1.983788 l 3.89851,8e-6 v 1.451666 c 0,0 4.8546896,3.19148 7.5712196,3.6796 2.71652,0.48811 8.20768,1.85083 8.20768,1.85083 l 1.05226,1.55987 c 0,0 14.0542,-1.93069 24.83354,-1.42541 10.77934,0.50526 28.937381,2.86028 50.929782,2.86028 21.992401,-10e-6 83.129171,-4.12918 85.865071,-4.8382 2.68626,-0.69619 5.04077,-3.181414 4.94566,-6.097669 0.003,-0.0525 -0.002,-0.107307 0,-0.159523 0.098,-2.918438 -2.25786,-5.401094 -4.94566,-6.097665 C 181.13281,4.629146 119.99605,0.499977 98.003683,0.5 76.011318,0.500023 57.853267,2.855035 47.073937,3.360306 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..5436cf8 --- /dev/null +++ b/panels/wacom/wacom-stylus-no-eraser.svg @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="190.35817" + height="31.02216" + sodipodi:docname="wacom-stylus-no-eraser.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.5080049" + inkscape:cx="262.26706" + inkscape:cy="141.90936" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="77.933462" + originy="-80.812147" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="rotate(90,-56.008515,534.91126)" + 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 V 516 L 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 h 1.53125 v 2.15625 l 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 v -2.1875 h 1.53125 l 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 V 388.48207 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" /> + </g> + <g + style="display:inline;opacity:0.2" + id="g10631" + transform="translate(670.36721,-383.29631)" /> +</svg> diff --git a/panels/wacom/wacom-stylus.svg b/panels/wacom/wacom-stylus.svg new file mode 100644 index 0000000..19122b2 --- /dev/null +++ b/panels/wacom/wacom-stylus.svg @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86343" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="190.28186" + height="31.02216" + sodipodi:docname="wacom-stylus.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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" /> + </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="1080" + inkscape:window-height="651" + id="namedview86345" + showgrid="false" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:zoom="1.9779054" + inkscape:cx="84.685547" + inkscape:cy="92.016534" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86343" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:pagecheckerboard="0" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0"> + <inkscape:grid + type="xygrid" + id="grid86802" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="-0.20774507" + originy="-0.50172038" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="rotate(90,-56.008515,534.91126)" + 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 H 344.125 c -1.9944,0 -3.59375,1.59935 -3.59375,3.59375 V 516 L 338,545.125 c -0.1873,2.15512 1.62589,3.92035 3.75,4.125 l 4.625,10.90625 h 1.53125 v 2.15625 l 3.61536,8.57242 1.18546,0.0214 0.44918,3.78119 0.33938,-3.7414 1.14797,-0.0687 3.76265,-8.53366 v -2.1875 h 1.53125 l 4.65625,-10.96875 c 1.96694,-0.35188 3.54637,-2.02216 3.40625,-4.0625 L 365.53125,516 V 399.59375 c 0,-1.9944 -1.59935,-3.59375 -3.59375,-3.59375 h -1.40625 L 360,389.47786 c -0.23272,-2.85711 -1.26201,-4.51323 -3.69531,-4.51323 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" + 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;display:inline;overflow:visible;visibility:visible;fill:#eeeeec;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" /> + </g> +</svg> diff --git a/panels/wacom/wacom-tablet-cintiq.svg b/panels/wacom/wacom-tablet-cintiq.svg new file mode 100644 index 0000000..42d9005 --- /dev/null +++ b/panels/wacom/wacom-tablet-cintiq.svg @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86858" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="117.00018" + height="91.995674" + sodipodi:docname="wacom-tablet-cintiq.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86860" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:zoom="1" + inkscape:cx="-127.5" + inkscape:cy="-18" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86858" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86892" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="-3.0021704" + originy="-2.0021639" /> + </sodipodi:namedview> + <g + style="display:inline;enable-background:new" + transform="matrix(0.68936116,0,0,0.68936115,462.30735,-77.493976)" + id="g4353-3"> + <path + inkscape:connector-curvature="0" + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1.45062px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + d="m 191,253 v 122 h 30.45602 v 10 h 20.32523 v -10 h 65.25887 v 10 h 20.4159 v -10 h 31.81597 V 253 Z" + transform="translate(-860.90625,-139.8605)" + id="rect22528" + sodipodi:nodetypes="ccccccccccccc" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#babdb6;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1.45062;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;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..fd523eb --- /dev/null +++ b/panels/wacom/wacom-tablet-pc.svg @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86858" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="117.33909" + height="85.952255" + sodipodi:docname="wacom-tablet-pc.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86860" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:zoom="1" + inkscape:cx="214.5" + inkscape:cy="-26" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg86858" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86892" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="-3.7432745" + originy="-4.9999999" /> + </sodipodi:namedview> + <path + 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" + d="M 13.793082,0.49999997 V 60.214196 L 0.81922544,85.452254 H 116.49285 l -14.68849,-26.666627 -0.012,-58.28562703 z" + id="rect22528" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + <rect + y="9.5" + x="24.756723" + height="42" + width="66" + id="rect4133-4" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#babdb6;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> +</svg> diff --git a/panels/wacom/wacom-tablet.svg b/panels/wacom/wacom-tablet.svg new file mode 100644 index 0000000..270144f --- /dev/null +++ b/panels/wacom/wacom-tablet.svg @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg86858" + version="1.1" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + width="112.47625" + height="87.658791" + sodipodi:docname="wacom-tablet.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <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="1080" + inkscape:window-height="651" + id="namedview86860" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:zoom="1.4142136" + inkscape:cx="39.244425" + inkscape:cy="86.974132" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="g4353-0" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid86892" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + originx="-3.027458" + originy="-2.8750015" /> + </sodipodi:namedview> + <g + style="display:inline" + transform="matrix(0.59415025,0,0,0.67531282,402.41987,-76.934106)" + 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;display:inline;overflow:visible;visibility:visible;fill:#babdb6;fill-opacity:1;fill-rule:nonzero;stroke:#babdb6;stroke-width:1.5787;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;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..1aa4df2 --- /dev/null +++ b/panels/wacom/wacom.gresource.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/wacom"> + <file preprocess="xml-stripblanks">cc-wacom-panel.ui</file> + <file preprocess="xml-stripblanks">cc-wacom-page.ui</file> + <file preprocess="xml-stripblanks">cc-wacom-ekr-page.ui</file> + <file preprocess="xml-stripblanks">cc-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> |