[gnome-settings-daemon] wacom: implement OSD help window



commit 8eb78560cc698a196cdc995a8585ea85d6896cea
Author: Olivier Fourdan <ofourdan redhat com>
Date:   Tue Dec 4 12:04:05 2012 +0100

    wacom: implement OSD help window
    
    The purpose of that OSD window is to give users a simple way
    to display the current pad button functions.
    
    The on-screen buttons also show when a pad button is pressed
    so that users can visually check the matching button/function.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=679062

 configure.ac                         |    2 +-
 data/gsd-enums.h                     |    3 +-
 plugins/wacom/Makefile.am            |   55 ++-
 plugins/wacom/gsd-wacom-device.c     |   89 ++-
 plugins/wacom/gsd-wacom-device.h     |   18 +
 plugins/wacom/gsd-wacom-manager.c    |  120 +++
 plugins/wacom/gsd-wacom-osd-window.c | 1507 ++++++++++++++++++++++++++++++++++
 plugins/wacom/gsd-wacom-osd-window.h |   61 ++
 plugins/wacom/tablet-layout.css      |   34 +
 plugins/wacom/test-osd-window.c      |  137 +++
 plugins/wacom/wacom.gresource.xml    |    7 +
 po/POTFILES.in                       |    1 +
 12 files changed, 2023 insertions(+), 11 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1faec53..8a3c950 100644
--- a/configure.ac
+++ b/configure.ac
@@ -249,7 +249,7 @@ case $host_os in
       have_wacom=no
     else
       if test x$enable_gudev != xno; then
-        PKG_CHECK_MODULES(WACOM, [libwacom >= $LIBWACOM_REQUIRED_VERSION x11 xi xtst gudev-1.0 gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION xorg-wacom])
+        PKG_CHECK_MODULES(WACOM, [libwacom >= $LIBWACOM_REQUIRED_VERSION x11 xi xtst gudev-1.0 gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION xorg-wacom librsvg-2.0])
       else
         AC_MSG_ERROR([GUdev is necessary to compile Wacom support])
       fi
diff --git a/data/gsd-enums.h b/data/gsd-enums.h
index 8b8f2f5..b6876a3 100644
--- a/data/gsd-enums.h
+++ b/data/gsd-enums.h
@@ -95,7 +95,8 @@ typedef enum
 {
   GSD_WACOM_ACTION_TYPE_NONE,
   GSD_WACOM_ACTION_TYPE_CUSTOM,
-  GSD_WACOM_ACTION_TYPE_SWITCH_MONITOR
+  GSD_WACOM_ACTION_TYPE_SWITCH_MONITOR,
+  GSD_WACOM_ACTION_TYPE_HELP
 } GsdWacomActionType;
 
 typedef enum
diff --git a/plugins/wacom/Makefile.am b/plugins/wacom/Makefile.am
index 662388b..67701b1 100644
--- a/plugins/wacom/Makefile.am
+++ b/plugins/wacom/Makefile.am
@@ -7,8 +7,11 @@ libgsdwacom_la_SOURCES =	\
 	gsd-wacom-plugin.c	\
 	gsd-wacom-manager.h	\
 	gsd-wacom-manager.c	\
+	gsd-wacom-osd-window.h	\
+	gsd-wacom-osd-window.c	\
 	gsd-wacom-device.c	\
-	gsd-wacom-device.h
+	gsd-wacom-device.h	\
+	gsd-wacom-resources.c
 
 libgsdwacom_la_CPPFLAGS = \
 	-I$(top_srcdir)/gnome-settings-daemon		\
@@ -35,6 +38,14 @@ libgsdwacom_la_LIBADD  =						\
 org.gnome.settings-daemon.plugins.wacom.policy.in: org.gnome.settings-daemon.plugins.wacom.policy.in.in Makefile
 	$(AM_V_GEN) sed -e "s|\ libexecdir\@|$(libexecdir)|" $< > $@
 
+gsd-wacom-resources.c: wacom.gresource.xml tablet-layout.css
+	glib-compile-resources 				\
+		--target=$@ 				\
+		--sourcedir=$(srcdir) 			\
+		--generate-source 			\
+		--c-name gsd_wacom 			\
+		$(srcdir)/wacom.gresource.xml
+
 @INTLTOOL_POLICY_RULE@
 polkit_policydir = $(datadir)/polkit-1/actions
 polkit_policy_in_files = org.gnome.settings-daemon.plugins.wacom.policy.in
@@ -43,7 +54,7 @@ polkit_policy_DATA = $(polkit_policy_in_files:.policy.in=.policy)
 # so it always gets included in the tarball
 gsd_wacom_led_helper_SOURCES =	gsd-wacom-led-helper.c
 
-EXTRA_DIST = $(gsd_wacom_led_helper_SOURCES)
+EXTRA_DIST = $(gsd_wacom_led_helper_SOURCES) wacom.gresource.xml tablet-layout.css
 
 if HAVE_GUDEV
 libexec_PROGRAMS = gsd-wacom-led-helper
@@ -60,14 +71,17 @@ endif
 
 EXTRA_DIST += org.gnome.settings-daemon.plugins.wacom.policy.in.in
 
-libexec_PROGRAMS += gsd-test-wacom gsd-list-wacom
+libexec_PROGRAMS += gsd-test-wacom gsd-list-wacom gsd-test-wacom-osd
 
 gsd_test_wacom_SOURCES =	\
 	test-wacom.c		\
 	gsd-wacom-manager.c	\
 	gsd-wacom-manager.h	\
+	gsd-wacom-osd-window.h	\
+	gsd-wacom-osd-window.c	\
 	gsd-wacom-device.c	\
-	gsd-wacom-device.h
+	gsd-wacom-device.h	\
+	gsd-wacom-resources.c
 
 gsd_test_wacom_CPPFLAGS = \
 	-I$(top_srcdir)/data/					\
@@ -123,6 +137,38 @@ gsd_list_wacom_LDADD =						\
 	$(WACOM_LIBS)						\
 	-lm
 
+gsd_test_wacom_osd_SOURCES =					\
+	test-osd-window.c					\
+	gsd-wacom-osd-window.h					\
+	gsd-wacom-osd-window.c					\
+	gsd-wacom-device.c					\
+	gsd-wacom-device.h					\
+	gsd-wacom-resources.c
+
+gsd_test_wacom_osd_CPPFLAGS = \
+	-I$(top_srcdir)/data/					\
+	-I$(top_srcdir)/gnome-settings-daemon			\
+	-I$(top_srcdir)/plugins/common				\
+	-DBINDIR=\"$(bindir)\"					\
+	-DPIXMAPDIR=\""$(pkgdatadir)"\"				\
+	-DGTKBUILDERDIR=\""$(pkgdatadir)"\"			\
+	-DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\"	\
+	-DLIBEXECDIR=\""$(libexecdir)"\"			\
+	$(AM_CPPFLAGS)
+
+gsd_test_wacom_osd_CFLAGS =					\
+	$(SETTINGS_PLUGIN_CFLAGS)				\
+	$(WACOM_CFLAGS)						\
+	$(AM_CFLAGS)
+
+gsd_test_wacom_osd_LDADD = \
+	$(top_builddir)/gnome-settings-daemon/libgsd.la		\
+	$(top_builddir)/plugins/common/libcommon.la		\
+	$(SETTINGS_DAEMON_LIBS)					\
+	$(SETTINGS_PLUGIN_LIBS)					\
+	$(WACOM_LIBS)						\
+	-lm
+
 plugin_in_files = wacom.gnome-settings-plugin.in
 
 plugin_DATA = $(plugin_in_files:.gnome-settings-plugin.in=.gnome-settings-plugin)
@@ -130,6 +176,7 @@ plugin_DATA = $(plugin_in_files:.gnome-settings-plugin.in=.gnome-settings-plugin
 EXTRA_DIST += $(plugin_in_files) README.config-storage
 CLEANFILES =						\
 	$(plugin_DATA)					\
+	gsd-wacom-resources.c				\
 	org.gnome.settings-daemon.plugins.wacom.policy	\
 	org.gnome.settings-daemon.plugins.wacom.policy.in
 
diff --git a/plugins/wacom/gsd-wacom-device.c b/plugins/wacom/gsd-wacom-device.c
index ba1f83e..28d6e4d 100644
--- a/plugins/wacom/gsd-wacom-device.c
+++ b/plugins/wacom/gsd-wacom-device.c
@@ -255,6 +255,7 @@ gsd_wacom_tablet_button_new (const char               *name,
 			     const char               *id,
 			     const char               *settings_path,
 			     GsdWacomTabletButtonType  type,
+			     GsdWacomTabletButtonPos   pos,
 			     int                       group_id,
 			     int                       idx,
 			     int                       status_led)
@@ -274,6 +275,7 @@ gsd_wacom_tablet_button_new (const char               *name,
 	ret->group_id = group_id;
 	ret->idx = idx;
 	ret->type = type;
+	ret->pos = pos;
 	ret->status_led = status_led;
 
 	return ret;
@@ -330,6 +332,7 @@ struct GsdWacomDevicePrivate
 	char *path;
 	char *machine_id;
 	const char *icon_name;
+	char *layout_path;
 	char *tool_name;
 	gboolean reversible;
 	gboolean is_screen_tablet;
@@ -957,6 +960,32 @@ add_stylus_to_device (GsdWacomDevice *device,
 }
 
 int
+gsd_wacom_device_get_num_modes (GsdWacomDevice *device,
+				int             group_id)
+{
+	int num_modes;
+
+	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (device), -1);
+	num_modes = GPOINTER_TO_INT (g_hash_table_lookup (device->priv->num_modes, GINT_TO_POINTER(group_id)));
+
+	return num_modes;
+}
+
+int
+gsd_wacom_device_get_current_mode (GsdWacomDevice *device,
+				   int             group_id)
+{
+	int current_idx;
+
+	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (device), -1);
+	current_idx = GPOINTER_TO_INT (g_hash_table_lookup (device->priv->modes, GINT_TO_POINTER(group_id)));
+	/* That means that the mode doesn't exist, see gsd_wacom_device_add_modes() */
+	g_return_val_if_fail (current_idx != 0, -1);
+
+	return current_idx;
+}
+
+int
 gsd_wacom_device_set_next_mode (GsdWacomDevice       *device,
 				GsdWacomTabletButton *button)
 {
@@ -996,9 +1025,9 @@ gsd_wacom_device_set_next_mode (GsdWacomDevice       *device,
 
 	/* Only one mode-switch? cycle through the modes */
 	if (num_switches == 1) {
-		current_idx = GPOINTER_TO_INT (g_hash_table_lookup (device->priv->modes, GINT_TO_POINTER(group_id)));
-		/* That means that the mode doesn't exist, see gsd_wacom_device_add_modes() */
-		g_return_val_if_fail (current_idx != 0, -1);
+		current_idx = gsd_wacom_device_get_current_mode (device, group_id);
+		/* gsd_wacom_device_get_current_mode() returns -1 when the mode doesn't exist */
+		g_return_val_if_fail (current_idx > 0, -1);
 
 		current_idx++;
 	}
@@ -1048,6 +1077,7 @@ gsd_wacom_device_add_ring_modes (WacomDevice      *wacom_device,
 									   "left-ring-mode-1",
 									   settings_path,
 									   WACOM_TABLET_BUTTON_TYPE_RING,
+									   WACOM_TABLET_BUTTON_POS_LEFT,
 									   group,
 									   0,
 									   GSD_WACOM_NO_LED));
@@ -1059,6 +1089,7 @@ gsd_wacom_device_add_ring_modes (WacomDevice      *wacom_device,
 				                                                   id,
 				                                                   settings_path,
 				                                                   WACOM_TABLET_BUTTON_TYPE_RING,
+										   WACOM_TABLET_BUTTON_POS_LEFT,
 				                                                   group,
 				                                                   i - 1,
 										   GSD_WACOM_NO_LED));
