diff options
Diffstat (limited to 'panels/wacom/cc-wacom-panel.c')
-rw-r--r-- | panels/wacom/cc-wacom-panel.c | 753 |
1 files changed, 753 insertions, 0 deletions
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; +} |