[gnome-settings-daemon] color: Move the device registration functionality from gcm-session to the color plugin



commit 0d278e7d66ee6f57d496d6de46d395f11a4d7cd4
Author: Richard Hughes <richard hughsie com>
Date:   Wed Jun 1 10:15:31 2011 +0100

    color: Move the device registration functionality from gcm-session to the color plugin
    
    We have to monitor X devices in the session as colord as a system daemon and is
    unable to access the per-session X screens and outputs without evil hacks.
    
    When a display output is added or changed we:
    
    * Register the display device with colord and add the required metadata entries
    * Create an auto-EDID profile based from the chromacity data in the EDID
      (if it does not already exist)
    * Check to see if it's the default device, and if so, set the _ICC_PROFILE atom
    * Make sure the default profile VCGT data for the device is loaded into X.
    
    When a display is removed we:
    
    * Unregister the device from colord.

 acinclude.m4                                    |   12 +
 configure.ac                                    |   18 +-
 plugins/color/Makefile.am                       |   67 +-
 plugins/color/gcm-dmi.c                         |  170 +++
 plugins/color/gcm-dmi.h                         |   60 +
 plugins/color/gcm-edid.c                        |  457 ++++++++
 plugins/color/gcm-edid.h                        |   83 ++
 plugins/color/gcm-self-test.c                   |  145 +++
 plugins/color/gcm-tables.c                      |  171 +++
 plugins/color/gcm-tables.h                      |   67 ++
 plugins/color/gsd-color-manager.c               | 1303 ++++++++++++++++++++++-
 plugins/color/gsd-color-manager.h               |    7 +
 plugins/color/test-data/LG-L225W-External.bin   |  Bin 0 -> 128 bytes
 plugins/color/test-data/Lenovo-T61-Internal.bin |  Bin 0 -> 128 bytes
 14 files changed, 2525 insertions(+), 35 deletions(-)
---
diff --git a/acinclude.m4 b/acinclude.m4
index f2f8ec3..ec8da1f 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -134,3 +134,15 @@ AC_DEFUN([AS_AC_EXPAND],
   prefix=$prefix_save
   exec_prefix=$exec_prefix_save
 ])
+
+AC_DEFUN([AC_PATH_PNPIDS], [
+        AC_ARG_WITH(pnpids,
+                   AS_HELP_STRING([--with-pnpids=PATH],[Path to the pnp.ids file @<:@auto@:>@]),
+                   [ac_with_pnpids=$withval],
+                   [ac_with_pnpids="/usr/share/hwdata/pnp.ids"])
+        AC_CHECK_FILE($ac_with_pnpids,has_pnpids=yes)
+        if test "x$has_pnpids" != "xyes"; then
+		AC_MSG_ERROR([*** Unable to find pnp.ids, use --with-pnpids to specify the path.])
+        fi
+        AC_DEFINE_UNQUOTED(PNPIDS_FILE, ["$ac_with_pnpids"], [Define the pnp.ids file path])
+])
diff --git a/configure.ac b/configure.ac
index e6c853f..ca2d2a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -21,6 +21,7 @@ AC_STDC_HEADERS
 AC_PROG_CXX
 AM_PROG_CC_C_O
 AC_PROG_LIBTOOL
+AC_PATH_PNPIDS
 
 AC_HEADER_STDC
 