@@ -1075,6 +1106,7 @@ gsd_wacom_device_add_ring_modes (WacomDevice      *wacom_device,
 									   "right-ring-mode-1",
 									   settings_path,
 									   WACOM_TABLET_BUTTON_TYPE_RING,
+									   WACOM_TABLET_BUTTON_POS_RIGHT,
 									   group,
 									   0,
 									   GSD_WACOM_NO_LED));
@@ -1086,6 +1118,7 @@ gsd_wacom_device_add_ring_modes (WacomDevice      *wacom_device,
 				                                                   id,
 				                                                   settings_path,
 				                                                   WACOM_TABLET_BUTTON_TYPE_RING,
+										   WACOM_TABLET_BUTTON_POS_RIGHT,
 				                                                   group,
 				                                                   i - 1,
 										   GSD_WACOM_NO_LED));
@@ -1124,6 +1157,7 @@ gsd_wacom_device_add_strip_modes (WacomDevice      *wacom_device,
 									   "left-strip-mode-1",
 									   settings_path,
 									   WACOM_TABLET_BUTTON_TYPE_STRIP,
+									   WACOM_TABLET_BUTTON_POS_LEFT,
 									   group,
 									   0,
 									   GSD_WACOM_NO_LED));
@@ -1135,6 +1169,7 @@ gsd_wacom_device_add_strip_modes (WacomDevice      *wacom_device,
 				                                                   id,
 				                                                   settings_path,
 				                                                   WACOM_TABLET_BUTTON_TYPE_STRIP,
+										   WACOM_TABLET_BUTTON_POS_LEFT,
 				                                                   group,
 				                                                   i - 1,
 										   GSD_WACOM_NO_LED));
@@ -1151,6 +1186,7 @@ gsd_wacom_device_add_strip_modes (WacomDevice      *wacom_device,
 									   "right-strip-mode-1",
 									   settings_path,
 									   WACOM_TABLET_BUTTON_TYPE_STRIP,
+									   WACOM_TABLET_BUTTON_POS_RIGHT,
 									   group,
 									   0,
 									   GSD_WACOM_NO_LED));
@@ -1162,6 +1198,7 @@ gsd_wacom_device_add_strip_modes (WacomDevice      *wacom_device,
 				                                                   id,
 				                                                   settings_path,
 				                                                   WACOM_TABLET_BUTTON_TYPE_STRIP,
+										   WACOM_TABLET_BUTTON_POS_RIGHT,
 				                                                   group,
 				                                                   i - 1,
 										   GSD_WACOM_NO_LED));
@@ -1195,6 +1232,23 @@ gsd_wacom_device_modeswitch_name (WacomButtonFlags flags,
 	return g_strdup_printf (_("Mode Switch #%d"), button_num);
 }
 
+static GsdWacomTabletButtonType
+gsd_wacom_device_button_pos (WacomButtonFlags flags)
+{
+	if (flags & WACOM_BUTTON_POSITION_LEFT)
+		return WACOM_TABLET_BUTTON_POS_LEFT;
+	else if (flags & WACOM_BUTTON_POSITION_RIGHT)
+		return WACOM_TABLET_BUTTON_POS_RIGHT;
+	else if (flags & WACOM_BUTTON_POSITION_TOP)
+		return WACOM_TABLET_BUTTON_POS_TOP;
+	else if (flags & WACOM_BUTTON_POSITION_BOTTOM)
+		return WACOM_TABLET_BUTTON_POS_BOTTOM;
+
+	g_warning ("Unhandled button position");
+
+	return WACOM_TABLET_BUTTON_POS_UNDEF;
+}
+
 static GList *
 gsd_wacom_device_add_buttons_dir (WacomDevice      *wacom_device,
 				  const char       *settings_path,
@@ -1221,7 +1275,14 @@ gsd_wacom_device_add_buttons_dir (WacomDevice      *wacom_device,
 
 		name = g_strdup_printf (button_str, button_num++);
 		id = g_strdup_printf ("%s%c", button_str_id, i);
-		l = g_list_append (l, gsd_wacom_tablet_button_new (name, id, settings_path, WACOM_TABLET_BUTTON_TYPE_NORMAL, flags_to_group (flags), -1, GSD_WACOM_NO_LED));
+		l = g_list_append (l, gsd_wacom_tablet_button_new (name,
+		                                                   id,
+		                                                   settings_path,
+		                                                   WACOM_TABLET_BUTTON_TYPE_NORMAL,
+		                                                   gsd_wacom_device_button_pos (flags),
+		                                                   flags_to_group (flags),
+		                                                   -1,
+		                                                   GSD_WACOM_NO_LED));
 		g_free (name);
 		g_free (id);
 	}
@@ -1242,7 +1303,14 @@ gsd_wacom_device_add_buttons_dir (WacomDevice      *wacom_device,
 		name = gsd_wacom_device_modeswitch_name (flags, button_num++);
 		id = g_strdup_printf ("%s%c", button_str_id, i);
 		status_led = libwacom_get_button_led_group (wacom_device, i);
-		l = g_list_append (l, gsd_wacom_tablet_button_new (name, id, settings_path, WACOM_TABLET_BUTTON_TYPE_HARDCODED, flags_to_group (flags), -1, status_led));
+		l = g_list_append (l, gsd_wacom_tablet_button_new (name,
+		                                                   id,
+		                                                   settings_path,
+		                                                   WACOM_TABLET_BUTTON_TYPE_HARDCODED,
+		                                                   gsd_wacom_device_button_pos (flags),
+		                                                   flags_to_group (flags),
+		                                                   -1,
+		                                                   status_led));
 		g_free (name);
 		g_free (id);
 	}
@@ -1343,6 +1411,7 @@ gsd_wacom_device_update_from_db (GsdWacomDevice *device,
 								 settings_path);
 
 	device->priv->name = g_strdup (libwacom_get_name (wacom_device));
+	device->priv->layout_path = g_strdup (libwacom_get_layout_filename (wacom_device));
 	device->priv->reversible = libwacom_is_reversible (wacom_device);
 	integration_flags = libwacom_get_integration_flags (wacom_device);
 	device->priv->is_screen_tablet = (integration_flags & WACOM_DEVICE_INTEGRATED_DISPLAY);
@@ -1592,6 +1661,8 @@ gsd_wacom_device_finalize (GObject *object)
                 p->num_modes = NULL;
         }
 
+	g_clear_pointer (&p->layout_path, g_free);
+
 	gdk_window_remove_filter (NULL,
 				  (GdkFilterFunc) filter_events,
 				  device);
@@ -1641,6 +1712,14 @@ gsd_wacom_device_get_name (GsdWacomDevice *device)
 }
 
 const char *
+gsd_wacom_device_get_layout_path (GsdWacomDevice *device)
+{
+	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (device), NULL);
+
+	return device->priv->layout_path;
+}
+
+const char *
 gsd_wacom_device_get_path (GsdWacomDevice *device)
 {
 	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (device), NULL);
diff --git a/plugins/wacom/gsd-wacom-device.h b/plugins/wacom/gsd-wacom-device.h
index c593e66..335945f 100644
--- a/plugins/wacom/gsd-wacom-device.h
+++ b/plugins/wacom/gsd-wacom-device.h
@@ -98,6 +98,18 @@ typedef enum {
 	WACOM_TABLET_BUTTON_TYPE_HARDCODED
 } GsdWacomTabletButtonType;
 
+/*
+ * Positions of the buttons on the tablet in default right-handed mode
+ * (ie with no rotation applied).
+ */
+typedef enum {
+	WACOM_TABLET_BUTTON_POS_UNDEF = 0,
+	WACOM_TABLET_BUTTON_POS_LEFT,
+	WACOM_TABLET_BUTTON_POS_RIGHT,
+	WACOM_TABLET_BUTTON_POS_TOP,
+	WACOM_TABLET_BUTTON_POS_BOTTOM
+} GsdWacomTabletButtonPos;
+
 #define MAX_GROUP_ID 4
 
 #define GSD_WACOM_NO_LED -1
@@ -108,6 +120,7 @@ typedef struct
 	char                     *id;
 	GSettings                *settings;
 	GsdWacomTabletButtonType  type;
+	GsdWacomTabletButtonPos   pos;
 	int                       group_id, idx;
 	int                       status_led;
 } GsdWacomTabletButton;
@@ -141,6 +154,7 @@ GsdWacomRotation gsd_wacom_device_get_display_rotation (GsdWacomDevice *device);
 GsdWacomDevice * gsd_wacom_device_new              (GdkDevice *device);
 GList          * gsd_wacom_device_list_styli       (GsdWacomDevice *device);
 const char     * gsd_wacom_device_get_name         (GsdWacomDevice *device);
+const char     * gsd_wacom_device_get_layout_path  (GsdWacomDevice *device);
 const char     * gsd_wacom_device_get_path         (GsdWacomDevice *device);
 const char     * gsd_wacom_device_get_icon_name    (GsdWacomDevice *device);
 const char     * gsd_wacom_device_get_tool_name    (GsdWacomDevice *device);
@@ -163,6 +177,10 @@ GList          * gsd_wacom_device_get_buttons       (GsdWacomDevice *device);
 GsdWacomTabletButton *gsd_wacom_device_get_button   (GsdWacomDevice   *device,
 						     int               button,
 						     GtkDirectionType *dir);
+int gsd_wacom_device_get_num_modes                  (GsdWacomDevice   *device,
+						     int               group_id);
+int gsd_wacom_device_get_current_mode               (GsdWacomDevice   *device,
+						     int               group_id);
 int gsd_wacom_device_set_next_mode                  (GsdWacomDevice       *device,
 						     GsdWacomTabletButton *button);
 GsdWacomRotation gsd_wacom_device_rotation_name_to_type (const char *rotation);
diff --git a/plugins/wacom/gsd-wacom-manager.c b/plugins/wacom/gsd-wacom-manager.c
index eb5c23e..0e6e9c7 100644
--- a/plugins/wacom/gsd-wacom-manager.c
+++ b/plugins/wacom/gsd-wacom-manager.c
@@ -47,6 +47,7 @@
 #include "gnome-settings-profile.h"
 #include "gsd-wacom-manager.h"
 #include "gsd-wacom-device.h"
+#include "gsd-wacom-osd-window.h"
 
 #define GSD_WACOM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_WACOM_MANAGER, GsdWacomManagerPrivate))
 
