diff options
Diffstat (limited to 'plugins/wacom')
-rw-r--r-- | plugins/wacom/gsd-wacom-manager.c | 528 | ||||
-rw-r--r-- | plugins/wacom/gsd-wacom-manager.h | 39 | ||||
-rw-r--r-- | plugins/wacom/gsd-wacom-oled-constants.h | 41 | ||||
-rw-r--r-- | plugins/wacom/gsd-wacom-oled-helper.c | 422 | ||||
-rw-r--r-- | plugins/wacom/gsd-wacom-oled.c | 258 | ||||
-rw-r--r-- | plugins/wacom/gsd-wacom-oled.h | 34 | ||||
-rw-r--r-- | plugins/wacom/main.c | 7 | ||||
-rw-r--r-- | plugins/wacom/meson.build | 62 | ||||
-rw-r--r-- | plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in | 46 |
9 files changed, 1437 insertions, 0 deletions
diff --git a/plugins/wacom/gsd-wacom-manager.c b/plugins/wacom/gsd-wacom-manager.c new file mode 100644 index 0000000..0364063 --- /dev/null +++ b/plugins/wacom/gsd-wacom-manager.c @@ -0,0 +1,528 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * Copyright (C) 2010 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/>. + * + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <locale.h> + +#include <gdk/gdk.h> + +#ifdef GDK_WINDOWING_WAYLAND +#include <gdk/gdkwayland.h> +#endif +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + +#if HAVE_WACOM +#include <libwacom/libwacom.h> +#endif + +#include "gsd-enums.h" +#include "gnome-settings-profile.h" +#include "gnome-settings-bus.h" +#include "gsd-wacom-manager.h" +#include "gsd-wacom-oled.h" +#include "gsd-settings-migrate.h" +#include "gsd-input-helper.h" + + +#define UNKNOWN_DEVICE_NOTIFICATION_TIMEOUT 15000 + +#define GSD_DBUS_NAME "org.gnome.SettingsDaemon" +#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon" +#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon" + +#define GSD_WACOM_DBUS_PATH GSD_DBUS_PATH "/Wacom" +#define GSD_WACOM_DBUS_NAME GSD_DBUS_NAME ".Wacom" + +#define LEFT_HANDED_KEY "left-handed" + +static const gchar introspection_xml[] = +"<node name='/org/gnome/SettingsDaemon/Wacom'>" +" <interface name='org.gnome.SettingsDaemon.Wacom'>" +" <method name='SetOLEDLabels'>" +" <arg name='device_path' direction='in' type='s'/>" +" <arg name='labels' direction='in' type='as'/>" +" </method>" +" </interface>" +"</node>"; + +struct _GsdWacomManager +{ + GObject parent; + + guint start_idle_id; + GdkSeat *seat; + guint device_added_id; + + GsdShell *shell_proxy; + + gchar *machine_id; + +#if HAVE_WACOM + WacomDeviceDatabase *wacom_db; +#endif + + /* DBus */ + GDBusNodeInfo *introspection_data; + GDBusConnection *dbus_connection; + GCancellable *dbus_cancellable; + guint dbus_register_object_id; + guint name_id; +}; + +static void gsd_wacom_manager_class_init (GsdWacomManagerClass *klass); +static void gsd_wacom_manager_init (GsdWacomManager *wacom_manager); +static void gsd_wacom_manager_finalize (GObject *object); + +static gboolean is_opaque_tablet (GsdWacomManager *manager, + GdkDevice *device); + +G_DEFINE_TYPE (GsdWacomManager, gsd_wacom_manager, G_TYPE_OBJECT) + +static gpointer manager_object = NULL; + +static GVariant * +map_tablet_mapping (GVariant *value, GVariant *old_default, GVariant *new_default) +{ + const gchar *mapping; + + mapping = g_variant_get_boolean (value) ? "absolute" : "relative"; + return g_variant_new_string (mapping); +} + +static GVariant * +map_tablet_left_handed (GVariant *value, GVariant *old_default, GVariant *new_default) +{ + const gchar *rotation = g_variant_get_string (value, NULL); + return g_variant_new_boolean (g_strcmp0 (rotation, "half") == 0 || + g_strcmp0 (rotation, "ccw") == 0); +} + +static void +migrate_tablet_settings (GsdWacomManager *manager, + GdkDevice *device) +{ + GsdSettingsMigrateEntry tablet_settings[] = { + { "is-absolute", "mapping", map_tablet_mapping }, + { "keep-aspect", "keep-aspect", NULL }, + { "rotation", "left-handed", map_tablet_left_handed }, + }; + gchar *old_path, *new_path; + const gchar *vendor, *product; + + vendor = gdk_device_get_vendor_id (device); + product = gdk_device_get_product_id (device); + + old_path = g_strdup_printf ("/org/gnome/settings-daemon/peripherals/wacom/%s-usb:%s:%s/", + manager->machine_id, vendor, product); + new_path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/", + vendor, product); + + gsd_settings_migrate_check ("org.gnome.settings-daemon.peripherals.wacom.deprecated", + old_path, + "org.gnome.desktop.peripherals.tablet", + new_path, + tablet_settings, G_N_ELEMENTS (tablet_settings)); + + /* Opaque tablets' mapping may be modified by users, so only these + * need migration of settings. + */ + if (is_opaque_tablet (manager, device)) { + GsdSettingsMigrateEntry display_setting[] = { + { "display", "output", NULL }, + }; + + gsd_settings_migrate_check ("org.gnome.desktop.peripherals.tablet.deprecated", + new_path, + "org.gnome.desktop.peripherals.tablet", + new_path, + display_setting, G_N_ELEMENTS (display_setting)); + } + + g_free (old_path); + g_free (new_path); +} + +static void +gsd_wacom_manager_class_init (GsdWacomManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_wacom_manager_finalize; +} + +static gchar * +get_device_path (GdkDevice *device) +{ +#if HAVE_WAYLAND + if (gnome_settings_is_wayland ()) + return g_strdup (gdk_wayland_device_get_node_path (device)); + else +#endif + return xdevice_get_device_node (gdk_x11_device_get_id (device)); +} + +static gboolean +is_opaque_tablet (GsdWacomManager *manager, + GdkDevice *device) +{ + gboolean is_opaque = FALSE; +#if HAVE_WACOM + WacomDevice *wacom_device; + gchar *devpath; + + devpath = get_device_path (device); + wacom_device = libwacom_new_from_path (manager->wacom_db, devpath, + WFALLBACK_GENERIC, NULL); + if (wacom_device) { + WacomIntegrationFlags integration_flags; + + integration_flags = libwacom_get_integration_flags (wacom_device); + is_opaque = (integration_flags & + (WACOM_DEVICE_INTEGRATED_DISPLAY | WACOM_DEVICE_INTEGRATED_SYSTEM)) == 0; + libwacom_destroy (wacom_device); + } + +#endif + return is_opaque; +} + +static GdkDevice * +lookup_device_by_path (GsdWacomManager *manager, + const gchar *path) +{ + GList *devices, *l; + + devices = gdk_seat_get_slaves (manager->seat, + GDK_SEAT_CAPABILITY_ALL); + + for (l = devices; l; l = l->next) { + GdkDevice *device = l->data; + gchar *dev_path = get_device_path (device); + + if (g_strcmp0 (dev_path, path) == 0) { + g_free (dev_path); + return device; + } + + g_free (dev_path); + } + + g_list_free (devices); + + return NULL; +} + +static GSettings * +device_get_settings (GdkDevice *device) +{ + GSettings *settings; + gchar *path; + + path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/", + gdk_device_get_vendor_id (device), + gdk_device_get_product_id (device)); + settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet", + path); + g_free (path); + + return settings; +} + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer data) +{ + GsdWacomManager *self = GSD_WACOM_MANAGER (data); + GError *error = NULL; + GdkDevice *device; + + if (g_strcmp0 (method_name, "SetOLEDLabels") == 0) { + gchar *device_path, *label; + gboolean left_handed; + GSettings *settings; + GVariantIter *iter; + gint i = 0; + + g_variant_get (parameters, "(sas)", &device_path, &iter); + device = lookup_device_by_path (self, device_path); + if (!device) { + g_dbus_method_invocation_return_value (invocation, NULL); + return; + } + + settings = device_get_settings (device); + left_handed = g_settings_get_boolean (settings, LEFT_HANDED_KEY); + g_object_unref (settings); + + while (g_variant_iter_loop (iter, "s", &label)) { + if (!set_oled (device_path, left_handed, i, label, &error)) { + g_free (label); + break; + } + i++; + } + + g_variant_iter_free (iter); + + if (error) + g_dbus_method_invocation_return_gerror (invocation, error); + else + g_dbus_method_invocation_return_value (invocation, NULL); + } +} + +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + NULL, /* Get Property */ + NULL, /* Set Property */ +}; + +static void +device_added_cb (GdkSeat *seat, + GdkDevice *device, + GsdWacomManager *manager) +{ + if (gdk_device_get_source (device) == GDK_SOURCE_PEN && + gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_SLAVE) { + migrate_tablet_settings (manager, device); + } +} + +static void +add_devices (GsdWacomManager *manager, + GdkSeatCapabilities capabilities) +{ + GList *devices, *l; + + devices = gdk_seat_get_slaves (manager->seat, capabilities); + for (l = devices; l ; l = l->next) + device_added_cb (manager->seat, l->data, manager); + g_list_free (devices); +} + +static void +set_devicepresence_handler (GsdWacomManager *manager) +{ + GdkSeat *seat; + + seat = gdk_display_get_default_seat (gdk_display_get_default ()); + manager->device_added_id = g_signal_connect (seat, "device-added", + G_CALLBACK (device_added_cb), manager); + manager->seat = seat; +} + +static void +gsd_wacom_manager_init (GsdWacomManager *manager) +{ +#if HAVE_WACOM + manager->wacom_db = libwacom_database_new (); +#endif +} + +static gboolean +gsd_wacom_manager_idle_cb (GsdWacomManager *manager) +{ + gnome_settings_profile_start (NULL); + + set_devicepresence_handler (manager); + + add_devices (manager, GDK_SEAT_CAPABILITY_TABLET_STYLUS); + + gnome_settings_profile_end (NULL); + + manager->start_idle_id = 0; + + return FALSE; +} + +static void +on_bus_gotten (GObject *source_object, + GAsyncResult *res, + GsdWacomManager *manager) +{ + GDBusConnection *connection; + GError *error = NULL; + + connection = g_bus_get_finish (res, &error); + + if (connection == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Couldn't get session bus: %s", error->message); + g_error_free (error); + return; + } + + manager->dbus_connection = connection; + manager->dbus_register_object_id = g_dbus_connection_register_object (connection, + GSD_WACOM_DBUS_PATH, + manager->introspection_data->interfaces[0], + &interface_vtable, + manager, + NULL, + &error); + + if (manager->dbus_register_object_id == 0) { + g_warning ("Error registering object: %s", error->message); + g_error_free (error); + return; + } + + manager->name_id = g_bus_own_name_on_connection (connection, + GSD_WACOM_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, + NULL, + NULL, + NULL); +} + +static void +register_manager (GsdWacomManager *manager) +{ + manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + manager->dbus_cancellable = g_cancellable_new (); + g_assert (manager->introspection_data != NULL); + + g_bus_get (G_BUS_TYPE_SESSION, + manager->dbus_cancellable, + (GAsyncReadyCallback) on_bus_gotten, + manager); +} + +static gchar * +get_machine_id (void) +{ + gchar *no_per_machine_file, *machine_id = NULL; + gboolean per_machine; + gsize len; + + no_per_machine_file = g_build_filename (g_get_user_config_dir (), "gnome-settings-daemon", "no-per-machine-config", NULL); + per_machine = !g_file_test (no_per_machine_file, G_FILE_TEST_EXISTS); + g_free (no_per_machine_file); + + if (!per_machine || + (!g_file_get_contents ("/etc/machine-id", &machine_id, &len, NULL) && + !g_file_get_contents ("/var/lib/dbus/machine-id", &machine_id, &len, NULL))) { + return g_strdup ("00000000000000000000000000000000"); + } + + machine_id[len - 1] = '\0'; + return machine_id; +} + +gboolean +gsd_wacom_manager_start (GsdWacomManager *manager, + GError **error) +{ + gnome_settings_profile_start (NULL); + + register_manager (manager_object); + + manager->machine_id = get_machine_id (); + + manager->start_idle_id = g_idle_add ((GSourceFunc) gsd_wacom_manager_idle_cb, manager); + g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] gsd_wacom_manager_idle_cb"); + + gnome_settings_profile_end (NULL); + + return TRUE; +} + +void +gsd_wacom_manager_stop (GsdWacomManager *manager) +{ + g_debug ("Stopping wacom manager"); + + g_clear_pointer (&manager->machine_id, g_free); + + if (manager->name_id != 0) { + g_bus_unown_name (manager->name_id); + manager->name_id = 0; + } + + if (manager->dbus_register_object_id) { + g_dbus_connection_unregister_object (manager->dbus_connection, + manager->dbus_register_object_id); + manager->dbus_register_object_id = 0; + } + + if (manager->seat != NULL) { + g_signal_handler_disconnect (manager->seat, manager->device_added_id); + manager->seat = NULL; + } +} + +static void +gsd_wacom_manager_finalize (GObject *object) +{ + GsdWacomManager *wacom_manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_WACOM_MANAGER (object)); + + wacom_manager = GSD_WACOM_MANAGER (object); + + g_return_if_fail (wacom_manager != NULL); + + gsd_wacom_manager_stop (wacom_manager); + + if (wacom_manager->start_idle_id != 0) + g_source_remove (wacom_manager->start_idle_id); + + g_clear_object (&wacom_manager->shell_proxy); + +#if HAVE_WACOM + libwacom_database_destroy (wacom_manager->wacom_db); +#endif + + G_OBJECT_CLASS (gsd_wacom_manager_parent_class)->finalize (object); +} + +GsdWacomManager * +gsd_wacom_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + manager_object = g_object_new (GSD_TYPE_WACOM_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + } + + return GSD_WACOM_MANAGER (manager_object); +} diff --git a/plugins/wacom/gsd-wacom-manager.h b/plugins/wacom/gsd-wacom-manager.h new file mode 100644 index 0000000..faef607 --- /dev/null +++ b/plugins/wacom/gsd-wacom-manager.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * Copyright (C) 2010 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/>. + * + */ + +#ifndef __GSD_WACOM_MANAGER_H +#define __GSD_WACOM_MANAGER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GSD_TYPE_WACOM_MANAGER (gsd_wacom_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (GsdWacomManager, gsd_wacom_manager, GSD, WACOM_MANAGER, GObject) + +GsdWacomManager * gsd_wacom_manager_new (void); +gboolean gsd_wacom_manager_start (GsdWacomManager *manager, + GError **error); +void gsd_wacom_manager_stop (GsdWacomManager *manager); + +G_END_DECLS + +#endif /* __GSD_WACOM_MANAGER_H */ diff --git a/plugins/wacom/gsd-wacom-oled-constants.h b/plugins/wacom/gsd-wacom-oled-constants.h new file mode 100644 index 0000000..e93f744 --- /dev/null +++ b/plugins/wacom/gsd-wacom-oled-constants.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Przemo Firszt <przemo@firszt.eu> + * + * 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/>. + * + */ + +#ifndef __GSD_WACOM_OLED_CONSTANTS_H +#define __GSD_WACOM_OLED_CONSTANTS_H + +G_BEGIN_DECLS + +typedef enum { + GSD_WACOM_OLED_TYPE_USB, + GSD_WACOM_OLED_TYPE_BLUETOOTH, + GSD_WACOM_OLED_TYPE_RAW_BLUETOOTH +} GsdWacomOledType; + +/* OLED parameters */ +#define OLED_WIDTH 64 /*Width of OLED icon - hardware dependent*/ +#define OLED_HEIGHT 32 /*Height of OLED icon - hardware dependent*/ +#define LABEL_SIZE 30 /*Maximum length of text for OLED icon*/ +#define MAX_TOKEN (LABEL_SIZE >> 1) /*Maximum number of tokens equals half of maximum number of characters*/ +#define MAX_IMAGE_SIZE 1024 /*Size of buffer for storing OLED image*/ +#define MAX_1ST_LINE_LEN 10 /*Maximum number of characters in 1st line of OLED icon*/ + +G_END_DECLS + +#endif /* __GSD_WACOM_OLED_CONSTANTS_H */ diff --git a/plugins/wacom/gsd-wacom-oled-helper.c b/plugins/wacom/gsd-wacom-oled-helper.c new file mode 100644 index 0000000..86f2891 --- /dev/null +++ b/plugins/wacom/gsd-wacom-oled-helper.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2012 Przemo Firszt <przemo@firszt.eu> + * + * The code is derived from gsd-wacom-led-helper.c + * written by: + * Copyright (C) 2010-2011 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2012 Bastien Nocera <hadess@hadess.net> + * + * Licensed under the GNU General Public License Version 2 + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include <unistd.h> + +#include <stdlib.h> +#include "config.h" + +#include <glib.h> +#include <locale.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <gudev/gudev.h> + +#include "gsd-wacom-oled-constants.h" + +#define USB_PIXELS_PER_BYTE 2 +#define BT_PIXELS_PER_BYTE 8 +#define USB_BUF_LEN OLED_HEIGHT * OLED_WIDTH / USB_PIXELS_PER_BYTE +#define BT_BUF_LEN OLED_WIDTH * OLED_HEIGHT / BT_PIXELS_PER_BYTE + +static void +oled_scramble_icon (guchar *image) +{ + guchar buf[USB_BUF_LEN]; + int x, y, i; + guchar l1, l2, h1, h2; + + for (i = 0; i < USB_BUF_LEN; i++) + buf[i] = image[i]; + + for (y = 0; y < (OLED_HEIGHT / 2); y++) { + for (x = 0; x < (OLED_WIDTH / 2); x++) { + l1 = (0x0F & (buf[OLED_HEIGHT - 1 - x + OLED_WIDTH * y])); + l2 = (0x0F & (buf[OLED_HEIGHT - 1 - x + OLED_WIDTH * y] >> 4)); + h1 = (0xF0 & (buf[OLED_WIDTH - 1 - x + OLED_WIDTH * y] << 4)); + h2 = (0xF0 & (buf[OLED_WIDTH - 1 - x + OLED_WIDTH * y])); + + image[2 * x + OLED_WIDTH * y] = h1 | l1; + image[2 * x + 1 + OLED_WIDTH * y] = h2 | l2; + } + } +} + +static void +oled_bt_scramble_icon (guchar *input_image) +{ + unsigned char image[BT_BUF_LEN]; + unsigned mask; + unsigned s1; + unsigned s2; + unsigned r1 ; + unsigned r2 ; + unsigned r; + unsigned char buf[256]; + int i, w, x, y, z; + + for (i = 0; i < BT_BUF_LEN; i++) + image[i] = input_image[i]; + + for (x = 0; x < 32; x++) { + for (y = 0; y < 8; y++) + buf[(8 * x) + (7 - y)] = image[(8 * x) + y]; + } + + /* Change 76543210 into GECA6420 as required by Intuos4 WL + * HGFEDCBA HFDB7531 + */ + for (x = 0; x < 4; x++) { + for (y = 0; y < 4; y++) { + for (z = 0; z < 8; z++) { + mask = 0x0001; + r1 = 0; + r2 = 0; + i = (x << 6) + (y << 4) + z; + s1 = buf[i]; + s2 = buf[i+8]; + for (w = 0; w < 8; w++) { + r1 |= (s1 & mask); + r2 |= (s2 & mask); + s1 <<= 1; + s2 <<= 1; + mask <<= 2; + } + r = r1 | (r2 << 1); + i = (x << 6) + (y << 4) + (z << 1); + image[i] = 0xFF & r; + image[i+1] = (0xFF00 & r) >> 8; + } + } + } + for (i = 0; i < BT_BUF_LEN; i++) + input_image[i] = image[i]; +} + +static void +gsd_wacom_oled_convert_1_bit (guchar *image) +{ + guchar buf[BT_BUF_LEN]; + guchar b0, b1, b2, b3, b4, b5, b6, b7; + int i; + + for (i = 0; i < BT_BUF_LEN; i++) { + b0 = 0b10000000 & (image[(4 * i) + 0] >> 0); + b1 = 0b01000000 & (image[(4 * i) + 0] << 3); + b2 = 0b00100000 & (image[(4 * i) + 1] >> 2); + b3 = 0b00010000 & (image[(4 * i) + 1] << 1); + b4 = 0b00001000 & (image[(4 * i) + 2] >> 4); + b5 = 0b00000100 & (image[(4 * i) + 2] >> 1); + b6 = 0b00000010 & (image[(4 * i) + 3] >> 6); + b7 = 0b00000001 & (image[(4 * i) + 3] >> 3); + buf[i] = b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7; + } + for (i = 0; i < BT_BUF_LEN; i++) + image[i] = buf[i]; +} + +static int +gsd_wacom_oled_prepare_buf (guchar *image, GsdWacomOledType type) +{ + int len = 0; + + switch (type) { + case GSD_WACOM_OLED_TYPE_USB: + /* Image has to be scrambled for devices connected over USB ... */ + oled_scramble_icon (image); + len = USB_BUF_LEN; + break; + case GSD_WACOM_OLED_TYPE_BLUETOOTH: + /* ... but for bluetooth it has to be converted to 1 bit colour instead of scrambling */ + gsd_wacom_oled_convert_1_bit (image); + len = BT_BUF_LEN; + break; + case GSD_WACOM_OLED_TYPE_RAW_BLUETOOTH: + /* Image has also to be scrambled for devices connected over BT using the raw API ... */ + gsd_wacom_oled_convert_1_bit (image); + len = BT_BUF_LEN; + oled_bt_scramble_icon (image); + break; + default: + g_assert_not_reached (); + } + + return len; +} + +static gboolean +gsd_wacom_oled_helper_write (const gchar *filename, gchar *buffer, GsdWacomOledType type, GError **error) +{ + guchar *image; + gint retval; + gsize length; + gint fd = -1; + gboolean ret = TRUE; + + fd = open (filename, O_WRONLY); + if (fd < 0) { + ret = FALSE; + g_set_error (error, 1, 0, "Failed to open filename: %s", filename); + goto out; + } + + image = g_base64_decode (buffer, &length); + if (length != USB_BUF_LEN) { + ret = FALSE; + g_set_error (error, 1, 0, "Base64 buffer has length of %" G_GSIZE_FORMAT " (expected %i)", length, USB_BUF_LEN); + goto out; + } + if (!image) { + ret = FALSE; + g_set_error (error, 1, 0, "Decoding base64 buffer failed"); + goto out; + } + + length = gsd_wacom_oled_prepare_buf (image, type); + if (!length) { + ret = FALSE; + g_set_error (error, 1, 0, "Invalid image buffer length"); + goto out; + } + + retval = write (fd, image, length); + if (retval != length) { + ret = FALSE; + g_set_error (error, 1, 0, "Writing to %s failed", filename); + } + + g_free (image); +out: + if (fd >= 0) + close (fd); + return ret; +} + +static char * +get_oled_sysfs_path (GUdevDevice *device, + int button_num) +{ + char *status; + char *filename; + + status = g_strdup_printf ("button%d_rawimg", button_num); + filename = g_build_filename (g_udev_device_get_sysfs_path (device), "wacom_led", status, NULL); + g_free (status); + + return filename; +} + +static char * +get_bt_oled_sysfs_path (GUdevDevice *device, int button_num) +{ + char *status; + char *filename; + + status = g_strdup_printf ("/oled%i_img", button_num); + filename = g_build_filename (g_udev_device_get_sysfs_path (device), status, NULL); + g_free (status); + return filename; +} + +static char * +get_bt_oled_filename (GUdevClient *client, GUdevDevice *device, int button_num) +{ + GUdevDevice *hid_dev; + const char *dev_uniq; + GList *hid_list; + GList *element; + const char *dev_hid_uniq; + char *filename = NULL; + + dev_uniq = g_udev_device_get_property (device, "UNIQ"); + + hid_list = g_udev_client_query_by_subsystem (client, "hid"); + element = g_list_first(hid_list); + while (element) { + hid_dev = (GUdevDevice*)element->data; + dev_hid_uniq = g_udev_device_get_property (hid_dev, "HID_UNIQ"); + if (g_strrstr (dev_uniq, dev_hid_uniq)){ + filename = get_bt_oled_sysfs_path (hid_dev, button_num); + g_object_unref (hid_dev); + break; + } + g_object_unref (hid_dev); + element = g_list_next(element); + } + g_list_free(hid_list); + return filename; +} + +static char * +get_oled_sys_path (GUdevClient *client, + GUdevDevice *device, + int button_num, + gboolean usb, + GsdWacomOledType *type) +{ + GUdevDevice *parent; + char *filename = NULL; + + /* check for new unified hid implementation first */ + parent = g_udev_device_get_parent_with_subsystem (device, "hid", NULL); + if (parent) { + filename = get_oled_sysfs_path (parent, button_num); + g_object_unref (parent); + if(g_file_test (filename, G_FILE_TEST_EXISTS)) { + *type = usb ? GSD_WACOM_OLED_TYPE_USB : GSD_WACOM_OLED_TYPE_RAW_BLUETOOTH; + return filename; + } + g_clear_pointer (&filename, g_free); + } + + /* old kernel */ + if (usb) { + parent = g_udev_device_get_parent_with_subsystem (device, "usb", "usb_interface"); + if (!parent) + goto no_parent; + + filename = get_oled_sysfs_path (parent, button_num); + *type = GSD_WACOM_OLED_TYPE_USB; + } else if (g_strrstr (g_udev_device_get_property (device, "DEVPATH"), "bluetooth")) { + parent = g_udev_device_get_parent_with_subsystem (device, "input", NULL); + if (!parent) + goto no_parent; + + filename = get_bt_oled_filename (client, parent, button_num); + *type = GSD_WACOM_OLED_TYPE_BLUETOOTH; + } else { + g_critical ("Not an expected device: '%s'", + g_udev_device_get_device_file (device)); + goto out_err; + } + + g_object_unref (parent); + + return filename; + +no_parent: + g_debug ("Could not find proper parent device for '%s'", + g_udev_device_get_device_file (device)); + +out_err: + return NULL; +} + + +int main (int argc, char **argv) +{ + GOptionContext *context; + GUdevClient *client; + GUdevDevice *device; + int uid, euid; + char *filename; + GError *error = NULL; + const char * const subsystems[] = { "input", NULL }; + int ret = 1; + gboolean usb; + GsdWacomOledType type; + + char *path = NULL; + char *buffer = NULL; + int button_num = -1; + + const GOptionEntry options[] = { + { "path", '\0', 0, G_OPTION_ARG_FILENAME, &path, "Device path for the Wacom device", NULL }, + { "buffer", '\0', 0, G_OPTION_ARG_STRING, &buffer, "Image to set base64 encoded", NULL }, + { "button", '\0', 0, G_OPTION_ARG_INT, &button_num, "Which button icon to set", NULL }, + { NULL} + }; + + /* get calling process */ + uid = getuid (); + euid = geteuid (); + if (uid != 0 || euid != 0) { + g_print ("This program can only be used by the root user\n"); + return 1; + } + + context = g_option_context_new (NULL); + g_option_context_set_summary (context, "GNOME Settings Daemon Wacom OLED Icon Helper"); + g_option_context_add_main_entries (context, options, NULL); + g_option_context_parse (context, &argc, &argv, NULL); + + if (path == NULL || + button_num < 0 || + buffer == NULL) { + char *txt; + + txt = g_option_context_get_help (context, FALSE, NULL); + g_print ("%s", txt); + g_free (txt); + + g_option_context_free (context); + + return 1; + } + g_option_context_free (context); + + client = g_udev_client_new (subsystems); + device = g_udev_client_query_by_device_file (client, path); + if (device == NULL) { + g_critical ("Could not find device '%s' in udev database", path); + goto out; + } + + if (g_udev_device_get_property_as_boolean (device, "ID_INPUT_TABLET") == FALSE && + g_udev_device_get_property_as_boolean (device, "ID_INPUT_TOUCHPAD") == FALSE) { + g_critical ("Device '%s' is not a Wacom tablet", path); + goto out; + } + + if (g_strcmp0 (g_udev_device_get_property (device, "ID_BUS"), "usb") != 0) + usb = FALSE; + else + usb = TRUE; + + filename = get_oled_sys_path (client, device, button_num, usb, &type); + if (!filename) + goto out; + + if (gsd_wacom_oled_helper_write (filename, buffer, type, &error) == FALSE) { + g_critical ("Could not set OLED icon for '%s': %s", path, error->message); + g_error_free (error); + g_free (filename); + goto out; + } + g_free (filename); + + g_debug ("Successfully set OLED icon for '%s', button %d", path, button_num); + + ret = 0; + +out: + g_free (path); + g_free (buffer); + + g_clear_object (&device); + g_clear_object (&client); + + return ret; +} diff --git a/plugins/wacom/gsd-wacom-oled.c b/plugins/wacom/gsd-wacom-oled.c new file mode 100644 index 0000000..4c2daf1 --- /dev/null +++ b/plugins/wacom/gsd-wacom-oled.c @@ -0,0 +1,258 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Przemo Firszt <przemo@firszt.eu> + * + * 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 <unistd.h> +#include <math.h> +#include <pango/pango.h> +#include <pango/pangocairo.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "gsd-wacom-oled.h" + +#define MAGIC_BASE64 "base64:" /*Label starting with base64: is treated as already encoded*/ +#define MAGIC_BASE64_LEN strlen(MAGIC_BASE64) + +static void +oled_surface_to_image (guchar *image, + cairo_surface_t *surface) +{ + unsigned char *csurf; + int i, x, y; + unsigned char lo, hi; + + cairo_surface_flush (surface); + csurf = cairo_image_surface_get_data (surface); + i = 0; + for (y = 0; y < OLED_HEIGHT; y++) { + for (x = 0; x < (OLED_WIDTH / 2); x++) { + hi = 0xf0 & csurf[4 * OLED_WIDTH * y + 8 * x + 1]; + lo = 0x0f & (csurf[4 * OLED_WIDTH * y + 8 * x + 5] >> 4); + image[i] = hi | lo; + i++; + } + } +} + +static void +oled_split_text (char *label, + char *line1, + char *line2) +{ + char delimiters[5] = "+-_ "; + char **token; + int token_len[MAX_TOKEN]; + gsize length; + int i; + + if (g_utf8_strlen (label, LABEL_SIZE) <= MAX_1ST_LINE_LEN) { + g_utf8_strncpy (line1, label, MAX_1ST_LINE_LEN); + return; + } + + token = g_strsplit_set (label, delimiters, -1); + + if (g_utf8_strlen (token[0], LABEL_SIZE) > MAX_1ST_LINE_LEN) { + g_utf8_strncpy (line1, label, MAX_1ST_LINE_LEN); + g_utf8_strncpy (line2, label + MAX_1ST_LINE_LEN, LABEL_SIZE - MAX_1ST_LINE_LEN); + return; + } + + for (i = 0; token[i] != NULL; i++) + token_len[i] = g_utf8_strlen (token[i], LABEL_SIZE); + + length = token_len[0]; + i = 0; + while ((length + token_len[i + 1] + 1) <= MAX_1ST_LINE_LEN) { + i++; + length = length + token_len[i] + 1; + } + + g_utf8_strncpy (line1, label, length); + g_utf8_strncpy (line2, label + length + 1, LABEL_SIZE - length); + + return; +} + +static void +oled_render_text (char *label, + guchar *image, + gboolean left_handed) +{ + cairo_t *cr; + cairo_surface_t *surface; + PangoFontDescription *desc; + PangoLayout *layout; + int width, height; + double dx, dy; + char line1[LABEL_SIZE + 1] = ""; + char line2[LABEL_SIZE + 1] = ""; + char *buf; + + oled_split_text (label ,line1, line2); + + buf = g_strdup_printf ("%s\n%s", line1, line2); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, OLED_WIDTH, OLED_HEIGHT); + cr = cairo_create (surface); + + /* Rotate text so it's seen correctly on the device, or at + * least from top to bottom for LTR text in vertical modes. + */ + if (left_handed) { + cairo_translate (cr, OLED_WIDTH, OLED_HEIGHT); + cairo_scale (cr, -1, -1); + } + + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.99); + cairo_paint (cr); + + layout = pango_cairo_create_layout (cr); + pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); + pango_layout_set_text (layout, buf, - 1); + g_free (buf); + desc = pango_font_description_new (); + + pango_font_description_set_family (desc, "Terminal"); + pango_font_description_set_absolute_size (desc, PANGO_SCALE * 11); + pango_layout_set_font_description (layout, desc); + pango_font_description_free (desc); + pango_layout_get_size (layout, &width, &height); + width = width/PANGO_SCALE; + cairo_new_path (cr); + + dx = trunc (((double)OLED_WIDTH - width) / 2); + + if (*line2 == '\0') + dy = 10; + else + dy = 4; + + cairo_move_to (cr, dx, dy); + cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0); + pango_cairo_update_layout (cr, layout); + + pango_cairo_layout_path (cr, layout); + cairo_fill (cr); + + oled_surface_to_image (image, surface); + + g_object_unref (layout); + cairo_destroy (cr); + + cairo_surface_destroy (surface); +} + +char * +gsd_wacom_oled_gdkpixbuf_to_base64 (GdkPixbuf *pixbuf) +{ + int i, x, y, ch, rs; + guchar *pix, *p; + guchar *image; + guchar lo, hi; + char *base_string, *base64; + + if (OLED_WIDTH != gdk_pixbuf_get_width (pixbuf)) + return NULL; + + if (OLED_HEIGHT != gdk_pixbuf_get_height (pixbuf)) + return NULL; + + ch = gdk_pixbuf_get_n_channels (pixbuf); + rs = gdk_pixbuf_get_rowstride (pixbuf); + pix = gdk_pixbuf_get_pixels (pixbuf); + + image = g_malloc (MAX_IMAGE_SIZE); + i = 0; + for (y = 0; y < OLED_HEIGHT; y++) { + for (x = 0; x < (OLED_WIDTH / 2); x++) { + p = pix + y * rs + 2 * x * ch; + hi = 0xf0 & ((p[0] + p[1] + p[2])/ 3 * p[3] / 0xff); + p = pix + y * rs + ((2 * x) + 1) * ch; + lo = 0x0f & (((p[0] + p[1] + p[2])/ 3 * p[3] / 0xff) >> 4); + image[i] = hi | lo; + i++; + } + } + + base_string = g_base64_encode (image, MAX_IMAGE_SIZE); + base64 = g_strconcat (MAGIC_BASE64, base_string, NULL); + g_free (base_string); + g_free (image); + + return base64; +} + +static char * +oled_encode_image (char *label, + gboolean left_handed) +{ + unsigned char *image; + + image = g_malloc (MAX_IMAGE_SIZE); + + /* convert label to image */ + oled_render_text (label, image, left_handed); + + return (g_base64_encode (image, MAX_IMAGE_SIZE)); +} + +gboolean +set_oled (const gchar *device_path, + gboolean left_handed, + guint button, + char *label, + GError **error) +{ + char *command; + gboolean ret; + char *buffer; + gint status; + +#ifndef HAVE_GUDEV + /* Not implemented on non-Linux systems */ + return TRUE; +#endif + + if (g_str_has_prefix (label, MAGIC_BASE64)) { + buffer = g_strdup (label + MAGIC_BASE64_LEN); + } else { + buffer = oled_encode_image (label, left_handed); + } + + g_debug ("Setting OLED label '%s' on button %d (device %s)", label, button, device_path); + + command = g_strdup_printf ("pkexec " LIBEXECDIR "/gsd-wacom-oled-helper --path %s --button %d --buffer %s", + device_path, button, buffer); + ret = g_spawn_command_line_sync (command, + NULL, + NULL, + &status, + error); + + if (ret == TRUE && status != 0) + ret = FALSE; + + g_free (command); + + return ret; +} diff --git a/plugins/wacom/gsd-wacom-oled.h b/plugins/wacom/gsd-wacom-oled.h new file mode 100644 index 0000000..dc5d39c --- /dev/null +++ b/plugins/wacom/gsd-wacom-oled.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Przemo Firszt <przemo@firszt.eu> + * + * 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 "gsd-wacom-oled-constants.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +#ifndef __GSD_WACOM_OLED_H +#define __GSD_WACOM_OLED_H + +G_BEGIN_DECLS + +gboolean set_oled (const gchar *device_path, gboolean left_handed, guint button, char *label, GError **error); +char *gsd_wacom_oled_gdkpixbuf_to_base64 (GdkPixbuf *pixbuf); + +G_END_DECLS + +#endif /* __GSD_WACOM_OLED_H */ diff --git a/plugins/wacom/main.c b/plugins/wacom/main.c new file mode 100644 index 0000000..a19a71b --- /dev/null +++ b/plugins/wacom/main.c @@ -0,0 +1,7 @@ +#define NEW gsd_wacom_manager_new +#define START gsd_wacom_manager_start +#define STOP gsd_wacom_manager_stop +#define MANAGER GsdWacomManager +#include "gsd-wacom-manager.h" + +#include "daemon-skeleton-gtk.h" diff --git a/plugins/wacom/meson.build b/plugins/wacom/meson.build new file mode 100644 index 0000000..c00323d --- /dev/null +++ b/plugins/wacom/meson.build @@ -0,0 +1,62 @@ +policy = 'org.gnome.settings-daemon.plugins.wacom.policy' + +policy_in = configure_file( + input: policy + '.in.in', + output: policy + '.in', + configuration: plugins_conf +) + +i18n.merge_file( + input: policy_in, + output: policy, + po_dir: po_dir, + install: true, + install_dir: join_paths(gsd_datadir, 'polkit-1', 'actions') +) + +sources = files( + 'gsd-wacom-manager.c', + 'gsd-wacom-oled.c', + 'main.c' +) + +deps = plugins_deps + [ + gtk_dep, + libcommon_dep, + m_dep, + pango_dep +] + +if enable_wacom + deps += libwacom_dep +endif + +cflags += ['-DLIBEXECDIR="@0@"'.format(gsd_libexecdir)] + +executable( + 'gsd-' + plugin_name, + sources, + include_directories: [top_inc, data_inc], + dependencies: deps, + c_args: cflags, + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir +) + +if enable_gudev + deps = [ + gudev_dep, + m_dep + ] + + executable( + 'gsd-wacom-oled-helper', + 'gsd-wacom-oled-helper.c', + include_directories: top_inc, + dependencies: deps, + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir + ) +endif diff --git a/plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in b/plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in new file mode 100644 index 0000000..fe0df93 --- /dev/null +++ b/plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> +<policyconfig> + + <!-- + Policy definitions for gnome-settings-daemon system-wide actions. + --> + + <vendor>GNOME Settings Daemon</vendor> + <vendor_url>http://git.gnome.org/browse/gnome-settings-daemon</vendor_url> + <icon_name>input-tablet</icon_name> + + <action id="org.gnome.settings-daemon.plugins.wacom.wacom-led-helper"> + <!-- SECURITY: + - A normal active user on the local machine does not need permission + to change the LED setting for a Wacom tablet + --> + <description>Modify the lit LED for a Wacom tablet</description> + <message>Authentication is required to modify the lit LED for a Wacom tablet</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-wacom-led-helper</annotate> + </action> + + <action id="org.gnome.settings-daemon.plugins.wacom.wacom-oled-helper"> + <!-- SECURITY: + - A normal active user on the local machine does not need permission + to change the OLED images for a Wacom tablet + --> + <description>Modify the OLED image for a Wacom tablet</description> + <message>Authentication is required to modify the OLED image for a Wacom tablet</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.exec.path">@libexecdir@/gsd-wacom-oled-helper</annotate> + </action> + +</policyconfig> + |