Blob Blame History Raw
diff --git a/configure.ac b/configure.ac
index 0e5d5f9..bfbecd8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -253,10 +253,12 @@ case $host_os in
       have_wacom=no
     else
       if test x$enable_gudev != xno; then
+        PKG_CHECK_MODULES(LIBWACOM, [libwacom >= $LIBWACOM_REQUIRED_VERSION])
         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 >= $LIBRSVG_REQUIRED_VERSION])
       else
         AC_MSG_ERROR([GUdev is necessary to compile Wacom support])
       fi
+      AC_DEFINE_UNQUOTED(HAVE_WACOM, 1, [Define to 1 if wacom support is available])
       have_wacom=yes
     fi
     ;;
diff --git a/plugins/common/Makefile.am b/plugins/common/Makefile.am
index b0e907c..3b84b21 100644
--- a/plugins/common/Makefile.am
+++ b/plugins/common/Makefile.am
@@ -3,25 +3,35 @@ plugin_name = common
 noinst_LTLIBRARIES = libcommon.la
 
 libcommon_la_SOURCES = \
+	edid.h			\
+	edid-parse.c		\
+	gsd-device-mapper.c	\
+	gsd-device-mapper.h	\
 	gsd-keygrab.c		\
 	gsd-keygrab.h		\
 	gsd-input-helper.c	\
 	gsd-input-helper.h
 
 libcommon_la_CPPFLAGS = \
+	-I$(top_srcdir)/data/	\
 	$(AM_CPPFLAGS)
 
 libcommon_la_CFLAGS = \
 	$(PLUGIN_CFLAGS)		\
+	$(GNOME_DESKTOP_CFLAGS)		\
 	$(SETTINGS_PLUGIN_CFLAGS)	\
 	$(COMMON_CFLAGS)		\
+	$(LIBWACOM_CFLAGS)		\
 	$(AM_CFLAGS)
 
 libcommon_la_LDFLAGS = \
 	$(GSD_PLUGIN_LDFLAGS)
 
 libcommon_la_LIBADD  = \
+	-lm                             \
 	$(SETTINGS_PLUGIN_LIBS)		\
+	$(GNOME_DESKTOP_LIBS)		\
+	$(LIBWACOM_LIBS)		\
 	$(COMMON_LIBS)
 
 libexec_PROGRAMS = gsd-test-input-helper