@@ -83,6 +84,9 @@ struct GsdWacomManagerPrivate
         /* button capture */
         GSList *screens;
         int      opcode;
+
+        /* Help OSD window */
+        GtkWidget *osd_window;
 };
 
 static void     gsd_wacom_manager_class_init  (GsdWacomManagerClass *klass);
@@ -851,6 +855,97 @@ last_stylus_changed (GsdWacomDevice  *device,
 }
 
 static void
+osd_window_destroy (GsdWacomManager *manager)
+{
+	g_return_if_fail (manager != NULL);
+
+        g_clear_pointer (&manager->priv->osd_window, gtk_widget_destroy);
+}
+
+static gboolean
+osd_window_on_key_release_event (GtkWidget   *widget,
+                                 GdkEventKey *event,
+                                 GsdWacomManager *manager)
+{
+	osd_window_destroy (manager);
+
+	return FALSE;
+}
+
+static gboolean
+osd_window_on_focus_out_event (GtkWidget *widget,
+                               GdkEvent  *event,
+                               GsdWacomManager *manager)
+{
+	/* If the OSD window loses focus, hide it */
+	osd_window_destroy (manager);
+
+	return FALSE;
+}
+
+static gboolean
+osd_window_toggle_visibility (GsdWacomManager *manager,
+                              GsdWacomDevice  *device)
+{
+	GtkWidget *widget;
+        const gchar *layout_path;
+
+	if (manager->priv->osd_window) {
+		osd_window_destroy (manager);
+		return FALSE;
+	}
+
+        layout_path = gsd_wacom_device_get_layout_path (device);
+        if (layout_path == NULL) {
+                g_warning ("Cannot display the on-screen help window as the tablet "
+                           "definition for %s is missing the layout\n"
+                           "Please consider contributing the layout for your "
+                           "tablet to libwacom at linuxwacom-devel lists sourceforge net\n",
+                           gsd_wacom_device_get_name (device));
+		return FALSE;
+        }
+
+        if (g_file_test (layout_path, G_FILE_TEST_EXISTS) == FALSE) {
+                g_warning ("Cannot display the on-screen help window as the "
+                           "layout file %s cannot be found on disk\n"
+                           "Please check your libwacom installation\n",
+                           layout_path);
+		return FALSE;
+        }
+
+	widget = gsd_wacom_osd_window_new (device, NULL);
+
+	/* Connect some signals to the OSD window */
+	g_signal_connect (widget, "key-release-event",
+			  G_CALLBACK(osd_window_on_key_release_event), manager);
+	g_signal_connect (widget, "focus-out-event",
+			  G_CALLBACK(osd_window_on_focus_out_event), manager);
+	g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &manager->priv->osd_window);
+
+	gtk_window_present (GTK_WINDOW(widget));
+	manager->priv->osd_window = widget;
+
+	return TRUE;
+}
+
+static gboolean
+osd_window_update_viewable (GsdWacomManager      *manager,
+                            GsdWacomTabletButton *button,
+                            GtkDirectionType      dir,
+                            XIEvent              *xiev)
+{
+	if (manager->priv->osd_window == NULL)
+		return FALSE;
+
+	gsd_wacom_osd_window_set_active (GSD_WACOM_OSD_WINDOW (manager->priv->osd_window),
+	                                 button,
+	                                 dir,
+	                                 xiev->evtype == XI_ButtonPress);
+
+	return TRUE;
+}
+
+static void
 device_added_cb (GdkDeviceManager *device_manager,
                  GdkDevice        *gdk_device,
                  GsdWacomManager  *manager)