@@ -100,7 +101,7 @@ PKG_CHECK_MODULES(GNOME_DESKTOP, gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VE
 dnl ---------------------------------------------------------------------------
 dnl - Check for colord
 dnl ---------------------------------------------------------------------------
-PKG_CHECK_MODULES(COLORD, colord >= 0.1.8)
+PKG_CHECK_MODULES(COLORD, colord >= 0.1.9)
 
 dnl ---------------------------------------------------------------------------
 dnl - Check for libcanberra
@@ -114,11 +115,18 @@ AM_CONDITIONAL(HAVE_LIBCANBERRA, test "x$have_libcanberra" = "xtrue")
 dnl ---------------------------------------------------------------------------
 dnl - Check for LCMS2
 dnl ---------------------------------------------------------------------------
-PKG_CHECK_MODULES(LCMS, lcms2, HAVE_LCMS="yes", HAVE_LCMS="no")
-if test "x$HAVE_LCMS" = "xyes"; then
+PKG_CHECK_MODULES(LCMS, lcms2 >= 2.2, have_new_lcms=yes, have_new_lcms=no)
+if test x$have_new_lcms = xyes; then
+        have_lcms="yes"
+        AC_DEFINE(HAVE_NEW_LCMS,1,[Got new lcms2])
+else
+        PKG_CHECK_MODULES(LCMS, lcms2, have_lcms="yes", have_lcms="no")
+fi
+if test "x$have_lcms" = "xyes"; then
         AC_DEFINE(HAVE_LCMS, 1, [define if LCMS is available])
 fi
 
+
 dnl ---------------------------------------------------------------------------
 dnl - Check for libnotify
 dnl ---------------------------------------------------------------------------
@@ -618,7 +626,8 @@ echo "
 
         dbus-1 system.d dir:      ${DBUS_SYS_DIR}
         PolicyKit support:        ${HAVE_POLKIT}
-        LCMS support:             ${HAVE_LCMS}
+        LCMS support:             ${have_lcms}
+        LCMS DICT support:        ${have_new_lcms}
 
         Libnotify support:        ${have_libnotify}
         Libcanberra support:      ${have_libcanberra}
@@ -631,4 +640,5 @@ ${NSS_DATABASE:+\
         System nssdb:             ${NSS_DATABASE}
 }\
         Profiling support:        ${enable_profiling}
+        pnp.ids filename:         ${ac_with_pnpids}
 "
diff --git a/plugins/color/Makefile.am b/plugins/color/Makefile.am
index 5d6174e..187ee7b 100644
--- a/plugins/color/Makefile.am
+++ b/plugins/color/Makefile.am
@@ -3,12 +3,18 @@ plugin_name = color
 plugin_LTLIBRARIES = \
 	libcolor.la
 
-libcolor_la_SOURCES = 		\
-	gcm-profile-store.c	\
-	gcm-profile-store.h	\
-	gsd-color-manager.c	\
-	gsd-color-manager.h	\
-	gsd-color-plugin.c	\
+libcolor_la_SOURCES = 			\
+	gcm-profile-store.c		\
+	gcm-profile-store.h		\
+	gcm-dmi.c			\
+	gcm-dmi.h			\
+	gcm-edid.c			\
+	gcm-edid.h			\
+	gcm-tables.c			\
+	gcm-tables.h			\
+	gsd-color-manager.c		\
+	gsd-color-manager.h		\
+	gsd-color-plugin.c		\
 	gsd-color-plugin.h
 
 libcolor_la_CPPFLAGS = \
@@ -22,30 +28,61 @@ libcolor_la_CFLAGS = \
 	$(COLORD_CFLAGS)		\
 	$(LIBCANBERRA_CFLAGS)		\
 	$(LCMS_CFLAGS)			\
+	$(GNOME_DESKTOP_CFLAGS)		\
 	$(SETTINGS_PLUGIN_CFLAGS)	\
 	$(AM_CFLAGS)
 
-libcolor_la_LDFLAGS = 		\
+libcolor_la_LDFLAGS = 			\
 	$(GSD_PLUGIN_LDFLAGS)
 
-libcolor_la_LIBADD  = 		\
-	$(COLORD_LIBS)		\
-	$(LIBCANBERRA_LIBS)	\
-	$(LCMS_LIBS)		\
+libcolor_la_LIBADD  = 			\
+	$(COLORD_LIBS)			\
+	$(LIBCANBERRA_LIBS)		\
+	$(GNOME_DESKTOP_LIBS)		\
+	$(LCMS_LIBS)			\
 	$(SETTINGS_PLUGIN_LIBS)
 
-plugin_in_files = 		\
+check_PROGRAMS =			\
+	gcm-self-test
+
+gcm_self_test_CPPFLAGS = \
+	-DTESTDATADIR=\""$(top_srcdir)/plugins/color/test-data"\" \
+	$(AM_CPPFLAGS)
+
+gcm_self_test_CFLAGS =			\
+	$(SETTINGS_PLUGIN_CFLAGS)	\
+	$(COLORD_CFLAGS)		\
+	$(PLUGIN_CFLAGS)		\
+	$(AM_CFLAGS)
+
+gcm_self_test_SOURCES =			\
+	gcm-tables.c			\
+	gcm-tables.h			\
+	gcm-dmi.c			\
+	gcm-dmi.h			\
+	gcm-edid.c			\
+	gcm-edid.h			\
+	gcm-self-test.c
+
+gcm_self_test_LDADD =			\
+	$(COLORD_LIBS)			\
+	$(LCMS_LIBS)			\
+	$(SETTINGS_PLUGIN_LIBS)
+
+TESTS = gcm-self-test
+
+plugin_in_files = 			\
 	color.gnome-settings-plugin.in
 
 plugin_DATA = $(plugin_in_files:.gnome-settings-plugin.in=.gnome-settings-plugin)
 
-EXTRA_DIST = 			\
+EXTRA_DIST = 				\
 	$(plugin_in_files)
 
-CLEANFILES = 			\
+CLEANFILES = 				\
 	$(plugin_DATA)
 
-DISTCLEANFILES =		\
+DISTCLEANFILES =			\
 	$(plugin_DATA)
 
 @GSD_INTLTOOL_PLUGIN_RULE@
diff --git a/plugins/color/gcm-dmi.c b/plugins/color/gcm-dmi.c
new file mode 100644
index 0000000..f4be19a
--- /dev/null
+++ b/plugins/color/gcm-dmi.c
@@ -0,0 +1,170 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2011 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <math.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+
+#include "gcm-dmi.h"
+#include "gcm-tables.h"
+
+static void     gcm_dmi_finalize        (GObject     *object);
+
+#define GCM_DMI_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_DMI, GcmDmiPrivate))
+
+struct _GcmDmiPrivate
+{
+        gchar                           *name;
+        gchar                           *version;
+        gchar                           *vendor;
+};
+
+static gpointer gcm_dmi_object = NULL;
+
+G_DEFINE_TYPE (GcmDmi, gcm_dmi, G_TYPE_OBJECT)
+
+static gchar *
+gcm_dmi_get_from_filename (const gchar *filename)
+{
+        gboolean ret;
+        GError *error = NULL;
+        gchar *data = NULL;
+
+        /* get the contents */
+        ret = g_file_get_contents (filename, &data, NULL, &error);
+        if (!ret) {
+                g_warning ("failed to get contents of %s: %s", filename, error->message);
+                g_error_free (error);
+        }
+
+        /* process the random chars and trailing spaces */
+        if (data != NULL) {
+                g_strdelimit (data, "\t_", ' ');
+                g_strdelimit (data, "\n\r", '\0');
+                g_strchomp (data);
+        }
+
+        /* don't return an empty string */
+        if (data != NULL && data[0] == '\0') {
+                g_free (data);
+                data = NULL;
+        }
+
+        return data;
+}
+
+static gchar *
+gcm_dmi_get_from_filenames (const gchar * const * filenames)
+{
+        guint i;
+        gchar *tmp = NULL;
+
+        /* try each one in preference order */
+        for (i = 0; filenames[i] != NULL; i++) {
+                tmp = gcm_dmi_get_from_filename (filenames[i]);
+                if (tmp != NULL)
+                        break;
+        }
+        return tmp;
+}
+
+const gchar *
+gcm_dmi_get_name (GcmDmi *dmi)
+{
+        g_return_val_if_fail (GCM_IS_DMI (dmi), NULL);
+        return dmi->priv->name;
+}
+
+const gchar *
+gcm_dmi_get_version (GcmDmi *dmi)
+{
+        g_return_val_if_fail (GCM_IS_DMI (dmi), NULL);
+        return dmi->priv->version;
+}
+
+const gchar *
+gcm_dmi_get_vendor (GcmDmi *dmi)
+{
+        g_return_val_if_fail (GCM_IS_DMI (dmi), NULL);
+        return dmi->priv->vendor;
+}
+
+static void
+gcm_dmi_class_init (GcmDmiClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        object_class->finalize = gcm_dmi_finalize;
+        g_type_class_add_private (klass, sizeof (GcmDmiPrivate));
+}
+
+static void
+gcm_dmi_init (GcmDmi *dmi)
+{
+        const gchar *sysfs_name[] = {
+                "/sys/class/dmi/id/product_name",
+                "/sys/class/dmi/id/board_name",
+                NULL};
+        const gchar *sysfs_version[] = {
+                "/sys/class/dmi/id/product_version",
+                "/sys/class/dmi/id/chassis_version",
+                "/sys/class/dmi/id/board_version",
+                NULL};
+        const gchar *sysfs_vendor[] = {
+                "/sys/class/dmi/id/sys_vendor",
+                "/sys/class/dmi/id/chassis_vendor",
+                "/sys/class/dmi/id/board_vendor",
+                NULL};
+
+        dmi->priv = GCM_DMI_GET_PRIVATE (dmi);
+
+        /* get all the possible data now */
+        dmi->priv->name = gcm_dmi_get_from_filenames (sysfs_name);
+        dmi->priv->version = gcm_dmi_get_from_filenames (sysfs_version);
+        dmi->priv->vendor = gcm_dmi_get_from_filenames (sysfs_vendor);
+}
+
+static void
+gcm_dmi_finalize (GObject *object)
+{
+        GcmDmi *dmi = GCM_DMI (object);
+
+        g_free (dmi->priv->name);
+        g_free (dmi->priv->version);
+        g_free (dmi->priv->vendor);
+
+        G_OBJECT_CLASS (gcm_dmi_parent_class)->finalize (object);
+}
+
+GcmDmi *
+gcm_dmi_new (void)
+{
+        if (gcm_dmi_object != NULL) {
+                g_object_ref (gcm_dmi_object);
+        } else {
+                gcm_dmi_object = g_object_new (GCM_TYPE_DMI, NULL);
+                g_object_add_weak_pointer (gcm_dmi_object, &gcm_dmi_object);
+        }
+        return GCM_DMI (gcm_dmi_object);
+}
diff --git a/plugins/color/gcm-dmi.h b/plugins/color/gcm-dmi.h
new file mode 100644
index 0000000..5aeb5b7
--- /dev/null
+++ b/plugins/color/gcm-dmi.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GCM_DMI_H
+#define __GCM_DMI_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_DMI            (gcm_dmi_get_type ())
+#define GCM_DMI(o)              (G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_DMI, GcmDmi))
+#define GCM_DMI_CLASS(k)        (G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_DMI, GcmDmiClass))
+#define GCM_IS_DMI(o)           (G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_DMI))
+#define GCM_IS_DMI_CLASS(k)     (G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_DMI))
+#define GCM_DMI_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_DMI, GcmDmiClass))
+
+typedef struct _GcmDmiPrivate   GcmDmiPrivate;
+typedef struct _GcmDmi          GcmDmi;
+typedef struct _GcmDmiClass     GcmDmiClass;
+
+struct _GcmDmi
+{
+         GObject                 parent;
+         GcmDmiPrivate          *priv;
+};
+
+struct _GcmDmiClass
+{
+        GObjectClass    parent_class;
+};
+
+GType            gcm_dmi_get_type                       (void);
+GcmDmi          *gcm_dmi_new                            (void);
+const gchar     *gcm_dmi_get_name                       (GcmDmi         *dmi);
+const gchar     *gcm_dmi_get_version                    (GcmDmi         *dmi);
+const gchar     *gcm_dmi_get_vendor                     (GcmDmi         *dmi);
+
+G_END_DECLS
+
+#endif /* __GCM_DMI_H */
+
diff --git a/plugins/color/gcm-edid.c b/plugins/color/gcm-edid.c
new file mode 100644
index 0000000..55cb0cc
--- /dev/null
+++ b/plugins/color/gcm-edid.c
@@ -0,0 +1,457 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Soren Sandmann <sandmann redhat com>
+ * Copyright (C) 2009-2011 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <math.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+
+#include "gcm-edid.h"
+#include "gcm-tables.h"
+
+static void     gcm_edid_finalize       (GObject     *object);
+
+#define GCM_EDID_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_EDID, GcmEdidPrivate))
+
+struct _GcmEdidPrivate
+{
+        gchar                           *monitor_name;
+        gchar                           *vendor_name;
+        gchar                           *serial_number;
+        gchar                           *eisa_id;
+        gchar                           *checksum;
+        gchar                           *pnp_id;
+        guint                            width;
+        guint                            height;
+        gfloat                           gamma;
+        CdColorYxy                      *red;
+        CdColorYxy                      *green;
+        CdColorYxy                      *blue;
+        CdColorYxy                      *white;
+        GcmTables                       *tables;
+};
+
+G_DEFINE_TYPE (GcmEdid, gcm_edid, G_TYPE_OBJECT)
+
+#define GCM_EDID_OFFSET_PNPID                           0x08
+#define GCM_EDID_OFFSET_SERIAL                          0x0c
+#define GCM_EDID_OFFSET_SIZE                            0x15
+#define GCM_EDID_OFFSET_GAMMA                           0x17
+#define GCM_EDID_OFFSET_DATA_BLOCKS                     0x36
+#define GCM_EDID_OFFSET_LAST_BLOCK                      0x6c
+#define GCM_EDID_OFFSET_EXTENSION_BLOCK_COUNT           0x7e
+
+#define GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME             0xfc
+#define GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER    0xff
+#define GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA            0xf9
+#define GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING         0xfe
+#define GCM_DESCRIPTOR_COLOR_POINT                      0xfb
+
+GQuark
+gcm_edid_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("gcm_edid_error");
+	return quark;
+}
+
+const gchar *
+gcm_edid_get_monitor_name (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->monitor_name;
+}
+
+const gchar *
+gcm_edid_get_vendor_name (GcmEdid *edid)
+{
+        GcmEdidPrivate *priv = edid->priv;
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+
+        if (priv->vendor_name == NULL)
+                priv->vendor_name = gcm_tables_get_pnp_id (priv->tables, priv->pnp_id, NULL);
+        return priv->vendor_name;
+}
+
+const gchar *
+gcm_edid_get_serial_number (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->serial_number;
+}
+
+const gchar *
+gcm_edid_get_eisa_id (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->eisa_id;
+}
+
+const gchar *
+gcm_edid_get_checksum (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->checksum;
+}
+
+const gchar *
+gcm_edid_get_pnp_id (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->pnp_id;
+}
+
+guint
+gcm_edid_get_width (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), 0);
+        return edid->priv->width;
+}
+
+guint
+gcm_edid_get_height (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), 0);
+        return edid->priv->height;
+}
+
+gfloat
+gcm_edid_get_gamma (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), 0.0f);
+        return edid->priv->gamma;
+}
+
+const CdColorYxy *
+gcm_edid_get_red (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->red;
+}
+
+const CdColorYxy *
+gcm_edid_get_green (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->green;
+}
+
+const CdColorYxy *
+gcm_edid_get_blue (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->blue;
+}
+
+const CdColorYxy *
+gcm_edid_get_white (GcmEdid *edid)
+{
+        g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+        return edid->priv->white;
+}
+
+void
+gcm_edid_reset (GcmEdid *edid)
+{
+        GcmEdidPrivate *priv = edid->priv;
+
+        g_return_if_fail (GCM_IS_EDID (edid));
+
+        /* free old data */
+        g_free (priv->monitor_name);
+        g_free (priv->vendor_name);
+        g_free (priv->serial_number);
+        g_free (priv->eisa_id);
+        g_free (priv->checksum);
+
+        /* do not deallocate, just blank */
+        priv->pnp_id[0] = '\0';
+
+        /* set to default values */
+        priv->monitor_name = NULL;
+        priv->vendor_name = NULL;
+        priv->serial_number = NULL;
+        priv->eisa_id = NULL;
+        priv->checksum = NULL;
+        priv->width = 0;
+        priv->height = 0;
+        priv->gamma = 0.0f;
+}
+
+static gint
+gcm_edid_get_bit (gint in, gint bit)
+{
+        return (in & (1 << bit)) >> bit;
+}
+
+/**
+ * gcm_edid_get_bits:
+ **/
+static gint
+gcm_edid_get_bits (gint in, gint begin, gint end)
+{
+        gint mask = (1 << (end - begin + 1)) - 1;
+
+        return (in >> begin) & mask;
+}
+
+/**
+ * gcm_edid_decode_fraction:
+ **/
+static gdouble
+gcm_edid_decode_fraction (gint high, gint low)
+{
+        gdouble result = 0.0;
+        gint i;
+
+        high = (high << 2) | low;
+        for (i = 0; i < 10; ++i)
+                result += gcm_edid_get_bit (high, i) * pow (2, i - 10);
+        return result;
+}
+
+static gchar *
+gcm_edid_parse_string (const guint8 *data)
+{
+        gchar *text;
+        guint i;
+        guint replaced = 0;
+
+        /* this is always 12 bytes, but we can't guarantee it's null
+         * terminated or not junk. */
+        text = g_strndup ((const gchar *) data, 12);
+
+        /* remove insane newline chars */
+        g_strdelimit (text, "\n\r", '\0');
+
+        /* remove spaces */
+        g_strchomp (text);
+
+        /* nothing left? */
+        if (text[0] == '\0') {
+                g_free (text);
+                text = NULL;
+                goto out;
+        }
+
+        /* ensure string is printable */
+        for (i = 0; text[i] != '\0'; i++) {
+                if (!g_ascii_isprint (text[i])) {
+                        text[i] = '-';
+                        replaced++;
+                }
+        }
+
+        /* if the string is junk, ignore the string */
+        if (replaced > 4) {
+                g_free (text);
+                text = NULL;
+                goto out;
+        }
+out:
+        return text;
+}
+
+gboolean
+gcm_edid_parse (GcmEdid *edid, const guint8 *data, gsize length, GError **error)
+{
+        gboolean ret = TRUE;
+        guint i;
+        GcmEdidPrivate *priv = edid->priv;
+        guint32 serial;
+        gchar *tmp;
+
+        /* check header */
+        if (length < 128) {
+                g_set_error_literal (error,
+                                     GCM_EDID_ERROR,
+                                     GCM_EDID_ERROR_FAILED_TO_PARSE,
+                                     "EDID length is too small");
+                ret = FALSE;
+                goto out;
+        }
+        if (data[0] != 0x00 || data[1] != 0xff) {
+                g_set_error_literal (error,
+                                     GCM_EDID_ERROR,
+                                     GCM_EDID_ERROR_FAILED_TO_PARSE,
+                                     "Failed to parse EDID header");
+                ret = FALSE;
+                goto out;
+        }
+
+        /* free old data */
+        gcm_edid_reset (edid);
+
+        /* decode the PNP ID from three 5 bit words packed into 2 bytes
+         * /--08--\/--09--\
+         * 7654321076543210
+         * |\---/\---/\---/
+         * R  C1   C2   C3 */
+        priv->pnp_id[0] = 'A' + ((data[GCM_EDID_OFFSET_PNPID+0] & 0x7c) / 4) - 1;
+        priv->pnp_id[1] = 'A' + ((data[GCM_EDID_OFFSET_PNPID+0] & 0x3) * 8) + ((data[GCM_EDID_OFFSET_PNPID+1] & 0xe0) / 32) - 1;
+        priv->pnp_id[2] = 'A' + (data[GCM_EDID_OFFSET_PNPID+1] & 0x1f) - 1;
+
+        /* maybe there isn't a ASCII serial number descriptor, so use this instead */
+        serial = (guint32) data[GCM_EDID_OFFSET_SERIAL+0];
+        serial += (guint32) data[GCM_EDID_OFFSET_SERIAL+1] * 0x100;
+        serial += (guint32) data[GCM_EDID_OFFSET_SERIAL+2] * 0x10000;
+        serial += (guint32) data[GCM_EDID_OFFSET_SERIAL+3] * 0x1000000;
+        if (serial > 0)
+                priv->serial_number = g_strdup_printf ("%" G_GUINT32_FORMAT, serial);
+
+        /* get the size */
+        priv->width = data[GCM_EDID_OFFSET_SIZE+0];
+        priv->height = data[GCM_EDID_OFFSET_SIZE+1];
+
+        /* we don't care about aspect */
+        if (priv->width == 0 || priv->height == 0) {
+                priv->width = 0;
+                priv->height = 0;
+        }
+
+        /* get gamma */
+        if (data[GCM_EDID_OFFSET_GAMMA] == 0xff) {
+                priv->gamma = 1.0f;
+        } else {
+                priv->gamma = ((gfloat) data[GCM_EDID_OFFSET_GAMMA] / 100) + 1;
+        }
+
+        /* get color red */
+        priv->red->x = gcm_edid_decode_fraction (data[0x1b], gcm_edid_get_bits (data[0x19], 6, 7));
+        priv->red->y = gcm_edid_decode_fraction (data[0x1c], gcm_edid_get_bits (data[0x19], 5, 4));
+
+        /* get color green */
+        priv->green->x = gcm_edid_decode_fraction (data[0x1d], gcm_edid_get_bits (data[0x19], 2, 3));
+        priv->green->y = gcm_edid_decode_fraction (data[0x1e], gcm_edid_get_bits (data[0x19], 0, 1));
+
+        /* get color blue */
+        priv->blue->x = gcm_edid_decode_fraction (data[0x1f], gcm_edid_get_bits (data[0x1a], 6, 7));
+        priv->blue->y = gcm_edid_decode_fraction (data[0x20], gcm_edid_get_bits (data[0x1a], 4, 5));
+
+        /* get color white */
+        priv->white->x = gcm_edid_decode_fraction (data[0x21], gcm_edid_get_bits (data[0x1a], 2, 3));
+        priv->white->y = gcm_edid_decode_fraction (data[0x22], gcm_edid_get_bits (data[0x1a], 0, 1));
+
+        /* parse EDID data */
+        for (i = GCM_EDID_OFFSET_DATA_BLOCKS;
+             i <= GCM_EDID_OFFSET_LAST_BLOCK;
+             i += 18) {
+                /* ignore pixel clock data */
+                if (data[i] != 0)
+                        continue;
+                if (data[i+2] != 0)
+                        continue;
+
+                /* any useful blocks? */
+                if (data[i+3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
+                        tmp = gcm_edid_parse_string (&data[i+5]);
+                        if (tmp != NULL) {
+                                g_free (priv->monitor_name);
+                                priv->monitor_name = tmp;
+                        }
+                } else if (data[i+3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
+                        tmp = gcm_edid_parse_string (&data[i+5]);
+                        if (tmp != NULL) {
+                                g_free (priv->serial_number);
+                                priv->serial_number = tmp;
+                        }
+                } else if (data[i+3] == GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA) {
+                        g_warning ("failing to parse color management data");
+                } else if (data[i+3] == GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
+                        tmp = gcm_edid_parse_string (&data[i+5]);
+                        if (tmp != NULL) {
+                                g_free (priv->eisa_id);
+                                priv->eisa_id = tmp;
+                        }
+                } else if (data[i+3] == GCM_DESCRIPTOR_COLOR_POINT) {
+                        if (data[i+3+9] != 0xff) {
+                                /* extended EDID block(1) which contains
+                                 * a better gamma value */
+                                priv->gamma = ((gfloat) data[i+3+9] / 100) + 1;
+                        }
+                        if (data[i+3+14] != 0xff) {
+                                /* extended EDID block(2) which contains
+                                 * a better gamma value */
+                                priv->gamma = ((gfloat) data[i+3+9] / 100) + 1;
+                        }
+                }
+        }
+
+        /* calculate checksum */
+        priv->checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, data, 0x6c);
+out:
+        return ret;
+}
+
+static void
+gcm_edid_class_init (GcmEdidClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        object_class->finalize = gcm_edid_finalize;
+        g_type_class_add_private (klass, sizeof (GcmEdidPrivate));
+}
+
+static void
+gcm_edid_init (GcmEdid *edid)
+{
+        edid->priv = GCM_EDID_GET_PRIVATE (edid);
+        edid->priv->tables = gcm_tables_new ();
+        edid->priv->pnp_id = g_new0 (gchar, 4);
+        edid->priv->red = cd_color_yxy_new ();
+        edid->priv->green = cd_color_yxy_new ();
+        edid->priv->blue = cd_color_yxy_new ();
+        edid->priv->white = cd_color_yxy_new ();
+}
+
+static void
+gcm_edid_finalize (GObject *object)
+{
+        GcmEdid *edid = GCM_EDID (object);
+        GcmEdidPrivate *priv = edid->priv;
+
+        g_free (priv->monitor_name);
+        g_free (priv->vendor_name);
+        g_free (priv->serial_number);
+        g_free (priv->eisa_id);
+        g_free (priv->checksum);
+        g_free (priv->pnp_id);
+        cd_color_yxy_free (priv->white);
+        cd_color_yxy_free (priv->red);
+        cd_color_yxy_free (priv->green);
+        cd_color_yxy_free (priv->blue);
+        g_object_unref (priv->tables);
+
+        G_OBJECT_CLASS (gcm_edid_parent_class)->finalize (object);
+}
+
+GcmEdid *
+gcm_edid_new (void)
+{
+        GcmEdid *edid;
+        edid = g_object_new (GCM_TYPE_EDID, NULL);
+        return GCM_EDID (edid);
+}
+
diff --git a/plugins/color/gcm-edid.h b/plugins/color/gcm-edid.h
new file mode 100644
index 0000000..5eff73c
--- /dev/null
+++ b/plugins/color/gcm-edid.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GCM_EDID_H
+#define __GCM_EDID_H
+
+#include <glib-object.h>
+#include <colord.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_EDID           (gcm_edid_get_type ())
+#define GCM_EDID(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_EDID, GcmEdid))
+#define GCM_EDID_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_EDID, GcmEdidClass))
+#define GCM_IS_EDID(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_EDID))
+#define GCM_IS_EDID_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_EDID))
+#define GCM_EDID_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_EDID, GcmEdidClass))
+#define GCM_EDID_ERROR          (gcm_edid_error_quark ())
+
+typedef struct _GcmEdidPrivate  GcmEdidPrivate;
+typedef struct _GcmEdid         GcmEdid;
+typedef struct _GcmEdidClass    GcmEdidClass;
+
+struct _GcmEdid
+{
+         GObject                 parent;
+         GcmEdidPrivate         *priv;
+};
+
+struct _GcmEdidClass
+{
+        GObjectClass    parent_class;
+};
+
+enum
+{
+        GCM_EDID_ERROR_FAILED_TO_PARSE
+};
+
+GType            gcm_edid_get_type                      (void);
+GQuark           gcm_edid_error_quark                   (void);
+GcmEdid         *gcm_edid_new                           (void);
+void             gcm_edid_reset                         (GcmEdid                *edid);
+gboolean         gcm_edid_parse                         (GcmEdid                *edid,
+                                                         const guint8           *data,
+                                                         gsize                   length,
+                                                         GError                 **error);
+const gchar     *gcm_edid_get_monitor_name              (GcmEdid                *edid);
+const gchar     *gcm_edid_get_vendor_name               (GcmEdid                *edid);
+const gchar     *gcm_edid_get_serial_number             (GcmEdid                *edid);
+const gchar     *gcm_edid_get_eisa_id                   (GcmEdid                *edid);
+const gchar     *gcm_edid_get_checksum                  (GcmEdid                *edid);
+const gchar     *gcm_edid_get_pnp_id                    (GcmEdid                *edid);
+guint            gcm_edid_get_width                     (GcmEdid                *edid);
+guint            gcm_edid_get_height                    (GcmEdid                *edid);
+gfloat           gcm_edid_get_gamma                     (GcmEdid                *edid);
+const CdColorYxy *gcm_edid_get_red                      (GcmEdid                *edid);
+const CdColorYxy *gcm_edid_get_green                    (GcmEdid                *edid);
+const CdColorYxy *gcm_edid_get_blue                     (GcmEdid                *edid);
+const CdColorYxy *gcm_edid_get_white                    (GcmEdid                *edid);
+
+G_END_DECLS
+
+#endif /* __GCM_EDID_H */
+
diff --git a/plugins/color/gcm-self-test.c b/plugins/color/gcm-self-test.c
new file mode 100644
index 0000000..073d090
--- /dev/null
+++ b/plugins/color/gcm-self-test.c
@@ -0,0 +1,145 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007-2011 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+
+#include "gcm-edid.h"
+#include "gcm-dmi.h"
+#include "gcm-tables.h"
+
+static void
+gcm_test_tables_func (void)
+{
+        GcmTables *tables;
+        GError *error = NULL;
+        gchar *vendor;
+
+        tables = gcm_tables_new ();
+        g_assert (tables != NULL);
+
+        vendor = gcm_tables_get_pnp_id (tables, "IBM", &error);
+        g_assert_no_error (error);
+        g_assert (vendor != NULL);
+        g_assert_cmpstr (vendor, ==, "IBM France");
+        g_free (vendor);
+
+        vendor = gcm_tables_get_pnp_id (tables, "MIL", &error);
+        g_assert_no_error (error);
+        g_assert (vendor != NULL);
+        g_assert_cmpstr (vendor, ==, "Marconi Instruments Ltd");
+        g_free (vendor);
+
+        vendor = gcm_tables_get_pnp_id (tables, "XXX", &error);
+        g_assert_error (error, GCM_TABLES_ERROR, GCM_TABLES_ERROR_FAILED);
+        g_assert_cmpstr (vendor, ==, NULL);
+        g_error_free (error);
+
+        g_object_unref (tables);
+}
+
+static void
+gcm_test_dmi_func (void)
+{
+        GcmDmi *dmi;
+
+        dmi = gcm_dmi_new ();
+        g_assert (dmi != NULL);
+        g_assert (gcm_dmi_get_name (dmi) != NULL);
+        g_assert (gcm_dmi_get_vendor (dmi) != NULL);
+        g_object_unref (dmi);
+}
+
+static void
+gcm_test_edid_func (void)
+{
+        GcmEdid *edid;
+        gchar *data;
+        gboolean ret;
+        GError *error = NULL;
+        gsize length = 0;
+
+        edid = gcm_edid_new ();
+        g_assert (edid != NULL);
+
+        /* LG 21" LCD panel */
+        ret = g_file_get_contents (TESTDATADIR "/LG-L225W-External.bin",
+                                   &data, &length, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+        ret = gcm_edid_parse (edid, (const guint8 *) data, length, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+
+        g_assert_cmpstr (gcm_edid_get_monitor_name (edid), ==, "L225W");
+        g_assert_cmpstr (gcm_edid_get_vendor_name (edid), ==, "Goldstar Company Ltd");
+        g_assert_cmpstr (gcm_edid_get_serial_number (edid), ==, "34398");
+        g_assert_cmpstr (gcm_edid_get_eisa_id (edid), ==, NULL);
+        g_assert_cmpstr (gcm_edid_get_checksum (edid), ==, "80b7dda4c74b06366abb8fa23e71d645");
+        g_assert_cmpstr (gcm_edid_get_pnp_id (edid), ==, "GSM");
+        g_assert_cmpint (gcm_edid_get_height (edid), ==, 30);
+        g_assert_cmpint (gcm_edid_get_width (edid), ==, 47);
+        g_assert_cmpfloat (gcm_edid_get_gamma (edid), >=, 2.2f - 0.01);
+        g_assert_cmpfloat (gcm_edid_get_gamma (edid), <, 2.2f + 0.01);
+        g_free (data);
+
+        /* Lenovo T61 internal Panel */
+        ret = g_file_get_contents (TESTDATADIR "/Lenovo-T61-Internal.bin",
+                                   &data, &length, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+        ret = gcm_edid_parse (edid, (const guint8 *) data, length, &error);
+        g_assert_no_error (error);
+        g_assert (ret);
+
+        g_assert_cmpstr (gcm_edid_get_monitor_name (edid), ==, NULL);
+        g_assert_cmpstr (gcm_edid_get_vendor_name (edid), ==, "IBM France");
+        g_assert_cmpstr (gcm_edid_get_serial_number (edid), ==, NULL);
+        g_assert_cmpstr (gcm_edid_get_eisa_id (edid), ==, "LTN154P2-L05");
+        g_assert_cmpstr (gcm_edid_get_checksum (edid), ==, "c585d9e80adc65c54f0a52597e850f83");
+        g_assert_cmpstr (gcm_edid_get_pnp_id (edid), ==, "IBM");
+        g_assert_cmpint (gcm_edid_get_height (edid), ==, 21);
+        g_assert_cmpint (gcm_edid_get_width (edid), ==, 33);
+        g_assert_cmpfloat (gcm_edid_get_gamma (edid), >=, 2.2f - 0.01);
+        g_assert_cmpfloat (gcm_edid_get_gamma (edid), <, 2.2f + 0.01);
+        g_free (data);
+
+        g_object_unref (edid);
+}
+
+int
+main (int argc, char **argv)
+{
+        if (! g_thread_supported ())
+                g_thread_init (NULL);
+        gtk_init (&argc, &argv);
+        g_test_init (&argc, &argv, NULL);
+
+        g_test_add_func ("/color/tables", gcm_test_tables_func);
+        g_test_add_func ("/color/dmi", gcm_test_dmi_func);
+        g_test_add_func ("/color/edid", gcm_test_edid_func);
+
+        return g_test_run ();
+}
+
diff --git a/plugins/color/gcm-tables.c b/plugins/color/gcm-tables.c
new file mode 100644
index 0000000..998ae80
--- /dev/null
+++ b/plugins/color/gcm-tables.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2011 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "gcm-tables.h"
+
+static void     gcm_tables_finalize     (GObject     *object);
+
+#define GCM_TABLES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_TABLES, GcmTablesPrivate))
+
+struct _GcmTablesPrivate
+{
+        gchar                           *data_dir;
+        gchar                           *table_data;
+        GHashTable                      *pnp_table;
+};
+
+static gpointer gcm_tables_object = NULL;
+
+G_DEFINE_TYPE (GcmTables, gcm_tables, G_TYPE_OBJECT)
+
+GQuark
+gcm_tables_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("gcm_tables_error");
+	return quark;
+}
+
+static gboolean
+gcm_tables_load (GcmTables *tables, GError **error)
+{
+        gboolean ret;
+        gchar *filename = NULL;
+        gchar *retval = NULL;
+        GcmTablesPrivate *priv = tables->priv;
+        guint i;
+
+        /* load the contents */
+        g_debug ("loading: %s", PNPIDS_FILE);
+        ret = g_file_get_contents (PNPIDS_FILE, &priv->table_data, NULL, error);
+        if (!ret)
+                goto out;
+
+        /* parse into lines */
+        retval = priv->table_data;
+        for (i = 0; priv->table_data[i] != '\0'; i++) {
+
+                /* ignore */
+                if (priv->table_data[i] != '\n')
+                        continue;
+
+                /* convert newline to NULL */
+                priv->table_data[i] = '\0';
+
+                /* the ID to text is a fixed offset */
+                retval[3] = '\0';
+                g_hash_table_insert (priv->pnp_table,
+                                     retval,
+                                     retval+4);
+                retval = &priv->table_data[i+1];
+        }
+out:
+        g_free (filename);
+        return ret;
+}
+
+gchar *
+gcm_tables_get_pnp_id (GcmTables *tables, const gchar *pnp_id, GError **error)
+{
+        gboolean ret;
+        gchar *retval = NULL;
+        GcmTablesPrivate *priv = tables->priv;
+        gpointer found;
+        guint size;
+
+        g_return_val_if_fail (GCM_IS_TABLES (tables), NULL);
+        g_return_val_if_fail (pnp_id != NULL, NULL);
+
+        /* if table is empty, try to load it */
+        size = g_hash_table_size (priv->pnp_table);
+        if (size == 0) {
+                ret = gcm_tables_load (tables, error);
+                if (!ret)
+                        goto out;
+        }
+
+        /* look this up in the table */
+        found = g_hash_table_lookup (priv->pnp_table, pnp_id);
+        if (found == NULL) {
+                g_set_error (error,
+                             GCM_TABLES_ERROR,
+                             GCM_TABLES_ERROR_FAILED,
+                             "could not find %s", pnp_id);
+                goto out;
+        }
+
+        /* return a copy */
+        retval = g_strdup (found);
+out:
+        return retval;
+}
+
+static void
+gcm_tables_class_init (GcmTablesClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+        object_class->finalize = gcm_tables_finalize;
+        g_type_class_add_private (klass, sizeof (GcmTablesPrivate));
+}
+
+static void
+gcm_tables_init (GcmTables *tables)
+{
+        tables->priv = GCM_TABLES_GET_PRIVATE (tables);
+
+        /* we don't keep malloc'd data in the hash; instead we read it
+         * out into priv->table_data and then link to it in the hash */
+        tables->priv->pnp_table = g_hash_table_new_full (g_str_hash,
+                                                         g_str_equal,
+                                                         NULL,
+                                                         NULL);
+}
+
+static void
+gcm_tables_finalize (GObject *object)
+{
+        GcmTables *tables = GCM_TABLES (object);
+        GcmTablesPrivate *priv = tables->priv;
+
+        g_free (priv->data_dir);
+        g_free (priv->table_data);
+        g_hash_table_unref (priv->pnp_table);
+
+        G_OBJECT_CLASS (gcm_tables_parent_class)->finalize (object);
+}
+
+GcmTables *
+gcm_tables_new (void)
+{
+        if (gcm_tables_object != NULL) {
+                g_object_ref (gcm_tables_object);
+        } else {
+                gcm_tables_object = g_object_new (GCM_TYPE_TABLES, NULL);
+                g_object_add_weak_pointer (gcm_tables_object, &gcm_tables_object);
+        }
+        return GCM_TABLES (gcm_tables_object);
+}
+
diff --git a/plugins/color/gcm-tables.h b/plugins/color/gcm-tables.h
new file mode 100644
index 0000000..7bc213c
--- /dev/null
+++ b/plugins/color/gcm-tables.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GCM_TABLES_H
+#define __GCM_TABLES_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_TABLES                 (gcm_tables_get_type ())
+#define GCM_TABLES(o)                   (G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_TABLES, GcmTables))
+#define GCM_TABLES_CLASS(k)             (G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_TABLES, GcmTablesClass))
+#define GCM_IS_TABLES(o)                (G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_TABLES))
+#define GCM_IS_TABLES_CLASS(k)          (G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_TABLES))
+#define GCM_TABLES_GET_CLASS(o)         (G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_TABLES, GcmTablesClass))
+#define GCM_TABLES_ERROR                (gcm_tables_error_quark ())
+
+typedef struct _GcmTablesPrivate        GcmTablesPrivate;
+typedef struct _GcmTables               GcmTables;
+typedef struct _GcmTablesClass          GcmTablesClass;
+
+struct _GcmTables
+{
+         GObject                         parent;
+         GcmTablesPrivate               *priv;
+};
+
+struct _GcmTablesClass
+{
+        GObjectClass    parent_class;
+};
+
+enum
+{
+        GCM_TABLES_ERROR_FAILED
+};
+
+GType            gcm_tables_get_type                    (void);
+GQuark           gcm_tables_error_quark                 (void);
+GcmTables       *gcm_tables_new                         (void);
+gchar           *gcm_tables_get_pnp_id                  (GcmTables              *tables,
+                                                         const gchar            *pnp_id,
+                                                         GError                 **error);
+
+G_END_DECLS
+
+#endif /* __GCM_TABLES_H */
+
diff --git a/plugins/color/gsd-color-manager.c b/plugins/color/gsd-color-manager.c
index 3eb7da5..f8e22d7 100644
--- a/plugins/color/gsd-color-manager.c
+++ b/plugins/color/gsd-color-manager.c
@@ -25,6 +25,7 @@
 #include <colord.h>
 #include <libnotify/notify.h>
 #include <gdk/gdk.h>
+#include <stdlib.h>
 
 #ifdef HAVE_LIBCANBERRA
 #include <canberra-gtk.h>
@@ -34,9 +35,14 @@
   #include <lcms2.h>
 #endif
 
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
 #include "gnome-settings-profile.h"
 #include "gsd-color-manager.h"
 #include "gcm-profile-store.h"
+#include "gcm-dmi.h"
+#include "gcm-edid.h"
 
 #define GSD_COLOR_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_COLOR_MANAGER, GsdColorManagerPrivate))
 
@@ -50,6 +56,10 @@ struct GsdColorManagerPrivate
         CdClient        *client;
         GSettings       *settings;
         GcmProfileStore *profile_store;
+        GcmDmi          *dmi;
+        GnomeRRScreen   *x11_screen;
+        GHashTable      *edid_cache;
+        GdkWindow       *gdk_window;
 };
 
 enum {
@@ -64,6 +74,1201 @@ G_DEFINE_TYPE (GsdColorManager, gsd_color_manager, G_TYPE_OBJECT)
 
 static gpointer manager_object = NULL;
 
+/* see http://www.oyranos.org/wiki/index.php?title=ICC_Profiles_in_X_Specification_0.3 */
+#define GCM_ICC_PROFILE_IN_X_VERSION_MAJOR      0
+#define GCM_ICC_PROFILE_IN_X_VERSION_MINOR      3
+
+typedef struct {
+        guint32          red;
+        guint32          green;
+        guint32          blue;
+} GnomeRROutputClutItem;
+
+GQuark
+gsd_color_manager_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("gsd_color_manager_error");
+	return quark;
+}
+
+static GcmEdid *
+gcm_session_get_output_edid (GsdColorManager *manager, GnomeRROutput *output, GError **error)
+{
+        const guint8 *data;
+        gsize size;
+        GcmEdid *edid = NULL;
+        gboolean ret;
+
+        /* can we find it in the cache */
+        edid = g_hash_table_lookup (manager->priv->edid_cache,
+                                    gnome_rr_output_get_name (output));
+        if (edid != NULL) {
+                g_object_ref (edid);
+                goto out;
+        }
+
+        /* parse edid */
+        data = gnome_rr_output_get_edid_data (output, &size);
+        if (data == NULL || size == 0) {
+                g_set_error_literal (error,
+                                     GNOME_RR_ERROR,
+                                     GNOME_RR_ERROR_UNKNOWN,
+                                     "unable to get EDID for output");
+                goto out;
+        }
+        edid = gcm_edid_new ();
+        ret = gcm_edid_parse (edid, data, size, error);
+        if (!ret) {
+                g_object_unref (edid);
+                edid = NULL;
+                goto out;
+        }
+
+        /* add to cache */
+        g_hash_table_insert (manager->priv->edid_cache,
+                             g_strdup (gnome_rr_output_get_name (output)),
+                             g_object_ref (edid));
+out:
+        return edid;
+}
+
+static gboolean
+gcm_session_screen_set_icc_profile (GsdColorManager *manager,
+                                    const gchar *filename,
+                                    GError **error)
+{
+        gboolean ret;
+        gchar *data = NULL;
+        gsize length;
+        guint version_data;
+        GsdColorManagerPrivate *priv = manager->priv;
+
+        g_return_val_if_fail (filename != NULL, FALSE);
+
+        g_debug ("setting root window ICC profile atom from %s", filename);
+
+        /* get contents of file */
+        ret = g_file_get_contents (filename, &data, &length, error);
+        if (!ret)
+                goto out;
+
+        /* set profile property */
+        gdk_property_change (priv->gdk_window,
+                             gdk_atom_intern_static_string ("_ICC_PROFILE"),
+                             gdk_atom_intern_static_string ("CARDINAL"),
+                             8,
+                             GDK_PROP_MODE_REPLACE,
+                             (const guchar *) data, length);
+
+        /* set version property */
+        version_data = GCM_ICC_PROFILE_IN_X_VERSION_MAJOR * 100 +
+                        GCM_ICC_PROFILE_IN_X_VERSION_MINOR * 1;
+        gdk_property_change (priv->gdk_window,
+                             gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"),
+                             gdk_atom_intern_static_string ("CARDINAL"),
+                             8,
+                             GDK_PROP_MODE_REPLACE,
+                             (const guchar *) &version_data, 1);
+out:
+        g_free (data);
+        return ret;
+}
+
+static gchar *
+gcm_session_get_output_id (GsdColorManager *manager, GnomeRROutput *output)
+{
+        const gchar *name;
+        const gchar *serial;
+        const gchar *vendor;
+        GcmEdid *edid = NULL;
+        GString *device_id;
+        GError *error = NULL;
+
+        /* all output devices are prefixed with this */
+        device_id = g_string_new ("xrandr");
+
+        /* get the output EDID if possible */
+        edid = gcm_session_get_output_edid (manager, output, &error);
+        if (edid == NULL) {
+                g_debug ("no edid for %s [%s], falling back to connection name",
+                         gnome_rr_output_get_name (output),
+                         error->message);
+                g_error_free (error);
+                g_string_append_printf (device_id,
+                                        "_%s",
+                                        gnome_rr_output_get_name (output));
+                goto out;
+        }
+
+        /* get EDID data */
+        vendor = gcm_edid_get_vendor_name (edid);
+        if (vendor != NULL)
+                g_string_append_printf (device_id, "-%s", vendor);
+        name = gcm_edid_get_monitor_name (edid);
+        if (name != NULL)
+                g_string_append_printf (device_id, "-%s", name);
+        serial = gcm_edid_get_serial_number (edid);
+        if (serial != NULL)
+                g_string_append_printf (device_id, "-%s", serial);
+out:
+        if (edid != NULL)
+                g_object_unref (edid);
+        return g_string_free (device_id, FALSE);
+}
+
+static GnomeRROutput *
+gcm_session_get_output_by_edid_checksum (GnomeRRScreen *screen,
+                                         const gchar *edid_md5,
+                                         GError **error)
+{
+        const guint8 *data;
+        gchar *checksum;
+        GnomeRROutput *output = NULL;
+        GnomeRROutput **outputs;
+        gsize size;
+        guint i;
+
+        outputs = gnome_rr_screen_list_outputs (screen);
+        if (outputs == NULL) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "Failed to get outputs");
+                goto out;
+        }
+
+        /* find the output */
+        for (i = 0; outputs[i] != NULL && output == NULL; i++) {
+
+                /* get edid */
+                data = gnome_rr_output_get_edid_data (outputs[i], &size);
+                if (data == NULL || size < 0x6c)
+                        continue;
+                checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, data, 0x6c);
+                if (g_strcmp0 (checksum, edid_md5) == 0)
+                        output = outputs[i];
+                g_free (checksum);
+        }
+        if (output == NULL) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "no connected output with that edid hash");
+        }
+out:
+        return output;
+}
+
+typedef struct {
+        GsdColorManager         *manager;
+        CdProfile               *profile;
+        CdDevice                *device;
+        GnomeRROutput           *output;
+} GcmSessionAsyncHelper;
+
+static void
+gcm_session_async_helper_free (GcmSessionAsyncHelper *helper)
+{
+        if (helper->manager != NULL)
+                g_object_unref (helper->manager);
+        if (helper->profile != NULL)
+                g_object_unref (helper->profile);
+        if (helper->device != NULL)
+                g_object_unref (helper->device);
+        g_free (helper);
+}
+
+static void
+gcm_session_profile_assign_add_profile_cb (GObject *object,
+                                           GAsyncResult *res,
+                                           gpointer user_data)
+{
+        CdDevice *device = CD_DEVICE (object);
+        gboolean ret;
+        GError *error = NULL;
+        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+
+        /* add the profile to the device */
+        ret = cd_device_add_profile_finish (device,
+                                            res,
+                                            &error);
+        if (!ret) {
+                /* this will fail if the profile is already added */
+                g_debug ("failed to assign auto-edid profile to device %s: %s",
+                         cd_device_get_id (device),
+                         error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* phew! */
+        g_debug ("successfully assigned %s to %s",
+                 cd_profile_get_object_path (helper->profile),
+                 cd_device_get_object_path (device));
+out:
+        gcm_session_async_helper_free (helper);
+}
+
+static void
+gcm_session_profile_assign_device_connect_cb (GObject *object,
+                                              GAsyncResult *res,
+                                              gpointer user_data)
+{
+        CdDevice *device = CD_DEVICE (object);
+        gboolean ret;
+        GError *error = NULL;
+        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+
+        /* get properties */
+        ret = cd_device_connect_finish (device, res, &error);
+        if (!ret) {
+                g_warning ("cannot connect to device: %s",
+                           error->message);
+                g_error_free (error);
+                gcm_session_async_helper_free (helper);
+                goto out;
+        }
+
+        /* add the profile to the device */
+        cd_device_add_profile (device,
+                               CD_DEVICE_RELATION_SOFT,
+                               helper->profile,
+                               NULL,
+                               gcm_session_profile_assign_add_profile_cb,
+                               helper);
+out:
+        return;
+}
+
+static void
+gcm_session_profile_assign_find_device_cb (GObject *object,
+                                           GAsyncResult *res,
+                                           gpointer user_data)
+{
+        CdClient *client = CD_CLIENT (object);
+        CdDevice *device = NULL;
+        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+        GError *error = NULL;
+
+        device = cd_client_find_device_finish (client,
+                                               res,
+                                               &error);
+        if (device == NULL) {
+                g_warning ("not found device %s which should have been added: %s",
+                           cd_device_get_id (device),
+                           error->message);
+                g_error_free (error);
+                gcm_session_async_helper_free (helper);
+                goto out;
+        }
+
+        /* get properties */
+        cd_device_connect (device,
+                           NULL,
+                           gcm_session_profile_assign_device_connect_cb,
+                           helper);
+out:
+        if (device != NULL)
+                g_object_unref (device);
+}
+
+static void
+gcm_session_profile_assign_profile_connect_cb (GObject *object,
+                                               GAsyncResult *res,
+                                               gpointer user_data)
+{
+        CdProfile *profile = CD_PROFILE (object);
+        const gchar *edid_md5;
+        gboolean ret;
+        gchar *device_id = NULL;
+        GcmSessionAsyncHelper *helper;
+        GError *error = NULL;
+        GHashTable *metadata = NULL;
+        GnomeRROutput *output = NULL;
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+        /* get properties */
+        ret = cd_profile_connect_finish (profile, res, &error);
+        if (!ret) {
+                g_warning ("cannot connect to profile: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* does the profile have EDID metadata? */
+        metadata = cd_profile_get_metadata (profile);
+        edid_md5 = g_hash_table_lookup (metadata,
+                                        CD_PROFILE_METADATA_EDID_MD5);
+        if (edid_md5 == NULL)
+                goto out;
+
+        /* get the GnomeRROutput for the edid */
+        output = gcm_session_get_output_by_edid_checksum (manager->priv->x11_screen,
+                                                          edid_md5,
+                                                          &error);
+        if (output == NULL) {
+                g_debug ("edid hash %s ignored: %s",
+                         edid_md5,
+                         error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* get the CdDevice for this ID */
+        helper = g_new0 (GcmSessionAsyncHelper, 1);
+        helper->manager = g_object_ref (manager);
+        helper->profile = g_object_ref (profile);
+        device_id = gcm_session_get_output_id (manager, output);
+        cd_client_find_device (manager->priv->client,
+                               device_id,
+                               NULL,
+                               gcm_session_profile_assign_find_device_cb,
+                               helper);
+out:
+        g_free (device_id);
+        if (metadata != NULL)
+                g_hash_table_unref (metadata);
+}
+
+static void
+gcm_session_profile_added_assign_cb (CdClient *client,
+                                     CdProfile *profile,
+                                     GsdColorManager *manager)
+{
+        cd_profile_connect (profile,
+                            NULL,
+                            gcm_session_profile_assign_profile_connect_cb,
+                            manager);
+}
+
+static cmsBool
+_cmsWriteTagTextAscii (cmsHPROFILE lcms_profile,
+                       cmsTagSignature sig,
+                       const gchar *text)
+{
+        cmsBool ret;
+        cmsMLU *mlu = cmsMLUalloc (0, 1);
+        cmsMLUsetASCII (mlu, "EN", "us", text);
+        ret = cmsWriteTag (lcms_profile, sig, mlu);
+        cmsMLUfree (mlu);
+        return ret;
+}
+
+static gboolean
+gcm_utils_mkdir_for_filename (const gchar *filename, GError **error)
+{
+        gboolean ret = FALSE;
+        GFile *file;
+        GFile *parent_dir = NULL;
+
+        /* get parent directory */
+        file = g_file_new_for_path (filename);
+        parent_dir = g_file_get_parent (file);
+        if (parent_dir == NULL) {
+                g_set_error (error,
+                             GSD_COLOR_MANAGER_ERROR,
+                             GSD_COLOR_MANAGER_ERROR_FAILED,
+                             "could not get parent dir %s",
+                             filename);
+                goto out;
+        }
+
+        /* ensure desination exists */
+        ret = g_file_make_directory_with_parents (parent_dir, NULL, error);
+        if (!ret)
+                goto out;
+out:
+        if (file != NULL)
+                g_object_unref (file);
+        if (parent_dir != NULL)
+                g_object_unref (parent_dir);
+        return ret;
+}
+
+#ifdef HAVE_NEW_LCMS
+static cmsBool
+_cmsDictAddEntryAscii (cmsHANDLE dict,
+                       const gchar *key,
+                       const gchar *value)
+{
+        cmsBool ret;
+        wchar_t mb_key[1024];
+        wchar_t mb_value[1024];
+        mbstowcs (mb_key, key, sizeof (mb_key));
+        mbstowcs (mb_value, value, sizeof (mb_value));
+        ret = cmsDictAddEntry (dict, mb_key, mb_value, NULL, NULL);
+        return ret;
+}
+#endif /* HAVE_NEW_LCMS */
+
+static gboolean
+gcm_apply_create_icc_profile_for_edid (GsdColorManager *manager,
+                                       GcmEdid *edid,
+                                       const gchar *filename,
+                                       GError **error)
+{
+        const CdColorYxy *tmp;
+        cmsCIExyYTRIPLE chroma;
+        cmsCIExyY white_point;
+        cmsHPROFILE lcms_profile = NULL;
+        cmsToneCurve *transfer_curve[3];
+        const gchar *data;
+        gboolean ret = FALSE;
+        gchar *title = NULL;
+        gfloat localgamma;
+#ifdef HAVE_NEW_LCMS
+        cmsHANDLE dict = NULL;
+#endif
+        GsdColorManagerPrivate *priv = manager->priv;
+
+        /* ensure the per-user directory exists */
+        ret = gcm_utils_mkdir_for_filename (filename, error);
+        if (!ret)
+                goto out;
+
+        /* copy color data from our structures */
+        tmp = gcm_edid_get_red (edid);
+        chroma.Red.x = tmp->x;
+        chroma.Red.y = tmp->y;
+        tmp = gcm_edid_get_green (edid);
+        chroma.Green.x = tmp->x;
+        chroma.Green.y = tmp->y;
+        tmp = gcm_edid_get_blue (edid);
+        chroma.Blue.x = tmp->x;
+        chroma.Blue.y = tmp->y;
+        tmp = gcm_edid_get_white (edid);
+        white_point.x = tmp->x;
+        white_point.y = tmp->y;
+        white_point.Y = 1.0;
+
+        /* estimate the transfer function for the gamma */
+        localgamma = gcm_edid_get_gamma (edid);
+        transfer_curve[0] = transfer_curve[1] = transfer_curve[2] = cmsBuildGamma (NULL, localgamma);
+
+        /* create our generated profile */
+        lcms_profile = cmsCreateRGBProfile (&white_point, &chroma, transfer_curve);
+        if (lcms_profile == NULL) {
+                g_set_error (error,
+                             GSD_COLOR_MANAGER_ERROR,
+                             GSD_COLOR_MANAGER_ERROR_FAILED,
+                             "failed to create profile");
+                goto out;
+        }
+
+        cmsSetColorSpace (lcms_profile, cmsSigRgbData);
+        cmsSetPCS (lcms_profile, cmsSigXYZData);
+        cmsSetHeaderRenderingIntent (lcms_profile,
+                                     INTENT_RELATIVE_COLORIMETRIC);
+        cmsSetDeviceClass (lcms_profile, cmsSigDisplayClass);
+
+        /* copyright */
+        ret = _cmsWriteTagTextAscii (lcms_profile,
+                                     cmsSigCopyrightTag,
+                                     "No copyright");
+        if (!ret) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "failed to write copyright");
+                goto out;
+        }
+
+        /* set model */
+        data = gcm_edid_get_monitor_name (edid);
+        if (data == NULL)
+                data = gcm_dmi_get_name (priv->dmi);
+        if (data == NULL)
+                data = "Unknown monitor";
+        ret = _cmsWriteTagTextAscii (lcms_profile,
+                                     cmsSigDeviceModelDescTag,
+                                     data);
+        if (!ret) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "failed to write model");
+                goto out;
+        }
+
+        /* write title */
+        title = g_strdup_printf ("%s, %s",
+                                 _("Default"),
+                                 data);
+        ret = _cmsWriteTagTextAscii (lcms_profile,
+                                     cmsSigProfileDescriptionTag,
+                                     title);
+        if (!ret) {
+                g_set_error_literal (error, GSD_COLOR_MANAGER_ERROR, GSD_COLOR_MANAGER_ERROR_FAILED, "failed to write description");
+                goto out;
+        }
+
+        /* get manufacturer */
+        data = gcm_edid_get_vendor_name (edid);
+        if (data == NULL)
+                data = gcm_dmi_get_vendor (priv->dmi);
+        if (data == NULL)
+                data = "Unknown vendor";
+        ret = _cmsWriteTagTextAscii (lcms_profile,
+                                     cmsSigDeviceMfgDescTag,
+                                     data);
+        if (!ret) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "failed to write manufacturer");
+                goto out;
+        }
+
+#ifdef HAVE_NEW_LCMS
+        /* just create a new dict */
+        dict = cmsDictAlloc (NULL);
+
+        /* set 'ICC meta Tag for Monitor Profiles' data */
+        _cmsDictAddEntryAscii (dict, "EDID_md5", gcm_edid_get_checksum (edid));
+        data = gcm_edid_get_monitor_name (edid);
+        if (data != NULL)
+                _cmsDictAddEntryAscii (dict, "EDID_model", data);
+        data = gcm_edid_get_serial_number (edid);
+        if (data != NULL)
+                _cmsDictAddEntryAscii (dict, "EDID_serial", data);
+        data = gcm_edid_get_pnp_id (edid);
+        if (data != NULL)
+                _cmsDictAddEntryAscii (dict, "EDID_mnft", data);
+        data = gcm_edid_get_vendor_name (edid);
+        if (data != NULL)
+                _cmsDictAddEntryAscii (dict, "EDID_manufacturer", data);
+#endif /* HAVE_NEW_LCMS */
+
+        /* write profile id */
+        ret = cmsMD5computeID (lcms_profile);
+        if (!ret) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "failed to write profile id");
+                goto out;
+        }
+
+        /* save, TODO: get error */
+        cmsSaveProfileToFile (lcms_profile, filename);
+        ret = TRUE;
+out:
+        g_free (title);
+#ifdef HAVE_NEW_LCMS
+        if (dict != NULL)
+                cmsDictFree (dict);
+#endif
+        if (*transfer_curve != NULL)
+                cmsFreeToneCurve (*transfer_curve);
+        return ret;
+}
+
+static GPtrArray *
+gcm_session_generate_vcgt (CdProfile *profile, guint size)
+{
+        GnomeRROutputClutItem *tmp;
+        GPtrArray *array = NULL;
+        const cmsToneCurve **vcgt;
+        cmsFloat32Number in;
+        guint i;
+        const gchar *filename;
+        cmsHPROFILE lcms_profile = NULL;
+
+        /* not an actual profile */
+        filename = cd_profile_get_filename (profile);
+        if (filename == NULL)
+                goto out;
+
+        /* open file */
+        lcms_profile = cmsOpenProfileFromFile (filename, "r");
+        if (lcms_profile == NULL)
+                goto out;
+
+        /* get tone curves from profile */
+        vcgt = cmsReadTag (lcms_profile, cmsSigVcgtType);
+        if (vcgt == NULL || vcgt[0] == NULL) {
+                g_debug ("profile does not have any VCGT data");
+                goto out;
+        }
+
+        /* create array */
+        array = g_ptr_array_new_with_free_func (g_free);
+        for (i = 0; i < size; i++) {
+                in = (gdouble) i / (gdouble) (size - 1);
+                tmp = g_new0 (GnomeRROutputClutItem, 1);
+                tmp->red = cmsEvalToneCurveFloat(vcgt[0], in) * (gdouble) 0xffff;
+                tmp->green = cmsEvalToneCurveFloat(vcgt[1], in) * (gdouble) 0xffff;
+                tmp->blue = cmsEvalToneCurveFloat(vcgt[2], in) * (gdouble) 0xffff;
+                g_ptr_array_add (array, tmp);
+        }
+out:
+        if (lcms_profile != NULL)
+                cmsCloseProfile (lcms_profile);
+        return array;
+}
+
+static guint
+gnome_rr_output_get_gamma_size (GnomeRROutput *output)
+{
+        GnomeRRCrtc *crtc;
+        gint len = 0;
+
+        crtc = gnome_rr_output_get_crtc (output);
+        gnome_rr_crtc_get_gamma (crtc,
+                                 &len,
+                                 NULL, NULL, NULL);
+        return (guint) len;
+}
+
+static gboolean
+gcm_session_output_set_gamma (GnomeRROutput *output,
+                              GPtrArray *array,
+                              GError **error)
+{
+        gboolean ret = TRUE;
+        guint16 *red = NULL;
+        guint16 *green = NULL;
+        guint16 *blue = NULL;
+        guint i;
+        GnomeRROutputClutItem *data;
+        GnomeRRCrtc *crtc;
+
+        /* no length? */
+        if (array->len == 0) {
+                ret = FALSE;
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "no data in the CLUT array");
+                goto out;
+        }
+
+        /* convert to a type X understands */
+        red = g_new (guint16, array->len);
+        green = g_new (guint16, array->len);
+        blue = g_new (guint16, array->len);
+        for (i = 0; i < array->len; i++) {
+                data = g_ptr_array_index (array, i);
+                red[i] = data->red;
+                green[i] = data->green;
+                blue[i] = data->blue;
+        }
+
+        /* send to LUT */
+        crtc = gnome_rr_output_get_crtc (output);
+        gnome_rr_crtc_set_gamma (crtc, array->len,
+                                 red, green, blue);
+out:
+        g_free (red);
+        g_free (green);
+        g_free (blue);
+        return ret;
+}
+
+static gboolean
+gcm_session_device_set_gamma (GnomeRROutput *output,
+                              CdProfile *profile,
+                              GError **error)
+{
+        gboolean ret;
+        GPtrArray *clut = NULL;
+
+        /* create a lookup table */
+        clut = gcm_session_generate_vcgt (profile,
+                                          gnome_rr_output_get_gamma_size (output));
+
+        /* apply the vcgt to this output */
+        ret = gcm_session_output_set_gamma (output, clut, error);
+        if (!ret)
+                goto out;
+out:
+        if (clut != NULL)
+                g_ptr_array_unref (clut);
+        return ret;
+}
+
+static gboolean
+gcm_session_device_reset_gamma (GnomeRROutput *output,
+                                GError **error)
+{
+        gboolean ret;
+        guint i;
+        guint size;
+        guint32 value;
+        GPtrArray *clut;
+        GnomeRROutputClutItem *data;
+
+        /* create a linear ramp */
+        g_debug ("falling back to dummy ramp");
+        clut = g_ptr_array_new_with_free_func (g_free);
+        size = gnome_rr_output_get_gamma_size (output);
+        for (i = 0; i < size; i++) {
+                value = (i * 0xffff) / (size - 1);
+                data = g_new0 (GnomeRROutputClutItem, 1);
+                data->red = value;
+                data->green = value;
+                data->blue = value;
+                g_ptr_array_add (clut, data);
+        }
+
+        /* apply the vcgt to this output */
+        ret = gcm_session_output_set_gamma (output, clut, error);
+        if (!ret)
+                goto out;
+out:
+        g_ptr_array_unref (clut);
+        return ret;
+}
+
+static GnomeRROutput *
+gcm_session_get_x11_output_by_id (GsdColorManager *manager,
+                                  const gchar *device_id,
+                                  GError **error)
+{
+        gchar *output_id;
+        GnomeRROutput *output = NULL;
+        GnomeRROutput **outputs = NULL;
+        guint i;
+        GsdColorManagerPrivate *priv = manager->priv;
+
+        /* search all X11 outputs for the device id */
+        outputs = gnome_rr_screen_list_outputs (priv->x11_screen);
+        if (outputs == NULL) {
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
+                                     "Failed to get outputs");
+                goto out;
+        }
+        for (i = 0; outputs[i] != NULL && output == NULL; i++) {
+                if (!gnome_rr_output_is_connected (outputs[i]))
+                        continue;
+                output_id = gcm_session_get_output_id (manager, outputs[i]);
+                if (g_strcmp0 (output_id, device_id) == 0)
+                        output = outputs[i];
+                g_free (output_id);
+        }
+        if (output == NULL) {
+                g_set_error (error,
+                             GSD_COLOR_MANAGER_ERROR,
+                             GSD_COLOR_MANAGER_ERROR_FAILED,
+                             "Failed to find output %s",
+                             device_id);
+        }
+out:
+        return output;
+}
+
+static void
+gcm_session_device_assign_profile_connect_cb (GObject *object,
+                                              GAsyncResult *res,
+                                              gpointer user_data)
+{
+        CdProfile *profile = CD_PROFILE (object);
+        const gchar *filename;
+        gboolean ret;
+        GError *error = NULL;
+        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+        GsdColorManager *manager = GSD_COLOR_MANAGER (helper->manager);
+
+        /* get properties */
+        ret = cd_profile_connect_finish (profile, res, &error);
+        if (!ret) {
+                g_warning ("failed to connect to profile: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* get the filename */
+        filename = cd_profile_get_filename (profile);
+        g_assert (filename != NULL);
+
+        /* set the _ICC_PROFILE atom */
+        if (gnome_rr_output_get_is_primary (helper->output)) {
+                ret = gcm_session_screen_set_icc_profile (manager,
+                                                          filename,
+                                                          &error);
+                if (!ret) {
+                        g_warning ("failed to set screen _ICC_PROFILE: %s",
+                                   error->message);
+                        g_clear_error (&error);
+                }
+        }
+
+        /* create a vcgt for this icc file */
+        ret = cd_profile_get_has_vcgt (profile);
+        if (ret) {
+                ret = gcm_session_device_set_gamma (helper->output,
+                                                    profile,
+                                                    &error);
+                if (!ret) {
+                        g_warning ("failed to set %s gamma tables: %s",
+                                   cd_device_get_id (helper->device),
+                                   error->message);
+                        g_error_free (error);
+                        goto out;
+                }
+        } else {
+                ret = gcm_session_device_reset_gamma (helper->output,
+                                                      &error);
+                if (!ret) {
+                        g_warning ("failed to reset %s gamma tables: %s",
+                                   cd_device_get_id (helper->device),
+                                   error->message);
+                        g_error_free (error);
+                        goto out;
+                }
+        }
+out:
+        gcm_session_async_helper_free (helper);
+}
+
+static void
+gcm_session_device_assign_connect_cb (GObject *object,
+                                      GAsyncResult *res,
+                                      gpointer user_data)
+{
+        CdDeviceKind kind;
+        CdProfile *profile = NULL;
+        gboolean ret;
+        gchar *autogen_filename = NULL;
+        gchar *autogen_path = NULL;
+        GcmEdid *edid = NULL;
+        GnomeRROutput *output = NULL;
+        GError *error = NULL;
+        const gchar *xrandr_id;
+        GcmSessionAsyncHelper *helper;
+        CdDevice *device = CD_DEVICE (object);
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+        GsdColorManagerPrivate *priv = manager->priv;
+
+        /* get properties */
+        ret = cd_device_connect_finish (device, res, &error);
+        if (!ret) {
+                g_warning ("failed to connect to device: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* check we care */
+        kind = cd_device_get_kind (device);
+        if (kind != CD_DEVICE_KIND_DISPLAY)
+                goto out;
+
+        g_debug ("need to assign display device %s",
+                 cd_device_get_id (device));
+
+        /* get the GnomeRROutput for the device id */
+        xrandr_id = cd_device_get_id (device);
+        output = gcm_session_get_x11_output_by_id (manager,
+                                                   xrandr_id,
+                                                   &error);
+        if (output == NULL) {
+                g_warning ("no %s device found: %s",
+                           cd_device_get_id (device),
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* get the output EDID */
+        edid = gcm_session_get_output_edid (manager, output, &error);
+        if (edid == NULL) {
+                g_warning ("unable to get EDID for %s: %s",
+                           cd_device_get_id (device),
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* create profile from device edid if it does not exist */
+        autogen_filename = g_strdup_printf ("edid-%s.icc",
+                                            gcm_edid_get_checksum (edid));
+        autogen_path = g_build_filename (g_get_user_data_dir (),
+                                         "icc", autogen_filename, NULL);
+
+        if (g_file_test (autogen_path, G_FILE_TEST_EXISTS)) {
+                g_debug ("auto-profile edid %s exists", autogen_path);
+        } else {
+                g_debug ("auto-profile edid does not exist, creating as %s",
+                         autogen_path);
+                ret = gcm_apply_create_icc_profile_for_edid (manager,
+                                                             edid,
+                                                             autogen_path,
+                                                             &error);
+                if (!ret) {
+                        g_warning ("failed to create profile from EDID data: %s",
+                                     error->message);
+                        g_clear_error (&error);
+                }
+        }
+
+        /* get the default profile for the device */
+        profile = cd_device_get_default_profile (device);
+        if (profile == NULL) {
+                g_debug ("%s has no default profile to set",
+                         cd_device_get_id (device));
+
+                /* the default output? */
+                if (gnome_rr_output_get_is_primary (output)) {
+                        gdk_property_delete (priv->gdk_window,
+                                             gdk_atom_intern_static_string ("_ICC_PROFILE"));
+                        gdk_property_delete (priv->gdk_window,
+                                             gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"));
+                }
+
+                /* reset, as we want linear profiles for profiling */
+                ret = gcm_session_device_reset_gamma (output,
+                                                      &error);
+                if (!ret) {
+                        g_warning ("failed to reset %s gamma tables: %s",
+                                   cd_device_get_id (device),
+                                   error->message);
+                        g_error_free (error);
+                        goto out;
+                }
+                goto out;
+        }
+
+        /* get properties */
+        helper = g_new0 (GcmSessionAsyncHelper, 1);
+        helper->output = output;
+        helper->manager = g_object_ref (manager);
+        helper->device = g_object_ref (device);
+        cd_profile_connect (profile,
+                            NULL,
+                            gcm_session_device_assign_profile_connect_cb,
+                            helper);
+out:
+        g_free (autogen_filename);
+        g_free (autogen_path);
+        if (edid != NULL)
+                g_object_unref (edid);
+        if (profile != NULL)
+                g_object_unref (profile);
+}
+
+static void
+gcm_session_device_assign (GsdColorManager *manager, CdDevice *device)
+{
+        cd_device_connect (device,
+                           NULL,
+                           gcm_session_device_assign_connect_cb,
+                           manager);
+}
+
+static void
+gcm_session_device_added_assign_cb (CdClient *client,
+                                    CdDevice *device,
+                                    GsdColorManager *manager)
+{
+        gcm_session_device_assign (manager, device);
+}
+
+static void
+gcm_session_device_changed_assign_cb (CdClient *client,
+                                      CdDevice *device,
+                                      GsdColorManager *manager)
+{
+        g_debug ("%s changed", cd_device_get_object_path (device));
+        gcm_session_device_assign (manager, device);
+}
+
+static void
+gcm_session_create_device_cb (GObject *object,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+        CdDevice *device;
+        GError *error = NULL;
+
+        device = cd_client_create_device_finish (CD_CLIENT (object),
+                                                 res,
+                                                 &error);
+        if (device == NULL) {
+                g_warning ("failed to create device: %s",
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+        g_object_unref (device);
+}
+
+static void
+gcm_session_add_x11_output (GsdColorManager *manager, GnomeRROutput *output)
+{
+        const gchar *model;
+        const gchar *serial;
+        const gchar *vendor;
+        gboolean ret;
+        gchar *device_id = NULL;
+        GcmEdid *edid;
+        GError *error = NULL;
+        GHashTable *device_props = NULL;
+        GsdColorManagerPrivate *priv = manager->priv;
+
+        /* get edid */
+        edid = gcm_session_get_output_edid (manager, output, &error);
+        if (edid == NULL) {
+                g_warning ("failed to get edid: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* is this an internal device? */
+        ret = gnome_rr_output_is_laptop (output);
+        if (ret) {
+                model = gcm_dmi_get_name (priv->dmi);
+                vendor = gcm_dmi_get_vendor (priv->dmi);
+        } else {
+                model = gcm_edid_get_monitor_name (edid);
+                if (model == NULL)
+                        model = gnome_rr_output_get_name (output);
+                vendor = gcm_edid_get_vendor_name (edid);
+        }
+
+        /* get a serial number if one exists */
+        serial = gcm_edid_get_serial_number (edid);
+        if (serial == NULL)
+                serial = "unknown";
+
+        device_id = gcm_session_get_output_id (manager, output);
+        g_debug ("output %s added", device_id);
+        device_props = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                              NULL, NULL);
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_PROPERTY_KIND,
+                             (gpointer) cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY));
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_PROPERTY_MODE,
+                             (gpointer) cd_device_mode_to_string (CD_DEVICE_MODE_PHYSICAL));
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_PROPERTY_COLORSPACE,
+                             (gpointer) cd_colorspace_to_string (CD_COLORSPACE_RGB));
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_PROPERTY_VENDOR,
+                             (gpointer) vendor);
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_PROPERTY_MODEL,
+                             (gpointer) model);
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_PROPERTY_SERIAL,
+                             (gpointer) serial);
+        g_hash_table_insert (device_props,
+                             (gpointer) CD_DEVICE_METADATA_XRANDR_NAME,
+                             (gpointer) gnome_rr_output_get_name (output));
+        cd_client_create_device (priv->client,
+                                 device_id,
+                                 CD_OBJECT_SCOPE_TEMP,
+                                 device_props,
+                                 NULL,
+                                 gcm_session_create_device_cb,
+                                 manager);
+out:
+        g_free (device_id);
+        if (device_props != NULL)
+                g_hash_table_unref (device_props);
+        if (edid != NULL)
+                g_object_unref (edid);
+}
+
+
+static void
+gnome_rr_screen_output_added_cb (GnomeRRScreen *screen,
+                                GnomeRROutput *output,
+                                GsdColorManager *manager)
+{
+        gcm_session_add_x11_output (manager, output);
+}
+
+static void
+gcm_session_screen_removed_delete_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+        gboolean ret;
+        GError *error = NULL;
+
+        /* deleted device */
+        ret = cd_client_delete_device_finish (CD_CLIENT (object),
+                                              res,
+                                              &error);
+        if (!ret) {
+                g_warning ("failed to delete device: %s",
+                           error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+gcm_session_screen_removed_find_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+        GError *error = NULL;
+        CdDevice *device;
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+        device = cd_client_find_device_finish (manager->priv->client,
+                                               res,
+                                               &error);
+        if (device == NULL) {
+                g_warning ("failed to find device: %s",
+                           error->message);
+                g_error_free (error);
+                return;
+        }
+        g_debug ("output %s found, and will be removed",
+                 cd_device_get_object_path (device));
+        cd_client_delete_device (manager->priv->client,
+                                 device,
+                                 NULL,
+                                 gcm_session_screen_removed_delete_device_cb,
+                                 manager);
+        g_object_unref (device);
+}
+
+static void
+gnome_rr_screen_output_removed_cb (GnomeRRScreen *screen,
+                                  GnomeRROutput *output,
+                                  GsdColorManager *manager)
+{
+        g_debug ("output %s removed",
+                 gnome_rr_output_get_name (output));
+        cd_client_find_device (manager->priv->client,
+                               gnome_rr_output_get_name (output),
+                               NULL,
+                               gcm_session_screen_removed_find_device_cb,
+                               manager);
+}
+
+static void
+gcm_session_get_devices_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+        CdDevice *device;
+        GError *error = NULL;
+        GPtrArray *array;
+        guint i;
+        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
+
+        array = cd_client_get_devices_finish (CD_CLIENT (object), res, &error);
+        if (array == NULL) {
+                g_warning ("failed to get devices: %s",
+                           error->message);
+                g_error_free (error);
+                goto out;
+        }
+        for (i = 0; i < array->len; i++) {
+                device = g_ptr_array_index (array, i);
+                gcm_session_device_assign (manager, device);
+        }
+out:
+        if (array != NULL)
+                g_ptr_array_unref (array);
+}
+
 static void
 gcm_session_client_connect_cb (GObject *source_object,
                                GAsyncResult *res,
@@ -71,6 +1276,8 @@ gcm_session_client_connect_cb (GObject *source_object,
 {
         gboolean ret;
         GError *error = NULL;
+        GnomeRROutput **outputs;
+        guint i;
         GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
         GsdColorManagerPrivate *priv = manager->priv;
 
@@ -85,6 +1292,50 @@ gcm_session_client_connect_cb (GObject *source_object,
 
         /* add profiles */
         gcm_profile_store_search (priv->profile_store);
+
+        /* add screens */
+        gnome_rr_screen_refresh (priv->x11_screen, &error);
+        if (error != NULL) {
+                g_warning ("failed to refresh: %s", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        /* get X11 outputs */
+        outputs = gnome_rr_screen_list_outputs (priv->x11_screen);
+        if (outputs == NULL) {
+                g_warning ("failed to get outputs");
+                goto out;
+        }
+        for (i = 0; outputs[i] != NULL; i++) {
+                if (gnome_rr_output_is_connected (outputs[i]))
+                        gcm_session_add_x11_output (manager, outputs[i]);
+        }
+
+        /* only connect when colord is awake */
+        g_signal_connect (priv->x11_screen, "output-connected",
+                          G_CALLBACK (gnome_rr_screen_output_added_cb),
+                          manager);
+        g_signal_connect (priv->x11_screen, "output-disconnected",
+                          G_CALLBACK (gnome_rr_screen_output_removed_cb),
+                          manager);
+
+        g_signal_connect (priv->client, "profile-added",
+                          G_CALLBACK (gcm_session_profile_added_assign_cb),
+                          manager);
+        g_signal_connect (priv->client, "device-added",
+                          G_CALLBACK (gcm_session_device_added_assign_cb),
+                          manager);
+        g_signal_connect (priv->client, "device-changed",
+                          G_CALLBACK (gcm_session_device_changed_assign_cb),
+                          manager);
+
+        /* set for each device that already exist */
+        cd_client_get_devices (priv->client, NULL,
+                               gcm_session_get_devices_cb,
+                               manager);
+out:
+        return;
 }
 
 gboolean
@@ -92,17 +1343,26 @@ gsd_color_manager_start (GsdColorManager *manager,
                          GError          **error)
 {
         GsdColorManagerPrivate *priv = manager->priv;
+        gboolean ret = FALSE;
 
         g_debug ("Starting color manager");
         gnome_settings_profile_start (NULL);
 
+        /* coldplug the list of screens */
+        priv->x11_screen = gnome_rr_screen_new (gdk_screen_get_default (), error);
+        if (priv->x11_screen == NULL)
+                goto out;
+
         cd_client_connect (priv->client,
                            NULL,
                            gcm_session_client_connect_cb,
                            manager);
 
+        /* success */
+        ret = TRUE;
+out:
         gnome_settings_profile_end (NULL);
-        return TRUE;
+        return ret;
 }
 
 void
@@ -271,11 +1531,6 @@ gcm_session_notify_device (GsdColorManager *manager, CdDevice *device)
         g_free (message);
 }
 
-typedef struct {
-        GsdColorManager         *manager;
-        CdDevice                *device;
-} GsdColorManagerDeviceHelper;
-
 static void
 gcm_session_profile_connect_cb (GObject *object,
                                 GAsyncResult *res,
@@ -287,7 +1542,7 @@ gcm_session_profile_connect_cb (GObject *object,
         gchar *basename = NULL;
         GError *error = NULL;
         CdProfile *profile = CD_PROFILE (object);
-        GsdColorManagerDeviceHelper *helper = (GsdColorManagerDeviceHelper *) user_data;
+        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
         GsdColorManager *manager = GSD_COLOR_MANAGER (helper->manager);
 
         ret = cd_profile_connect_finish (profile,
@@ -320,9 +1575,7 @@ gcm_session_profile_connect_cb (GObject *object,
         /* handle device */
         gcm_session_notify_device (manager, helper->device);
 out:
-        g_object_unref (helper->device);
-        g_object_unref (helper->manager);
-        g_free (helper);
+        gcm_session_async_helper_free (helper);
         g_free (basename);
 }
 
@@ -337,7 +1590,7 @@ gcm_session_device_connect_cb (GObject *object,
         CdProfile *profile = NULL;
         CdDevice *device = CD_DEVICE (object);
         GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
-        GsdColorManagerDeviceHelper *helper;
+        GcmSessionAsyncHelper *helper;
 
         ret = cd_device_connect_finish (device,
                                         res,
@@ -353,7 +1606,7 @@ gcm_session_device_connect_cb (GObject *object,
         kind = cd_device_get_kind (device);
         if (kind != CD_DEVICE_KIND_DISPLAY &&
             kind != CD_DEVICE_KIND_PRINTER)
-                return;
+                goto out;
 
         /* ensure we have a profile */
         profile = cd_device_get_default_profile (device);
@@ -363,7 +1616,7 @@ gcm_session_device_connect_cb (GObject *object,
         }
 
         /* connect to the profile */
-        helper = g_new0 (GsdColorManagerDeviceHelper, 1);
+        helper = g_new0 (GcmSessionAsyncHelper, 1);
         helper->manager = g_object_ref (manager);
         helper->device = g_object_ref (device);
         cd_profile_connect (profile,
@@ -398,7 +1651,7 @@ gcm_session_get_precooked_md5 (cmsHPROFILE lcms_profile)
 
         /* check to see if we have a pre-cooked MD5 */
         cmsGetHeaderProfileID (lcms_profile, profile_id);
-        for (i=0; i<16; i++) {
+        for (i = 0; i < 16; i++) {
                 if (profile_id[i] != 0) {
                         md5_precooked = TRUE;
                         break;
@@ -409,7 +1662,7 @@ gcm_session_get_precooked_md5 (cmsHPROFILE lcms_profile)
 
         /* convert to a hex string */
         md5 = g_new0 (gchar, 32 + 1);
-        for (i=0; i<16; i++)
+        for (i = 0; i < 16; i++)
                 g_snprintf (md5 + i*2, 3, "%02x", profile_id[i]);
 out:
         return md5;
@@ -431,7 +1684,9 @@ gcm_session_get_md5_for_filename (const gchar *filename,
         /* get the internal profile id, if it exists */
         lcms_profile = cmsOpenProfileFromFile (filename, "r");
         if (lcms_profile == NULL) {
-                g_set_error_literal (error, 1, 0,
+                g_set_error_literal (error,
+                                     GSD_COLOR_MANAGER_ERROR,
+                                     GSD_COLOR_MANAGER_ERROR_FAILED,
                                      "failed to load: not an ICC profile");
                 goto out;
         }
@@ -664,6 +1919,18 @@ gsd_color_manager_init (GsdColorManager *manager)
         GsdColorManagerPrivate *priv;
         priv = manager->priv = GSD_COLOR_MANAGER_GET_PRIVATE (manager);
 
+        /* set the _ICC_PROFILE atoms on the root screen */
+        priv->gdk_window = gdk_screen_get_root_window (gdk_screen_get_default ());
+
+        /* parsing the EDID is expensive */
+        priv->edid_cache = g_hash_table_new_full (g_str_hash,
+                                                  g_str_equal,
+                                                  g_free,
+                                                  g_object_unref);
+
+        /* use DMI data for internal panels */
+        priv->dmi = gcm_dmi_new ();
+
         priv->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
         priv->client = cd_client_new ();
         g_signal_connect (priv->client, "device-added",
@@ -701,6 +1968,10 @@ gsd_color_manager_finalize (GObject *object)
         g_object_unref (manager->priv->settings);
         g_object_unref (manager->priv->client);
         g_object_unref (manager->priv->profile_store);
+        g_object_unref (manager->priv->dmi);
+        g_hash_table_destroy (manager->priv->edid_cache);
+        if (manager->priv->x11_screen != NULL);
+                g_object_unref (manager->priv->x11_screen);
 
         G_OBJECT_CLASS (gsd_color_manager_parent_class)->finalize (object);
 }
diff --git a/plugins/color/gsd-color-manager.h b/plugins/color/gsd-color-manager.h
index 5180f3b..27321a7 100644
--- a/plugins/color/gsd-color-manager.h
+++ b/plugins/color/gsd-color-manager.h
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
 #define GSD_IS_COLOR_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_COLOR_MANAGER))
 #define GSD_IS_COLOR_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_COLOR_MANAGER))
 #define GSD_COLOR_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_COLOR_MANAGER, GsdColorManagerClass))
+#define GSD_COLOR_MANAGER_ERROR        (gsd_color_manager_error_quark ())
 
 typedef struct GsdColorManagerPrivate GsdColorManagerPrivate;
 
@@ -46,7 +47,13 @@ typedef struct
         GObjectClass   parent_class;
 } GsdColorManagerClass;
 
+enum
+{
+        GSD_COLOR_MANAGER_ERROR_FAILED
+};
+
 GType                   gsd_color_manager_get_type            (void);
+GQuark                  gsd_color_manager_error_quark         (void);
 
 GsdColorManager *       gsd_color_manager_new                 (void);
 gboolean                gsd_color_manager_start               (GsdColorManager *manager,
diff --git a/plugins/color/test-data/LG-L225W-External.bin b/plugins/color/test-data/LG-L225W-External.bin
new file mode 100644
index 0000000..f08310a
Binary files /dev/null and b/plugins/color/test-data/LG-L225W-External.bin differ
diff --git a/plugins/color/test-data/Lenovo-T61-Internal.bin b/plugins/color/test-data/Lenovo-T61-Internal.bin
new file mode 100644
index 0000000..45aec9d
Binary files /dev/null and b/plugins/color/test-data/Lenovo-T61-Internal.bin differ



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