diff --git a/plugins/common/edid-parse.c b/plugins/common/edid-parse.c
new file mode 100644
index 0000000..5b3283a
--- /dev/null
+++ b/plugins/common/edid-parse.c
@@ -0,0 +1,539 @@
+/*
+ * Copyright 2007 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* Author: Soren Sandmann <sandmann@redhat.com> */
+
+#include "edid.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <glib.h>
+
+static int
+get_bit (int in, int bit)
+{
+  return (in & (1 << bit)) >> bit;
+}
+
+static int
+get_bits (int in, int begin, int end)
+{
+  int mask = (1 << (end - begin + 1)) - 1;
+
+  return (in >> begin) & mask;
+}
+
+static int
+decode_header (const uchar *edid)
+{
+  if (memcmp (edid, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) == 0)
+    return TRUE;
+  return FALSE;
+}
+
+static int
+decode_vendor_and_product_identification (const uchar *edid, MonitorInfo *info)
+{
+  int is_model_year;
+
+  /* Manufacturer Code */
+  info->manufacturer_code[0]  = get_bits (edid[0x08], 2, 6);
+  info->manufacturer_code[1]  = get_bits (edid[0x08], 0, 1) << 3;
+  info->manufacturer_code[1] |= get_bits (edid[0x09], 5, 7);
+  info->manufacturer_code[2]  = get_bits (edid[0x09], 0, 4);
+  info->manufacturer_code[3]  = '\0';
+
+  info->manufacturer_code[0] += 'A' - 1;
+  info->manufacturer_code[1] += 'A' - 1;
+  info->manufacturer_code[2] += 'A' - 1;
+
+  /* Product Code */
+  info->product_code = edid[0x0b] << 8 | edid[0x0a];
+
+  /* Serial Number */
+  info->serial_number =
+    edid[0x0c] | edid[0x0d] << 8 | edid[0x0e] << 16 | edid[0x0f] << 24;
+
+  /* Week and Year */
+  is_model_year = FALSE;
+  switch (edid[0x10])
+    {
+    case 0x00:
+      info->production_week = -1;
+      break;
+
+    case 0xff:
+      info->production_week = -1;
+      is_model_year = TRUE;
+      break;
+
+    default:
+      info->production_week = edid[0x10];
+      break;
+    }
+
+  if (is_model_year)
+    {
+      info->production_year = -1;
+      info->model_year = 1990 + edid[0x11];
+    }
+  else
+    {
+      info->production_year = 1990 + edid[0x11];
+      info->model_year = -1;
+    }
+
+  return TRUE;
+}
+
+static int
+decode_edid_version (const uchar *edid, MonitorInfo *info)
+{
+  info->major_version = edid[0x12];
+  info->minor_version = edid[0x13];
+
+  return TRUE;
+}
+
+static int
+decode_display_parameters (const uchar *edid, MonitorInfo *info)
+{
+  /* Digital vs Analog */
+  info->is_digital = get_bit (edid[0x14], 7);
+
+  if (info->is_digital)
+    {
+      int bits;
+
+      static const int bit_depth[8] =
+        {
+          -1, 6, 8, 10, 12, 14, 16, -1
+        };
+
+      static const Interface interfaces[6] =
+        {
+          UNDEFINED, DVI, HDMI_A, HDMI_B, MDDI, DISPLAY_PORT
+        };
+
+      bits = get_bits (edid[0x14], 4, 6);
+      info->connector.digital.bits_per_primary = bit_depth[bits];
+
+      bits = get_bits (edid[0x14], 0, 3);
+
+      if (bits <= 5)
+        info->connector.digital.interface = interfaces[bits];
+      else
+        info->connector.digital.interface = UNDEFINED;
+    }
+  else
+    {
+      int bits = get_bits (edid[0x14], 5, 6);
+
+      static const double levels[][3] =
+        {
+          { 0.7,   0.3,    1.0 },
+          { 0.714, 0.286,  1.0 },
+          { 1.0,   0.4,    1.4 },
+          { 0.7,   0.0,    0.7 },
+        };
+
+      info->connector.analog.video_signal_level = levels[bits][0];
+      info->connector.analog.sync_signal_level = levels[bits][1];
+      info->connector.analog.total_signal_level = levels[bits][2];
+
+      info->connector.analog.blank_to_black = get_bit (edid[0x14], 4);
+
+      info->connector.analog.separate_hv_sync = get_bit (edid[0x14], 3);
+      info->connector.analog.composite_sync_on_h = get_bit (edid[0x14], 2);
+      info->connector.analog.composite_sync_on_green = get_bit (edid[0x14], 1);
+
+      info->connector.analog.serration_on_vsync = get_bit (edid[0x14], 0);
+    }
+
+  /* Screen Size / Aspect Ratio */
+  if (edid[0x15] == 0 && edid[0x16] == 0)
+    {
+      info->width_mm = -1;
+      info->height_mm = -1;
+      info->aspect_ratio = -1.0;
+    }
+  else if (edid[0x16] == 0)
+    {
+      info->width_mm = -1;
+      info->height_mm = -1; 
+      info->aspect_ratio = 100.0 / (edid[0x15] + 99);
+    }
+  else if (edid[0x15] == 0)
+    {
+      info->width_mm = -1;
+      info->height_mm = -1;
+      info->aspect_ratio = 100.0 / (edid[0x16] + 99);
+      info->aspect_ratio = 1/info->aspect_ratio; /* portrait */
+    }
+  else
+    {
+      info->width_mm = 10 * edid[0x15];
+      info->height_mm = 10 * edid[0x16];
+    }
+
+  /* Gamma */
+  if (edid[0x17] == 0xFF)
+    info->gamma = -1.0;
+  else
+    info->gamma = (edid[0x17] + 100.0) / 100.0;
+
+  /* Features */
+  info->standby = get_bit (edid[0x18], 7);
+  info->suspend = get_bit (edid[0x18], 6);
+  info->active_off = get_bit (edid[0x18], 5);
+
+  if (info->is_digital)
+    {
+      info->connector.digital.rgb444 = TRUE;
+      if (get_bit (edid[0x18], 3))
+        info->connector.digital.ycrcb444 = 1;
+      if (get_bit (edid[0x18], 4))
+        info->connector.digital.ycrcb422 = 1;
+    }
+  else
+    {
+      int bits = get_bits (edid[0x18], 3, 4);
+      ColorType color_type[4] =
+        {
+          MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR
+        };
+
+      info->connector.analog.color_type = color_type[bits];
+    }
+
+  info->srgb_is_standard = get_bit (edid[0x18], 2);
+
+  /* In 1.3 this is called "has preferred timing" */
+  info->preferred_timing_includes_native = get_bit (edid[0x18], 1);
+
+  /* FIXME: In 1.3 this indicates whether the monitor accepts GTF */
+  info->continuous_frequency = get_bit (edid[0x18], 0);
+  return TRUE;
+}
+
+static double
+decode_fraction (int high, int low)
+{
+  double result = 0.0;
+  int i;
+
+  high = (high << 2) | low;
+
+  for (i = 0; i < 10; ++i)
+    result += get_bit (high, i) * pow (2, i - 10);
+
+  return result;
+}
+
+static int
+decode_color_characteristics (const uchar *edid, MonitorInfo *info)
+{
+  info->red_x = decode_fraction (edid[0x1b], get_bits (edid[0x19], 6, 7));
+  info->red_y = decode_fraction (edid[0x1c], get_bits (edid[0x19], 5, 4));
+  info->green_x = decode_fraction (edid[0x1d], get_bits (edid[0x19], 2, 3));
+  info->green_y = decode_fraction (edid[0x1e], get_bits (edid[0x19], 0, 1));
+  info->blue_x = decode_fraction (edid[0x1f], get_bits (edid[0x1a], 6, 7));
+  info->blue_y = decode_fraction (edid[0x20], get_bits (edid[0x1a], 4, 5));
+  info->white_x = decode_fraction (edid[0x21], get_bits (edid[0x1a], 2, 3));
+  info->white_y = decode_fraction (edid[0x22], get_bits (edid[0x1a], 0, 1));
+
+  return TRUE;
+}
+
+static int
+decode_established_timings (const uchar *edid, MonitorInfo *info)
+{
+  static const Timing established[][8] = 
+    {
+      {
+        { 800, 600, 60 },
+        { 800, 600, 56 },
+        { 640, 480, 75 },
+        { 640, 480, 72 },
+        { 640, 480, 67 },
+        { 640, 480, 60 },
+        { 720, 400, 88 },
+        { 720, 400, 70 }
+      },
+      {
+        { 1280, 1024, 75 },
+        { 1024, 768, 75 },
+        { 1024, 768, 70 },
+        { 1024, 768, 60 },
+        { 1024, 768, 87 },
+        { 832, 624, 75 },
+        { 800, 600, 75 },
+        { 800, 600, 72 }
+	},
+      {
+        { 0, 0, 0 },
+        { 0, 0, 0 },
+        { 0, 0, 0 },
+        { 0, 0, 0 },
+        { 0, 0, 0 },
+        { 0, 0, 0 },
+        { 0, 0, 0 },
+        { 1152, 870, 75 }
+      },
+    };
+
+  int i, j, idx;
+
+  idx = 0;
+  for (i = 0; i < 3; ++i)
+    {
+      for (j = 0; j < 8; ++j)
+	{
+          int byte = edid[0x23 + i];
+
+          if (get_bit (byte, j) && established[i][j].frequency != 0)
+            info->established[idx++] = established[i][j];
+	}
+    }
+  return TRUE;
+}
+
+static int
+decode_standard_timings (const uchar *edid, MonitorInfo *info)
+{
+  int i;
+
+  for (i = 0; i < 8; i++)
+    {
+      int first = edid[0x26 + 2 * i];
+      int second = edid[0x27 + 2 * i];
+
+      if (first != 0x01 && second != 0x01)
+	{
+          int w = 8 * (first + 31);
+          int h = 0;
+
+          switch (get_bits (second, 6, 7))
+            {
+	    case 0x00: h = (w / 16) * 10; break;
+	    case 0x01: h = (w / 4) * 3; break;
+	    case 0x02: h = (w / 5) * 4; break;
+	    case 0x03: h = (w / 16) * 9; break;
+	    }
+
+          info->standard[i].width = w;
+          info->standard[i].height = h;
+          info->standard[i].frequency = get_bits (second, 0, 5) + 60;
+	}
+    }
+
+  return TRUE;
+}
+
+static void
+decode_lf_string (const uchar *s, int n_chars, char *result)
+{
+  int i;
+  for (i = 0; i < n_chars; ++i)
+    {
+      if (s[i] == 0x0a)
+	{
+          *result++ = '\0';
+          break;
+	}
+      else if (s[i] == 0x00)
+	{
+          /* Convert embedded 0's to spaces */
+          *result++ = ' ';
+	}
+      else
+	{
+          *result++ = s[i];
+	}
+    }
+}
+
+static void
+decode_display_descriptor (const uchar *desc,
+			   MonitorInfo *info)
+{
+  switch (desc[0x03])
+    {
+    case 0xFC:
+      decode_lf_string (desc + 5, 13, info->dsc_product_name);
+      break;
+    case 0xFF:
+      decode_lf_string (desc + 5, 13, info->dsc_serial_number);
+      break;
+    case 0xFE:
+      decode_lf_string (desc + 5, 13, info->dsc_string);
+      break;
+    case 0xFD:
+      /* Range Limits */
+      break;
+    case 0xFB:
+      /* Color Point */
+      break;
+    case 0xFA:
+      /* Timing Identifications */
+      break;
+    case 0xF9:
+      /* Color Management */
+      break;
+    case 0xF8:
+      /* Timing Codes */
+      break;
+    case 0xF7:
+      /* Established Timings */
+      break;
+    case 0x10:
+      break;
+    }
+}
+
+static void
+decode_detailed_timing (const uchar *timing,
+			DetailedTiming *detailed)
+{
+  int bits;
+  StereoType stereo[] =
+    {
+      NO_STEREO, NO_STEREO, FIELD_RIGHT, FIELD_LEFT,
+      TWO_WAY_RIGHT_ON_EVEN, TWO_WAY_LEFT_ON_EVEN,
+      FOUR_WAY_INTERLEAVED, SIDE_BY_SIDE
+    };
+
+  detailed->pixel_clock = (timing[0x00] | timing[0x01] << 8) * 10000;
+  detailed->h_addr = timing[0x02] | ((timing[0x04] & 0xf0) << 4);
+  detailed->h_blank = timing[0x03] | ((timing[0x04] & 0x0f) << 8);
+  detailed->v_addr = timing[0x05] | ((timing[0x07] & 0xf0) << 4);
+  detailed->v_blank = timing[0x06] | ((timing[0x07] & 0x0f) << 8);
+  detailed->h_front_porch = timing[0x08] | get_bits (timing[0x0b], 6, 7) << 8;
+  detailed->h_sync = timing[0x09] | get_bits (timing[0x0b], 4, 5) << 8;
+  detailed->v_front_porch =
+    get_bits (timing[0x0a], 4, 7) | get_bits (timing[0x0b], 2, 3) << 4;
+  detailed->v_sync =
+    get_bits (timing[0x0a], 0, 3) | get_bits (timing[0x0b], 0, 1) << 4;
+  detailed->width_mm =  timing[0x0c] | get_bits (timing[0x0e], 4, 7) << 8;
+  detailed->height_mm = timing[0x0d] | get_bits (timing[0x0e], 0, 3) << 8;
+  detailed->right_border = timing[0x0f];
+  detailed->top_border = timing[0x10];
+
+  detailed->interlaced = get_bit (timing[0x11], 7);
+
+  /* Stereo */
+  bits = get_bits (timing[0x11], 5, 6) << 1 | get_bit (timing[0x11], 0);
+  detailed->stereo = stereo[bits];
+
+  /* Sync */
+  bits = timing[0x11];
+
+  detailed->digital_sync = get_bit (bits, 4);
+  if (detailed->digital_sync)
+    {
+      detailed->connector.digital.composite = !get_bit (bits, 3);
+
+      if (detailed->connector.digital.composite)
+	{
+          detailed->connector.digital.serrations = get_bit (bits, 2);
+          detailed->connector.digital.negative_vsync = FALSE;
+	}
+      else
+	{
+          detailed->connector.digital.serrations = FALSE;
+          detailed->connector.digital.negative_vsync = !get_bit (bits, 2);
+	}
+
+      detailed->connector.digital.negative_hsync = !get_bit (bits, 0);
+    }
+  else
+    {
+      detailed->connector.analog.bipolar = get_bit (bits, 3);
+      detailed->connector.analog.serrations = get_bit (bits, 2);
+      detailed->connector.analog.sync_on_green = !get_bit (bits, 1);
+    }
+}
+
+static int
+decode_descriptors (const uchar *edid, MonitorInfo *info)
+{
+  int i;
+  int timing_idx;
+
+  timing_idx = 0;
+
+  for (i = 0; i < 4; ++i)
+    {
+      int index = 0x36 + i * 18;
+
+      if (edid[index + 0] == 0x00 && edid[index + 1] == 0x00)
+	{
+          decode_display_descriptor (edid + index, info);
+	}
+      else
+	{
+          decode_detailed_timing (edid + index, &(info->detailed_timings[timing_idx++]));
+	}
+    }
+
+  info->n_detailed_timings = timing_idx;
+
+  return TRUE;
+}
+
+static void
+decode_check_sum (const uchar *edid,
+		  MonitorInfo *info)
+{
+  int i;
+  uchar check = 0;
+
+  for (i = 0; i < 128; ++i)
+    check += edid[i];
+
+  info->checksum = check;
+}
+
+MonitorInfo *
+decode_edid (const uchar *edid)
+{
+  MonitorInfo *info = g_new0 (MonitorInfo, 1);
+
+  decode_check_sum (edid, info);
+
+  if (decode_header (edid)
+      && decode_vendor_and_product_identification (edid, info)
+      && decode_edid_version (edid, info)
+      && decode_display_parameters (edid, info)
+      && decode_color_characteristics (edid, info)
+      && decode_established_timings (edid, info)
+      && decode_standard_timings (edid, info)
+      && decode_descriptors (edid, info))
+    {
+      return info;
+    }
+  else
+    {
+      g_free (info);
+      return NULL;
+    }
+}
diff --git a/plugins/common/edid.h b/plugins/common/edid.h
new file mode 100644
index 0000000..703c639
--- /dev/null
+++ b/plugins/common/edid.h
@@ -0,0 +1,195 @@
+/* edid.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * 
+ * This file is part of the Gnome Library.
+ * 
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ * 
+ * Author: Soren Sandmann <sandmann@redhat.com>
+ */
+
+#ifndef EDID_H
+#define EDID_H
+
+typedef unsigned char uchar;
+typedef struct MonitorInfo MonitorInfo;
+typedef struct Timing Timing;
+typedef struct DetailedTiming DetailedTiming;
+
+typedef enum
+{
+  UNDEFINED,
+  DVI,
+  HDMI_A,
+  HDMI_B,
+  MDDI,
+  DISPLAY_PORT
+} Interface;
+
+typedef enum
+{
+  UNDEFINED_COLOR,
+  MONOCHROME,
+  RGB,
+  OTHER_COLOR
+} ColorType;
+
+typedef enum
+{
+  NO_STEREO,
+  FIELD_RIGHT,
+  FIELD_LEFT,
+  TWO_WAY_RIGHT_ON_EVEN,
+  TWO_WAY_LEFT_ON_EVEN,
+  FOUR_WAY_INTERLEAVED,
+  SIDE_BY_SIDE
+} StereoType;
+
+struct Timing
+{
+  int width;
+  int height;
+  int frequency;
+};
+
+struct DetailedTiming
+{
+  int		pixel_clock;
+  int		h_addr;
+  int		h_blank;
+  int		h_sync;
+  int		h_front_porch;
+  int		v_addr;
+  int		v_blank;
+  int		v_sync;
+  int		v_front_porch;
+  int		width_mm;
+  int		height_mm;
+  int		right_border;
+  int		top_border;
+  int		interlaced;
+  StereoType	stereo;
+
+  int		digital_sync;
+  union
+  {
+    struct
+    {
+      int bipolar;
+      int serrations;
+      int sync_on_green;
+    } analog;
+
+    struct
+    {
+      int composite;
+      int serrations;
+      int negative_vsync;
+      int negative_hsync;
+    } digital;
+  } connector;
+};
+
+struct MonitorInfo
+{
+  int		checksum;
+  char		manufacturer_code[4];
+  int		product_code;
+  unsigned int	serial_number;
+
+  int		production_week;	/* -1 if not specified */
+  int		production_year;	/* -1 if not specified */
+  int		model_year;		/* -1 if not specified */
+
+  int		major_version;
+  int		minor_version;
+
+  int		is_digital;
+
+  union
+  {
+    struct
+    {
+      int	bits_per_primary;
+      Interface	interface;
+      int	rgb444;
+      int	ycrcb444;
+      int	ycrcb422;
+    } digital;
+
+    struct
+    {
+      double	video_signal_level;
+      double	sync_signal_level;
+      double	total_signal_level;
+
+      int	blank_to_black;
+
+      int	separate_hv_sync;
+      int	composite_sync_on_h;
+      int	composite_sync_on_green;
+      int	serration_on_vsync;
+      ColorType	color_type;
+    } analog;
+  } connector;
+
+  int		width_mm;		/* -1 if not specified */
+  int		height_mm;		/* -1 if not specified */
+  double	aspect_ratio;		/* -1.0 if not specififed */
+
+  double	gamma;			/* -1.0 if not specified */
+
+  int		standby;
+  int		suspend;
+  int		active_off;
+
+  int		srgb_is_standard;
+  int		preferred_timing_includes_native;
+  int		continuous_frequency;
+
+  double	red_x;
+  double	red_y;
+  double	green_x;
+  double	green_y;
+  double	blue_x;
+  double	blue_y;
+  double	white_x;
+  double	white_y;
+
+  Timing	established[24];	/* Terminated by 0x0x0 */
+  Timing	standard[8];
+
+  int		n_detailed_timings;
+  DetailedTiming detailed_timings[4];	/* If monitor has a preferred
+                                         * mode, it is the first one
+                                         * (whether it has, is
+                                         * determined by the 
+                                         * preferred_timing_includes
+                                         * bit.
+                                         */
+
+  /* Optional product description */
+  char		dsc_serial_number[14];
+  char		dsc_product_name[14];
+  char		dsc_string[14];		/* Unspecified ASCII data */
+};
+
+MonitorInfo *decode_edid (const uchar *data);
+char *make_display_name (const MonitorInfo *info);
+char *make_display_size_string (int width_mm, int height_mm);
+
+#endif
diff --git a/plugins/common/gsd-device-mapper.c b/plugins/common/gsd-device-mapper.c
new file mode 100644
index 0000000..d8ea62a
--- /dev/null
+++ b/plugins/common/gsd-device-mapper.c
@@ -0,0 +1,1293 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gtk/gtkx.h>
+#include <X11/Xatom.h>
+
+#if HAVE_WACOM
+#include <libwacom/libwacom.h>
+#endif
+
+#include "gsd-device-mapper.h"
+#include "gsd-input-helper.h"
+#include "gsd-enums.h"
+#include "edid.h"
+
+typedef struct _GsdInputInfo GsdInputInfo;
+typedef struct _GsdOutputInfo GsdOutputInfo;
+typedef struct _MappingHelper MappingHelper;
+typedef struct _DeviceMapHelper DeviceMapHelper;
+
+#define NUM_ELEMS_MATRIX 9
+#define KEY_DISPLAY  "display"
+#define KEY_ROTATION "rotation"
+
+typedef enum {
+	GSD_INPUT_IS_SYSTEM_INTEGRATED = 1 << 0, /* eg. laptop tablets/touchscreens */
+	GSD_INPUT_IS_SCREEN_INTEGRATED = 1 << 1, /* eg. Wacom Cintiq devices */
+	GSD_INPUT_IS_TOUCH		   = 1 << 2, /* touch device, either touchscreen or tablet */
+	GSD_INPUT_IS_PEN		   = 1 << 3, /* pen device, either touchscreen or tablet */
+	GSD_INPUT_IS_ERASER		   = 1 << 4, /* eraser device, either touchscreen or tablet */
+	GSD_INPUT_IS_PAD		   = 1 << 5  /* pad device, most usually in tablets */
+} GsdInputCapabilityFlags;
+
+typedef enum {
+	GSD_PRIO_BUILTIN,		 /* Output is builtin, applies mainly to system-integrated devices */
+	GSD_PRIO_EDID_MATCH_FULL,	 /* Full EDID model match, eg. "Cintiq 12WX" */
+	GSD_PRIO_EDID_MATCH_PARTIAL, /* Partial EDID model match, eg. "Cintiq" */
+	GSD_PRIO_EDID_MATCH_VENDOR,	 /* EDID vendor match, eg. "WAC" for Wacom */
+	N_OUTPUT_PRIORITIES
+} GsdOutputPriority;
+
+struct _GsdInputInfo {
+	GdkDevice *device;
+	GSettings *settings;
+	GsdDeviceMapper *mapper;
+	GsdOutputInfo *output;
+	GsdOutputInfo *guessed_output;
+	guint changed_id;
+	GsdInputCapabilityFlags capabilities;
+};
+
+struct _GsdOutputInfo {
+	GnomeRROutput *output;
+	GList *input_devices;
+};
+
+struct _DeviceMapHelper {
+	GsdInputInfo *input;
+	GnomeRROutput *candidates[N_OUTPUT_PRIORITIES];
+	GsdOutputPriority highest_prio;
+	guint n_candidates;
+};
+
+struct _MappingHelper {
+	GArray *device_maps;
+};
+
+struct _GsdDeviceMapper {
+	GObject parent_instance;
+	GdkScreen *screen;
+	GnomeRRScreen *rr_screen;
+	GHashTable *input_devices; /* GdkDevice -> GsdInputInfo */
+	GHashTable *output_devices; /* GnomeRROutput -> GsdOutputInfo */
+#if HAVE_WACOM
+	WacomDeviceDatabase *wacom_db;
+#endif
+};
+
+struct _GsdDeviceMapperClass {
+	GObjectClass parent_class;
+};
+
+/* Array order matches GsdWacomRotation */
+struct {
+	GnomeRRRotation rotation;
+	/* Coordinate Transformation Matrix */
+	gfloat matrix[NUM_ELEMS_MATRIX];
+} rotation_matrices[] = {
+	{ GNOME_RR_ROTATION_0, { 1, 0, 0, 0, 1, 0, 0, 0, 1 } },
+	{ GNOME_RR_ROTATION_90, { 0, -1, 1, 1, 0, 0, 0, 0, 1 } },
+	{ GNOME_RR_ROTATION_270, { 0, 1, 0, -1, 0, 1, 0,  0, 1 } },
+	{ GNOME_RR_ROTATION_180, { -1, 0, 1, 0, -1, 1, 0, 0, 1 } }
+};
+
+enum {
+	PROP_0,
+	PROP_SCREEN
+};
+
+enum {
+	DEVICE_CHANGED,
+	N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+static void gsd_device_mapper_initable_iface_init (GInitableIface *iface);
+static GsdOutputInfo * output_info_new (GnomeRROutput *output);
+
+G_DEFINE_TYPE_WITH_CODE (GsdDeviceMapper, gsd_device_mapper, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+						gsd_device_mapper_initable_iface_init))
+
+static XDevice *
+open_device (GdkDevice *device)
+{
+	XDevice *xdev;
+	int id;
+
+	id = gdk_x11_device_get_id (device);
+
+	gdk_error_trap_push ();
+	xdev = XOpenDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), id);
+	if (gdk_error_trap_pop () || (xdev == NULL))
+		return NULL;
+
+	return xdev;
+}
+
+static gboolean
+device_apply_property (GdkDevice      *device,
+		       PropertyHelper *property)
+{
+	gboolean retval;
+	XDevice *xdev;
+
+	xdev = open_device (device);
+
+	if (!xdev)
+		return FALSE;
+
+	retval = device_set_property (xdev, gdk_device_get_name (device), property);
+	XCloseDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xdev);
+	return retval;
+}
+
+static gboolean
+device_set_matrix (GdkDevice *device,
+		   gfloat     matrix[NUM_ELEMS_MATRIX])
+{
+	PropertyHelper property = {
+		.name = "Coordinate Transformation Matrix",
+		.nitems = 9,
+		.format = 32,
+		.type = gdk_x11_get_xatom_by_name ("FLOAT"),
+		.data.i = (int *) matrix,
+	};
+
+	g_debug ("Setting '%s' matrix to:\n %f,%f,%f,\n %f,%f,%f,\n %f,%f,%f",
+		 gdk_device_get_name (device),
+		 matrix[0], matrix[1], matrix[2],
+		 matrix[3], matrix[4], matrix[5],
+		 matrix[6], matrix[7], matrix[8]);
+
+	return device_apply_property (device, &property);
+}
+
+static void
+get_edid (GnomeRROutput	 *output,
+	  gchar		**vendor,
+	  gchar		**product,
+	  gchar		**serial)
+{
+	const guchar *edid_data;
+	MonitorInfo *info;
+
+	if (vendor)
+		*vendor = NULL;
+	if (product)
+		*product = NULL;
+	if (serial)
+		*serial = NULL;
+
+	edid_data = gnome_rr_output_get_edid_data (output, NULL);
+
+	if (!edid_data)
+		return;
+
+	info = decode_edid (edid_data);
+
+	if (vendor)
+		*vendor = g_strndup (info->manufacturer_code, 4);
+	if (product) {
+		if (info->dsc_product_name[0])
+			*product = g_strndup (info->dsc_product_name, 14);
+		else
+			*product = g_strdup_printf ("0x%04x", (unsigned) info->product_code);
+	}
+
+	if (serial) {
+		if (info->dsc_serial_number[0])
+			*serial = g_strndup (info->dsc_serial_number, 14);
+		else
+			*serial = g_strdup_printf ("0x%04x", (unsigned) info->serial_number);
+	}
+
+	g_free (info);
+}
+
+/* Finds an output which matches the given EDID information. Any NULL
+ * parameter will be interpreted to match any value. */
+static GnomeRROutput *
+find_output_by_edid (GnomeRRScreen *rr_screen,
+		     const gchar   *edid[3])
+{
+	GnomeRROutput **outputs;
+	GnomeRROutput *retval = NULL;
+	guint i;
+
+	outputs = gnome_rr_screen_list_outputs (rr_screen);
+
+	for (i = 0; outputs[i] != NULL; i++) {
+		gchar *vendor, *product, *serial;
+		gboolean match;
+
+		get_edid (outputs[i], &vendor, &product, &serial);
+
+		match = (edid[0] == NULL || g_strcmp0 (edid[0], vendor)	 == 0) && \
+			(edid[1] == NULL || g_strcmp0 (edid[1], product) == 0) && \
+			(edid[2] == NULL || g_strcmp0 (edid[2], serial)	 == 0);
+
+		g_debug ("Checking match between ['%s','%s','%s'] and ['%s','%s','%s']",
+			 edid[0], edid[1], edid[2], vendor, product, serial);
+
+		g_free (vendor);
+		g_free (product);
+		g_free (serial);
+
+		if (match) {
+			g_debug ("Found a match");
+			retval = outputs[i];
+			break;
+		}
+	}
+
+	if (retval == NULL)
+		g_debug ("Did not find a matching output for EDID '%s,%s,%s'",
+			 edid[0], edid[1], edid[2]);
+	return retval;
+}
+
+static GnomeRROutput *
+find_builtin_output (GnomeRRScreen *rr_screen)
+{
+	GnomeRROutput **outputs;
+	guint i;
+
+	outputs = gnome_rr_screen_list_outputs (rr_screen);
+
+	for (i = 0; outputs[i] != NULL; i++) {
+		if (!gnome_rr_output_is_laptop (outputs[i]))
+			continue;
+
+		return outputs[i];
+	}
+
+	g_debug ("Did not find a built-in monitor");
+	return NULL;
+}
+
+static GnomeRROutput *
+monitor_to_output (GsdDeviceMapper *mapper,
+		   gint		    monitor_num)
+{
+	GnomeRROutput **outputs;
+	guint i;
+
+	outputs = gnome_rr_screen_list_outputs (mapper->rr_screen);
+
+	for (i = 0; outputs[i] != NULL; i++) {
+		GnomeRRCrtc *crtc = gnome_rr_output_get_crtc (outputs[i]);
+		gint x, y;
+
+		if (!crtc)
+			continue;
+
+		gnome_rr_crtc_get_position (crtc, &x, &y);
+
+		if (monitor_num == gdk_screen_get_monitor_at_point (mapper->screen, x, y))
+			return outputs[i];
+	}
+
+	return NULL;
+}
+
+static MappingHelper *
+mapping_helper_new (void)
+{
+	MappingHelper *helper;
+
+	helper = g_new0 (MappingHelper, 1);
+	helper->device_maps = g_array_new (FALSE, FALSE, sizeof (DeviceMapHelper));
+
+	return helper;
+}
+
+static void
+mapping_helper_free (MappingHelper *helper)
+{
+	g_array_unref (helper->device_maps);
+	g_free (helper);
+}
+
+static void
+mapping_helper_add (MappingHelper *helper,
+		    GsdInputInfo  *input,
+		    GnomeRROutput *outputs[N_OUTPUT_PRIORITIES])
+{
+	guint i, pos, highest = N_OUTPUT_PRIORITIES;
+	DeviceMapHelper info = { 0 }, *prev;
+
+	info.input = input;
+
+	for (i = 0; i < N_OUTPUT_PRIORITIES; i++) {
+		if (outputs[i] == NULL)
+			continue;
+
+		if (highest > i)
+			highest = i;
+
+		info.candidates[i] = outputs[i];
+		info.n_candidates++;
+	}
+
+	info.highest_prio = highest;
+	pos = helper->device_maps->len;
+
+	for (i = 0; i < helper->device_maps->len; i++) {
+		prev = &g_array_index (helper->device_maps, DeviceMapHelper, i);
+
+		if (prev->highest_prio < info.highest_prio)
+			pos = i;
+	}
+
+	if (pos >= helper->device_maps->len)
+		g_array_append_val (helper->device_maps, info);
+	else
+		g_array_insert_val (helper->device_maps, pos, info);
+}
+
+/* This function gets a map of outputs, sorted by confidence, for a given device,
+ * the array can actually contain NULLs if no output matched a priority. */
+static void
+input_info_guess_candidates (GsdInputInfo  *input,
+			     GnomeRROutput *outputs[N_OUTPUT_PRIORITIES])
+{
+	gboolean found = FALSE;
+	const gchar *name;
+	gchar **split;
+
+	name = gdk_device_get_name (input->device);
+	split = g_strsplit (name, " ", -1);
+
+	/* On Wacom devices that are integrated on a not-in-system screen (eg. Cintiqs),
+	 * there is usually a minimal relation between the input device name and the EDID
+	 * vendor/model fields. Attempt to find matching outputs and fill in the map
+	 * from GSD_PRIO_EDID_MATCH_FULL to GSD_PRIO_EDID_MATCH_VENDOR.
+	 */
+	if (input->capabilities & GSD_INPUT_IS_SCREEN_INTEGRATED &&
+	    g_str_has_prefix (name, "Wacom ")) {
+		gchar *product = g_strdup_printf ("%s %s", split[1], split[2]);
+		const gchar *edids[][3] = {
+			{ "WAC", product, NULL },
+			{ "WAC", split[1], NULL },
+			{ "WAC", NULL, NULL }
+		};
+		gint i;
+
+		for (i = 0; i < G_N_ELEMENTS (edids); i++) {
+			/* i + 1 matches the desired priority, we skip GSD_PRIO_BUILTIN here */
+			outputs[i + 1] =
+				find_output_by_edid (input->mapper->rr_screen,
+						     edids[i]);
+			found |= outputs[i + 1] != NULL;
+		}
+
+		g_free (product);
+	}
+
+	/* For input devices that we certainly know that are system-integrated, or
+	 * for screen-integrated devices we weren't able to find an output for,
+	 * find the builtin screen.
+	 */
+	if ((input->capabilities & GSD_INPUT_IS_SYSTEM_INTEGRATED) ||
+	    (!found && input->capabilities & GSD_INPUT_IS_SCREEN_INTEGRATED)) {
+		outputs[GSD_PRIO_BUILTIN] =
+			find_builtin_output (input->mapper->rr_screen);
+	}
+
+	g_strfreev (split);
+}
+
+static gboolean
+output_has_input_type (GsdOutputInfo *info,
+		       guint	      capabilities)
+{
+	GList *devices;
+
+	for (devices = info->input_devices; devices; devices = devices->next) {
+		GsdInputInfo *input = devices->data;
+
+		if (input->capabilities == capabilities)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static GnomeRROutput *
+settings_get_display (GSettings	      *settings,
+		      GsdDeviceMapper *mapper)
+{
+	GnomeRROutput *output = NULL;
+	gchar **edid;
+	guint nvalues;
+
+	edid = g_settings_get_strv (settings, KEY_DISPLAY);
+	nvalues = g_strv_length (edid);
+
+	if (nvalues == 3) {
+		output = find_output_by_edid (mapper->rr_screen, (const gchar **) edid);
+	} else {
+		g_warning ("Unable to get display property. Got %d items, "
+			   "expected %d items.\n", nvalues, 3);
+	}
+
+	g_strfreev (edid);
+
+	return output;
+}
+
+static void
+settings_set_display (GSettings	    *settings,
+		      GnomeRROutput *output)
+{
+	gchar **prev, *edid[4] = { NULL, NULL, NULL, NULL };
+	GVariant *value;
+	gsize nvalues;
+
+	prev = g_settings_get_strv (settings, KEY_DISPLAY);
+	nvalues = g_strv_length (prev);
+
+	if (output)
+		get_edid (output, &edid[0], &edid[1], &edid[2]);
+
+	if (nvalues != 3 ||
+	    g_strcmp0 (prev[0], edid[0]) != 0 ||
+	    g_strcmp0 (prev[1], edid[1]) != 0 ||
+	    g_strcmp0 (prev[2], edid[2]) != 0) {
+		value = g_variant_new_strv ((const gchar * const *) &edid, 3);
+		g_settings_set_value (settings, KEY_DISPLAY, value);
+	}
+
+	g_free (edid[0]);
+	g_free (edid[1]);
+	g_free (edid[2]);
+	g_strfreev (prev);
+}
+
+static void
+input_info_set_output (GsdInputInfo  *input,
+		       GsdOutputInfo *output,
+		       gboolean	      guessed,
+		       gboolean	      save)
+{
+	GnomeRROutput *rr_output = NULL;
+	GsdOutputInfo **ptr;
+
+	if (guessed) {
+		/* If there is already a non-guessed input, go for it */
+		if (input->output)
+			return;
+
+		ptr = &input->guessed_output;
+	} else {
+		/* Unset guessed output */
+		if (input->guessed_output)
+			input_info_set_output (input, NULL, TRUE, FALSE);
+		ptr = &input->output;
+	}
+
+	if (*ptr == output)
+		return;
+
+	if (*ptr) {
+		(*ptr)->input_devices = g_list_remove ((*ptr)->input_devices,
+						       input);
+	}
+
+	if (output) {
+		output->input_devices = g_list_prepend (output->input_devices,
+							input);
+		rr_output = output->output;
+	}
+
+	if (input->settings && !guessed && save)
+		settings_set_display (input->settings, rr_output);
+
+	*ptr = output;
+}
+
+static GsdOutputInfo *
+input_info_get_output (GsdInputInfo *input)
+{
+	if (input->output)
+		return input->output;
+
+	if (input->guessed_output)
+		return input->guessed_output;
+
+	if (g_hash_table_size (input->mapper->output_devices) == 1) {
+		GsdOutputInfo *output;
+		GHashTableIter iter;
+
+		g_hash_table_iter_init (&iter, input->mapper->output_devices);
+		g_hash_table_iter_next (&iter, NULL, (gpointer *) &output);
+
+		return output;
+	}
+
+	return NULL;
+}
+
+static void
+init_device_rotation_matrix (GsdWacomRotation rotation,
+			     float	      matrix[NUM_ELEMS_MATRIX])
+{
+	memcpy (matrix, rotation_matrices[rotation].matrix,
+		sizeof (rotation_matrices[rotation].matrix));
+}
+
+static void
+init_output_rotation_matrix (GnomeRRRotation rotation,
+			     float	     matrix[NUM_ELEMS_MATRIX])
+{
+	guint i;
+
+	for (i = 0; i < G_N_ELEMENTS (rotation_matrices); i++) {
+		if (rotation_matrices[i].rotation != rotation)
+			continue;
+
+		memcpy (matrix, rotation_matrices[i].matrix, sizeof (rotation_matrices[i].matrix));
+		return;
+	}
+
+	/* We know nothing about this rotation */
+	init_device_rotation_matrix (GSD_WACOM_ROTATION_NONE, matrix);
+}
+
+static void
+multiply_matrix (float a[NUM_ELEMS_MATRIX],
+		 float b[NUM_ELEMS_MATRIX],
+		 float res[NUM_ELEMS_MATRIX])
+{
+	float result[NUM_ELEMS_MATRIX];
+
+	result[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
+	result[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
+	result[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
+	result[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
+	result[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
+	result[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
+	result[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
+	result[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
+	result[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
+
+	memcpy (res, result, sizeof (result));
+}
+
+static void
+calculate_viewport_matrix (const GdkRectangle mapped,
+			   const GdkRectangle desktop,
+			   float	      viewport[NUM_ELEMS_MATRIX])
+{
+	float x_scale = (float) mapped.x / desktop.width;
+	float y_scale = (float) mapped.y / desktop.height;
+	float width_scale  = (float) mapped.width / desktop.width;
+	float height_scale = (float) mapped.height / desktop.height;
+
+	viewport[0] = width_scale;
+	viewport[1] = 0.0f;
+	viewport[2] = x_scale;
+
+	viewport[3] = 0.0f;
+	viewport[4] = height_scale;
+	viewport[5] = y_scale;
+
+	viewport[6] = 0.0f;
+	viewport[7] = 0.0f;
+	viewport[8] = 1.0f;
+}
+
+static gint
+monitor_for_output (GnomeRROutput *output)
+{
+	GdkScreen *screen = gdk_screen_get_default ();
+	GnomeRRCrtc *crtc = gnome_rr_output_get_crtc (output);
+	gint x, y;
+
+	if (!crtc)
+		return -1;
+
+	gnome_rr_crtc_get_position (crtc, &x, &y);
+
+	return gdk_screen_get_monitor_at_point (screen, x, y);
+}
+
+static void
+input_info_get_matrix (GsdInputInfo *input,
+		       float	     matrix[NUM_ELEMS_MATRIX])
+{
+	GsdOutputInfo *output;
+	GnomeRRCrtc *crtc;
+
+	output = input_info_get_output (input);
+
+	if (output)
+		crtc = gnome_rr_output_get_crtc (output->output);
+
+	if (!output || !crtc) {
+		init_output_rotation_matrix (GNOME_RR_ROTATION_0, matrix);
+	} else {
+		GdkScreen *screen = gdk_screen_get_default ();
+		float viewport[NUM_ELEMS_MATRIX];
+		float output_rot[NUM_ELEMS_MATRIX];
+		GdkRectangle display, desktop = { 0 };
+		GnomeRRRotation rotation;
+		int monitor;
+
+		g_debug ("Mapping '%s' to output '%s'",
+			 gdk_device_get_name (input->device),
+			 gnome_rr_output_get_name (output->output));
+
+		rotation = gnome_rr_crtc_get_current_rotation (crtc);
+		init_output_rotation_matrix (rotation, output_rot);
+
+		desktop.width = gdk_screen_get_width (screen);
+		desktop.height = gdk_screen_get_height (screen);
+
+		monitor = monitor_for_output (output->output);
+		gdk_screen_get_monitor_geometry (screen, monitor, &display);
+		calculate_viewport_matrix (display, desktop, viewport);
+
+		multiply_matrix (viewport, output_rot, matrix);
+	}
+
+	/* Apply device rotation after output rotation */
+	if (input->settings &&
+	    (input->capabilities &
+	     (GSD_INPUT_IS_SYSTEM_INTEGRATED | GSD_INPUT_IS_SCREEN_INTEGRATED)) == 0) {
+		gint rotation;
+
+		rotation = g_settings_get_enum (input->settings, KEY_ROTATION);
+
+		if (rotation > 0) {
+			float device_rot[NUM_ELEMS_MATRIX];
+
+			g_debug ("Applying device rotation %d to '%s'",
+				 rotation, gdk_device_get_name (input->device));
+
+			init_device_rotation_matrix (rotation, device_rot);
+			multiply_matrix (matrix, device_rot, matrix);
+		}
+	}
+}
+
+static void
+input_info_remap (GsdInputInfo *input)
+{
+	float matrix[NUM_ELEMS_MATRIX] = { 0 };
+
+	if (input->capabilities & GSD_INPUT_IS_PAD)
+		return;
+
+	input_info_get_matrix (input, matrix);
+
+	g_debug ("About to remap device '%s'",
+		 gdk_device_get_name (input->device));
+
+	if (!device_set_matrix (input->device, matrix)) {
+		g_warning ("Failed to map device '%s'",
+			   gdk_device_get_name (input->device));
+	}
+
+	g_signal_emit (input->mapper, signals[DEVICE_CHANGED], 0, input->device);
+}
+
+static void
+mapper_apply_helper_info (GsdDeviceMapper *mapper,
+			  MappingHelper	  *helper)
+{
+	guint i, j;
+
+	/* Now, decide which input claims which output */
+	for (i = 0; i < helper->device_maps->len; i++) {
+		GsdOutputInfo *last = NULL, *output = NULL;
+		DeviceMapHelper *info;
+
+		info = &g_array_index (helper->device_maps, DeviceMapHelper, i);
+		g_debug ("Mapping input device '%s', candidates: %d, Best candidate: %s",
+			 gdk_device_get_name (info->input->device), info->n_candidates,
+			 (info->highest_prio < N_OUTPUT_PRIORITIES) ?
+			 gnome_rr_output_get_name (info->candidates[info->highest_prio]) : "none");
+
+		for (j = 0; j < N_OUTPUT_PRIORITIES; j++) {
+			if (!info->candidates[j])
+				continue;
+
+			output = g_hash_table_lookup (mapper->output_devices,
+						      info->candidates[j]);
+
+			if (!output) {
+				g_debug ("Output '%s' had no information associated, creating it ad-hoc",
+					 gnome_rr_output_get_name (info->candidates[j]));
+				output = output_info_new (info->candidates[j]);
+				g_hash_table_insert (mapper->output_devices,
+						     info->candidates[j], output);
+			}
+
+			last = output;
+
+			if ((info->input->capabilities &
+			     (GSD_INPUT_IS_SYSTEM_INTEGRATED | GSD_INPUT_IS_SCREEN_INTEGRATED))) {
+				/* A single output is hardly going to have multiple devices
+				 * with the same capabilities, so punt any next output.
+				 */
+				if (output_has_input_type (output, info->input->capabilities))
+					continue;
+			}
+
+			input_info_set_output (info->input, output, TRUE, FALSE);
+			break;
+		}
+
+		/* Assign the last candidate if we came up empty */
+		if (!info->input->guessed_output && last)
+			input_info_set_output (info->input, last, TRUE, FALSE);
+
+		input_info_remap (info->input);
+	}
+}
+
+static void
+mapper_recalculate_candidates (GsdDeviceMapper *mapper)
+{
+	MappingHelper *helper;
+	GHashTableIter iter;
+	GsdInputInfo *input;
+
+	helper = mapping_helper_new ();
+	g_hash_table_iter_init (&iter, mapper->input_devices);
+	g_debug ("(Re)mapping all input devices");
+
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &input)) {
+		GnomeRROutput *outputs[N_OUTPUT_PRIORITIES] = { 0 };
+
+		/* Device has an output from settings */
+		if (input->output)
+			continue;
+
+		input_info_guess_candidates (input, outputs);
+		mapping_helper_add (helper, input, outputs);
+	}
+
+	mapper_apply_helper_info (mapper, helper);
+	mapping_helper_free (helper);
+}
+
+static void
+mapper_recalculate_input (GsdDeviceMapper *mapper,
+			  GsdInputInfo	  *input)
+{
+	GnomeRROutput *outputs[N_OUTPUT_PRIORITIES] = { 0 };
+	MappingHelper *helper;
+
+	/* Device has an output from settings */
+	if (input->output)
+		return;
+
+	g_debug ("(Re)mapping input device '%s'",
+		 gdk_device_get_name (input->device));
+
+	helper = mapping_helper_new ();
+	input_info_guess_candidates (input, outputs);
+	mapping_helper_add (helper, input, outputs);
+
+	mapper_apply_helper_info (mapper, helper);
+	mapping_helper_free (helper);
+}
+
+static gboolean
+input_info_update_capabilities_from_tool_type (GsdInputInfo *info)
+{
+	const char *tool_type;
+	int deviceid;
+
+	deviceid = gdk_x11_device_get_id (info->device);
+	tool_type = xdevice_get_wacom_tool_type (deviceid);
+
+	if (!tool_type)
+		return FALSE;
+
+	if (g_str_equal (tool_type, "STYLUS"))
+		info->capabilities |= GSD_INPUT_IS_PEN;
+	else if (g_str_equal (tool_type, "ERASER"))
+		info->capabilities |= GSD_INPUT_IS_ERASER;
+	else if (g_str_equal (tool_type, "PAD"))
+		info->capabilities |= GSD_INPUT_IS_PAD;
+	else
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+input_info_update_capabilities (GsdInputInfo *info)
+{
+#if HAVE_WACOM
+	WacomDevice *wacom_device;
+	gchar *devpath;
+
+	info->capabilities = 0;
+	devpath = xdevice_get_device_node (gdk_x11_device_get_id (info->device));
+	wacom_device = libwacom_new_from_path (info->mapper->wacom_db, devpath,
+					       WFALLBACK_GENERIC, NULL);
+
+	if (wacom_device) {
+		WacomIntegrationFlags integration_flags;
+
+		integration_flags = libwacom_get_integration_flags (wacom_device);
+
+		if (integration_flags & WACOM_DEVICE_INTEGRATED_DISPLAY)
+			info->capabilities |= GSD_INPUT_IS_SCREEN_INTEGRATED;
+
+		if (integration_flags & WACOM_DEVICE_INTEGRATED_SYSTEM)
+			info->capabilities |= GSD_INPUT_IS_SYSTEM_INTEGRATED;
+
+		libwacom_destroy (wacom_device);
+	}
+
+	g_free (devpath);
+#else
+	info->capabilities = 0;
+#endif /* HAVE_WACOM */
+
+	if (!input_info_update_capabilities_from_tool_type (info)) {
+		GdkInputSource source;
+
+		/* Fallback to GdkInputSource */
+		source = gdk_device_get_source (info->device);
+
+		if (source == GDK_SOURCE_TOUCHSCREEN)
+			info->capabilities |= GSD_INPUT_IS_TOUCH | GSD_INPUT_IS_SCREEN_INTEGRATED;
+		else if (source == GDK_SOURCE_PEN)
+			info->capabilities |= GSD_INPUT_IS_PEN;
+		else if (source == GDK_SOURCE_ERASER)
+			info->capabilities |= GSD_INPUT_IS_ERASER;
+	}
+}
+
+static void
+device_settings_changed_cb (GSettings	 *settings,
+			    gchar	 *key,
+			    GsdInputInfo *input)
+{
+	if (g_str_equal (key, KEY_DISPLAY)) {
+		GnomeRROutput *rr_output;
+		GsdOutputInfo *output;
+
+		rr_output = settings_get_display (settings, input->mapper);
+
+		if (rr_output) {
+			output = g_hash_table_lookup (input->mapper->output_devices,
+						      rr_output);
+			input_info_set_output (input, output, FALSE, FALSE);
+			input_info_remap (input);
+		} else {
+			/* Guess an output for this device */
+			mapper_recalculate_input (input->mapper, input);
+		}
+	} else if (g_str_equal (key, KEY_ROTATION)) {
+		/* Remap the device so the new rotation is applied */
+		input_info_remap (input);
+	}
+}
+
+static GsdInputInfo *
+input_info_new (GdkDevice	*device,
+		GSettings	*settings,
+		GsdDeviceMapper *mapper)
+{
+	GnomeRROutput *rr_output = NULL;
+	GsdOutputInfo *output = NULL;
+	GsdInputInfo *info;
+
+	info = g_new0 (GsdInputInfo, 1);
+	info->device = device;
+	info->settings = (settings) ? g_object_ref (settings) : NULL;
+	info->mapper = mapper;
+
+	if (info->settings) {
+		info->changed_id = g_signal_connect (info->settings, "changed",
+						     G_CALLBACK (device_settings_changed_cb),
+						     info);
+
+		/* Assign output from config */
+		rr_output = settings_get_display (settings, mapper);
+	}
+
+	input_info_update_capabilities (info);
+
+	if (rr_output) {
+		output = g_hash_table_lookup (mapper->output_devices,
+					      rr_output);
+		input_info_set_output (info, output, FALSE, FALSE);
+		input_info_remap (info);
+	} else {
+		mapper_recalculate_input (mapper, info);
+	}
+
+	return info;
+}
+
+static void
+input_info_free (GsdInputInfo *info)
+{
+	input_info_set_output (info, NULL, FALSE, FALSE);
+	input_info_set_output (info, NULL, TRUE, FALSE);
+
+	if (info->settings && info->changed_id)
+		g_signal_handler_disconnect (info->settings, info->changed_id);
+
+	if (info->settings)
+		g_object_unref (info->settings);
+
+	g_free (info);
+}
+
+static GsdOutputInfo *
+output_info_new (GnomeRROutput *output)
+{
+	GsdOutputInfo *info;
+
+	info = g_new0 (GsdOutputInfo, 1);
+	info->output = output;
+
+	return info;
+}
+
+static void
+output_info_free (GsdOutputInfo *info)
+{
+	while (info->input_devices) {
+		GsdInputInfo *input = info->input_devices->data;
+
+		if (input->output == info)
+			input_info_set_output (input, NULL, FALSE, FALSE);
+		if (input->guessed_output == info)
+			input_info_set_output (input, NULL, TRUE, FALSE);
+	}
+
+	g_free (info);
+}
+
+static void
+gsd_device_mapper_finalize (GObject *object)
+{
+	GsdDeviceMapper *mapper = GSD_DEVICE_MAPPER (object);
+
+	g_hash_table_unref (mapper->input_devices);
+
+	if (mapper->output_devices)
+		g_hash_table_unref (mapper->output_devices);
+
+#if HAVE_WACOM
+	libwacom_database_destroy (mapper->wacom_db);
+#endif
+
+	G_OBJECT_CLASS (gsd_device_mapper_parent_class)->finalize (object);
+}
+
+static void
+_device_mapper_update_outputs (GsdDeviceMapper *mapper)
+{
+	GnomeRROutput **outputs;
+	GHashTable *map;
+	gint i = 0;
+
+	map = g_hash_table_new_full (NULL, NULL, NULL,
+				     (GDestroyNotify) output_info_free);
+	outputs = gnome_rr_screen_list_outputs (mapper->rr_screen);
+	g_debug ("(Re)building output map\n");
+
+	while (outputs[i]) {
+		GsdOutputInfo *info = NULL;
+
+		if (mapper->output_devices) {
+			info = g_hash_table_lookup (mapper->output_devices,
+						    outputs[i]);
+
+			if (info)
+				g_hash_table_steal (mapper->output_devices,
+						    outputs[i]);
+		}
+
+		if (!info)
+			info = output_info_new (outputs[i]);
+
+		g_hash_table_insert (map, outputs[i], info);
+		i++;
+	}
+
+	if (mapper->output_devices)
+		g_hash_table_unref (mapper->output_devices);
+
+	mapper->output_devices = map;
+	mapper_recalculate_candidates (mapper);
+}
+
+static void
+outputs_changed_cb (GnomeRRScreen   *rr_screen,
+		    GnomeRROutput   *output,
+		    GsdDeviceMapper *mapper)
+{
+	_device_mapper_update_outputs (mapper);
+}
+
+static void
+screen_changed_cb (GnomeRRScreen   *rr_screen,
+		   GsdDeviceMapper *mapper)
+{
+	_device_mapper_update_outputs (mapper);
+}
+
+static gboolean
+gsd_device_mapper_initable_init (GInitable     *initable,
+				 GCancellable  *cancellable,
+				 GError	      **error)
+{
+	GsdDeviceMapper *mapper;
+
+	mapper = GSD_DEVICE_MAPPER (initable);
+	mapper->rr_screen = gnome_rr_screen_new (mapper->screen, error);
+
+	if (!mapper->rr_screen)
+		return FALSE;
+
+	g_signal_connect (mapper->rr_screen, "changed",
+			  G_CALLBACK (screen_changed_cb), initable);
+	g_signal_connect (mapper->rr_screen, "output-connected",
+			  G_CALLBACK (outputs_changed_cb), initable);
+	g_signal_connect (mapper->rr_screen, "output-disconnected",
+			  G_CALLBACK (outputs_changed_cb), initable);
+	_device_mapper_update_outputs (GSD_DEVICE_MAPPER (initable));
+	return TRUE;
+}
+
+static void
+gsd_device_mapper_initable_iface_init (GInitableIface *iface)
+{
+	iface->init = gsd_device_mapper_initable_init;
+}
+
+static void
+gsd_device_mapper_set_property (GObject	     *object,
+				guint	      param_id,
+				const GValue *value,
+				GParamSpec   *pspec)
+{
+	GsdDeviceMapper *mapper = GSD_DEVICE_MAPPER (object);
+
+	switch (param_id) {
+	case PROP_SCREEN:
+		mapper->screen = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+	}
+}
+
+static void
+gsd_device_mapper_get_property (GObject	   *object,
+				guint	    param_id,
+				GValue	   *value,
+				GParamSpec *pspec)
+{
+	GsdDeviceMapper *mapper = GSD_DEVICE_MAPPER (object);
+
+	switch (param_id) {
+	case PROP_SCREEN:
+		g_value_set_object (value, mapper->screen);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+	}
+}
+
+static void
+gsd_device_mapper_class_init (GsdDeviceMapperClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->set_property = gsd_device_mapper_set_property;
+	object_class->get_property = gsd_device_mapper_get_property;
+	object_class->finalize = gsd_device_mapper_finalize;
+
+	g_object_class_install_property (object_class,
+					 PROP_SCREEN,
+					 g_param_spec_object ("screen",
+							      "Screen",
+							      "Screen",
+							      GDK_TYPE_SCREEN,
+							      G_PARAM_CONSTRUCT_ONLY |
+							      G_PARAM_READWRITE));
+	signals[DEVICE_CHANGED] =
+		g_signal_new ("device-changed",
+			      GSD_TYPE_DEVICE_MAPPER,
+			      G_SIGNAL_RUN_LAST, 0,
+			      NULL, NULL, NULL,
+			      G_TYPE_NONE, 1, GDK_TYPE_DEVICE);
+}
+
+static void
+gsd_device_mapper_init (GsdDeviceMapper *mapper)
+{
+	mapper->input_devices = g_hash_table_new_full (NULL, NULL, NULL,
+						       (GDestroyNotify) input_info_free);
+#if HAVE_WACOM
+	mapper->wacom_db = libwacom_database_new ();
+#endif
+}
+
+GsdDeviceMapper *
+gsd_device_mapper_get (void)
+{
+	GsdDeviceMapper *mapper;
+	GdkScreen *screen;
+
+	screen = gdk_screen_get_default ();
+	g_return_val_if_fail (screen != NULL, NULL);
+
+	mapper = g_object_get_data (G_OBJECT (screen), "gsd-device-mapper-data");
+
+	if (!mapper) {
+		GError *error = NULL;
+
+		mapper = g_initable_new (GSD_TYPE_DEVICE_MAPPER, NULL, &error,
+					 "screen", screen, NULL);
+		if (error) {
+			g_critical ("Could not create device mapper: %s", error->message);
+			g_error_free (error);
+		} else {
+			g_object_set_data_full (G_OBJECT (screen), "gsd-device-mapper-data",
+						mapper, (GDestroyNotify) g_object_unref);
+		}
+	}
+
+	return mapper;
+}
+
+void
+gsd_device_mapper_add_input (GsdDeviceMapper *mapper,
+			     GdkDevice	     *device,
+			     GSettings	     *settings)
+{
+	GsdInputInfo *info;
+
+	g_return_if_fail (mapper != NULL);
+	g_return_if_fail (GDK_IS_DEVICE (device));
+	g_return_if_fail (!settings || G_IS_SETTINGS (settings));
+
+	if (g_hash_table_contains (mapper->input_devices, device))
+		return;
+
+	info = input_info_new (device, settings, mapper);
+	g_hash_table_insert (mapper->input_devices, device, info);
+}
+
+void
+gsd_device_mapper_remove_input (GsdDeviceMapper *mapper,
+				GdkDevice	*device)
+{
+	g_return_if_fail (mapper != NULL);
+	g_return_if_fail (GDK_IS_DEVICE (device));
+
+	g_hash_table_remove (mapper->input_devices, device);
+}
+
+GnomeRROutput *
+gsd_device_mapper_get_device_output (GsdDeviceMapper *mapper,
+				     GdkDevice	     *device)
+{
+	GsdOutputInfo *output;
+	GsdInputInfo *input;
+
+	g_return_val_if_fail (mapper != NULL, NULL);
+	g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
+
+	input = g_hash_table_lookup (mapper->input_devices, device);
+	output = input_info_get_output (input);
+
+	if (!output)
+		return NULL;
+
+	return output->output;
+}
+
+gint
+gsd_device_mapper_get_device_monitor (GsdDeviceMapper *mapper,
+				      GdkDevice	      *device)
+{
+	GsdOutputInfo *output;
+	GsdInputInfo *input;
+
+	g_return_val_if_fail (GSD_IS_DEVICE_MAPPER (mapper), -1);
+	g_return_val_if_fail (GDK_IS_DEVICE (device), -1);
+
+	input = g_hash_table_lookup (mapper->input_devices, device);
+
+	if (!input)
+		return -1;
+
+	output = input_info_get_output (input);
+
+	if (!output)
+		return -1;
+
+	return monitor_for_output (output->output);
+}
+
+void
+gsd_device_mapper_set_device_output (GsdDeviceMapper *mapper,
+				     GdkDevice	     *device,
+				     GnomeRROutput   *output)
+{
+	GsdInputInfo *input_info;
+	GsdOutputInfo *output_info;
+
+	g_return_if_fail (mapper != NULL);
+	g_return_if_fail (GDK_IS_DEVICE (device));
+
+	input_info = g_hash_table_lookup (mapper->input_devices, device);
+	output_info = g_hash_table_lookup (mapper->output_devices, output);
+
+	if (!input_info || !output_info)
+		return;
+
+	input_info_set_output (input_info, output_info, FALSE, TRUE);
+	input_info_remap (input_info);
+}
+
+void
+gsd_device_mapper_set_device_monitor (GsdDeviceMapper *mapper,
+				      GdkDevice	      *device,
+				      gint	       monitor_num)
+{
+	GnomeRROutput *output;
+
+	g_return_if_fail (GSD_IS_DEVICE_MAPPER (mapper));
+	g_return_if_fail (GDK_IS_DEVICE (device));
+
+	output = monitor_to_output (mapper, monitor_num);
+	gsd_device_mapper_set_device_output (mapper, device, output);
+}
diff --git a/plugins/common/gsd-device-mapper.h b/plugins/common/gsd-device-mapper.h
new file mode 100644
index 0000000..cce4b60
--- /dev/null
+++ b/plugins/common/gsd-device-mapper.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Carlos Garnacho <carlosg@gnome.org>
+ *
+ * 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.
+ */
+
+#ifndef __GSD_DEVICE_MAPPER_H__
+#define __GSD_DEVICE_MAPPER_H__
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+#undef GNOME_DESKTOP_USE_UNSTABLE_API
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_DEVICE_MAPPER	       (gsd_device_mapper_get_type ())
+#define GSD_DEVICE_MAPPER(o)	       (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_DEVICE_MAPPER, GsdDeviceMapper))
+#define GSD_DEVICE_MAPPER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GSD_TYPE_DEVICE_MAPPER, GsdDeviceMapperClass))
+#define GSD_IS_DEVICE_MAPPER(o)	       (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_DEVICE_MAPPER))
+#define GSD_IS_DEVICE_MAPPER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_DEVICE_MAPPER))
+#define GSD_DEVICE_MAPPER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_DEVICE_MAPPER, GsdDeviceMapperClass))
+
+typedef struct _GsdDeviceMapper GsdDeviceMapper;
+typedef struct _GsdDeviceMapperClass GsdDeviceMapperClass;
+
+GType		  gsd_device_mapper_get_type	      (void) G_GNUC_CONST;
+GsdDeviceMapper * gsd_device_mapper_get		      (void);
+
+void		  gsd_device_mapper_add_input	      (GsdDeviceMapper *mapper,
+						       GdkDevice       *device,
+						       GSettings       *settings);
+void		  gsd_device_mapper_remove_input      (GsdDeviceMapper *mapper,
+						       GdkDevice       *device);
+void		  gsd_device_mapper_add_output	      (GsdDeviceMapper *mapper,
+						       GnomeRROutput   *output);
+void		  gsd_device_mapper_remove_output     (GsdDeviceMapper *mapper,
+						       GnomeRROutput   *output);
+
+GnomeRROutput	* gsd_device_mapper_get_device_output (GsdDeviceMapper *mapper,
+						       GdkDevice       *device);
+
+void		  gsd_device_mapper_set_device_output (GsdDeviceMapper *mapper,
+						       GdkDevice       *device,
+						       GnomeRROutput   *output);
+
+gint		  gsd_device_mapper_get_device_monitor (GsdDeviceMapper *mapper,
+							GdkDevice	*device);
+void		  gsd_device_mapper_set_device_monitor (GsdDeviceMapper *mapper,
+							GdkDevice	*device,
+							gint		 monitor_num);
+
+G_END_DECLS
+
+#endif /* __GSD_DEVICE_MAPPER_H__ */
diff --git a/plugins/common/gsd-input-helper.c b/plugins/common/gsd-input-helper.c
index d5d2a2a..fcf6768 100644
--- a/plugins/common/gsd-input-helper.c
+++ b/plugins/common/gsd-input-helper.c
@@ -571,3 +571,38 @@ get_disabled_devices (GdkDeviceManager *manager)
 
         return ret;
 }
+
+const char *
+xdevice_get_wacom_tool_type (int deviceid)
+{
+        unsigned long nitems, bytes_after;
+        unsigned char *data = NULL;
+        Atom prop, realtype, tool;
+        GdkDisplay *display;
+        int realformat, rc;
+        const gchar *ret = NULL;
+
+        gdk_error_trap_push ();
+
+        display = gdk_display_get_default ();
+        prop = gdk_x11_get_xatom_by_name ("Wacom Tool Type");
+
+        rc = XIGetProperty (GDK_DISPLAY_XDISPLAY (display),
+                            deviceid, prop, 0, 1, False,
+                            XA_ATOM, &realtype, &realformat, &nitems,
+                            &bytes_after, &data);
+
+        gdk_error_trap_pop_ignored ();
+
+        if (rc != Success || nitems == 0)
+                return NULL;
+
+        if (realtype == XA_ATOM) {
+                tool = *((Atom*) data);
+                ret = gdk_x11_get_xatom_name (tool);
+        }
+
+        XFree (data);
+
+        return ret;
+}
diff --git a/plugins/common/gsd-input-helper.h b/plugins/common/gsd-input-helper.h
index 0bf328a..262f3b7 100644
--- a/plugins/common/gsd-input-helper.h
+++ b/plugins/common/gsd-input-helper.h
@@ -82,6 +82,8 @@ GList *   get_disabled_devices     (GdkDeviceManager       *manager);
 char *    xdevice_get_device_node  (int                     deviceid);
 int       xdevice_get_last_tool_id (int                     deviceid);
 
+const char * xdevice_get_wacom_tool_type (int               deviceid);
+
 G_END_DECLS
 
 #endif /* __GSD_INPUT_HELPER_H */
diff --git a/plugins/wacom/gsd-wacom-manager.c b/plugins/wacom/gsd-wacom-manager.c
index 5b01047..6ec2d8a 100644
--- a/plugins/wacom/gsd-wacom-manager.c
+++ b/plugins/wacom/gsd-wacom-manager.c
@@ -48,6 +48,7 @@
 #include "gsd-wacom-manager.h"
 #include "gsd-wacom-device.h"
 #include "gsd-wacom-osd-window.h"
+#include "gsd-device-mapper.h"
 
 #define GSD_WACOM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_WACOM_MANAGER, GsdWacomManagerPrivate))
 
@@ -81,6 +82,8 @@ struct GsdWacomManagerPrivate
         GHashTable *devices; /* key = GdkDevice, value = GsdWacomDevice */
         GList *rr_screens;
 
+        GsdDeviceMapper *device_mapper;
+
         /* button capture */
         GSList *screens;
         int      opcode;
@@ -173,22 +176,6 @@ wacom_set_property (GsdWacomDevice *device,
 }
 
 static void
-set_rotation (GsdWacomDevice *device,
-	      GsdWacomRotation rotation)
-{
-        gchar rot = rotation;
-        PropertyHelper property = {
-                .name = "Wacom Rotation",
-                .nitems = 1,
-                .format = 8,
-                .type   = XA_INTEGER,
-                .data.c = &rot,
-        };
-
-        wacom_set_property (device, &property);
-}
-
-static void
 set_pressurecurve (GsdWacomDevice *device,
                    GVariant       *value)
 {
@@ -237,73 +224,6 @@ set_area (GsdWacomDevice  *device,
         g_variant_unref (value);
 }
 
-/* Returns the rotation to apply a device relative to the current rotation of the output */
-static GsdWacomRotation
-get_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 + G_N_ELEMENTS (rotations) - 1) % G_N_ELEMENTS (rotations)];
-
-	if (output_rotation == GSD_WACOM_ROTATION_CCW)
-		return rotations[(i + 1) % G_N_ELEMENTS (rotations)];
-
-	/* fallback */
-	return GSD_WACOM_ROTATION_NONE;
-}
-
-static void
-set_display (GsdWacomDevice  *device,
-             GVariant        *value)
-{
-        GsdWacomRotation  device_rotation;
-	GsdWacomRotation  output_rotation;
-	GSettings        *settings;
-        float matrix[NUM_ELEMS_MATRIX];
-        PropertyHelper property = {
-                .name   = "Coordinate Transformation Matrix",
-                .nitems = NUM_ELEMS_MATRIX,
-                .format = 32,
-                .type   = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "FLOAT", True),
-        };
-
-        gsd_wacom_device_get_display_matrix (device, matrix);
-
-        property.data.i = (gint*)(&matrix);
-        g_debug ("Applying matrix to device...");
-        wacom_set_property (device, &property);
-
-        /* Compute rotation to apply relative to the output */
-	settings = gsd_wacom_device_get_settings (device);
-	device_rotation = g_settings_get_enum (settings, KEY_ROTATION);
-	output_rotation = gsd_wacom_device_get_display_rotation (device);
-
-        /* Apply display rotation to device */
-        set_rotation (device, get_relative_rotation (device_rotation, output_rotation));
-
-        g_variant_unref (value);
-}
-
 static void
 set_absolute (GsdWacomDevice  *device,
               gint             is_absolute)
@@ -377,7 +297,8 @@ set_keep_aspect (GsdWacomDevice *device,
 {
         GVariant *values[4], *variant;
 	guint i;
-
+	GdkDevice *gdk_device;
+	GsdDeviceMapper *mapper;
 	gint *area;
 	gint monitor = GSD_WACOM_SET_ALL_MONITORS;
 	GsdWacomRotation rotation;
@@ -412,7 +333,9 @@ set_keep_aspect (GsdWacomDevice *device,
 	}
 
 	/* Get corresponding monitor size */
-	monitor = gsd_wacom_device_get_display_monitor (device);
+	mapper = gsd_device_mapper_get ();
+	g_object_get (device, "gdk-device", &gdk_device, NULL);
+	monitor = gsd_device_mapper_get_device_monitor (mapper, gdk_device);
 
 	/* Adjust area to match the monitor aspect ratio */
 	g_debug ("Initial device area: (%d,%d) (%d,%d)", area[0], area[1], area[2], area[3]);
@@ -715,7 +638,6 @@ set_wacom_settings (GsdWacomManager *manager,
 		 gsd_wacom_device_type_to_string (gsd_wacom_device_get_device_type (device)));
 
 	settings = gsd_wacom_device_get_settings (device);
-        set_rotation (device, g_settings_get_enum (settings, KEY_ROTATION));
         set_touch (device, g_settings_get_boolean (settings, KEY_TOUCH));
 
         type = gsd_wacom_device_get_device_type (device);
@@ -760,7 +682,6 @@ set_wacom_settings (GsdWacomManager *manager,
 			set_keep_aspect (device, g_settings_get_boolean (settings, KEY_KEEP_ASPECT));
 		set_area (device, g_settings_get_value (settings, KEY_AREA));
 	}
-	set_display (device, g_settings_get_value (settings, KEY_DISPLAY));
 
         /* only pen and eraser have pressure threshold and curve settings */
         if (type == WACOM_TYPE_STYLUS ||
@@ -779,8 +700,7 @@ wacom_settings_changed (GSettings      *settings,
 	type = gsd_wacom_device_get_device_type (device);
 
 	if (g_str_equal (key, KEY_ROTATION)) {
-	        if (type != WACOM_TYPE_PAD)
-		        set_rotation (device, g_settings_get_enum (settings, key));
+                /* Real device rotation is handled in GsdDeviceMapper */
 	} else if (g_str_equal (key, KEY_TOUCH)) {
 		set_touch (device, g_settings_get_boolean (settings, key));
 	} else if (g_str_equal (key, KEY_TPCBUTTON)) {
@@ -796,9 +716,7 @@ wacom_settings_changed (GSettings      *settings,
 		    type != WACOM_TYPE_TOUCH)
 			set_area (device, g_settings_get_value (settings, key));
 	} else if (g_str_equal (key, KEY_DISPLAY)) {
-		if (type != WACOM_TYPE_CURSOR &&
-		    type != WACOM_TYPE_PAD)
-			set_display (device, g_settings_get_value (settings, key));
+                /* Unhandled, GsdDeviceMapper handles this */
 	} else if (g_str_equal (key, KEY_KEEP_ASPECT)) {
 		if (type != WACOM_TYPE_CURSOR &&
 		    type != WACOM_TYPE_PAD &&
@@ -973,6 +891,16 @@ device_added_cb (GdkDeviceManager *device_manager,
 	g_signal_connect (G_OBJECT (settings), "changed",
 			  G_CALLBACK (wacom_settings_changed), device);
 
+	/* Map devices, the xrandr module handles touchscreens in general, so bypass these here */
+	if (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_PAD ||
+	    gsd_wacom_device_get_device_type (device) == WACOM_TYPE_STYLUS ||
+	    gsd_wacom_device_get_device_type (device) == WACOM_TYPE_ERASER ||
+	    (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_TOUCH &&
+             !gsd_wacom_device_is_screen_tablet (device))) {
+		gsd_device_mapper_add_input (manager->priv->device_mapper,
+					     gdk_device, settings);
+	}
+
 	if (gsd_wacom_device_get_device_type (device) == WACOM_TYPE_STYLUS ||
 	    gsd_wacom_device_get_device_type (device) == WACOM_TYPE_ERASER) {
 		GList *styli, *l;
@@ -1003,6 +931,9 @@ device_removed_cb (GdkDeviceManager *device_manager,
 		 gdk_device_get_name (gdk_device));
 	g_hash_table_remove (manager->priv->devices, gdk_device);
 
+        gsd_device_mapper_remove_input (manager->priv->device_mapper,
+                                        gdk_device);
+
 	/* Enable this chunk of code if you want to valgrind
 	 * test-wacom. It will exit when there are no Wacom devices left */
 #if 0
@@ -1171,9 +1102,11 @@ generate_key (GsdWacomTabletButton *wbutton,
 }
 
 static void
-switch_monitor (GsdWacomDevice *device)
+switch_monitor (GsdWacomManager *manager,
+                GsdWacomDevice *device)
 {
 	gint current_monitor, n_monitors;
+        GdkDevice *gdk_device;
 
 	/* We dont; do that for screen tablets, sorry... */
 	if (gsd_wacom_device_is_screen_tablet (device))
@@ -1185,15 +1118,19 @@ switch_monitor (GsdWacomDevice *device)
 	if (n_monitors < 2)
 		return;
 
-	current_monitor = gsd_wacom_device_get_display_monitor (device);
+        g_object_get (device, "gdk-device", &gdk_device, NULL);
+        current_monitor =
+                gsd_device_mapper_get_device_monitor (manager->priv->device_mapper,
+                                                      gdk_device);
 
 	/* Select next monitor */
 	current_monitor++;
 
 	if (current_monitor >= n_monitors)
-		current_monitor = GSD_WACOM_SET_ALL_MONITORS;
+		current_monitor = 0;
 
-	gsd_wacom_device_set_display (device, current_monitor);
+        gsd_device_mapper_set_device_monitor (manager->priv->device_mapper,
+                                              gdk_device, current_monitor);
 }
 
 static const char*
@@ -1303,7 +1240,7 @@ filter_button_events (XEvent          *xevent,
 	/* Switch monitor */
 	if (g_settings_get_enum (wbutton->settings, KEY_ACTION_TYPE) == GSD_WACOM_ACTION_TYPE_SWITCH_MONITOR) {
 		if (xiev->evtype == XI_ButtonRelease)
-			switch_monitor (device);
+			switch_monitor (manager, device);
 		return GDK_FILTER_REMOVE;
 	}
 
@@ -1343,6 +1280,8 @@ gsd_wacom_manager_idle_cb (GsdWacomManager *manager)
 
         gnome_settings_profile_start (NULL);
 
+        manager->priv->device_mapper = gsd_device_mapper_get ();
+
         manager->priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
 
         set_devicepresence_handler (manager);
@@ -1402,7 +1341,6 @@ on_screen_changed_cb (GnomeRRScreen *rr_screen,
 				set_keep_aspect (device, g_settings_get_boolean (settings, KEY_KEEP_ASPECT));
 			set_area (device, g_settings_get_value (settings, KEY_AREA));
 		}
-		set_display (device, g_settings_get_value (settings, KEY_DISPLAY));
 	}
 	g_list_free (devices);
 }
diff --git a/plugins/xrandr/gsd-xrandr-manager.c b/plugins/xrandr/gsd-xrandr-manager.c
index 8e6aa83..bc02f67 100644
--- a/plugins/xrandr/gsd-xrandr-manager.c
+++ b/plugins/xrandr/gsd-xrandr-manager.c
@@ -47,16 +47,13 @@
 #include <libgnome-desktop/gnome-rr.h>
 #include <libgnome-desktop/gnome-pnp-ids.h>
 
-#ifdef HAVE_WACOM
-#include <libwacom/libwacom.h>
-#endif /* HAVE_WACOM */
-
 #include "gsd-enums.h"
 #include "gsd-input-helper.h"
 #include "gnome-settings-plugin.h"
 #include "gnome-settings-profile.h"
 #include "gnome-settings-session.h"
 #include "gsd-xrandr-manager.h"
+#include "gsd-device-mapper.h"
 
 #define GSD_XRANDR_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_XRANDR_MANAGER, GsdXrandrManagerPrivate))
 
@@ -117,16 +114,17 @@ struct GsdXrandrManagerPrivate {
         GDBusConnection *connection;
         GCancellable    *bus_cancellable;
 
+        GsdDeviceMapper  *device_mapper;
+        GdkDeviceManager *device_manager;
+        guint             device_added_id;
+        guint             device_removed_id;
+
         /* fn-F7 status */
         int             current_fn_f7_config;             /* -1 if no configs */
         GnomeRRConfig **fn_f7_configs;  /* NULL terminated, NULL if there are no configs */
 
         /* Last time at which we got a "screen got reconfigured" event; see on_randr_event() */
         guint32 last_config_timestamp;
-
-#ifdef HAVE_WACOM
-        WacomDeviceDatabase *wacom_db;
-#endif /* HAVE_WACOM */
 };
 
 static const GnomeRRRotation possible_rotations[] = {
@@ -1491,137 +1489,6 @@ get_next_rotation (GnomeRRRotation allowed_rotations, GnomeRRRotation current_ro
         }
 }
 
-struct {
-        GnomeRRRotation rotation;
-        /* evdev */
-        gboolean x_axis_inversion;
-        gboolean y_axis_inversion;
-        gboolean axes_swap;
-} evdev_rotations[] = {
-        { GNOME_RR_ROTATION_0, 0, 0, 0 },
-        { GNOME_RR_ROTATION_90, 1, 0, 1 },
-        { GNOME_RR_ROTATION_180, 1, 1, 0 },
-        { GNOME_RR_ROTATION_270, 0, 1, 1 }
-};
-
-static guint
-get_rotation_index (GnomeRRRotation rotation)
-{
-        guint i;
-
-        for (i = 0; i < G_N_ELEMENTS (evdev_rotations); i++) {
-                if (evdev_rotations[i].rotation == rotation)
-                        return i;
-        }
-        g_assert_not_reached ();
-}
-
-static gboolean
-is_wacom_tablet_device (GsdXrandrManager *mgr,
-                        XDeviceInfo      *device_info)
-{
-#ifdef HAVE_WACOM
-        GsdXrandrManagerPrivate *priv = mgr->priv;
-        gchar       *device_node;
-        WacomDevice *wacom_device;
-        gboolean     is_tablet = FALSE;
-
-        if (priv->wacom_db == NULL)
-                priv->wacom_db = libwacom_database_new ();
-
-        device_node = xdevice_get_device_node (device_info->id);
-        if (device_node == NULL)
-                return FALSE;
-
-        wacom_device = libwacom_new_from_path (priv->wacom_db, device_node, FALSE, NULL);
-        g_free (device_node);
-        if (wacom_device == NULL) {
-                g_free (device_node);
-                return FALSE;
-        }
-        is_tablet = libwacom_has_touch (wacom_device) &&
-                    libwacom_is_builtin (wacom_device);
-
-        libwacom_destroy (wacom_device);
-
-        return is_tablet;
-#else  /* HAVE_WACOM */
-        return FALSE;
-#endif /* HAVE_WACOM */
-}
-
-static void
-rotate_touchscreens (GsdXrandrManager *mgr,
-                     GnomeRRRotation   rotation)
-{
-        XDeviceInfo *device_info;
-        gint n_devices;
-        guint i, rot_idx;
-
-        if (!supports_xinput_devices ())
-                return;
-
-        g_debug ("Rotating touchscreen devices");
-
-        device_info = XListInputDevices (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &n_devices);
-        if (device_info == NULL)
-                return;
-
-        rot_idx = get_rotation_index (rotation);
-
-        for (i = 0; i < n_devices; i++) {
-                if (is_wacom_tablet_device  (mgr, &device_info[i])) {
-                        g_debug ("Not rotating tablet device '%s'", device_info[i].name);
-                        continue;
-                }
-
-                if (device_info_is_touchscreen (&device_info[i]) ||
-                            device_info_is_tablet (&device_info[i])) {
-                        XDevice *device;
-                        char c = evdev_rotations[rot_idx].axes_swap;
-                        PropertyHelper axes_swap = {
-                                .name = "Evdev Axes Swap",
-                                .nitems = 1,
-                                .format = 8,
-                                .type   = XA_INTEGER,
-                                .data.c = &c,
-                        };
-
-                        g_debug ("About to rotate '%s'", device_info[i].name);
-
-                        gdk_error_trap_push ();
-                        device = XOpenDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), device_info[i].id);
-                        if (gdk_error_trap_pop () || (device == NULL))
-                                continue;
-
-                        if (device_set_property (device, device_info[i].name, &axes_swap) != FALSE) {
-                                char axis[] = {
-                                        evdev_rotations[rot_idx].x_axis_inversion,
-                                        evdev_rotations[rot_idx].y_axis_inversion
-                                };
-                                PropertyHelper axis_invert = {
-                                        .name = "Evdev Axis Inversion",
-                                        .nitems = 2,
-                                        .format = 8,
-                                        .type   = XA_INTEGER,
-                                        .data.c = axis,
-                                };
-
-                                device_set_property (device, device_info[i].name, &axis_invert);
-
-                                g_debug ("Rotated '%s' to configuration '%d, %d, %d'",
-                                         device_info[i].name,
-                                         evdev_rotations[rot_idx].x_axis_inversion,
-                                         evdev_rotations[rot_idx].y_axis_inversion,
-                                         evdev_rotations[rot_idx].axes_swap);
-                        }
-
-                        XCloseDevice (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), device);
-                }
-        }
-        XFreeDeviceList (device_info);
-}
-
 /* We use this when the XF86RotateWindows key is pressed, or the
  * orientation of a tablet changes. The key is present
  * on some tablet PCs; they use it so that the user can rotate the tablet
@@ -1639,7 +1506,7 @@ handle_rotate_windows (GsdXrandrManager *mgr,
         int num_allowed_rotations;
         GnomeRRRotation allowed_rotations;
         GnomeRRRotation next_rotation;
-        gboolean success, show_error;
+        gboolean show_error;
 
         g_debug ("Handling XF86RotateWindows with rotation %d", rotation);
 
@@ -1673,9 +1540,7 @@ handle_rotate_windows (GsdXrandrManager *mgr,
 
         gnome_rr_output_info_set_rotation (rotatable_output_info, next_rotation);
 
-        success = apply_configuration (mgr, current, timestamp, show_error, TRUE);
-        if (success)
-                rotate_touchscreens (mgr, next_rotation);
+        apply_configuration (mgr, current, timestamp, show_error, TRUE);
 
 out:
         g_object_unref (current);
@@ -2040,6 +1905,56 @@ power_client_changed_cb (UpClient *client, gpointer data)
         }
 }
 
+static void
+manager_device_added (GsdXrandrManager *manager,
+                      GdkDevice        *device)
+{
+        if (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER ||
+            gdk_device_get_source (device) != GDK_SOURCE_TOUCHSCREEN)
+                return;
+
+        gsd_device_mapper_add_input (manager->priv->device_mapper, device, NULL);
+}
+
+static void
+manager_device_removed (GsdXrandrManager *manager,
+                        GdkDevice        *device)
+{
+        if (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER ||
+            gdk_device_get_source (device) != GDK_SOURCE_TOUCHSCREEN)
+                return;
+
+        gsd_device_mapper_remove_input (manager->priv->device_mapper, device);
+}
+
+static void
+manager_init_devices (GsdXrandrManager *manager)
+{
+        GdkDisplay *display;
+        GList *devices, *d;
+        GdkScreen *screen;
+
+        screen = gdk_screen_get_default ();
+        display = gdk_screen_get_display (screen);
+
+        manager->priv->device_mapper = gsd_device_mapper_get ();
+        manager->priv->device_manager = gdk_display_get_device_manager (display);
+        manager->priv->device_added_id =
+                g_signal_connect_swapped (manager->priv->device_manager, "device-added",
+                                          G_CALLBACK (manager_device_added), manager);
+        manager->priv->device_removed_id =
+                g_signal_connect_swapped (manager->priv->device_manager, "device-removed",
+                                  G_CALLBACK (manager_device_removed), manager);
+
+        devices = gdk_device_manager_list_devices (manager->priv->device_manager,
+                                                   GDK_DEVICE_TYPE_SLAVE);
+
+        for (d = devices; d; d = d->next)
+                manager_device_added (manager, d->data);
+
+        g_list_free (devices);
+}
+
 gboolean
 gsd_xrandr_manager_start (GsdXrandrManager *manager,
                           GError          **error)
@@ -2081,6 +1996,8 @@ gsd_xrandr_manager_start (GsdXrandrManager *manager,
         log_msg ("State of screen after initial configuration:\n");
         log_screen (manager->priv->rw_screen);
 
+        manager_init_devices (manager);
+
         log_close ();
 
         gnome_settings_profile_end (NULL);
@@ -2127,12 +2044,12 @@ gsd_xrandr_manager_stop (GsdXrandrManager *manager)
                 manager->priv->connection = NULL;
         }
 
-#ifdef HAVE_WACOM
-        if (manager->priv->wacom_db != NULL) {
-                libwacom_database_destroy (manager->priv->wacom_db);
-                manager->priv->wacom_db = NULL;
+        if (manager->priv->device_manager != NULL) {
+                g_signal_handler_disconnect (manager->priv->device_manager,
+                                             manager->priv->device_added_id);
+                g_signal_handler_disconnect (manager->priv->device_manager,
+                                             manager->priv->device_removed_id);
         }
-#endif /* HAVE_WACOM */
 
         log_open ();
         log_msg ("STOPPING XRANDR PLUGIN\n------------------------------------------------------------\n");