@@ -1121,6 +1216,7 @@ filter_button_events (XEvent          *xevent,
 	int                  button;
 	GsdWacomTabletButton *wbutton;
 	GtkDirectionType      dir;
+	gboolean emulate;
 
         /* verify we have a key event */
 	if (xevent->type != GenericEvent)
@@ -1142,6 +1238,11 @@ filter_button_events (XEvent          *xevent,
 	if (gsd_wacom_device_get_device_type (device) != WACOM_TYPE_PAD)
 		return GDK_FILTER_CONTINUE;
 
+	if ((manager->priv->osd_window != NULL) &&
+	    (device != gsd_wacom_osd_window_get_device (GSD_WACOM_OSD_WINDOW(manager->priv->osd_window))))
+		/* This is a button event from another device while showing OSD window */
+		osd_window_destroy (manager);
+
 	button = xev->detail;
 
 	wbutton = gsd_wacom_device_get_button (device, button, &dir);
@@ -1162,6 +1263,10 @@ filter_button_events (XEvent          *xevent,
 	if (wbutton->type == WACOM_TABLET_BUTTON_TYPE_HARDCODED) {
 		int new_mode;
 
+		/* Update OSD window if shown */
+		if (osd_window_update_viewable (manager, wbutton, dir, xiev))
+			return GDK_FILTER_REMOVE;
+
 		/* We switch modes on key release */
 		if (xiev->evtype == XI_ButtonRelease)
 			return GDK_FILTER_REMOVE;
@@ -1171,10 +1276,23 @@ filter_button_events (XEvent          *xevent,
 		return GDK_FILTER_REMOVE;
 	}
 
+	/* Update OSD window if shown */
+	emulate = osd_window_update_viewable (manager, wbutton, dir, xiev);
+
 	/* Nothing to do */
 	if (g_settings_get_enum (wbutton->settings, KEY_ACTION_TYPE) == GSD_WACOM_ACTION_TYPE_NONE)
 		return GDK_FILTER_REMOVE;
 
+	/* Show OSD window when requested */
+	if (g_settings_get_enum (wbutton->settings, KEY_ACTION_TYPE) == GSD_WACOM_ACTION_TYPE_HELP) {
+		if (xiev->evtype == XI_ButtonRelease)
+			osd_window_toggle_visibility (manager, device);
+		return GDK_FILTER_REMOVE;
+	}
+
+	if (emulate)
+		return GDK_FILTER_REMOVE;
+
 	/* Switch monitor */
 	if (g_settings_get_enum (wbutton->settings, KEY_ACTION_TYPE) == GSD_WACOM_ACTION_TYPE_SWITCH_MONITOR) {
 		if (xiev->evtype == XI_ButtonRelease)
@@ -1385,6 +1503,8 @@ gsd_wacom_manager_stop (GsdWacomManager *manager)
 
 	for (l = p->rr_screens; l != NULL; l = l->next)
 		g_signal_handlers_disconnect_by_func (l->data, on_screen_changed_cb, manager);
+
+        g_clear_pointer (&p->osd_window, gtk_widget_destroy);
 }
 
 static void
diff --git a/plugins/wacom/gsd-wacom-osd-window.c b/plugins/wacom/gsd-wacom-osd-window.c
new file mode 100644
index 0000000..9055251
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-osd-window.c
@@ -0,0 +1,1507 @@
+/*
+ * Copyright (C) 2012 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Olivier Fourdan <ofourdan redhat com>
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <cairo.h>
+#include <librsvg/rsvg.h>
+
+#include "gsd-wacom-osd-window.h"
+#include "gsd-wacom-device.h"
+#include "gsd-enums.h"
+
+#define ROTATION_KEY                "rotation"
+#define ACTION_TYPE_KEY             "action-type"
+#define CUSTOM_ACTION_KEY           "custom-action"
+#define CUSTOM_ELEVATOR_ACTION_KEY  "custom-elevator-action"
+#define RES_PATH                    "/org/gnome/settings-daemon/plugins/wacom/"
+
+#define BACK_OPACITY		0.8
+#define INACTIVE_COLOR		"#ededed"
+#define ACTIVE_COLOR		"#729fcf"
+#define STROKE_COLOR		"#000000"
+#define DARK_COLOR		"#535353"
+#define BACK_COLOR		"#000000"
+
+#define ELEVATOR_TIMEOUT	250 /* ms */
+
+static struct {
+	const gchar     *color_name;
+	const gchar     *color_value;
+} css_color_table[] = {
+	{ "inactive_color", INACTIVE_COLOR },
+	{ "active_color",   ACTIVE_COLOR   },
+	{ "stroke_color",   STROKE_COLOR   },
+	{ "dark_color",     DARK_COLOR     },
+	{ "back_color",     BACK_COLOR     }
+};
+
+static gchar *
+replace_string (gchar **string, const gchar *search, const char *replacement)
+{
+	GRegex *regex;
+	gchar *res;
+
+	g_return_val_if_fail (*string != NULL, NULL);
+	g_return_val_if_fail (string != NULL, NULL);
+	g_return_val_if_fail (search != NULL, *string);
+	g_return_val_if_fail (replacement != NULL, *string);
+
+	regex = g_regex_new (search, 0, 0, NULL);
+	res = g_regex_replace_literal (regex, *string, -1, 0, replacement, 0, NULL);
+	g_regex_unref (regex);
+	/* The given string is freed and replaced by the resulting replacement */
+	g_free (*string);
+	*string = res;
+
+	return res;
+}
+
+static gchar
+get_last_char (gchar *string)
+{
+	size_t pos;
+
+	g_return_val_if_fail (string != NULL, '\0');
+	pos = strlen (string);
+	g_return_val_if_fail (pos > 0, '\0');
+
+	return string[pos - 1];
+}
+
+static double
+get_rotation_in_radian (GsdWacomRotation rotation)
+{
+	switch (rotation) {
+	case GSD_WACOM_ROTATION_NONE:
+		return 0.0;
+		break;
+	case GSD_WACOM_ROTATION_HALF:
+		return G_PI;
+		break;
+	/* We only support left-handed/right-handed */
+	case GSD_WACOM_ROTATION_CCW:
+	case GSD_WACOM_ROTATION_CW:
+	default:
+		break;
+	}
+
+	/* Fallback */
+	return 0.0;
+}
+
+static gboolean
+get_sub_location (RsvgHandle *handle,
+                  const char *sub,
+                  cairo_t    *cr,
+                  double     *x,
+                  double     *y)
+{
+	RsvgPositionData  position;
+	double tx, ty;
+
+	if (!rsvg_handle_get_position_sub (handle, &position, sub)) {
+		g_warning ("Failed to retrieve '%s' position", sub);
+		return FALSE;
+	}
+
+	tx = (double) position.x;
+	ty = (double) position.y;
+	cairo_user_to_device (cr, &tx, &ty);
+
+	if (x)
+		*x = tx;
+	if (y)
+		*y = ty;
+
+	return TRUE;
+}
+
+static gboolean
+get_image_size (const char *filename, int *width, int *height)
+{
+	RsvgHandle       *handle;
+	RsvgDimensionData dimensions;
+	GError* error = NULL;
+
+	if (filename == NULL)
+		return FALSE;
+
+	handle = rsvg_handle_new_from_file (filename, &error);
+	if (error != NULL) {
+		g_printerr ("%s\n", error->message);
+		g_error_free (error);
+	}
+	if (handle == NULL)
+		return FALSE;
+
+	/* Compute image size */
+	rsvg_handle_get_dimensions (handle, &dimensions);
+	g_object_unref (handle);
+
+	if (dimensions.width == 0 || dimensions.height == 0)
+		return FALSE;
+
+	if (width)
+		*width = dimensions.width;
+
+	if (height)
+		*height = dimensions.height;
+
+	return TRUE;
+}
+
+static int
+get_pango_vertical_offset (PangoLayout *layout)
+{
+	const PangoFontDescription *desc;
+	PangoContext               *context;
+	PangoLanguage              *language;
+	PangoFontMetrics           *metrics;
+	int                         baseline;
+	int                         strikethrough;
+	int                         thickness;
+
+	context = pango_layout_get_context (layout);
+	language = pango_language_get_default ();
+	desc = pango_layout_get_font_description (layout);
+	metrics = pango_context_get_metrics (context, desc, language);
+
+	baseline = pango_layout_get_baseline (layout);
+	strikethrough =  pango_font_metrics_get_strikethrough_position (metrics);
+	thickness =  pango_font_metrics_get_underline_thickness (metrics);
+
+	return PANGO_PIXELS (baseline - strikethrough - thickness / 2);
+}
+
+static void
+set_grab_keyboard (GdkWindow *window, gboolean grab)
+{
+	GdkDisplay *display;
+	GdkDeviceManager *device_manager;
+	GList *devices, *dev;
+
+	display = gdk_window_get_display (window);
+	device_manager = gdk_display_get_device_manager (display);
+	devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER);
+
+	for (dev = devices; dev; dev = dev->next) {
+		GdkDevice *device = dev->data;
+		if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
+			continue;
+		if (grab)
+			gdk_device_grab (device,
+			                 window,
+			                 GDK_OWNERSHIP_NONE,
+			                 TRUE,
+			                 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+			                 NULL,
+			                 GDK_CURRENT_TIME);
+		else
+			gdk_device_ungrab (device, GDK_CURRENT_TIME);
+	}
+}
+
+#define GSD_TYPE_WACOM_OSD_BUTTON         (gsd_wacom_osd_button_get_type ())
+#define GSD_WACOM_OSD_BUTTON(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_WACOM_OSD_BUTTON, GsdWacomOSDButton))
+#define GSD_WACOM_OSD_BUTTON_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_WACOM_OSD_BUTTON, GsdWacomOSDButtonClass))
+#define GSD_IS_WACOM_OSD_BUTTON(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_WACOM_OSD_BUTTON))
+#define GSD_IS_WACOM_OSD_BUTTON_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_WACOM_OSD_BUTTON))
+#define GSD_WACOM_OSD_BUTTON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_WACOM_OSD_BUTTON, GsdWacomOSDButtonClass))
+
+typedef struct GsdWacomOSDButtonPrivate GsdWacomOSDButtonPrivate;
+
+typedef struct {
+        GObject                   parent;
+        GsdWacomOSDButtonPrivate *priv;
+} GsdWacomOSDButton;
+
+typedef struct {
+        GObjectClass              parent_class;
+} GsdWacomOSDButtonClass;
+
+GType                     gsd_wacom_osd_button_get_type        (void) G_GNUC_CONST;
+
+enum {
+	PROP_OSD_BUTTON_0,
+	PROP_OSD_BUTTON_ID,
+	PROP_OSD_BUTTON_CLASS,
+	PROP_OSD_BUTTON_LABEL,
+	PROP_OSD_BUTTON_ACTIVE,
+	PROP_OSD_BUTTON_AUTO_OFF
+};
+
+#define GSD_WACOM_OSD_BUTTON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+					     GSD_TYPE_WACOM_OSD_BUTTON, \
+					     GsdWacomOSDButtonPrivate))
+
+struct GsdWacomOSDButtonPrivate {
+	GtkWidget                *widget;
+	char                     *id;
+	char                     *class;
+	char                     *label;
+	double                    label_x;
+	double                    label_y;
+	GsdWacomTabletButtonType  type;
+	GsdWacomTabletButtonPos   position;
+	gboolean                  active;
+	guint                     auto_off;
+	guint                     timeout;
+};
+
+static void     gsd_wacom_osd_button_class_init  (GsdWacomOSDButtonClass *klass);
+static void     gsd_wacom_osd_button_init        (GsdWacomOSDButton      *osd_button);
+static void     gsd_wacom_osd_button_finalize    (GObject                *object);
+
+G_DEFINE_TYPE (GsdWacomOSDButton, gsd_wacom_osd_button, G_TYPE_OBJECT)
+
+static void
+gsd_wacom_osd_button_set_id (GsdWacomOSDButton *osd_button,
+			     const gchar       *id)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	osd_button->priv->id = g_strdup (id);
+}
+
+static void
+gsd_wacom_osd_button_set_class (GsdWacomOSDButton *osd_button,
+			        const gchar       *class)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	osd_button->priv->class = g_strdup (class);
+}
+
+static gchar*
+gsd_wacom_osd_button_get_label_class (GsdWacomOSDButton *osd_button)
+{
+	gchar *label_class;
+
+	label_class = g_strconcat ("#Label", osd_button->priv->class, NULL);
+
+	return (label_class);
+}
+
+static void
+gsd_wacom_osd_button_set_label (GsdWacomOSDButton *osd_button,
+				const gchar       *str)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	g_free (osd_button->priv->label);
+	osd_button->priv->label = g_strdup (str ? str : "");
+}
+
+static void
+gsd_wacom_osd_button_set_button_type (GsdWacomOSDButton        *osd_button,
+				      GsdWacomTabletButtonType  type)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	osd_button->priv->type = type;
+}
+
+static void
+gsd_wacom_osd_button_set_position (GsdWacomOSDButton        *osd_button,
+				   GsdWacomTabletButtonPos   position)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	osd_button->priv->position = position;
+}
+
+static void
+gsd_wacom_osd_button_set_location (GsdWacomOSDButton        *osd_button,
+				   double                    x,
+				   double                    y)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	osd_button->priv->label_x = x;
+	osd_button->priv->label_y = y;
+}
+
+static void
+gsd_wacom_osd_button_set_auto_off (GsdWacomOSDButton        *osd_button,
+				   guint                     timeout)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	osd_button->priv->auto_off = timeout;
+}
+
+static void
+gsd_wacom_osd_button_redraw (GsdWacomOSDButton *osd_button)
+{
+	GdkWindow *window;
+
+	g_return_if_fail (GTK_IS_WIDGET (osd_button->priv->widget));
+
+	window = gtk_widget_get_window (GTK_WIDGET (osd_button->priv->widget));
+	gdk_window_invalidate_rect (window, NULL, FALSE);
+}
+
+static gboolean
+gsd_wacom_osd_button_timer (GsdWacomOSDButton *osd_button)
+{
+	/* Auto de-activate the button */
+	osd_button->priv->active = FALSE;
+	gsd_wacom_osd_button_redraw (osd_button);
+
+	return FALSE;
+}
+
+static void
+gsd_wacom_osd_button_set_active (GsdWacomOSDButton *osd_button,
+				 gboolean           active)
+{
+	gboolean previous_state;
+
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	previous_state = osd_button->priv->active;
+	if (osd_button->priv->auto_off > 0) {
+		/* For auto-off buttons, apply only if active, de-activation is done in the timeout */
+		if (active == TRUE)
+			osd_button->priv->active = active;
+
+		if (osd_button->priv->timeout)
+			g_source_remove (osd_button->priv->timeout);
+		osd_button->priv->timeout = g_timeout_add (osd_button->priv->auto_off,
+		                                           (GSourceFunc) gsd_wacom_osd_button_timer,
+		                                           osd_button);
+	} else {
+		/* Whereas for other buttons, apply the change straight away */
+		osd_button->priv->active = active;
+	}
+
+	if (previous_state != osd_button->priv->active)
+		gsd_wacom_osd_button_redraw (osd_button);
+}
+
+static GsdWacomOSDButton *
+gsd_wacom_osd_button_new (GtkWidget *widget,
+                          gchar *id)
+{
+	GsdWacomOSDButton *osd_button;
+
+	osd_button = GSD_WACOM_OSD_BUTTON (g_object_new (GSD_TYPE_WACOM_OSD_BUTTON,
+	                                                 "id", id,
+	                                                 NULL));
+	osd_button->priv->widget = widget;
+
+	return osd_button;
+}
+
+static void
+gsd_wacom_osd_button_set_property (GObject        *object,
+				   guint           prop_id,
+				   const GValue   *value,
+				   GParamSpec     *pspec)
+{
+	GsdWacomOSDButton *osd_button;
+
+	osd_button = GSD_WACOM_OSD_BUTTON (object);
+
+	switch (prop_id) {
+	case PROP_OSD_BUTTON_ID:
+		gsd_wacom_osd_button_set_id (osd_button, g_value_get_string (value));
+		break;
+	case PROP_OSD_BUTTON_CLASS:
+		gsd_wacom_osd_button_set_class (osd_button, g_value_get_string (value));
+		break;
+	case PROP_OSD_BUTTON_LABEL:
+		gsd_wacom_osd_button_set_label (osd_button, g_value_get_string (value));
+		break;
+	case PROP_OSD_BUTTON_ACTIVE:
+		gsd_wacom_osd_button_set_active (osd_button, g_value_get_boolean (value));
+		break;
+	case PROP_OSD_BUTTON_AUTO_OFF:
+		gsd_wacom_osd_button_set_auto_off (osd_button, g_value_get_uint (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gsd_wacom_osd_button_get_property (GObject        *object,
+				   guint           prop_id,
+				   GValue         *value,
+				   GParamSpec     *pspec)
+{
+	GsdWacomOSDButton *osd_button;
+
+	osd_button = GSD_WACOM_OSD_BUTTON (object);
+
+	switch (prop_id) {
+	case PROP_OSD_BUTTON_ID:
+		g_value_set_string (value, osd_button->priv->id);
+		break;
+	case PROP_OSD_BUTTON_CLASS:
+		g_value_set_string (value, osd_button->priv->class);
+		break;
+	case PROP_OSD_BUTTON_LABEL:
+		g_value_set_string (value, osd_button->priv->label);
+		break;
+	case PROP_OSD_BUTTON_ACTIVE:
+		g_value_set_boolean (value, osd_button->priv->active);
+		break;
+	case PROP_OSD_BUTTON_AUTO_OFF:
+		g_value_set_uint (value, osd_button->priv->auto_off);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gsd_wacom_osd_button_class_init (GsdWacomOSDButtonClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->set_property = gsd_wacom_osd_button_set_property;
+	object_class->get_property = gsd_wacom_osd_button_get_property;
+	object_class->finalize = gsd_wacom_osd_button_finalize;
+
+	g_object_class_install_property (object_class,
+	                                 PROP_OSD_BUTTON_ID,
+	                                 g_param_spec_string ("id",
+	                                                      "Button Id",
+	                                                      "The Wacom Button ID",
+	                                                      "",
+	                                                      G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+	                                 PROP_OSD_BUTTON_CLASS,
+	                                 g_param_spec_string ("class",
+	                                                      "Button Class",
+	                                                      "The Wacom Button Class",
+	                                                      "",
+	                                                      G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+	                                 PROP_OSD_BUTTON_LABEL,
+	                                 g_param_spec_string ("label",
+	                                                      "Label",
+	                                                      "The button label",
+	                                                      "",
+	                                                      G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+	                                 PROP_OSD_BUTTON_ACTIVE,
+	                                 g_param_spec_boolean ("active",
+	                                                       "Active",
+	                                                       "Whether the button is active",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+	                                 PROP_OSD_BUTTON_AUTO_OFF,
+	                                 g_param_spec_uint    ("auto-off",
+	                                                       "Auto Off",
+	                                                       "Timeout before button disables itself automatically",
+	                                                       0,
+	                                                       G_MAXUINT,
+	                                                       0, /* disabled by default */
+	                                                       G_PARAM_READWRITE));
+
+	g_type_class_add_private (klass, sizeof (GsdWacomOSDButtonPrivate));
+}
+
+static void
+gsd_wacom_osd_button_init (GsdWacomOSDButton *osd_button)
+{
+	osd_button->priv = GSD_WACOM_OSD_BUTTON_GET_PRIVATE (osd_button);
+}
+
+static void
+gsd_wacom_osd_button_finalize (GObject *object)
+{
+	GsdWacomOSDButton *osd_button;
+	GsdWacomOSDButtonPrivate *priv;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (object));
+
+	osd_button = GSD_WACOM_OSD_BUTTON (object);
+
+	g_return_if_fail (osd_button->priv != NULL);
+
+	priv = osd_button->priv;
+
+	if (priv->timeout > 0)
+		g_source_remove (priv->timeout);
+	g_clear_pointer (&priv->id, g_free);
+	g_clear_pointer (&priv->class, g_free);
+	g_clear_pointer (&priv->label, g_free);
+
+	G_OBJECT_CLASS (gsd_wacom_osd_button_parent_class)->finalize (object);
+}
+
+/* Compute the new actual position once rotation is applied */
+static GsdWacomTabletButtonPos
+get_actual_position (GsdWacomTabletButtonPos position,
+		     GsdWacomRotation        rotation)
+{
+	switch (rotation) {
+	case GSD_WACOM_ROTATION_NONE:
+		return position;
+		break;
+	case GSD_WACOM_ROTATION_HALF:
+		if (position == WACOM_TABLET_BUTTON_POS_LEFT)
+			return WACOM_TABLET_BUTTON_POS_RIGHT;
+		if (position == WACOM_TABLET_BUTTON_POS_RIGHT)
+			return WACOM_TABLET_BUTTON_POS_LEFT;
+		if (position == WACOM_TABLET_BUTTON_POS_TOP)
+			return WACOM_TABLET_BUTTON_POS_BOTTOM;
+		if (position == WACOM_TABLET_BUTTON_POS_BOTTOM)
+			return WACOM_TABLET_BUTTON_POS_TOP;
+		break;
+	/* We only support left-handed/right-handed */
+	case GSD_WACOM_ROTATION_CCW:
+	case GSD_WACOM_ROTATION_CW:
+	default:
+		break;
+	}
+	/* fallback */
+	return position;
+}
+
+static void
+gsd_wacom_osd_button_draw_label (GsdWacomOSDButton *osd_button,
+			         GtkStyleContext   *style_context,
+			         PangoContext      *pango_context,
+			         cairo_t           *cr,
+			         GsdWacomRotation   rotation)
+{
+	GsdWacomOSDButtonPrivate *priv;
+	PangoLayout              *layout;
+	PangoRectangle            logical_rect;
+	GsdWacomTabletButtonPos   actual_position;
+	double                    lx, ly;
+	gchar                    *markup;
+
+	g_return_if_fail (GSD_IS_WACOM_OSD_BUTTON (osd_button));
+
+	priv = osd_button->priv;
+
+	actual_position = get_actual_position (priv->position, rotation);
+	layout = pango_layout_new (pango_context);
+	if (priv->active)
+		markup = g_strdup_printf ("<span foreground=\"" ACTIVE_COLOR "\" weight=\"normal\">%s</span>", priv->label);
+	else
+		markup = g_strdup_printf ("<span foreground=\"" INACTIVE_COLOR "\" weight=\"normal\">%s</span>", priv->label);
+	pango_layout_set_markup (layout, markup, -1);
+	g_free (markup);
+
+	pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+	switch (actual_position) {
+	case WACOM_TABLET_BUTTON_POS_LEFT:
+		pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
+		lx = priv->label_x + logical_rect.x;
+		ly = priv->label_y + logical_rect.y - get_pango_vertical_offset (layout);
+		break;
+	case WACOM_TABLET_BUTTON_POS_RIGHT:
+		pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
+		lx = priv->label_x + logical_rect.x - logical_rect.width;
+		ly = priv->label_y + logical_rect.y - get_pango_vertical_offset (layout);
+		break;
+	case WACOM_TABLET_BUTTON_POS_TOP:
+		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+		lx = priv->label_x + logical_rect.x - logical_rect.width / 2;
+		ly = priv->label_y + logical_rect.y;
+		break;
+	case WACOM_TABLET_BUTTON_POS_BOTTOM:
+		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+		lx = priv->label_x + logical_rect.x - logical_rect.width / 2;
+		ly = priv->label_y + logical_rect.y - logical_rect.height;
+		break;
+	default:
+		g_warning ("Unhandled button position");
+		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+		lx = priv->label_x + logical_rect.x - logical_rect.width / 2;
+		ly = priv->label_y + logical_rect.y - logical_rect.height / 2;
+		break;
+	}
+	gtk_render_layout (style_context, cr, lx, ly, layout);
+	g_object_unref (layout);
+}
+
+enum {
+  PROP_OSD_WINDOW_0,
+  PROP_OSD_WINDOW_MESSAGE,
+  PROP_OSD_WINDOW_GSD_WACOM_DEVICE
+};
+
+#define GSD_WACOM_OSD_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+					     GSD_TYPE_WACOM_OSD_WINDOW, \
+					     GsdWacomOSDWindowPrivate))
+
+struct GsdWacomOSDWindowPrivate
+{
+	RsvgHandle               *handle;
+	GsdWacomDevice           *pad;
+	GsdWacomRotation          rotation;
+	GdkRectangle              screen_area;
+	GdkRectangle              monitor_area;
+	GdkRectangle              tablet_area;
+	char                     *message;
+	GList                    *buttons;
+};
+
+static void     gsd_wacom_osd_window_class_init  (GsdWacomOSDWindowClass *klass);
+static void     gsd_wacom_osd_window_init        (GsdWacomOSDWindow      *osd_window);
+static void     gsd_wacom_osd_window_finalize    (GObject                *object);
+
+G_DEFINE_TYPE (GsdWacomOSDWindow, gsd_wacom_osd_window, GTK_TYPE_WINDOW)
+
+static void
+gsd_wacom_osd_window_update (GsdWacomOSDWindow *osd_window)
+{
+	GError      *error = NULL;
+	gchar       *width, *height;
+	gchar       *buttons_section;
+	gchar       *css_string;
+	const gchar *layout_file;
+	GBytes      *css_data;
+        guint i;
+	GList *l;
+
+	g_return_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window));
+	g_return_if_fail (GSD_IS_WACOM_DEVICE (osd_window->priv->pad));
+
+	css_data = g_resources_lookup_data (RES_PATH "tablet-layout.css", 0, &error);
+	if (error != NULL) {
+		g_printerr ("GResource error: %s\n", error->message);
+		g_clear_pointer (&error, g_error_free);
+	}
+	if (css_data == NULL)
+		return;
+	css_string = g_strdup ((gchar *) g_bytes_get_data (css_data, NULL));
+	g_bytes_unref(css_data);
+
+	width = g_strdup_printf ("%d", osd_window->priv->tablet_area.width);
+	replace_string (&css_string, "layout_width", width);
+	g_free (width);
+
+	height = g_strdup_printf ("%d", osd_window->priv->tablet_area.height);
+	replace_string (&css_string, "layout_height", height);
+	g_free (height);
+
+	/* Build the buttons section */
+	buttons_section = g_strdup ("");
+	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
+		GsdWacomOSDButton *osd_button = l->data;
+
+		if (osd_button->priv->active) {
+			buttons_section = g_strconcat (buttons_section,
+			                               ".", osd_button->priv->class, " {\n"
+			                               "      stroke:   active_color !important;\n"
+			                               "      fill:     active_color !important;\n"
+			                               "    }\n",
+			                               NULL);
+		}
+	}
+	replace_string (&css_string, "buttons_section", buttons_section);
+	g_free (buttons_section);
+
+        for (i = 0; i < G_N_ELEMENTS (css_color_table); i++)
+		replace_string (&css_string,
+		                css_color_table[i].color_name,
+		                css_color_table[i].color_value);
+
+	layout_file = gsd_wacom_device_get_layout_path (osd_window->priv->pad);
+	replace_string (&css_string, "layout_file", layout_file);
+
+	/* Render the SVG with the CSS applied */
+	if (osd_window->priv->handle)
+		g_object_unref (osd_window->priv->handle);
+	osd_window->priv->handle = rsvg_handle_new_from_data ((guint8 *) css_string,
+	                                                      strlen (css_string),
+	                                                      &error);
+	if (error != NULL) {
+		g_debug ("CSS applied:\n%s\n", css_string);
+		g_printerr ("RSVG error: %s\n", error->message);
+		g_clear_pointer (&error, g_error_free);
+	}
+	g_free (css_string);
+}
+
+static void
+gsd_wacom_osd_window_draw_message (GsdWacomOSDWindow   *osd_window,
+				   GtkStyleContext     *style_context,
+				   PangoContext        *pango_context,
+				   cairo_t             *cr)
+{
+	GdkRectangle  *monitor_area = &osd_window->priv->monitor_area;
+	PangoRectangle logical_rect;
+	PangoLayout *layout;
+	char *markup;
+	double x;
+	double y;
+
+	if (osd_window->priv->message == NULL)
+		return;
+
+	layout = pango_layout_new (pango_context);
+	pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+
+	markup = g_strdup_printf ("<span foreground=\"white\">%s</span>", osd_window->priv->message);
+	pango_layout_set_markup (layout, markup, -1);
+	g_free (markup);
+
+	pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+	x = (monitor_area->width - logical_rect.width) / 2 + logical_rect.x;
+	y = (monitor_area->height - logical_rect.height) / 2 + logical_rect.y;
+
+	gtk_render_layout (style_context, cr, x, y, layout);
+	g_object_unref (layout);
+}
+
+static void
+gsd_wacom_osd_window_draw_labels (GsdWacomOSDWindow   *osd_window,
+				  GtkStyleContext     *style_context,
+				  PangoContext        *pango_context,
+				  cairo_t             *cr)
+{
+	GList *l;
+
+	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
+		GsdWacomOSDButton *osd_button = l->data;
+
+		gsd_wacom_osd_button_draw_label (osd_button,
+			                         style_context,
+			                         pango_context,
+			                         cr,
+			                         osd_window->priv->rotation);
+	}
+}
+
+static void
+gsd_wacom_osd_window_place_buttons (GsdWacomOSDWindow *osd_window,
+				    cairo_t           *cr)
+{
+	GList            *l;
+
+	g_return_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window));
+
+	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
+		GsdWacomOSDButton *osd_button = l->data;
+		double             label_x, label_y;
+		gchar             *sub;
+
+		sub = gsd_wacom_osd_button_get_label_class (osd_button);
+		if (!get_sub_location (osd_window->priv->handle, sub, cr, &label_x, &label_y)) {
+			g_warning ("Failed to retrieve %s position", sub);
+			g_free (sub);
+			continue;
+		}
+		g_free (sub);
+		gsd_wacom_osd_button_set_location (osd_button, label_x, label_y);
+	}
+}
+
+/* Note: this function does modify the given cairo context */
+static void
+gsd_wacom_osd_window_adjust_cairo (GsdWacomOSDWindow *osd_window,
+                                   cairo_t           *cr)
+{
+	double         scale, twidth, theight;
+	GdkRectangle  *tablet_area  = &osd_window->priv->tablet_area;
+	GdkRectangle  *screen_area  = &osd_window->priv->screen_area;
+	GdkRectangle  *monitor_area = &osd_window->priv->monitor_area;
+
+	/* Rotate */
+	cairo_rotate (cr, get_rotation_in_radian (osd_window->priv->rotation));
+
+	/* Scale to fit in window */
+	scale = MIN ((double) monitor_area->width / tablet_area->width,
+	             (double) monitor_area->height / tablet_area->height);
+	cairo_scale (cr, scale, scale);
+
+	/* Center the result in window */
+	twidth = (double) tablet_area->width;
+	theight = (double) tablet_area->height;
+	cairo_user_to_device_distance (cr, &twidth, &theight);
+
+	twidth = ((double) monitor_area->width - twidth) / 2.0;
+	theight = ((double) monitor_area->height - theight) / 2.0;
+	cairo_device_to_user_distance (cr, &twidth, &theight);
+
+	twidth = twidth + (double) (monitor_area->x - screen_area->x);
+	theight = theight + (double) (monitor_area->y - screen_area->y);
+
+	cairo_translate (cr, twidth, theight);
+}
+
+static gboolean
+gsd_wacom_osd_window_draw (GtkWidget *widget,
+			   cairo_t   *cr)
+{
+	GsdWacomOSDWindow *osd_window = GSD_WACOM_OSD_WINDOW (widget);
+
+	g_return_val_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window), FALSE);
+	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (osd_window->priv->pad), FALSE);
+
+	if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget))) {
+		GtkStyleContext     *style_context;
+		PangoContext        *pango_context;
+
+		style_context = gtk_widget_get_style_context (widget);
+		pango_context = gtk_widget_get_pango_context (widget);
+
+		cairo_set_source_rgba (cr, 0, 0, 0, BACK_OPACITY);
+		cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+		cairo_paint (cr);
+		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+		/* Save original matrix */
+		cairo_save (cr);
+
+		/* Apply new cairo transformation matrix */
+		gsd_wacom_osd_window_adjust_cairo (osd_window, cr);
+
+		/* And render the tablet layout */
+		gsd_wacom_osd_window_update(osd_window);
+		rsvg_handle_render_cairo (osd_window->priv->handle, cr);
+
+		gsd_wacom_osd_window_place_buttons (osd_window, cr);
+
+		/* Reset to original matrix */
+		cairo_restore (cr);
+
+		/* Draw button labels and message */
+		gsd_wacom_osd_window_draw_labels (osd_window,
+		                                  style_context,
+		                                  pango_context,
+		                                  cr);
+		gsd_wacom_osd_window_draw_message (osd_window,
+		                                   style_context,
+		                                   pango_context,
+		                                   cr);
+	}
+
+	return FALSE;
+}
+
+static gchar *
+get_escaped_accel_shortcut (const gchar *accel)
+{
+	guint keyval;
+	GdkModifierType mask;
+	gchar *str, *label;
+
+	if (accel == NULL || accel[0] == '\0')
+		return g_strdup (C_("Action type", "None"));
+
+	gtk_accelerator_parse (accel, &keyval, &mask);
+
+	str = gtk_accelerator_get_label (keyval, mask);
+	label = g_markup_printf_escaped (C_("Action type", "Send Keystroke %s"), str);
+	g_free (str);
+
+	return label;
+}
+
+static gchar *
+get_tablet_button_label_normal (GsdWacomDevice       *device,
+				GsdWacomTabletButton *button)
+{
+	GsdWacomActionType type;
+	gchar *name, *str;
+
+	type = g_settings_get_enum (button->settings, ACTION_TYPE_KEY);
+	if (type == GSD_WACOM_ACTION_TYPE_NONE)
+		return g_strdup (C_("Action type", "None"));
+
+	if (type == GSD_WACOM_ACTION_TYPE_HELP)
+		return g_strdup (C_("Action type", "Show On-Screen Help"));
+
+	if (type == GSD_WACOM_ACTION_TYPE_SWITCH_MONITOR)
+		return g_strdup (C_("Action type", "Switch Monitor"));
+
+	str = g_settings_get_string (button->settings, CUSTOM_ACTION_KEY);
+	if (str == NULL || *str == '\0') {
+		g_free (str);
+		return g_strdup (C_("Action type", "None"));
+	}
+
+	name = get_escaped_accel_shortcut (str);
+	g_free (str);
+
+	return name;
+}
+
+static gchar *
+get_tablet_button_label_touch  (GsdWacomDevice       *device,
+				GsdWacomTabletButton *button,
+				GtkDirectionType      dir)
+{
+	char **strv, *name, *str;
+	gint mode;
+
+
+	strv = g_settings_get_strv (button->settings, CUSTOM_ELEVATOR_ACTION_KEY);
+	name = NULL;
+
+	if (strv) {
+		if (g_strv_length (strv) >= 1 && dir == GTK_DIR_UP)
+			name = g_strdup (strv[0]);
+		else if (g_strv_length (strv) >= 2 && dir == GTK_DIR_DOWN)
+			name = g_strdup (strv[1]);
+		g_strfreev (strv);
+	}
+
+	str = get_escaped_accel_shortcut (name);
+	g_free (name);
+	name = str;
+
+	/* With multiple modes, also show the current mode for that action */
+	if (gsd_wacom_device_get_num_modes (device, button->group_id) > 1) {
+		mode = gsd_wacom_device_get_current_mode (device, button->group_id);
+		name = g_strdup_printf (_("Mode %d: %s"), mode, str);
+		g_free (str);
+	}
+
+	return name;
+}
+
+static gchar *
+get_tablet_button_label (GsdWacomDevice       *device,
+	                 GsdWacomTabletButton *button,
+	                 GtkDirectionType      dir)
+{
+	g_return_val_if_fail (button, NULL);
+
+	if (!button->settings)
+		goto out;
+
+	switch (button->type) {
+	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
+		return get_tablet_button_label_normal (device, button);
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_RING:
+	case WACOM_TABLET_BUTTON_TYPE_STRIP:
+		return get_tablet_button_label_touch (device, button, dir);
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
+	default:
+		break;
+	}
+out:
+	return g_strdup (button->name);
+}
+
+static gchar*
+get_tablet_button_class_name (GsdWacomTabletButton *tablet_button,
+                              GtkDirectionType      dir)
+{
+	gchar *id;
+	gchar  c;
+
+	id = tablet_button->id;
+	switch (tablet_button->type) {
+	case WACOM_TABLET_BUTTON_TYPE_RING:
+		if (id[0] == 'l') /* left-ring */
+			return g_strdup_printf ("Ring%s", (dir == GTK_DIR_UP ? "CCW" : "CW"));
+		if (id[0] == 'r') /* right-ring */
+			return g_strdup_printf ("Ring2%s", (dir == GTK_DIR_UP ? "CCW" : "CW"));
+		g_warning ("Unknown ring type '%s'", id);
+		return NULL;
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_STRIP:
+		if (id[0] == 'l') /* left-strip */
+			return g_strdup_printf ("Strip%s", (dir == GTK_DIR_UP ? "Up" : "Down"));
+		if (id[0] == 'r') /* right-strip */
+			return g_strdup_printf ("Strip2%s", (dir == GTK_DIR_UP ? "Up" : "Down"));
+		g_warning ("Unknown strip type '%s'", id);
+		return NULL;
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
+	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
+		c = get_last_char (id);
+		return g_strdup_printf ("%c", g_ascii_toupper (c));
+		break;
+	default:
+		g_warning ("Unknown button type '%s'", id);
+		break;
+	}
+
+	return NULL;
+}
+
+static gchar*
+get_tablet_button_id_name (GsdWacomTabletButton *tablet_button,
+                           GtkDirectionType      dir)
+{
+	gchar *id;
+	gchar  c;
+
+	id = tablet_button->id;
+	switch (tablet_button->type) {
+	case WACOM_TABLET_BUTTON_TYPE_RING:
+		return g_strconcat (id, (dir == GTK_DIR_UP ? "-ccw" : "-cw"), NULL);
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_STRIP:
+		return g_strconcat (id, (dir == GTK_DIR_UP ? "-up" : "-down"), NULL);
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
+	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
+		c = get_last_char (id);
+		return g_strdup_printf ("%c", g_ascii_toupper (c));
+		break;
+	default:
+		g_warning ("Unknown button type '%s'", id);
+		break;
+	}
+
+	return NULL;
+}
+
+static gint
+get_elevator_current_mode (GsdWacomOSDWindow    *osd_window,
+                           GsdWacomTabletButton *elevator_button)
+{
+	GList *list, *l;
+	gint   mode;
+
+	mode = 1;
+	/* Search in the list of buttons the corresponding
+	 * mode-switch button and get the current mode
+	 */
+	list = gsd_wacom_device_get_buttons (osd_window->priv->pad);
+	for (l = list; l != NULL; l = l->next) {
+		GsdWacomTabletButton *tablet_button = l->data;
+
+		if (tablet_button->type != WACOM_TABLET_BUTTON_TYPE_HARDCODED)
+			continue;
+		if (elevator_button->group_id != tablet_button->group_id)
+			continue;
+		mode = gsd_wacom_device_get_current_mode (osd_window->priv->pad,
+		                                          tablet_button->group_id);
+		break;
+	}
+	g_list_free (list);
+
+	return mode;
+}
+
+static void
+gsd_wacom_osd_window_add_button_with_dir (GsdWacomOSDWindow    *osd_window,
+                                          GsdWacomTabletButton *tablet_button,
+                                          guint                 timeout,
+                                          GtkDirectionType      dir)
+{
+	GsdWacomOSDButton    *osd_button;
+	gchar                *str;
+
+	str = get_tablet_button_id_name (tablet_button, dir);
+	osd_button = gsd_wacom_osd_button_new (GTK_WIDGET (osd_window), str);
+	g_free (str);
+
+	str = get_tablet_button_class_name (tablet_button, dir);
+	gsd_wacom_osd_button_set_class (osd_button, str);
+	g_free (str);
+
+	str = get_tablet_button_label (osd_window->priv->pad, tablet_button, dir);
+	gsd_wacom_osd_button_set_label (osd_button, str);
+	g_free (str);
+
+	gsd_wacom_osd_button_set_button_type (osd_button, tablet_button->type);
+	gsd_wacom_osd_button_set_position (osd_button, tablet_button->pos);
+	gsd_wacom_osd_button_set_auto_off (osd_button, timeout);
+	osd_window->priv->buttons = g_list_append (osd_window->priv->buttons, osd_button);
+}
+
+static void
+gsd_wacom_osd_window_add_tablet_button (GsdWacomOSDWindow    *osd_window,
+                                        GsdWacomTabletButton *tablet_button)
+{
+	gint                  mode;
+
+	switch (tablet_button->type) {
+	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
+	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
+		gsd_wacom_osd_window_add_button_with_dir (osd_window,
+		                                          tablet_button,
+		                                          0,
+		                                          0);
+		break;
+	case WACOM_TABLET_BUTTON_TYPE_RING:
+	case WACOM_TABLET_BUTTON_TYPE_STRIP:
+		mode = get_elevator_current_mode (osd_window, tablet_button);
+		if (tablet_button->idx != mode - 1)
+			break;
+
+		/* Add 2 buttons per elevator, one "Up"... */
+		gsd_wacom_osd_window_add_button_with_dir (osd_window,
+		                                          tablet_button,
+		                                          ELEVATOR_TIMEOUT,
+		                                          GTK_DIR_UP);
+		/* ... and one "Down" */
+		gsd_wacom_osd_window_add_button_with_dir (osd_window,
+		                                          tablet_button,
+		                                          ELEVATOR_TIMEOUT,
+		                                          GTK_DIR_DOWN);
+		break;
+	default:
+		g_warning ("Unknown button type");
+		break;
+	}
+}
+
+/*
+ * Returns the rotation to apply a device to get a representation relative to
+ * the current rotation of the output.
+ * (This function is _not_ the same as in gsd-wacom-manager.c)
+ */
+static GsdWacomRotation
+display_relative_rotation (GsdWacomRotation device_rotation,
+			   GsdWacomRotation output_rotation)
+{
+	GsdWacomRotation rotations[] = { GSD_WACOM_ROTATION_HALF,
+	                                 GSD_WACOM_ROTATION_CW,
+	                                 GSD_WACOM_ROTATION_NONE,
+	                                 GSD_WACOM_ROTATION_CCW };
+	guint i;
+
+	if (device_rotation == output_rotation)
+		return GSD_WACOM_ROTATION_NONE;
+
+	if (output_rotation == GSD_WACOM_ROTATION_NONE)
+		return device_rotation;
+
+	for (i = 0; i < G_N_ELEMENTS (rotations); i++) {
+		if (device_rotation == rotations[i])
+			break;
+	}
+
+	if (output_rotation == GSD_WACOM_ROTATION_HALF)
+		return rotations[(i + G_N_ELEMENTS (rotations) - 2) % G_N_ELEMENTS (rotations)];
+
+	if (output_rotation == GSD_WACOM_ROTATION_CW)
+		return rotations[(i + 1) % G_N_ELEMENTS (rotations)];
+
+	if (output_rotation == GSD_WACOM_ROTATION_CCW)
+		return rotations[(i + G_N_ELEMENTS (rotations) - 1) % G_N_ELEMENTS (rotations)];
+
+	/* fallback */
+	return GSD_WACOM_ROTATION_NONE;
+}
+
+static void
+gsd_wacom_osd_window_set_device (GsdWacomOSDWindow *osd_window,
+				 GsdWacomDevice    *device)
+{
+	GsdWacomRotation  device_rotation;
+	GsdWacomRotation  output_rotation;
+	GSettings        *settings;
+	gint              monitor;
+	GdkScreen        *screen;
+	GList            *list, *l;
+	gboolean          status;
+
+	g_return_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window));
+	g_return_if_fail (GSD_IS_WACOM_DEVICE (device));
+
+	/* If we had a layout previously handled, get rid of it */
+	if (osd_window->priv->handle)
+		g_object_unref (osd_window->priv->handle);
+	osd_window->priv->handle = NULL;
+
+	/* Bind the device with the OSD window */
+	if (osd_window->priv->pad)
+		g_object_weak_unref (G_OBJECT(osd_window->priv->pad),
+		                     (GWeakNotify) gtk_widget_destroy,
+		                     osd_window);
+	osd_window->priv->pad = device;
+	g_object_weak_ref (G_OBJECT(osd_window->priv->pad),
+	                   (GWeakNotify) gtk_widget_destroy,
+	                   osd_window);
+
+	/* Determine the monitor for that device */
+	screen = gdk_screen_get_default ();
+	monitor = gsd_wacom_device_get_display_monitor (device);
+	if (monitor == GSD_WACOM_SET_ALL_MONITORS) {
+		/* Covers the entire screen */
+		osd_window->priv->screen_area.x = 0;
+		osd_window->priv->screen_area.y = 0;
+		osd_window->priv->screen_area.width = gdk_screen_get_width (screen);
+		osd_window->priv->screen_area.height = gdk_screen_get_height (screen);
+		gdk_screen_get_monitor_geometry (screen, 0, &osd_window->priv->monitor_area);
+	} else {
+		gdk_screen_get_monitor_geometry (screen, monitor, &osd_window->priv->screen_area);
+		osd_window->priv->monitor_area = osd_window->priv->screen_area;
+	}
+	status = get_image_size (gsd_wacom_device_get_layout_path (device),
+	                         &osd_window->priv->tablet_area.width,
+	                         &osd_window->priv->tablet_area.height);
+	if (status == FALSE)
+		osd_window->priv->tablet_area = osd_window->priv->monitor_area;
+
+	/* Capture current rotation, we do not update that later, OSD window is meant to be short lived */
+	settings = gsd_wacom_device_get_settings (osd_window->priv->pad);
+	device_rotation = g_settings_get_enum (settings, ROTATION_KEY);
+	output_rotation = gsd_wacom_device_get_display_rotation (osd_window->priv->pad);
+	osd_window->priv->rotation = display_relative_rotation (device_rotation, output_rotation);
+
+	/* Create the buttons */
+	list = gsd_wacom_device_get_buttons (device);
+	for (l = list; l != NULL; l = l->next) {
+		GsdWacomTabletButton *tablet_button = l->data;
+
+		gsd_wacom_osd_window_add_tablet_button (osd_window, tablet_button);
+	}
+	g_list_free (list);
+}
+
+GsdWacomDevice *
+gsd_wacom_osd_window_get_device (GsdWacomOSDWindow *osd_window)
+{
+	g_return_val_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window), NULL);
+
+	return osd_window->priv->pad;
+}
+
+void
+gsd_wacom_osd_window_set_message (GsdWacomOSDWindow *osd_window,
+				  const gchar       *str)
+{
+	g_return_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window));
+
+	g_free (osd_window->priv->message);
+	osd_window->priv->message = g_strdup (str);
+}
+
+const char *
+gsd_wacom_osd_window_get_message (GsdWacomOSDWindow *osd_window)
+{
+	g_return_val_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window), NULL);
+
+	return osd_window->priv->message;
+}
+
+static void
+gsd_wacom_osd_window_set_property (GObject        *object,
+				   guint           prop_id,
+				   const GValue   *value,
+				   GParamSpec     *pspec)
+{
+	GsdWacomOSDWindow *osd_window;
+
+	osd_window = GSD_WACOM_OSD_WINDOW (object);
+
+	switch (prop_id) {
+	case PROP_OSD_WINDOW_MESSAGE:
+		gsd_wacom_osd_window_set_message (osd_window, g_value_get_string (value));
+		break;
+	case PROP_OSD_WINDOW_GSD_WACOM_DEVICE:
+		gsd_wacom_osd_window_set_device (osd_window, g_value_get_object (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gsd_wacom_osd_window_get_property (GObject        *object,
+				   guint           prop_id,
+				   GValue         *value,
+				   GParamSpec     *pspec)
+{
+	GsdWacomOSDWindow *osd_window;
+
+	osd_window = GSD_WACOM_OSD_WINDOW (object);
+
+	switch (prop_id) {
+	case PROP_OSD_WINDOW_MESSAGE:
+		g_value_set_string (value, osd_window->priv->message);
+		break;
+	case PROP_OSD_WINDOW_GSD_WACOM_DEVICE:
+		g_value_set_object (value, (GObject*) osd_window->priv->pad);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+void
+gsd_wacom_osd_window_set_active (GsdWacomOSDWindow    *osd_window,
+				 GsdWacomTabletButton *button,
+				 GtkDirectionType      dir,
+				 gboolean              active)
+{
+	GList     *l;
+	gchar     *id;
+
+	g_return_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window));
+	g_return_if_fail (button != NULL);
+
+	id = get_tablet_button_id_name (button, dir);
+	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
+		GsdWacomOSDButton *osd_button = l->data;
+		if (g_strcmp0 (osd_button->priv->id, id) == 0)
+			gsd_wacom_osd_button_set_active (osd_button, active);
+	}
+	g_free (id);
+}
+
+GtkWidget *
+gsd_wacom_osd_window_new (GsdWacomDevice       *pad,
+                          const gchar          *message)
+{
+	GsdWacomOSDWindow *osd_window;
+	GdkWindow         *window;
+	GdkRGBA            transparent;
+	GdkCursor         *cursor;
+	GdkVisual         *visual;
+	GdkScreen         *screen;
+
+	osd_window = GSD_WACOM_OSD_WINDOW (g_object_new (GSD_TYPE_WACOM_OSD_WINDOW,
+	                                                 "wacom-device", pad,
+	                                                 "message", message,
+	                                                 "type", GTK_WINDOW_POPUP,
+	                                                 NULL));
+
+	gtk_widget_set_app_paintable (GTK_WIDGET (osd_window), TRUE);
+
+	screen = gtk_widget_get_screen (GTK_WIDGET (osd_window));
+	visual = gdk_screen_get_rgba_visual (screen);
+	if (visual == NULL)
+		visual = gdk_screen_get_system_visual (screen);
+	gtk_widget_set_visual (GTK_WIDGET (osd_window), visual);
+	gtk_widget_realize (GTK_WIDGET (osd_window));
+
+	gtk_window_move (GTK_WINDOW (osd_window),
+	                 osd_window->priv->screen_area.x,
+	                 osd_window->priv->screen_area.y);
+	gtk_window_set_default_size (GTK_WINDOW (osd_window),
+	                             osd_window->priv->screen_area.width,
+	                             osd_window->priv->screen_area.height);
+
+	transparent.red = transparent.green = transparent.blue = 0.0;
+	transparent.alpha = BACK_OPACITY;
+	window = gtk_widget_get_window (GTK_WIDGET (osd_window));
+	gdk_window_set_background_rgba (window, &transparent);
+
+	cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
+	gdk_window_set_cursor (window, cursor);
+	g_object_unref (cursor);
+
+	return GTK_WIDGET (osd_window);
+}
+
+static gboolean
+gsd_wacom_osd_window_map_event (GtkWidget   *widget,
+                                GdkEventAny *event)
+{
+	GsdWacomOSDWindow *osd_window = GSD_WACOM_OSD_WINDOW (widget);
+
+	g_return_val_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window), FALSE);
+	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (osd_window->priv->pad), FALSE);
+
+	set_grab_keyboard (gtk_widget_get_window (widget), TRUE);
+
+	return TRUE;
+}
+
+static gboolean
+gsd_wacom_osd_window_unmap_event (GtkWidget   *widget,
+                                  GdkEventAny *event)
+{
+	GsdWacomOSDWindow *osd_window = GSD_WACOM_OSD_WINDOW (widget);
+
+	g_return_val_if_fail (GSD_IS_WACOM_OSD_WINDOW (osd_window), FALSE);
+	g_return_val_if_fail (GSD_IS_WACOM_DEVICE (osd_window->priv->pad), FALSE);
+
+	set_grab_keyboard (gtk_widget_get_window (widget), FALSE);
+
+	return TRUE;
+}
+
+static void
+gsd_wacom_osd_window_class_init (GsdWacomOSDWindowClass *klass)
+{
+	GObjectClass *gobject_class;
+	GtkWidgetClass *widget_class;
+
+	gobject_class = G_OBJECT_CLASS (klass);
+	widget_class  = GTK_WIDGET_CLASS (klass);
+
+	gobject_class->set_property = gsd_wacom_osd_window_set_property;
+	gobject_class->get_property = gsd_wacom_osd_window_get_property;
+	gobject_class->finalize     = gsd_wacom_osd_window_finalize;
+	widget_class->draw          = gsd_wacom_osd_window_draw;
+	widget_class->map_event     = gsd_wacom_osd_window_map_event;
+	widget_class->unmap_event   = gsd_wacom_osd_window_unmap_event;
+
+	g_object_class_install_property (gobject_class,
+	                                 PROP_OSD_WINDOW_MESSAGE,
+	                                 g_param_spec_string ("message",
+	                                                      "Window message",
+	                                                      "The message shown in the OSD window",
+	                                                      "",
+	                                                      G_PARAM_READWRITE));
+	g_object_class_install_property (gobject_class,
+	                                 PROP_OSD_WINDOW_GSD_WACOM_DEVICE,
+	                                 g_param_spec_object ("wacom-device",
+	                                                      "Wacom device",
+	                                                      "The Wacom device represented by the OSD window",
+	                                                      GSD_TYPE_WACOM_DEVICE,
+	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_type_class_add_private (klass, sizeof (GsdWacomOSDWindowPrivate));
+}
+
+static void
+gsd_wacom_osd_window_init (GsdWacomOSDWindow *osd_window)
+{
+	osd_window->priv = GSD_WACOM_OSD_WINDOW_GET_PRIVATE (osd_window);
+}
+
+static void
+gsd_wacom_osd_window_finalize (GObject *object)
+{
+	GsdWacomOSDWindow *osd_window;
+	GsdWacomOSDWindowPrivate *priv;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GSD_IS_WACOM_OSD_WINDOW (object));
+
+	osd_window = GSD_WACOM_OSD_WINDOW (object);
+	g_return_if_fail (osd_window->priv != NULL);
+
+	priv = osd_window->priv;
+	g_clear_object (&priv->handle);
+	g_clear_pointer (&priv->message, g_free);
+	if (priv->buttons) {
+		g_list_free_full (priv->buttons, g_object_unref);
+		priv->buttons = NULL;
+	}
+
+	G_OBJECT_CLASS (gsd_wacom_osd_window_parent_class)->finalize (object);
+}
diff --git a/plugins/wacom/gsd-wacom-osd-window.h b/plugins/wacom/gsd-wacom-osd-window.h
new file mode 100644
index 0000000..a567b9d
--- /dev/null
+++ b/plugins/wacom/gsd-wacom-osd-window.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Olivier Fourdan <ofourdan redhat com>
+ *
+ */
+
+#ifndef __GSD_WACOM_OSD_WINDOW_H
+#define __GSD_WACOM_OSD_WINDOW_H
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+#include "gsd-wacom-device.h"
+
+#define GSD_TYPE_WACOM_OSD_WINDOW         (gsd_wacom_osd_window_get_type ())
+#define GSD_WACOM_OSD_WINDOW(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_WACOM_OSD_WINDOW, GsdWacomOSDWindow))
+#define GSD_WACOM_OSD_WINDOW_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_WACOM_OSD_WINDOW, GsdWacomOSDWindowClass))
+#define GSD_IS_WACOM_OSD_WINDOW(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_WACOM_OSD_WINDOW))
+#define GSD_IS_WACOM_OSD_WINDOW_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_WACOM_OSD_WINDOW))
+#define GSD_WACOM_OSD_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_WACOM_OSD_WINDOW, GsdWacomOSDWindowClass))
+
+typedef struct GsdWacomOSDWindowPrivate GsdWacomOSDWindowPrivate;
+
+typedef struct
+{
+        GtkWindow                 window;
+        GsdWacomOSDWindowPrivate *priv;
+} GsdWacomOSDWindow;
+
+typedef struct
+{
+        GtkWindowClass            parent_class;
+} GsdWacomOSDWindowClass;
+
+GType                     gsd_wacom_osd_window_get_type        (void) G_GNUC_CONST;
+GsdWacomDevice *          gsd_wacom_osd_window_get_device      (GsdWacomOSDWindow        *osd_window);
+void                      gsd_wacom_osd_window_set_message     (GsdWacomOSDWindow        *osd_window,
+                                                                const gchar              *str);
+const char *              gsd_wacom_osd_window_get_message     (GsdWacomOSDWindow        *osd_window);
+void                      gsd_wacom_osd_window_set_active      (GsdWacomOSDWindow        *osd_window,
+                                                                GsdWacomTabletButton     *button,
+                                                                GtkDirectionType          dir,
+                                                                gboolean                  active);
+GtkWidget *               gsd_wacom_osd_window_new             (GsdWacomDevice           *pad,
+                                                                const gchar              *message);
+
+#endif /* __GSD_WACOM_OSD_WINDOW_H */
diff --git a/plugins/wacom/tablet-layout.css b/plugins/wacom/tablet-layout.css
new file mode 100644
index 0000000..dde9d06
--- /dev/null
+++ b/plugins/wacom/tablet-layout.css
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg";
+     xmlns:xi="http://www.w3.org/2001/XInclude";
+     width="layout_width"
+     height="layout_height" >
+  <style type="text/css">
+    .Leader {
+      stroke-width:  .5 !important;
+      stroke:   dark_color;
+      fill:        none !important;
+    }
+    .Button {
+      stroke-width: .25;
+      stroke:   inactive_color;
+      fill:     inactive_color;
+    }
+    buttons_section
+    .Leader {
+      fill:        none !important;
+    }
+    .Label {
+      stroke:      none !important;
+      stroke-width:  .1 !important;
+      font-size:     .1 !important;
+      fill:     back_color !important;
+    }
+    .TouchStrip,.TouchRing {
+      stroke-width:  .1 !important;
+      stroke:   inactive_color !important;
+      fill:     dark_color !important;
+    }
+  </style>
+  <xi:include href="layout_file" />
+</svg>
diff --git a/plugins/wacom/test-osd-window.c b/plugins/wacom/test-osd-window.c
new file mode 100644
index 0000000..8eba75a
--- /dev/null
+++ b/plugins/wacom/test-osd-window.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Olivier Fourdan <ofourdan redhat com>
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <dirent.h>
+#include <glib/gi18n.h>
+#include "gsd-wacom-osd-window.h"
+
+static gboolean option_debug = FALSE;
+
+static GsdWacomDevice *
+search_pad_device (void)
+{
+	GdkDeviceManager *mgr;
+	GList *list, *l;
+
+	mgr = gdk_display_get_device_manager (gdk_display_get_default ());
+	list = gdk_device_manager_list_devices (mgr, GDK_DEVICE_TYPE_SLAVE);
+	for (l = list; l ; l = l->next) {
+		GsdWacomDevice *device;
+
+		device = gsd_wacom_device_new (l->data);
+		if (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_PAD)
+			return (device);
+		g_object_unref (device);
+	}
+	g_list_free (list);
+
+	return NULL;
+}
+
+static GsdWacomDevice *
+create_fake_device (const char *tablet)
+{
+	GsdWacomDevice *device;
+	gchar *tool;
+
+	tool = g_strdup_printf ("%s pad", tablet);
+	device = gsd_wacom_device_create_fake (WACOM_TYPE_PAD, tablet, tool);
+	g_free (tool);
+
+	return device;
+}
+
+static gboolean
+on_key_release_event(GtkWidget   *widget,
+                     GdkEventKey *event,
+                     gpointer     data)
+{
+	gtk_main_quit();
+
+	return FALSE;
+}
+
+int main(int argc, char** argv)
+{
+	GtkWidget *widget;
+	GError *error = NULL;
+	GOptionContext *context;
+	GsdWacomDevice *device;
+	gchar *message;
+	gchar *tablet = NULL;
+	const GOptionEntry entries[] = {
+		{ "tablet", 't', 0, G_OPTION_ARG_STRING, &tablet, "Name of the tablet to show", "<string>"},
+		{ "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, "Debug output", NULL },
+		{ NULL }
+	};
+
+	gtk_init (&argc, &argv);
+
+	context = g_option_context_new ("- test functions");
+	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+	g_option_context_add_group (context, gtk_get_option_group (TRUE));
+	g_option_context_set_help_enabled (context, TRUE);
+	if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) {
+		g_print ("%s\n", error->message);
+		return 1;
+	}
+	g_option_context_free (context);
+
+	if (option_debug)
+		g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+	if (tablet)
+		device = create_fake_device (tablet);
+	else
+		device = search_pad_device ();
+
+	if (device == NULL) {
+		g_print ("No pad device found, consider using --tablet\n");
+		return 1;
+	}
+
+	if (gsd_wacom_device_get_layout_path (device) == NULL) {
+		g_print ("This device has not layout available in libwacom\n");
+		return 1;
+	}
+
+	message = g_strdup_printf ("<big><b>%s</b></big>\n<i>(Press a key to exit)</i>",
+	                           gsd_wacom_device_get_name (device));
+	widget = gsd_wacom_osd_window_new (device, message);
+	g_free (message);
+
+	g_signal_connect (widget, "key-release-event",
+			  G_CALLBACK(on_key_release_event), NULL);
+	g_signal_connect (widget, "delete-event",
+			  G_CALLBACK (gtk_main_quit), NULL);
+	g_signal_connect (widget, "unmap",
+			  G_CALLBACK (gtk_main_quit), NULL);
+
+	gtk_widget_show (widget);
+	gtk_main ();
+
+	return 0;
+}
diff --git a/plugins/wacom/wacom.gresource.xml b/plugins/wacom/wacom.gresource.xml
new file mode 100644
index 0000000..57ac200
--- /dev/null
+++ b/plugins/wacom/wacom.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/settings-daemon/plugins/wacom">
+    <file>tablet-layout.css</file>
+  </gresource>
+</gresources>
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7eb291b..46dd915 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -44,6 +44,7 @@ plugins/smartcard/gsd-smartcard-manager.c
 plugins/updates/gsd-updates-firmware.c
 plugins/updates/gsd-updates-manager.c
 plugins/wacom/gsd-wacom-device.c
+plugins/wacom/gsd-wacom-osd-window.c
 plugins/wacom/org.gnome.settings-daemon.plugins.wacom.policy.in.in
 plugins/xrandr/gsd-xrandr-manager.c
 [type: gettext/ini]plugins/xrandr/xrandr.gnome-settings-plugin.in



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]