[gnome-color-manager: 2/80] Add a simple command gcm-ddc-util to be able to send custom DDC packets to a monitor in userspace



commit b562e48d8a11d29802e28494c4e1f481ad664b62
Author: Richard Hughes <richard hughsie com>
Date:   Sat Jul 17 22:27:01 2010 +0100

    Add a simple command gcm-ddc-util to be able to send custom DDC packets to a monitor in userspace

 contrib/reset.sh                |    1 +
 libcolor-glib/.gitignore        |   16 +
 libcolor-glib/Makefile.am       |   16 +-
 libcolor-glib/gcm-ddc-client.c  |  342 +++++++++++++
 libcolor-glib/gcm-ddc-client.h  |   99 ++++
 libcolor-glib/gcm-ddc-common.c  |  171 +++++++
 libcolor-glib/gcm-ddc-common.h  |   59 +++
 libcolor-glib/gcm-ddc-control.c |  446 +++++++++++++++++
 libcolor-glib/gcm-ddc-control.h |  116 +++++
 libcolor-glib/gcm-ddc-device.c  | 1041 +++++++++++++++++++++++++++++++++++++++
 libcolor-glib/gcm-ddc-device.h  |  132 +++++
 libcolor-glib/gcm-self-test.c   |   26 +
 libcolor-glib/libcolor-glib.h   |    4 +
 src/.gitignore                  |    1 +
 src/Makefile.am                 |   16 +
 src/gcm-ddc-util.c              |  310 ++++++++++++
 16 files changed, 2794 insertions(+), 2 deletions(-)
---
diff --git a/contrib/reset.sh b/contrib/reset.sh
new file mode 100755
index 0000000..01ea901
--- /dev/null
+++ b/contrib/reset.sh
@@ -0,0 +1 @@
+xrandr --output DVI1 --off && xrandr --output DVI1 --auto && xrandr --output DVI1 --right-of LVDS1
diff --git a/libcolor-glib/.gitignore b/libcolor-glib/.gitignore
new file mode 100644
index 0000000..a87d1f0
--- /dev/null
+++ b/libcolor-glib/.gitignore
@@ -0,0 +1,16 @@
+.deps
+.libs
+*.a
+*.o
+*.la
+*.lo
+*-marshal.c
+*-marshal.h
+*-self-test
+*.loT
+*.db
+*.sh
+*-version.h
+*.gir
+*.typelib
+*.pc
diff --git a/libcolor-glib/Makefile.am b/libcolor-glib/Makefile.am
index 1819424..b639dd0 100644
--- a/libcolor-glib/Makefile.am
+++ b/libcolor-glib/Makefile.am
@@ -29,14 +29,26 @@ libcolor_glib_includedir = $(includedir)/libcolor-glib
 
 libcolor_glib_include_HEADERS =					\
 	libcolor-glib.h						\
-	gcm-version.h						\
 	gcm-common.h						\
+	gcm-ddc-client.h					\
+	gcm-ddc-device.h					\
+	gcm-ddc-control.h					\
+	gcm-ddc-common.h					\
+	gcm-version.h						\
 	$(NULL)
 
 libcolor_glib_la_SOURCES =					\
 	libcolor-glib.h						\
-	gcm-common.h						\
 	gcm-common.c						\
+	gcm-common.h						\
+	gcm-ddc-client.c					\
+	gcm-ddc-client.h					\
+	gcm-ddc-device.c					\
+	gcm-ddc-device.h					\
+	gcm-ddc-control.c					\
+	gcm-ddc-control.h					\
+	gcm-ddc-common.c					\
+	gcm-ddc-common.h					\
 	gcm-version.h						\
 	$(NULL)
 
diff --git a/libcolor-glib/gcm-ddc-client.c b/libcolor-glib/gcm-ddc-client.c
new file mode 100644
index 0000000..af79c85
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-client.c
@@ -0,0 +1,342 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:gcm-ddc-client
+ * @short_description: For managing different i2c devices
+ *
+ * A GObject to use for accessing devices.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <stdlib.h>
+
+#include <gcm-ddc-client.h>
+#include <gcm-ddc-device.h>
+
+static void     gcm_ddc_client_finalize	(GObject     *object);
+
+#define GCM_DDC_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_DDC_CLIENT, GcmDdcClientPrivate))
+
+/**
+ * GcmDdcClientPrivate:
+ *
+ * Private #GcmDdcClient data
+ **/
+struct _GcmDdcClientPrivate
+{
+	GPtrArray		*devices;
+	gboolean		 has_coldplug;
+	GcmVerbose		 verbose;
+};
+
+enum {
+	PROP_0,
+	PROP_HAS_COLDPLUG,
+	PROP_LAST
+};
+
+G_DEFINE_TYPE (GcmDdcClient, gcm_ddc_client, G_TYPE_OBJECT)
+
+/**
+ * gcm_ddc_client_ensure_coldplug:
+ **/
+static gboolean
+gcm_ddc_client_ensure_coldplug (GcmDdcClient *client, GError **error)
+{
+	gboolean ret = FALSE;
+	gboolean any_found = FALSE;
+	guint i;
+	gchar *filename;
+	GError *error_local = NULL;
+	GcmDdcDevice *device;
+
+	g_return_val_if_fail (GCM_IS_DDC_CLIENT(client), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* already done */
+	if (client->priv->has_coldplug)
+		return TRUE;
+
+	/* ensure we have the module loaded */
+	ret = g_file_test ("/sys/module/i2c_dev/srcversion", G_FILE_TEST_EXISTS);
+	if (!ret) {
+		g_set_error_literal (error, GCM_DDC_CLIENT_ERROR, GCM_DDC_CLIENT_ERROR_FAILED,
+				     "unable to use I2C, you need to 'modprobe i2c-dev'");
+		goto out;
+	}
+
+	/* try each i2c port */
+	for (i=0; i<16; i++) {
+		filename = g_strdup_printf ("/dev/i2c-%i", i);
+		if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
+			g_free (filename);
+			break;
+		}
+		device = gcm_ddc_device_new ();
+		gcm_ddc_device_set_verbose (device, client->priv->verbose);
+		ret = gcm_ddc_device_open (device, filename, &error_local);
+		if (!ret) {
+			if (client->priv->verbose == GCM_VERBOSE_OVERVIEW)
+				g_warning ("failed to open %s: %s", filename, error_local->message);
+			g_clear_error (&error_local);
+		} else {
+			if (client->priv->verbose == GCM_VERBOSE_OVERVIEW)
+				g_debug ("success, adding %s", filename);
+			any_found = TRUE;
+			g_ptr_array_add (client->priv->devices, g_object_ref (device));
+		}
+		g_object_unref (device);
+		g_free (filename);
+	}
+
+	/* nothing found */
+	if (!any_found) {
+		g_set_error_literal (error, GCM_DDC_CLIENT_ERROR, GCM_DDC_CLIENT_ERROR_FAILED,
+				     "No devices found");
+		goto out;
+	}
+
+	/* success */
+	client->priv->has_coldplug = TRUE;
+out:
+	return any_found;
+}
+
+/**
+ * gcm_ddc_client_close:
+ **/
+gboolean
+gcm_ddc_client_close (GcmDdcClient *client, GError **error)
+{
+	guint i;
+	gboolean ret = TRUE;
+	GcmDdcDevice *device;
+
+	g_return_val_if_fail (GCM_IS_DDC_CLIENT(client), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* iterate each device */
+	for (i=0; i<client->priv->devices->len; i++) {
+		device = g_ptr_array_index (client->priv->devices, i);
+		ret = gcm_ddc_device_close (device, error);
+		if (!ret)
+			goto out;
+	}
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_client_get_devices:
+ **/
+GPtrArray *
+gcm_ddc_client_get_devices (GcmDdcClient *client, GError **error)
+{
+	gboolean ret;
+	GPtrArray *devices = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_CLIENT(client), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_client_ensure_coldplug (client, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	devices = g_ptr_array_ref (client->priv->devices);
+out:
+	return devices;
+}
+
+/**
+ * gcm_ddc_client_get_device_from_edid:
+ **/
+GcmDdcDevice *
+gcm_ddc_client_get_device_from_edid (GcmDdcClient *client, const gchar *edid_md5, GError **error)
+{
+	guint i;
+	gboolean ret;
+	const gchar *edid_md5_tmp;
+	GcmDdcDevice *device = NULL;
+	GcmDdcDevice *device_tmp;
+
+	g_return_val_if_fail (GCM_IS_DDC_CLIENT(client), NULL);
+	g_return_val_if_fail (edid_md5 != NULL, NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_client_ensure_coldplug (client, error);
+	if (!ret)
+		goto out;
+
+	/* iterate each device */
+	for (i=0; i<client->priv->devices->len; i++) {
+		device_tmp = g_ptr_array_index (client->priv->devices, i);
+
+		/* get the md5 of the device */
+		edid_md5_tmp = gcm_ddc_device_get_edid_md5 (device_tmp, error);
+		if (edid_md5_tmp == NULL)
+			goto out;
+
+		/* matches? */
+		if (g_strcmp0 (edid_md5, edid_md5_tmp) == 0) {
+			device = device_tmp;
+			break;
+		}
+	}
+
+	/* failure */
+	if (device == NULL) {
+		g_set_error (error, GCM_DDC_CLIENT_ERROR, GCM_DDC_CLIENT_ERROR_FAILED,
+			     "No devices found with edid %s", edid_md5);
+	}
+out:
+	return device;
+}
+
+/**
+ * gcm_ddc_client_set_verbose:
+ **/
+void
+gcm_ddc_client_set_verbose (GcmDdcClient *client, GcmVerbose verbose)
+{
+	g_return_if_fail (GCM_IS_DDC_CLIENT(client));
+	client->priv->verbose = verbose;
+}
+
+/**
+ * gcm_ddc_client_error_quark:
+ *
+ * Return value: Our personal error quark.
+ *
+ * Since: 0.0.1
+ **/
+GQuark
+gcm_ddc_client_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("gcm_ddc_client_error");
+	return quark;
+}
+
+/**
+ * gcm_ddc_client_get_property:
+ **/
+static void
+gcm_ddc_client_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	GcmDdcClient *client = GCM_DDC_CLIENT (object);
+	GcmDdcClientPrivate *priv = client->priv;
+
+	switch (prop_id) {
+	case PROP_HAS_COLDPLUG:
+		g_value_set_boolean (value, priv->has_coldplug);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * gcm_ddc_client_set_property:
+ **/
+static void
+gcm_ddc_client_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	switch (prop_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * gcm_ddc_client_class_init:
+ **/
+static void
+gcm_ddc_client_class_init (GcmDdcClientClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = gcm_ddc_client_finalize;
+	object_class->get_property = gcm_ddc_client_get_property;
+	object_class->set_property = gcm_ddc_client_set_property;
+
+	/**
+	 * GcmDdcClient:has-coldplug:
+	 *
+	 * Since: 0.0.1
+	 */
+	pspec = g_param_spec_boolean ("has-coldplug", NULL, "if there are no transactions in progress on this client",
+				      TRUE,
+				      G_PARAM_READABLE);
+	g_object_class_install_property (object_class, PROP_HAS_COLDPLUG, pspec);
+
+	g_type_class_add_private (klass, sizeof (GcmDdcClientPrivate));
+}
+
+/**
+ * gcm_ddc_client_init:
+ **/
+static void
+gcm_ddc_client_init (GcmDdcClient *client)
+{
+	client->priv = GCM_DDC_CLIENT_GET_PRIVATE (client);
+	client->priv->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+}
+
+/**
+ * gcm_ddc_client_finalize:
+ **/
+static void
+gcm_ddc_client_finalize (GObject *object)
+{
+	GcmDdcClient *client = GCM_DDC_CLIENT (object);
+	GcmDdcClientPrivate *priv = client->priv;
+
+	g_return_if_fail (GCM_IS_DDC_CLIENT(client));
+
+	g_ptr_array_unref (priv->devices);
+	G_OBJECT_CLASS (gcm_ddc_client_parent_class)->finalize (object);
+}
+
+/**
+ * gcm_ddc_client_new:
+ *
+ * GcmDdcClient is a nice GObject wrapper for gcm.
+ *
+ * Return value: A new %GcmDdcClient instance
+ *
+ * Since: 0.0.1
+ **/
+GcmDdcClient *
+gcm_ddc_client_new (void)
+{
+	GcmDdcClient *client;
+	client = g_object_new (GCM_TYPE_DDC_CLIENT, NULL);
+	return GCM_DDC_CLIENT (client);
+}
diff --git a/libcolor-glib/gcm-ddc-client.h b/libcolor-glib/gcm-ddc-client.h
new file mode 100644
index 0000000..736f88c
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-client.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#if !defined (__GCM_H_INSIDE__) && !defined (GCM_COMPILATION)
+#error "Only <gcm.h> can be included directly."
+#endif
+
+#ifndef __GCM_DDC_CLIENT_H
+#define __GCM_DDC_CLIENT_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <gcm-ddc-common.h>
+#include <gcm-ddc-device.h>
+#include <gcm-ddc-client.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_DDC_CLIENT		(gcm_ddc_client_get_type ())
+#define GCM_DDC_CLIENT(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_DDC_CLIENT, GcmDdcClient))
+#define GCM_DDC_CLIENT_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_DDC_CLIENT, GcmDdcClientClass))
+#define GCM_IS_DDC_CLIENT(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_DDC_CLIENT))
+#define GCM_IS_DDC_CLIENT_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_DDC_CLIENT))
+#define GCM_DDC_CLIENT_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_DDC_CLIENT, GcmDdcClientClass))
+#define GCM_DDC_CLIENT_ERROR		(gcm_ddc_client_error_quark ())
+#define GCM_DDC_CLIENT_TYPE_ERROR	(gcm_ddc_client_error_get_type ())
+
+/**
+ * GcmDdcClientError:
+ * @GCM_DDC_CLIENT_ERROR_FAILED: the transaction failed for an unknown reason
+ *
+ * Errors that can be thrown
+ */
+typedef enum
+{
+	GCM_DDC_CLIENT_ERROR_FAILED
+} GcmDdcClientError;
+
+typedef struct _GcmDdcClientPrivate		GcmDdcClientPrivate;
+typedef struct _GcmDdcClient			GcmDdcClient;
+typedef struct _GcmDdcClientClass		GcmDdcClientClass;
+
+struct _GcmDdcClient
+{
+	 GObject		 parent;
+	 GcmDdcClientPrivate	*priv;
+};
+
+struct _GcmDdcClientClass
+{
+	GObjectClass	parent_class;
+
+	/* signals */
+	void		(* changed)			(GcmDdcClient	*client);
+	/* padding for future expansion */
+	void (*_gcm_reserved1) (void);
+	void (*_gcm_reserved2) (void);
+	void (*_gcm_reserved3) (void);
+	void (*_gcm_reserved4) (void);
+	void (*_gcm_reserved5) (void);
+};
+
+GQuark		 gcm_ddc_client_error_quark		(void);
+GType		 gcm_ddc_client_get_type		  	(void);
+GcmDdcClient	*gcm_ddc_client_new			(void);
+
+gboolean	 gcm_ddc_client_close			(GcmDdcClient		*client,
+							 GError			**error);
+GPtrArray	*gcm_ddc_client_get_devices		(GcmDdcClient		*client,
+							 GError			**error);
+GcmDdcDevice	*gcm_ddc_client_get_device_from_edid	(GcmDdcClient		*client,
+							 const gchar		*edid_md5,
+							 GError			**error);
+void		 gcm_ddc_client_set_verbose		(GcmDdcClient		*client,
+							 GcmVerbose		 verbose);
+
+G_END_DECLS
+
+#endif /* __GCM_DDC_CLIENT_H */
+
diff --git a/libcolor-glib/gcm-ddc-common.c b/libcolor-glib/gcm-ddc-common.c
new file mode 100644
index 0000000..465a09a
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-common.c
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:gcm-ddc-common
+ * @short_description: Common functionality
+ *
+ * A GObject to use for common shizzle.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include <gcm-ddc-common.h>
+
+typedef struct {
+	guchar		 index;
+	const gchar	*shortname;
+} GcmDescription;
+
+static const GcmDescription vcp_descriptions[] = {
+//	{ 0x00, "degauss" },
+	{ 0x01,	"degauss" },
+	{ 0x02,	"secondary-degauss" },
+	{ 0x04,	"reset-factory-defaults" },
+	{ 0x05,	"reset-brightness-and-contrast" },
+	{ 0x06,	"reset-factory-geometry" },
+	{ 0x08,	"reset-factory-default-color" },
+	{ 0x0a,	"reset-factory-default-position" },
+	{ 0x0c,	"reset-factory-default-size" },
+	{ 0x0e, "image-lock-coarse" },
+	{ 0x10, "brightness" },
+	{ 0x12, "contrast" },
+	{ 0x13, "backlight" },
+	{ 0x14,	"select-color-preset" },
+	{ 0x16,	"red-video-gain" },
+	{ 0x18,	"green-video-gain" },
+	{ 0x1a,	"blue-video-gain" },
+	{ 0x1c, "hue" },
+	{ 0x1e,	"auto-size-center" },
+	{ 0x20,	"horizontal-position" },
+	{ 0x22,	"horizontal-size" },
+	{ 0x24,	"horizontal-pincushion" },
+	{ 0x26,	"horizontal-pincushion-balance" },
+	{ 0x28,	"horizontal-misconvergence" },
+	{ 0x2a,	"horizontal-linearity" },
+	{ 0x2c,	"horizontal-linearity-balance" },
+	{ 0x30,	"vertical-position" },
+	{ 0x32,	"vertical-size" },
+	{ 0x34,	"vertical-pincushion" },
+	{ 0x36,	"vertical-pincushion-balance" },
+	{ 0x38,	"vertical-misconvergence" },
+	{ 0x3a,	"vertical-linearity" },
+	{ 0x3c,	"vertical-linearity-balance" },
+	{ 0x3e,	"image-lock-fine" },
+	{ 0x40,	"parallelogram-distortion" },
+	{ 0x42,	"trapezoidal-distortion" },
+	{ 0x44, "tilt" },
+	{ 0x46,	"top-corner-distortion-control" },
+	{ 0x48,	"top-corner-distortion-balance" },
+	{ 0x4a,	"bottom-corner-distortion-control" },
+	{ 0x4c,	"bottom-corner-distortion-balance" },
+	{ 0x50,	"hue" },
+	{ 0x52,	"saturation" },
+	{ 0x54, "color-temp" },
+	{ 0x56,	"horizontal-moire" },
+	{ 0x58,	"vertical-moire" },
+	{ 0x5a, "auto-size" },
+	{ 0x5c,	"landing-adjust" },
+	{ 0x5e,	"input-level-select" },
+	{ 0x60,	"input-source-select" },
+	{ 0x62,	"audio-speaker-volume-adjust" },
+	{ 0x64,	"audio-microphone-volume-adjust" },
+	{ 0x66,	"on-screen-displa" },
+	{ 0x68,	"language-select" },
+	{ 0x6c,	"red-video-black-level" },
+	{ 0x6e,	"green-video-black-level" },
+	{ 0x70,	"blue-video-black-level" },
+	{ 0x8c, "sharpness" },
+	{ 0x94, "mute" },
+	{ 0xa2,	"auto-size-center" },
+	{ 0xa4,	"polarity-horizontal-synchronization" },
+	{ 0xa6,	"polarity-vertical-synchronization" },
+	{ 0xa8,	"synchronization-type" },
+	{ 0xaa,	"screen-orientation" },
+	{ 0xac,	"horizontal-frequency" },
+	{ 0xae,	"vertical-frequency" },
+	{ 0xb0,	"settings" },
+	{ 0xca,	"on-screen-display" },
+	{ 0xcc,	"samsung-on-screen-display-language" },
+	{ 0xc9,	"firmware-version" },
+	{ 0xd4,	"stereo-mode" },
+	{ 0xd6,	"dpms-control-(1---on/4---stby)" },
+	{ 0xdc,	"magicbright-(1---text/2---internet/3---entertain/4---custom)" },
+	{ 0xdf,	"vcp-version" },
+	{ 0xe0,	"samsung-color-preset-(0---normal/1---warm/2---cool)" },
+	{ 0xe1,	"power-control-(0---off/1---on)" },
+	{ 0xe2, "auto-source" },
+	{ 0xe8, "tl-purity" },
+	{ 0xe9, "tr-purity" },
+	{ 0xea, "bl-purity" },
+	{ 0xeb, "br-purity" },
+	{ 0xed,	"samsung-red-video-black-level" },
+	{ 0xee,	"samsung-green-video-black-level" },
+	{ 0xef,	"samsung-blue-video-black-level" },
+	{ 0xf0, "magic-color" },
+	{ 0xf1, "fe-brightness" },
+	{ 0xf2, "fe-clarity / gamma" },
+	{ 0xf3, "fe-color" },
+	{ 0xf5, "samsung-osd" },
+	{ 0xf6, "resolutionnotifier" },
+	{ 0xf9, "super-bright" },
+	{ 0xfc, "fe-mode" },
+	{ 0xfd, "power-led" },
+	{ GCM_VCP_ID_INVALID, NULL }
+	};
+
+/**
+ * gcm_get_vcp_description_from_index:
+ **/
+const gchar *
+gcm_get_vcp_description_from_index (guchar idx)
+{
+	guint i;
+
+	g_return_val_if_fail (idx != GCM_VCP_ID_INVALID, NULL);
+
+	for (i=0;;i++) {
+		if (vcp_descriptions[i].index == idx ||
+		    vcp_descriptions[i].index == GCM_VCP_ID_INVALID)
+			break;
+	}
+	return vcp_descriptions[i].shortname;
+}
+
+/**
+ * gcm_get_vcp_index_from_description:
+ **/
+guchar
+gcm_get_vcp_index_from_description (const gchar *description)
+{
+	guint i;
+
+	g_return_val_if_fail (description != NULL, GCM_VCP_ID_INVALID);
+
+	for (i=0;;i++) {
+		if (g_strcmp0 (vcp_descriptions[i].shortname, description) == 0 ||
+		    vcp_descriptions[i].shortname == NULL)
+			break;
+	}
+	return vcp_descriptions[i].index;
+}
diff --git a/libcolor-glib/gcm-ddc-common.h b/libcolor-glib/gcm-ddc-common.h
new file mode 100644
index 0000000..9aea474
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-common.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 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.
+ */
+
+#if !defined (__GCM_H_INSIDE__) && !defined (GCM_COMPILATION)
+#error "Only <gcm.h> can be included directly."
+#endif
+
+#ifndef __GCM_DDC_COMMON_H__
+#define __GCM_DDC_COMMON_H__
+
+#define __GCM_DDC_COMMON_H_INSIDE__
+
+typedef enum {
+	GCM_VERBOSE_NONE,
+	GCM_VERBOSE_OVERVIEW,
+	GCM_VERBOSE_PROTOCOL
+} GcmVerbose;
+
+#define GCM_VCP_REQUEST			0x01
+#define GCM_VCP_REPLY			0x02
+#define GCM_VCP_SET			0x03
+#define GCM_VCP_RESET			0x09
+#define GCM_DEFAULT_DDCCI_ADDR		0x37
+#define GCM_DEFAULT_EDID_ADDR		0x50
+#define GCM_CAPABILITIES_REQUEST	0xf3
+#define GCM_CAPABILITIES_REPLY		0xe3
+#define GCM_COMMAND_PRESENCE		0xf7
+#define GCM_ENABLE_APPLICATION_REPORT	0xf5
+
+#define	GCM_VCP_ID_INVALID		0x00
+
+#define GCM_CTRL_DISABLE		0x0000
+#define GCM_CTRL_ENABLE			0x0001
+
+const gchar	*gcm_get_vcp_description_from_index	(guchar		 idx);
+guchar		 gcm_get_vcp_index_from_description	(const gchar	*description);
+
+#undef __GCM_DDC_COMMON_H_INSIDE__
+
+#endif /* __GCM_DDC_COMMON_H__ */
+
diff --git a/libcolor-glib/gcm-ddc-control.c b/libcolor-glib/gcm-ddc-control.c
new file mode 100644
index 0000000..3e51a31
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-control.c
@@ -0,0 +1,446 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:gcm-ddc-control
+ * @short_description: For managing different i2c controls
+ *
+ * A GObject to use for accessing controls.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib-object.h>
+
+#include <gcm-ddc-device.h>
+#include <gcm-ddc-control.h>
+
+static void     gcm_ddc_control_finalize	(GObject     *object);
+
+#define GCM_DDC_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_DDC_CONTROL, GcmDdcControlPrivate))
+
+/**
+ * GcmDdcControlPrivate:
+ *
+ * Private #GcmDdcControl data
+ **/
+struct _GcmDdcControlPrivate
+{
+	guchar			 id;
+	gboolean		 supported;
+	GcmDdcDevice		*device;
+	GcmVerbose		 verbose;
+	GArray			*values;
+};
+
+enum {
+	PROP_0,
+	PROP_SUPPORTED,
+	PROP_LAST
+};
+
+G_DEFINE_TYPE (GcmDdcControl, gcm_ddc_control, G_TYPE_OBJECT)
+
+#define GCM_VCP_SET_DELAY_USECS   		50000
+
+/**
+ * gcm_ddc_control_get_description:
+ **/
+const gchar *
+gcm_ddc_control_get_description (GcmDdcControl *control)
+{
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), NULL);
+
+	return gcm_get_vcp_description_from_index (control->priv->id);
+}
+
+/**
+ * gcm_ddc_control_is_value_valid:
+ **/
+static gboolean
+gcm_ddc_control_is_value_valid (GcmDdcControl *control, guint16 value, GError **error)
+{
+	guint i;
+	gboolean ret = TRUE;
+	GArray *array;
+	GString *possible;
+
+	/* no data */
+	array = control->priv->values;
+	if (array->len == 0)
+		goto out;
+
+	/* see if it is present in the description */
+	for (i=0; i<array->len; i++) {
+		ret = (g_array_index (array, guint16, i) == value);
+		if (ret)
+			goto out;
+	}
+
+	/* not found */
+	if (!ret) {
+		possible = g_string_new ("");
+		for (i=0; i<array->len; i++)
+			g_string_append_printf (possible, "%i ", g_array_index (array, guint16, i));
+		g_set_error (error, GCM_DDC_CONTROL_ERROR, GCM_DDC_CONTROL_ERROR_FAILED,
+			     "%i is not an allowed value for 0x%02x, possible values include %s",
+			     value, control->priv->id, possible->str);
+		g_string_free (possible, TRUE);
+	}
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_control_set:
+ *
+ * write value to register ctrl of ddc/ci
+ **/
+gboolean
+gcm_ddc_control_set (GcmDdcControl *control, guint16 value, GError **error)
+{
+	gboolean ret = FALSE;
+	guchar buf[4];
+
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* check this value is allowed */
+	ret = gcm_ddc_control_is_value_valid (control, value, error);
+	if (!ret)
+		goto out;
+
+	buf[0] = GCM_VCP_SET;
+	buf[1] = control->priv->id;
+	buf[2] = (value >> 8);
+	buf[3] = (value & 255);
+
+	ret = gcm_ddc_device_write (control->priv->device, buf, sizeof(buf), error);
+	if (!ret)
+		goto out;
+
+	/* Do the delay */
+	g_usleep (GCM_VCP_SET_DELAY_USECS);
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_control_reset:
+ **/
+gboolean
+gcm_ddc_control_reset (GcmDdcControl *control, GError **error)
+{
+	gboolean ret;
+	guchar buf[2];
+
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	buf[0] = GCM_VCP_RESET;
+	buf[1] = control->priv->id;
+
+	ret = gcm_ddc_device_write (control->priv->device, buf, sizeof(buf), error);
+	if (!ret)
+		goto out;
+
+	/* Do the delay */
+	g_usleep (GCM_VCP_SET_DELAY_USECS);
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_control_request:
+ **/
+gboolean
+gcm_ddc_control_request (GcmDdcControl *control, guint16 *value, guint16 *maximum, GError **error)
+{
+	gboolean ret = FALSE;
+	guchar buf[8];
+	gsize len;
+
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* request data */
+	buf[0] = GCM_VCP_REQUEST;
+	buf[1] = control->priv->id;
+	if (!gcm_ddc_device_write (control->priv->device, buf, 2, error))
+		goto out;
+
+	/* get data */
+	ret = gcm_ddc_device_read (control->priv->device, buf, 8, &len, error);
+	if (!ret)
+		goto out;
+
+	/* check we got enough data */
+	if (len != sizeof(buf)) {
+		g_set_error (error, GCM_DDC_CONTROL_ERROR, GCM_DDC_CONTROL_ERROR_FAILED,
+			     "Failed to parse control 0x%02x as incorrect length", control->priv->id);
+		ret = FALSE;
+		goto out;
+	}
+
+	/* message type incorrect */
+	if (buf[0] != GCM_VCP_REPLY) {
+		g_set_error (error, GCM_DDC_CONTROL_ERROR, GCM_DDC_CONTROL_ERROR_FAILED,
+			     "Failed to parse control 0x%02x as incorrect command returned", control->priv->id);
+		ret = FALSE;
+		goto out;
+	}
+
+	/* ensure the control is supported by the display */
+	if (buf[1] != 0) {
+		g_set_error (error, GCM_DDC_CONTROL_ERROR, GCM_DDC_CONTROL_ERROR_FAILED,
+			     "Failed to parse control 0x%02x as unsupported", control->priv->id);
+		ret = FALSE;
+		goto out;
+	}
+
+	/* check we are getting the correct control */
+	if (buf[2] != control->priv->id) {
+		g_set_error (error, GCM_DDC_CONTROL_ERROR, GCM_DDC_CONTROL_ERROR_FAILED,
+			     "Failed to parse control 0x%02x as incorrect id returned", control->priv->id);
+		ret = FALSE;
+		goto out;
+	}
+
+	if (value != NULL)
+		*value = buf[6] * 256 + buf[7];
+	if (maximum != NULL)
+		*maximum = buf[4] * 256 + buf[5];
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_control_run:
+ **/
+gboolean
+gcm_ddc_control_run (GcmDdcControl *control, GError **error)
+{
+	guchar buf[1];
+
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	buf[0] = control->priv->id;
+	return gcm_ddc_device_write (control->priv->device, buf, sizeof(buf), error);
+}
+
+/**
+ * gcm_ddc_control_parse:
+ **/
+void
+gcm_ddc_control_parse (GcmDdcControl *control, guchar id, const gchar *values)
+{
+	guint i;
+	guint16 value;
+	gchar **split = NULL;
+
+	g_return_if_fail (GCM_IS_DDC_CONTROL(control));
+
+	/* just save this */
+	control->priv->id = id;
+	if (control->priv->verbose == GCM_VERBOSE_OVERVIEW)
+		g_debug ("add control 0x%02x (%s)", id, gcm_ddc_control_get_description (control));
+
+	/* do we have any values to parse */
+	if (values == NULL)
+		goto out;
+
+	/* tokenize */
+	split = g_strsplit (values, " ", -1);
+	for (i=0; split[i] != NULL; i++) {
+		value = atoi (split[i]);
+		if (control->priv->verbose == GCM_VERBOSE_OVERVIEW)
+			g_debug ("add value %i to control 0x%02x", value, id);
+		g_array_append_val (control->priv->values, value);
+	}
+out:
+	g_strfreev (split);
+}
+
+/**
+ * gcm_ddc_control_set_device:
+ **/
+void
+gcm_ddc_control_set_device (GcmDdcControl *control, GcmDdcDevice *device)
+{
+	g_return_if_fail (GCM_IS_DDC_CONTROL(control));
+	g_return_if_fail (GCM_IS_DDC_DEVICE(device));
+	control->priv->device = g_object_ref (device);
+}
+
+/**
+ * gcm_ddc_control_get_id:
+ **/
+guchar
+gcm_ddc_control_get_id (GcmDdcControl *control)
+{
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), 0);
+
+	return control->priv->id;
+}
+
+/**
+ * gcm_ddc_control_get_values:
+ *
+ * Return value: an array of guint16
+ **/
+GArray *
+gcm_ddc_control_get_values (GcmDdcControl *control)
+{
+	g_return_val_if_fail (GCM_IS_DDC_CONTROL(control), NULL);
+
+	return g_array_ref (control->priv->values);
+}
+
+/**
+ * gcm_ddc_control_set_verbose:
+ **/
+void
+gcm_ddc_control_set_verbose (GcmDdcControl *control, GcmVerbose verbose)
+{
+	g_return_if_fail (GCM_IS_DDC_CONTROL(control));
+	control->priv->verbose = verbose;
+}
+
+/**
+ * gcm_ddc_control_error_quark:
+ *
+ * Return value: Our personal error quark.
+ *
+ * Since: 0.0.1
+ **/
+GQuark
+gcm_ddc_control_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("gcm_ddc_control_error");
+	return quark;
+}
+
+/**
+ * gcm_ddc_control_get_property:
+ **/
+static void
+gcm_ddc_control_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	GcmDdcControl *control = GCM_DDC_CONTROL (object);
+	GcmDdcControlPrivate *priv = control->priv;
+
+	switch (prop_id) {
+	case PROP_SUPPORTED:
+		g_value_set_boolean (value, priv->supported);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * gcm_ddc_control_set_property:
+ **/
+static void
+gcm_ddc_control_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	switch (prop_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * gcm_ddc_control_class_init:
+ **/
+static void
+gcm_ddc_control_class_init (GcmDdcControlClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = gcm_ddc_control_finalize;
+	object_class->get_property = gcm_ddc_control_get_property;
+	object_class->set_property = gcm_ddc_control_set_property;
+
+	/**
+	 * GcmDdcControl:supported:
+	 *
+	 * Since: 0.0.1
+	 */
+	pspec = g_param_spec_boolean ("supported", NULL, "if there are no transactions in progress on this control",
+				      TRUE,
+				      G_PARAM_READABLE);
+	g_object_class_install_property (object_class, PROP_SUPPORTED, pspec);
+
+	g_type_class_add_private (klass, sizeof (GcmDdcControlPrivate));
+}
+
+/**
+ * gcm_ddc_control_init:
+ **/
+static void
+gcm_ddc_control_init (GcmDdcControl *control)
+{
+	control->priv = GCM_DDC_CONTROL_GET_PRIVATE (control);
+	control->priv->id = 0xff;
+	control->priv->values = g_array_new (FALSE, FALSE, sizeof(guint16));
+}
+
+/**
+ * gcm_ddc_control_finalize:
+ **/
+static void
+gcm_ddc_control_finalize (GObject *object)
+{
+	GcmDdcControl *control = GCM_DDC_CONTROL (object);
+	GcmDdcControlPrivate *priv = control->priv;
+
+	g_return_if_fail (GCM_IS_DDC_CONTROL(control));
+
+	g_array_free (priv->values, TRUE);
+	if (priv->device != NULL)
+		g_object_unref (priv->device);
+
+	G_OBJECT_CLASS (gcm_ddc_control_parent_class)->finalize (object);
+}
+
+/**
+ * gcm_ddc_control_new:
+ *
+ * GcmDdcControl is a nice GObject wrapper for gcm.
+ *
+ * Return value: A new %GcmDdcControl instance
+ *
+ * Since: 0.0.1
+ **/
+GcmDdcControl *
+gcm_ddc_control_new (void)
+{
+	GcmDdcControl *control;
+	control = g_object_new (GCM_TYPE_DDC_CONTROL, NULL);
+	return GCM_DDC_CONTROL (control);
+}
diff --git a/libcolor-glib/gcm-ddc-control.h b/libcolor-glib/gcm-ddc-control.h
new file mode 100644
index 0000000..eb7e036
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-control.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#if !defined (__GCM_H_INSIDE__) && !defined (GCM_COMPILATION)
+#error "Only <gcm.h> can be included directly."
+#endif
+
+#ifndef __GCM_DDC_CONTROL_H
+#define __GCM_DDC_CONTROL_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <gcm-ddc-common.h>
+#include <gcm-ddc-control.h>
+#include <gcm-ddc-device.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_DDC_CONTROL		(gcm_ddc_control_get_type ())
+#define GCM_DDC_CONTROL(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_DDC_CONTROL, GcmDdcControl))
+#define GCM_DDC_CONTROL_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_DDC_CONTROL, GcmDdcControlClass))
+#define GCM_IS_DDC_CONTROL(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_DDC_CONTROL))
+#define GCM_IS_DDC_CONTROL_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_DDC_CONTROL))
+#define GCM_DDC_CONTROL_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_DDC_CONTROL, GcmDdcControlClass))
+#define GCM_DDC_CONTROL_ERROR		(gcm_ddc_control_error_quark ())
+#define GCM_DDC_CONTROL_TYPE_ERROR	(gcm_ddc_control_error_get_type ())
+
+/**
+ * GcmDdcControlError:
+ * @GCM_DDC_CONTROL_ERROR_FAILED: the transaction failed for an unknown reason
+ *
+ * Errors that can be thrown
+ */
+typedef enum
+{
+	GCM_DDC_CONTROL_ERROR_FAILED
+} GcmDdcControlError;
+
+typedef struct _GcmDdcControlPrivate		GcmDdcControlPrivate;
+typedef struct _GcmDdcControl			GcmDdcControl;
+typedef struct _GcmDdcControlClass		GcmDdcControlClass;
+
+struct _GcmDdcControl
+{
+	 GObject		 parent;
+	 GcmDdcControlPrivate	*priv;
+};
+
+struct _GcmDdcControlClass
+{
+	GObjectClass	parent_class;
+	/* padding for future expansion */
+	void (*_gcm_reserved1) (void);
+	void (*_gcm_reserved2) (void);
+	void (*_gcm_reserved3) (void);
+	void (*_gcm_reserved4) (void);
+	void (*_gcm_reserved5) (void);
+};
+
+typedef struct {
+	guint			 id;
+	GPtrArray		*int_values;
+} GcmDdcControlCap;
+
+/* control numbers */
+#define GCM_DDC_CONTROL_ID_BRIGHTNESS			0x10
+
+GQuark		 gcm_ddc_control_error_quark		(void);
+GType		 gcm_ddc_control_get_type		(void);
+GcmDdcControl	*gcm_ddc_control_new			(void);
+
+void		 gcm_ddc_control_parse			(GcmDdcControl	*control,
+							 guchar		 id,
+							 const gchar	*values);
+void		 gcm_ddc_control_set_device		(GcmDdcControl	*control,
+							 GcmDdcDevice	*device);
+void		 gcm_ddc_control_set_verbose		(GcmDdcControl	*control,
+							 GcmVerbose verbose);
+gboolean	 gcm_ddc_control_run			(GcmDdcControl	*control,
+							 GError		**error);
+gboolean	 gcm_ddc_control_request			(GcmDdcControl	*control,
+							 guint16	*value,
+							 guint16	*maximum,
+							 GError		**error);
+gboolean	 gcm_ddc_control_set			(GcmDdcControl	*control,
+							 guint16	 value,
+							 GError		**error);
+gboolean	 gcm_ddc_control_reset			(GcmDdcControl	*control,
+							 GError		**error);
+guchar		 gcm_ddc_control_get_id			(GcmDdcControl	*control);
+const gchar	*gcm_ddc_control_get_description		(GcmDdcControl	*control);
+GArray		*gcm_ddc_control_get_values		(GcmDdcControl	*control);
+
+G_END_DECLS
+
+#endif /* __GCM_DDC_CONTROL_H */
+
diff --git a/libcolor-glib/gcm-ddc-device.c b/libcolor-glib/gcm-ddc-device.c
new file mode 100644
index 0000000..912c04a
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-device.c
@@ -0,0 +1,1041 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:gcm-ddc-device
+ * @short_description: For managing different i2c devices
+ *
+ * A GObject to use for accessing devices.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <glib/gstdio.h>
+#include <string.h>
+
+#include <gcm-ddc-device.h>
+#include <gcm-ddc-control.h>
+
+static void     gcm_ddc_device_finalize	(GObject     *object);
+
+#define GCM_DDC_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCM_TYPE_DDC_DEVICE, GcmDdcDevicePrivate))
+
+/* ddc/ci iface tunables */
+#define GCM_MAX_MESSAGE_BYTES		127
+#define GCM_READ_DELAY_SECS   		0.04f
+#define GCM_WRITE_DELAY_SECS   		0.05f
+
+#define GCM_SAVE_CURRENT_SETTINGS	0x0c
+#define GCM_SAVE_DELAY_USECS   		200000
+
+/* magic numbers */
+#define GCM_MAGIC_BYTE1			0x51	/* host address */
+#define GCM_MAGIC_BYTE2			0x80	/* ored with length */
+#define GCM_MAGIC_XOR 			0x50	/* initial xor for received frame */
+
+/**
+ * GcmDdcDevicePrivate:
+ *
+ * Private #GcmDdcDevice data
+ **/
+struct _GcmDdcDevicePrivate
+{
+	gint			 fd;
+	guint			 addr;
+	GcmDdcDeviceKind	 kind;
+	gchar			*model;
+	gchar			*pnpid;
+	guint8			*edid_data;
+	gsize			 edid_length;
+	gchar			*edid_md5;
+	GPtrArray		*controls;
+	gboolean		 has_controls;
+	gboolean		 has_edid;
+	gdouble			 required_wait;
+	GTimer			*timer;
+	GcmVerbose		 verbose;
+};
+
+enum {
+	PROP_0,
+	PROP_HAS_EDID,
+	PROP_LAST
+};
+
+G_DEFINE_TYPE (GcmDdcDevice, gcm_ddc_device, G_TYPE_OBJECT)
+
+/**
+ * gcm_ddc_device_print_hex_data:
+ **/
+static void
+gcm_ddc_device_print_hex_data (const gchar *text, const guchar *data, gsize length)
+{
+	guint i;
+
+	g_print ("%s: ", text);
+	for (i=0; i<length; i++) {
+		if (!g_ascii_isprint (data[i]))
+			g_print ("%02x [?]  ", data[i]);
+		else
+			g_print ("%02x [%c]  ", data[i], data[i]);
+		if (i % 8 == 7)
+			g_print ("\n      ");
+	}
+	g_print ("\n");
+}
+
+/**
+ * gcm_ddc_device_set_required_wait:
+ **/
+static void
+gcm_ddc_device_set_required_wait (GcmDdcDevice *device, gdouble delay)
+{
+	device->priv->required_wait = delay;
+}
+
+/**
+ * gcm_ddc_device_i2c_write:
+ **/
+static gboolean
+gcm_ddc_device_i2c_write (GcmDdcDevice *device, guint addr, const guchar *data, gsize length, GError **error)
+{
+	gint i;
+	struct i2c_rdwr_ioctl_data msg_rdwr;
+	struct i2c_msg i2cmsg;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* done, prepare message */
+	msg_rdwr.msgs = &i2cmsg;
+	msg_rdwr.nmsgs = 1;
+
+	i2cmsg.addr  = addr;
+	i2cmsg.flags = 0;
+	i2cmsg.len   = length;
+	i2cmsg.buf   = (unsigned char *) data;
+
+	/* hit hardware */
+	i = ioctl (device->priv->fd, I2C_RDWR, &msg_rdwr);
+	if (i < 0 ) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "ioctl returned %d", i);
+		return FALSE;
+	}
+
+	if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+		gcm_ddc_device_print_hex_data ("Send", data, length);
+	return TRUE;
+}
+
+/**
+ * gcm_ddc_device_i2c_read:
+ **/
+static gboolean
+gcm_ddc_device_i2c_read (GcmDdcDevice *device, guint addr, guchar *data, gsize data_length, gsize *recieved_length, GError **error)
+{
+	struct i2c_rdwr_ioctl_data msg_rdwr;
+	struct i2c_msg i2cmsg;
+	gint i;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	msg_rdwr.msgs = &i2cmsg;
+	msg_rdwr.nmsgs = 1;
+
+	i2cmsg.addr  = addr;
+	i2cmsg.flags = I2C_M_RD;
+	i2cmsg.len   = data_length;
+	i2cmsg.buf   = data;
+
+	/* hit hardware */
+	i = ioctl (device->priv->fd, I2C_RDWR, &msg_rdwr);
+	if (i < 0) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "ioctl returned %d", i);
+		return FALSE;
+	}
+
+	if (recieved_length != NULL)
+		*recieved_length = i2cmsg.len;
+
+	if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+		gcm_ddc_device_print_hex_data ("Recv", data, i2cmsg.len);
+	return TRUE;
+}
+
+/**
+ * gcm_ddc_device_edid_valid:
+ **/
+static gboolean
+gcm_ddc_device_edid_valid (const guint8 *data, gsize length)
+{
+	if (length < 0x0f)
+		return FALSE;
+	if (data[0] != 0 || data[1] != 0xff || data[2] != 0xff || data[3] != 0xff ||
+	    data[4] != 0xff || data[5] != 0xff || data[6] != 0xff || data[7] != 0) {
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/**
+ * gcm_ddc_device_ensure_edid:
+ **/
+static gboolean
+gcm_ddc_device_ensure_edid (GcmDdcDevice *device, GError **error)
+{
+	gboolean ret = FALSE;
+	GError *error_local = NULL;
+	gint addr = GCM_DEFAULT_EDID_ADDR;
+	guchar buf[128];
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* already done */
+	if (device->priv->has_edid)
+		return TRUE;
+
+	/* send edid with offset zero */
+	buf[0] = 0;
+	if (!gcm_ddc_device_i2c_write (device, addr, buf, 1, &error_local)) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "failed to request EDID: %s", error_local->message);
+		g_error_free (error_local);
+		goto out;
+	}
+
+	/* read out data */
+	device->priv->edid_data = g_new0 (guint8, 128);
+	if (!gcm_ddc_device_i2c_read (device, addr, device->priv->edid_data, 128, &device->priv->edid_length, &error_local)) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "failed to recieve EDID: %s", error_local->message);
+		g_error_free (error_local);
+		goto out;
+	}
+
+	/* check valid */
+	ret = gcm_ddc_device_edid_valid (device->priv->edid_data, device->priv->edid_length);
+	if (!ret) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "corrupted EDID at 0x%02x", addr);
+		goto out;
+	}
+
+	/* get md5 hash */
+	device->priv->edid_md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+							device->priv->edid_data,
+							device->priv->edid_length);
+
+	/* print */
+	device->priv->pnpid = g_strdup_printf ("%c%c%c%02X%02X",
+		 ((device->priv->edid_data[8] >> 2) & 31) + 'A' - 1,
+		 ((device->priv->edid_data[8] & 3) << 3) + (device->priv->edid_data[9] >> 5) + 'A' - 1,
+		 (device->priv->edid_data[9] & 31) + 'A' - 1, device->priv->edid_data[11], device->priv->edid_data[10]);
+	device->priv->has_edid = TRUE;
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_wait_for_hardware:
+ *
+ * Stalls execution, allowing write transaction to complete
+ **/
+static void
+gcm_ddc_device_wait_for_hardware (GcmDdcDevice *device)
+{
+	gdouble elapsed;
+	GcmDdcDevicePrivate *priv = device->priv;
+
+	/* only wait if enough time hasn't yet passed */
+	elapsed = g_timer_elapsed (priv->timer, NULL);
+	if (elapsed < priv->required_wait)
+		g_usleep ((priv->required_wait - elapsed) * G_USEC_PER_SEC);
+	g_timer_reset (priv->timer);
+}
+
+/**
+ * gcm_ddc_device_write:
+ *
+ * write data to ddc/ci at address addr
+ **/
+gboolean
+gcm_ddc_device_write (GcmDdcDevice *device, guchar *data, gsize length, GError **error)
+{
+	gint i = 0;
+	guchar buf[GCM_MAX_MESSAGE_BYTES + 3];
+	unsigned xor;
+	gboolean ret;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* initial xor value */
+	xor = ((guchar)device->priv->addr << 1);
+
+	/* put first magic */
+	xor ^= (buf[i++] = GCM_MAGIC_BYTE1);
+
+	/* second magic includes message size */
+	xor ^= (buf[i++] = GCM_MAGIC_BYTE2 | length);
+
+	/* bytes to send */
+	while (length--)
+		xor ^= (buf[i++] = *data++);
+
+	/* finally put checksum */
+	buf[i++] = xor;
+
+	/* wait for previous write to complete */
+	gcm_ddc_device_wait_for_hardware (device);
+
+	/* write to device */
+	ret = gcm_ddc_device_i2c_write (device, device->priv->addr, buf, i, error);
+	if (!ret)
+		goto out;
+
+	/* we have to wait at least this much time before submitting another command */
+	gcm_ddc_device_set_required_wait (device, GCM_WRITE_DELAY_SECS);
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_read:
+ *
+ * Read ddc/ci formatted frame from ddc/ci
+ **/
+gboolean
+gcm_ddc_device_read (GcmDdcDevice *device, guchar *data, gsize data_length, gsize *recieved_length, GError **error)
+{
+	guchar buf[GCM_MAX_MESSAGE_BYTES];
+	guchar xor = GCM_MAGIC_XOR;
+	guint i;
+	gsize len;
+	gboolean ret;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* wait for previous write to complete */
+	gcm_ddc_device_wait_for_hardware (device);
+
+	/* get data */
+	ret = gcm_ddc_device_i2c_read (device, device->priv->addr, buf, data_length + 3, recieved_length, error);
+	if (!ret)
+		goto out;
+
+	/* validate answer */
+	if (buf[0] != device->priv->addr * 2) { /* busy ??? */
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "Invalid response, first byte is 0x%02x, should be 0x%02x",
+			     buf[0], device->priv->addr * 2);
+		if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+			gcm_ddc_device_print_hex_data ("Bugz", buf, data_length + 3);
+		ret = FALSE;
+		goto out;
+	}
+
+	if ((buf[1] & GCM_MAGIC_BYTE2) == 0) {
+		/* Fujitsu Siemens P19-2 and NEC LCD 1970NX send wrong magic when reading caps. */
+		g_debug ( "Invalid response, magic is 0x%02x, correcting", buf[1]);
+	}
+
+	len = buf[1] & ~GCM_MAGIC_BYTE2;
+	if (len > data_length || len > sizeof(buf)) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "Invalid response, length is %d, should be %d at most",
+			     len, data_length);
+		ret = FALSE;
+		goto out;
+	}
+
+	/* get the xor value */
+	for (i = 0; i < len + 3; i++)
+		xor ^= buf[i];
+	if (xor != 0) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "Invalid response, corrupted data - xor is 0x%02x, length 0x%02x", xor, len);
+		if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+			gcm_ddc_device_print_hex_data ("Bugz", buf, data_length + 3);
+		ret = FALSE;
+		goto out;
+	}
+
+	/* copy payload data */
+	memcpy (data, buf + 2, len);
+	if (recieved_length != NULL)
+		*recieved_length = len;
+
+	/* we have to wait at least this much time before reading the results */
+	gcm_ddc_device_set_required_wait (device, GCM_READ_DELAY_SECS);
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_capabilities_request:
+ *
+ * read capabilities raw data of ddc/ci
+ **/
+static gint
+gcm_ddc_device_capabilities_request (GcmDdcDevice *device, guint offset, guchar *data, gsize data_length, gsize *recieved_length, GError **error)
+{
+	guchar buf[3];
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	buf[0] = GCM_CAPABILITIES_REQUEST;
+	buf[1] = offset >> 8;
+	buf[2] = offset & 255;
+
+	if (!gcm_ddc_device_write (device, buf, sizeof(buf), error))
+		return FALSE;
+
+	return gcm_ddc_device_read (device, data, data_length, recieved_length, error);
+}
+
+/**
+ * gcm_ddc_device_add_control:
+ **/
+static gboolean
+gcm_ddc_device_add_control (GcmDdcDevice *device, const gchar *index_str, const gchar *controls_str)
+{
+	GcmDdcControl *control;
+	control = gcm_ddc_control_new ();
+	gcm_ddc_control_set_verbose (control, device->priv->verbose);
+	gcm_ddc_control_set_device (control, device);
+	gcm_ddc_control_parse (control, strtoul (index_str, NULL, 16), controls_str);
+	g_ptr_array_add (device->priv->controls, control);
+	return TRUE;
+}
+
+/**
+ * gcm_ddc_device_set_device_property:
+ **/
+static gboolean
+gcm_ddc_device_set_device_property (GcmDdcDevice *device, const gchar *key, const gchar *value)
+{
+	if (device->priv->verbose == GCM_VERBOSE_OVERVIEW)
+		g_debug ("key=%s, value=%s", key, value);
+	if (g_strcmp0 (key, "type") == 0) {
+		if (g_strcmp0 (value, "lcd") == 0)
+			device->priv->kind = GCM_DDC_DEVICE_KIND_LCD;
+		else if (g_strcmp0 (value, "crt") == 0)
+			device->priv->kind = GCM_DDC_DEVICE_KIND_CRT;
+	} else if (g_strcmp0 (key, "model") == 0)
+		device->priv->model = g_strdup (value);
+	else if (g_strcmp0 (key, "vcp") == 0) {
+		guint i;
+		gchar *tmp;
+		gchar *index_str;
+		gchar *caps_str = NULL;
+		guint refcount = 0;
+
+		tmp = g_strdup (value);
+		index_str = tmp;
+		for (i=0; tmp[i] != '\0'; i++) {
+			if (tmp[i] == '(') {
+				tmp[i] = '\0';
+				refcount++;
+				caps_str = &tmp[i] + 1;
+			} else if (tmp[i] == ')') {
+				tmp[i] = '\0';
+				refcount--;
+			} else if (tmp[i] == ' ' && refcount == 0) {
+				tmp[i] = '\0';
+				gcm_ddc_device_add_control (device, index_str, caps_str);
+				index_str = &tmp[i] + 1;
+				caps_str = NULL;
+			}
+		}
+		gcm_ddc_device_add_control (device, index_str, caps_str);
+	}
+	return TRUE;
+}
+
+/**
+ * gcm_ddc_device_parse_caps:
+ **/
+static gboolean
+gcm_ddc_device_parse_caps (GcmDdcDevice *device, const gchar *caps)
+{
+	guint i;
+	gchar *tmp = g_strdup (caps);
+	gchar *key = tmp+1;
+	gchar *value = NULL;
+	guint refcount = 0;
+
+	/* decode string */
+	for (i=1; tmp[i] != '\0'; i++) {
+		if (tmp[i] == '(') {
+			if (refcount++ == 0) {
+				tmp[i] = '\0';
+				value = &tmp[i+1];
+			}
+		} else if (tmp[i] == ')') {
+			if (--refcount == 0) {
+				tmp[i] = '\0';
+				gcm_ddc_device_set_device_property (device, key, value);
+				key = &tmp[i]+1;
+			}
+		}
+	}
+
+	g_free (tmp);
+	return TRUE;
+}
+
+/**
+ * gcm_ddc_device_ensure_controls:
+ **/
+static gboolean
+gcm_ddc_device_ensure_controls (GcmDdcDevice *device, GError **error)
+{
+	guchar buf[64];
+	gint offset = 0;
+	gsize len;
+	gint retries = 5;
+	GString *string;
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* already done */
+	if (device->priv->has_controls)
+		return TRUE;
+
+	/* allocate space for the controls */
+	string = g_string_new ("");
+	do {
+		/* we're shit out of luck, Brian */
+		if (retries == 0)
+			goto out;
+
+		/* clear previous error */
+		g_clear_error (error);
+
+		/* try to read */
+		ret = gcm_ddc_device_capabilities_request (device, offset, buf, sizeof(buf), &len, error);
+		if (!ret) {
+			if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+				g_warning ("Failed to read capabilities offset 0x%02x.", offset);
+			retries--;
+			continue;
+		}
+
+		/* not enough data */
+		if (len < 3) {
+			if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+				g_warning ("Not enough capabilities data at offset 0x%02x.", offset);
+			retries--;
+			continue;
+		}
+
+		/* check response */
+		if (buf[0] != GCM_CAPABILITIES_REPLY) {
+			if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+				g_warning ("Not correct capabilities reply at offset 0x%02x.", offset);
+			retries--;
+			continue;
+		}
+
+		/* check offset */
+		if ((buf[1] * 256 + buf[2]) != offset) {
+			if (device->priv->verbose == GCM_VERBOSE_PROTOCOL)
+				g_warning ("Not correct capabilities offset at offset 0x%02x.", offset);
+			retries--;
+			continue;
+		}
+
+		/* add to results */
+		g_string_append_len (string, (const gchar *) buf + 3, len - 3);
+		offset += len - 3;
+		retries = 3;
+	} while (len != 3);
+
+	if (device->priv->verbose == GCM_VERBOSE_OVERVIEW)
+		g_debug ("raw caps: %s", string->str);
+
+	/* parse */
+	ret = gcm_ddc_device_parse_caps (device, string->str);
+	if (!ret) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "failed to parse caps");
+		goto out;
+	}
+
+	/* success */
+	device->priv->has_controls = TRUE;
+out:
+	g_string_free (string, TRUE);
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_save:
+ **/
+gboolean
+gcm_ddc_device_save (GcmDdcDevice *device, GError **error)
+{
+	gboolean ret = FALSE;
+	GcmDdcControl *control;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* get control */
+	control = gcm_ddc_device_get_control_by_id (device, GCM_SAVE_CURRENT_SETTINGS, error);
+	if (control == NULL)
+		goto out;
+
+	/* run it */
+	ret = gcm_ddc_control_run (control, error);
+	if (!ret)
+		goto out;
+
+	/* super long delay to allow for saving to eeprom */
+	g_usleep (GCM_SAVE_DELAY_USECS);
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_startup:
+ **/
+static gboolean
+gcm_ddc_device_startup (GcmDdcDevice *device, GError **error)
+{
+	GcmDdcControl *control;
+	gboolean ret = FALSE;
+	if (device->priv->pnpid != NULL && g_str_has_prefix (device->priv->pnpid, "SAM")) {
+		control = gcm_ddc_device_get_control_by_id (device, GCM_ENABLE_APPLICATION_REPORT, error);
+		if (control == NULL)
+			goto out;
+		ret = gcm_ddc_control_set (control, GCM_CTRL_ENABLE, error);
+	} else {
+		/* this is not fatal if it's not found */
+		control = gcm_ddc_device_get_control_by_id (device, GCM_COMMAND_PRESENCE, NULL);
+		if (control == NULL) {
+			ret = TRUE;
+			goto out;
+		}
+		ret = gcm_ddc_control_run (control, error);
+	}
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_close:
+ **/
+gboolean
+gcm_ddc_device_close (GcmDdcDevice *device, GError **error)
+{
+	GcmDdcControl *control;
+	gboolean ret = FALSE;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	if (device->priv->pnpid != NULL && g_str_has_prefix (device->priv->pnpid, "SAM")) {
+		control = gcm_ddc_device_get_control_by_id (device, GCM_ENABLE_APPLICATION_REPORT, error);
+		if (control == NULL)
+			goto out;
+		ret = gcm_ddc_control_set (control, GCM_CTRL_DISABLE, error);
+	} else {
+		ret = TRUE;
+	}
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_open:
+ **/
+gboolean
+gcm_ddc_device_open (GcmDdcDevice *device, const gchar *filename, GError **error)
+{
+	gboolean ret;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), FALSE);
+	g_return_val_if_fail (filename != NULL, FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	/* ensure we have the module loaded */
+	ret = g_file_test ("/sys/module/i2c_dev/srcversion", G_FILE_TEST_EXISTS);
+	if (!ret) {
+		g_set_error_literal (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "unable to use I2C, you need to 'modprobe i2c-dev'");
+		goto out;
+	}
+
+	/* open file */
+	device->priv->fd = open (filename, O_RDWR);
+	if (device->priv->fd < 0) {
+		ret = FALSE;
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "failed to open: %i", device->priv->fd);
+		goto out;
+	}
+
+	/* enable interface (need edid for pnpid) */
+	ret = gcm_ddc_device_ensure_edid (device, error);
+	if (!ret)
+		goto out;
+
+	/* startup for samsung mode */
+	ret = gcm_ddc_device_startup (device, error);
+	if (!ret)
+		goto out;
+out:
+	return ret;
+}
+
+/**
+ * gcm_ddc_device_get_controls:
+ **/
+GPtrArray *
+gcm_ddc_device_get_controls (GcmDdcDevice *device, GError **error)
+{
+	gboolean ret;
+	GPtrArray *controls = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_controls (device, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	controls = g_ptr_array_ref (device->priv->controls);
+out:
+	return controls;
+}
+
+/**
+ * gcm_ddc_device_get_control_by_id:
+ **/
+GcmDdcControl *
+gcm_ddc_device_get_control_by_id (GcmDdcDevice *device, guchar id, GError **error)
+{
+	guint i;
+	gboolean ret;
+	GcmDdcControl *control = NULL;
+	GcmDdcControl *control_tmp = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_controls (device, error);
+	if (!ret)
+		goto out;
+
+	/* search each control */
+	for (i=0; i<device->priv->controls->len; i++) {
+		control_tmp = g_ptr_array_index (device->priv->controls, i);
+		if (gcm_ddc_control_get_id (control_tmp) == id) {
+			control = g_object_ref (control_tmp);
+			break;
+		}
+	}
+
+	/* failure */
+	if (control == NULL) {
+		g_set_error (error, GCM_DDC_DEVICE_ERROR, GCM_DDC_DEVICE_ERROR_FAILED,
+			     "could not find a control id 0x%02x", (guint) id);
+	}
+out:
+	return control;
+}
+
+/**
+ * gcm_ddc_device_get_edid:
+ **/
+const guint8 *
+gcm_ddc_device_get_edid	(GcmDdcDevice *device, gsize *length, GError **error)
+{
+	gboolean ret;
+	const guint8 *data = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_edid (device, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	data = device->priv->edid_data;
+	if (length != NULL)
+		*length = device->priv->edid_length;
+out:
+	return data;
+}
+
+/**
+ * gcm_ddc_device_get_edid_md5:
+ **/
+const gchar *
+gcm_ddc_device_get_edid_md5 (GcmDdcDevice *device, GError **error)
+{
+	gboolean ret;
+	const gchar *data = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_edid (device, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	data = device->priv->edid_md5;
+out:
+	return data;
+}
+
+/**
+ * gcm_ddc_device_get_pnpid:
+ **/
+const gchar *
+gcm_ddc_device_get_pnpid (GcmDdcDevice	*device, GError **error)
+{
+	gboolean ret;
+	const gchar *pnpid = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_edid (device, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	pnpid = device->priv->pnpid;
+out:
+	return pnpid;
+}
+
+/**
+ * gcm_ddc_device_get_model:
+ **/
+const gchar *
+gcm_ddc_device_get_model (GcmDdcDevice	*device, GError **error)
+{
+	gboolean ret;
+	const gchar *model = NULL;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_controls (device, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	model = device->priv->model;
+out:
+	return model;
+}
+
+/**
+ * gcm_ddc_device_get_kind:
+ **/
+GcmDdcDeviceKind
+gcm_ddc_device_get_kind (GcmDdcDevice *device, GError **error)
+{
+	gboolean ret;
+	GcmDdcDeviceKind kind = GCM_DDC_DEVICE_KIND_UNKNOWN;
+
+	g_return_val_if_fail (GCM_IS_DDC_DEVICE(device), kind);
+	g_return_val_if_fail (error == NULL || *error == NULL, kind);
+
+	/* get capabilities */
+	ret = gcm_ddc_device_ensure_controls (device, error);
+	if (!ret)
+		goto out;
+
+	/* success */
+	kind = device->priv->kind;
+out:
+	return kind;
+}
+
+/**
+ * gcm_ddc_device_set_verbose:
+ **/
+void
+gcm_ddc_device_set_verbose (GcmDdcDevice *device, GcmVerbose verbose)
+{
+	g_return_if_fail (GCM_IS_DDC_DEVICE(device));
+	device->priv->verbose = verbose;
+}
+
+/**
+ * gcm_ddc_device_error_quark:
+ *
+ * Return value: Our personal error quark.
+ *
+ * Since: 0.0.1
+ **/
+GQuark
+gcm_ddc_device_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("gcm_ddc_device_error");
+	return quark;
+}
+
+/**
+ * gcm_ddc_device_get_property:
+ **/
+static void
+gcm_ddc_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	GcmDdcDevice *device = GCM_DDC_DEVICE (object);
+	GcmDdcDevicePrivate *priv = device->priv;
+
+	switch (prop_id) {
+	case PROP_HAS_EDID:
+		g_value_set_boolean (value, priv->has_edid);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * gcm_ddc_device_set_property:
+ **/
+static void
+gcm_ddc_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	switch (prop_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/**
+ * gcm_ddc_device_class_init:
+ **/
+static void
+gcm_ddc_device_class_init (GcmDdcDeviceClass *klass)
+{
+	GParamSpec *pspec;
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = gcm_ddc_device_finalize;
+	object_class->get_property = gcm_ddc_device_get_property;
+	object_class->set_property = gcm_ddc_device_set_property;
+
+	/**
+	 * GcmDdcDevice:has-coldplug:
+	 *
+	 * Since: 0.0.1
+	 */
+	pspec = g_param_spec_boolean ("has-coldplug", NULL, "if there are no transactions in progress on this device",
+				      TRUE,
+				      G_PARAM_READABLE);
+	g_object_class_install_property (object_class, PROP_HAS_EDID, pspec);
+
+	g_type_class_add_private (klass, sizeof (GcmDdcDevicePrivate));
+}
+
+/**
+ * gcm_ddc_device_init:
+ **/
+static void
+gcm_ddc_device_init (GcmDdcDevice *device)
+{
+	device->priv = GCM_DDC_DEVICE_GET_PRIVATE (device);
+	device->priv->kind = GCM_DDC_DEVICE_KIND_UNKNOWN;
+	device->priv->addr = GCM_DEFAULT_DDCCI_ADDR;
+	device->priv->controls = g_ptr_array_new ();
+	device->priv->fd = -1;
+	/* assume the hardware is busy */
+	device->priv->required_wait = GCM_WRITE_DELAY_SECS;
+	device->priv->timer = g_timer_new ();
+}
+
+/**
+ * gcm_ddc_device_finalize:
+ **/
+static void
+gcm_ddc_device_finalize (GObject *object)
+{
+	GcmDdcDevice *device = GCM_DDC_DEVICE (object);
+	GcmDdcDevicePrivate *priv = device->priv;
+
+	g_return_if_fail (GCM_IS_DDC_DEVICE(device));
+	if (priv->fd > 0)
+		close (priv->fd);
+	g_free (priv->model);
+	g_free (priv->pnpid);
+	g_free (priv->edid_data);
+	g_free (priv->edid_md5);
+	g_timer_destroy (priv->timer);
+	g_ptr_array_free (priv->controls, TRUE);
+
+	G_OBJECT_CLASS (gcm_ddc_device_parent_class)->finalize (object);
+}
+
+/**
+ * gcm_ddc_device_new:
+ *
+ * GcmDdcDevice is a nice GObject wrapper for gcm.
+ *
+ * Return value: A new %GcmDdcDevice instance
+ *
+ * Since: 0.0.1
+ **/
+GcmDdcDevice *
+gcm_ddc_device_new (void)
+{
+	GcmDdcDevice *device;
+	device = g_object_new (GCM_TYPE_DDC_DEVICE, NULL);
+	return GCM_DDC_DEVICE (device);
+}
diff --git a/libcolor-glib/gcm-ddc-device.h b/libcolor-glib/gcm-ddc-device.h
new file mode 100644
index 0000000..bc7b507
--- /dev/null
+++ b/libcolor-glib/gcm-ddc-device.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#if !defined (__GCM_H_INSIDE__) && !defined (GCM_COMPILATION)
+#error "Only <gcm.h> can be included directly."
+#endif
+
+#ifndef __GCM_DDC_DEVICE_H
+#define __GCM_DDC_DEVICE_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <gcm-ddc-common.h>
+#include <gcm-ddc-device.h>
+
+G_BEGIN_DECLS
+
+#define GCM_TYPE_DDC_DEVICE		(gcm_ddc_device_get_type ())
+#define GCM_DDC_DEVICE(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GCM_TYPE_DDC_DEVICE, GcmDdcDevice))
+#define GCM_DDC_DEVICE_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GCM_TYPE_DDC_DEVICE, GcmDdcDeviceClass))
+#define GCM_IS_DDC_DEVICE(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GCM_TYPE_DDC_DEVICE))
+#define GCM_IS_DDC_DEVICE_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GCM_TYPE_DDC_DEVICE))
+#define GCM_DDC_DEVICE_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GCM_TYPE_DDC_DEVICE, GcmDdcDeviceClass))
+#define GCM_DDC_DEVICE_ERROR		(gcm_ddc_device_error_quark ())
+#define GCM_DDC_DEVICE_TYPE_ERROR	(gcm_ddc_device_error_get_type ())
+
+/**
+ * GcmDdcDeviceError:
+ * @GCM_DDC_DEVICE_ERROR_FAILED: the transaction failed for an unknown reason
+ *
+ * Errors that can be thrown
+ */
+typedef enum
+{
+	GCM_DDC_DEVICE_ERROR_FAILED
+} GcmDdcDeviceError;
+
+typedef struct _GcmDdcDevicePrivate		GcmDdcDevicePrivate;
+typedef struct _GcmDdcDevice			GcmDdcDevice;
+typedef struct _GcmDdcDeviceClass		GcmDdcDeviceClass;
+
+struct _GcmDdcDevice
+{
+	 GObject		 parent;
+	 GcmDdcDevicePrivate	*priv;
+};
+
+struct _GcmDdcDeviceClass
+{
+	GObjectClass	parent_class;
+
+	/* signals */
+	void		(* changed)			(GcmDdcDevice	*device);
+	/* padding for future expansion */
+	void (*_gcm_reserved1) (void);
+	void (*_gcm_reserved2) (void);
+	void (*_gcm_reserved3) (void);
+	void (*_gcm_reserved4) (void);
+	void (*_gcm_reserved5) (void);
+};
+
+typedef enum {
+	GCM_DDC_DEVICE_KIND_LCD,
+	GCM_DDC_DEVICE_KIND_CRT,
+	GCM_DDC_DEVICE_KIND_UNKNOWN
+} GcmDdcDeviceKind;
+
+/* incest */
+#include <gcm-ddc-control.h>
+
+GQuark		 gcm_ddc_device_error_quark		(void);
+GType		 gcm_ddc_device_get_type		  	(void);
+GcmDdcDevice	*gcm_ddc_device_new			(void);
+
+gboolean	 gcm_ddc_device_open			(GcmDdcDevice	*device,
+							 const gchar	*filename,
+							 GError		**error);
+gboolean	 gcm_ddc_device_close			(GcmDdcDevice	*device,
+							 GError		**error);
+const guint8	*gcm_ddc_device_get_edid		(GcmDdcDevice	*device,
+							 gsize		*length,
+							 GError		**error);
+const gchar	*gcm_ddc_device_get_edid_md5		(GcmDdcDevice	*device,
+							 GError		**error);
+gboolean	 gcm_ddc_device_write			(GcmDdcDevice	*device,
+							 guchar		 *data,
+							 gsize		 length,
+							 GError		**error);
+gboolean	 gcm_ddc_device_read			(GcmDdcDevice	*device,
+							 guchar		*data,
+							 gsize		 data_length,
+							 gsize		*recieved_length,
+							 GError		**error);
+gboolean	 gcm_ddc_device_save			(GcmDdcDevice	*device,
+							 GError		**error);
+const gchar	*gcm_ddc_device_get_pnpid		(GcmDdcDevice	*device,
+							 GError		**error);
+const gchar	*gcm_ddc_device_get_model		(GcmDdcDevice	*device,
+							 GError		**error);
+GcmDdcDeviceKind gcm_ddc_device_get_kind		(GcmDdcDevice	*device,
+							 GError		**error);
+GPtrArray	*gcm_ddc_device_get_controls		(GcmDdcDevice	*device,
+							 GError		**error);
+GcmDdcControl	*gcm_ddc_device_get_control_by_id	(GcmDdcDevice	*device,
+							 guchar		 id,
+							 GError		**error);
+void		 gcm_ddc_device_set_verbose		(GcmDdcDevice	*device,
+							 GcmVerbose verbose);
+
+G_END_DECLS
+
+#endif /* __GCM_DDC_DEVICE_H */
+
diff --git a/libcolor-glib/gcm-self-test.c b/libcolor-glib/gcm-self-test.c
index db59fdc..1abab34 100644
--- a/libcolor-glib/gcm-self-test.c
+++ b/libcolor-glib/gcm-self-test.c
@@ -24,6 +24,8 @@
 #include <glib-object.h>
 
 #include "gcm-common.h"
+#include "gcm-ddc-client.h"
+#include "gcm-ddc-device.h"
 
 static void
 gcm_test_common_func (void)
@@ -109,6 +111,28 @@ gcm_test_common_func (void)
 	g_assert_cmpfloat (mat.m22, >, -0.001f);
 }
 
+static void
+gcm_test_ddc_device_func (void)
+{
+	GcmDdcDevice *device;
+
+	device = gcm_ddc_device_new ();
+	g_assert (device != NULL);
+
+	g_object_unref (device);
+}
+
+static void
+gcm_test_ddc_client_func (void)
+{
+	GcmDdcClient *client;
+
+	client = gcm_ddc_client_new ();
+	g_assert (client != NULL);
+
+	g_object_unref (client);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -118,6 +142,8 @@ main (int argc, char **argv)
 
 	/* tests go here */
 	g_test_add_func ("/libcolor-glib/common", gcm_test_common_func);
+	g_test_add_func ("/libcolor-glib/ddc-device", gcm_test_ddc_device_func);
+	g_test_add_func ("/libcolor-glib/ddc-client", gcm_test_ddc_client_func);
 
 	return g_test_run ();
 }
diff --git a/libcolor-glib/libcolor-glib.h b/libcolor-glib/libcolor-glib.h
index c26a71b..3a2d42d 100644
--- a/libcolor-glib/libcolor-glib.h
+++ b/libcolor-glib/libcolor-glib.h
@@ -29,6 +29,10 @@
 #define __GCM_H_INSIDE__
 
 #include <gcm-common.h>
+#include <gcm-ddc-common.h>
+#include <gcm-ddc-device.h>
+#include <gcm-ddc-client.h>
+#include <gcm-ddc-control.h>
 
 #undef __GCM_H_INSIDE__
 
diff --git a/src/.gitignore b/src/.gitignore
index 133d6d8..389a65d 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -18,3 +18,4 @@ gcm-self-test
 gcm-install-system-wide
 gcm-helper-exiv
 gcm-glsl-demo
+gcm-ddc-util
diff --git a/src/Makefile.am b/src/Makefile.am
index 720b2f1..d027be1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,6 +17,7 @@ INCLUDES =						\
 	-DG_UDEV_API_IS_SUBJECT_TO_CHANGE		\
 	-DGNOME_DESKTOP_USE_UNSTABLE_API		\
 	$(GUDEV_CFLAGS)					\
+	-I$(top_srcdir)/libcolor-glib			\
 	-DBINDIR=\"$(bindir)\"				\
 	-DLIBEXECDIR=\"$(libexecdir)\"			\
 	-DSBINDIR=\"$(sbindir)\"			\
@@ -26,6 +27,7 @@ INCLUDES =						\
 	-DLOCALEDIR=\""$(localedir)"\"			\
 	-DTESTDATADIR=\""$(top_srcdir)/data/tests"\"	\
 	-DGCM_SYSTEM_PROFILES_DIR="\"$(GCM_SYSTEM_PROFILES_DIR)"\" \
+	-DI_KNOW_THE_GCM_GLIB_API_IS_SUBJECT_TO_CHANGE	\
 	-DGCM_DATA=\"$(pkgdatadir)\"
 
 introspectiondir = $(datadir)/dbus-1/interfaces
@@ -116,6 +118,7 @@ bin_PROGRAMS =						\
 	gcm-viewer					\
 	gcm-session					\
 	gcm-picker					\
+	gcm-ddc-util					\
 	gcm-import
 
 if HAVE_EXIV
@@ -340,6 +343,19 @@ libcolor_la_LIBADD =					\
 libcolor_la_LDFLAGS = -avoid-version -module
 libcolor_la_CFLAGS = $(WARNINGFLAGS_C)
 
+COLOR_GLIB_LIBS =						\
+	$(top_builddir)/libcolor-glib/libcolor-glib.la
+
+gcm_ddc_util_SOURCES =					\
+	gcm-ddc-util.c
+
+gcm_ddc_util_LDADD =					\
+	$(GLIB_LIBS)					\
+	$(COLOR_GLIB_LIBS)
+
+gcm_ddc_util_CFLAGS =					\
+	$(WARNINGFLAGS_C)
+
 if HAVE_TESTS
 
 check_PROGRAMS =					\
diff --git a/src/gcm-ddc-util.c b/src/gcm-ddc-util.c
new file mode 100644
index 0000000..0aa80d1
--- /dev/null
+++ b/src/gcm-ddc-util.c
@@ -0,0 +1,310 @@
+/* -*- 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.
+ */
+
+#include <config.h>
+#include <glib/gstdio.h>
+
+#include <libcolor-glib.h>
+
+/**
+ * show_device_md5_cb:
+ **/
+static void
+show_device_md5_cb (GcmDdcDevice *device, gpointer user_data)
+{
+	GError *error = NULL;
+	const gchar *desc;
+
+	desc = gcm_ddc_device_get_edid_md5 (device, &error);
+	if (desc == NULL) {
+		g_warning ("failed to get EDID: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	g_print ("EDID: \t%s\n", desc);
+out:
+	return;
+}
+
+/**
+ * show_device:
+ **/
+static void
+show_device (GcmDdcDevice *device)
+{
+	guint i, j;
+	GPtrArray *array;
+	GcmDdcControl *control;
+	GArray *values;
+	GError *error = NULL;
+	const gchar *desc;
+
+	desc = gcm_ddc_device_get_edid_md5 (device, &error);
+	if (desc == NULL) {
+		g_warning ("failed to get EDID: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	g_print ("EDID: \t%s\n", desc);
+
+	desc = gcm_ddc_device_get_pnpid (device, &error);
+	if (desc == NULL) {
+		g_warning ("failed to get PNPID: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	g_print ("PNPID:\t%s\n", desc);
+
+	desc = gcm_ddc_device_get_model (device, &error);
+	if (desc == NULL) {
+		g_warning ("failed to get model: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	g_print ("Model:\t%s\n", desc);
+
+	array = gcm_ddc_device_get_controls (device, &error);
+	if (array == NULL) {
+		g_warning ("failed to get caps: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	for (i = 0; i < array->len; i++) {
+		control = g_ptr_array_index (array, i);
+
+		desc = gcm_ddc_control_get_description (control);
+		g_print ("0x%02x\t[%s]", gcm_ddc_control_get_id (control), desc != NULL ? desc : "unknown");
+
+		/* print allowed values */
+		values = gcm_ddc_control_get_values (control);
+		if (values->len > 0) {
+			g_print ("\t ( ");
+			for (j=0; j<values->len; j++) {
+				g_print ("%i ", g_array_index (values, guint16, j));
+			}
+			g_print (")");
+		}
+		g_print ("\n");
+		g_array_unref (values);
+	}
+	g_ptr_array_unref (array);
+out:
+	return;
+}
+
+/**
+ * show_device_cb:
+ **/
+static void
+show_device_cb (GcmDdcDevice *device, gpointer user_data)
+{
+	show_device (device);
+}
+
+/**
+ * main:
+ **/
+int
+main (int argc, char **argv)
+{
+	gboolean ret;
+	GcmVerbose verbose = GCM_VERBOSE_NONE;
+	gboolean enumerate = FALSE;
+	gboolean caps = FALSE;
+	gchar *display_md5 = NULL;
+	gchar *control_name = NULL;
+	gboolean control_get = FALSE;
+	gint control_set = -1;
+	GcmDdcClient *client;
+	GcmDdcDevice *device = NULL;
+	GcmDdcControl *control = NULL;
+	gint brightness = -1;
+	GOptionContext *context;
+	GError *error = NULL;
+	GPtrArray *array;
+	guint16 value, max;
+	guchar idx;
+	guint i;
+
+	const GOptionEntry options[] = {
+		{ "verbose", '\0', 0, G_OPTION_ARG_INT, &verbose,
+		  "Enable verbose debugging mode", NULL},
+		{ "enumerate", '\0', 0, G_OPTION_ARG_NONE, &enumerate,
+		  "Enumerate all displays and display capabilities", NULL},
+		{ "display", '\0', 0, G_OPTION_ARG_STRING, &display_md5,
+		  "Set the display MD5 to operate on", NULL},
+		{ "caps", '\0', 0, G_OPTION_ARG_NONE, &caps,
+		  "Print the capabilities of the selected display", NULL},
+		{ "brightness", '\0', 0, G_OPTION_ARG_INT, &brightness,
+		  "Set the display brightness", NULL},
+		{ "control", '\0', 0, G_OPTION_ARG_STRING, &control_name,
+		  "Use a control value, e.g. 'select-color-preset'", NULL},
+		{ "get", '\0', 0, G_OPTION_ARG_NONE, &control_get,
+		  "Get a control value", NULL},
+		{ "set", '\0', 0, G_OPTION_ARG_INT, &control_set,
+		  "Set a control value", NULL},
+		{ NULL}
+	};
+
+	g_type_init ();
+
+	context = g_option_context_new ("DDC/CI utility program");
+	g_option_context_set_summary (context, "This exposes the DDC/CI protocol used by most modern displays.");
+	g_option_context_add_main_entries (context, options, NULL);
+	g_option_context_parse (context, &argc, &argv, NULL);
+	g_option_context_free (context);
+
+	client = gcm_ddc_client_new ();
+	gcm_ddc_client_set_verbose (client, verbose);
+
+	/* we want to enumerate all devices */
+	if (enumerate) {
+		array = gcm_ddc_client_get_devices (client, &error);
+		if (array == NULL) {
+			g_warning ("failed to get device list: %s", error->message);
+			goto out;
+		}
+
+		/* show device details */
+		g_ptr_array_foreach (array, (GFunc) show_device_cb, NULL);
+		g_ptr_array_unref (array);
+		goto out;
+	}
+
+	if (display_md5 == NULL) {
+		array = gcm_ddc_client_get_devices (client, &error);
+		if (array == NULL) {
+			g_warning ("failed to get device list: %s", error->message);
+			goto out;
+		}
+
+		/* show device details */
+		g_print ("No --display specified, please select from:\n");
+		g_ptr_array_foreach (array, (GFunc) show_device_md5_cb, NULL);
+		g_ptr_array_unref (array);
+		goto out;
+	}
+
+	/* get the correct device */
+	device = gcm_ddc_client_get_device_from_edid (client, display_md5, &error);
+	if (device == NULL) {
+		g_warning ("failed to get device list: %s", error->message);
+		goto out;
+	}
+
+	/* get caps? */
+	if (caps) {
+		show_device (device);
+		goto out;
+	}
+
+	/* set brightness? */
+	if (brightness != -1) {
+		control = gcm_ddc_device_get_control_by_id (device, GCM_DDC_CONTROL_ID_BRIGHTNESS, &error);
+		if (control == NULL) {
+			g_warning ("Failed to get brightness control: %s", error->message);
+			goto out;
+		}
+
+		/* get old value */
+		ret = gcm_ddc_control_request (control, &value, &max, &error);
+		if (!ret) {
+			g_warning ("failed to read: %s", error->message);
+			goto out;
+		}
+
+		/* set new value */
+		ret = gcm_ddc_control_set (control, brightness, &error);
+		if (!ret) {
+			g_warning ("failed to write: %s", error->message);
+			goto out;
+		}
+
+		/* print what we did */
+		g_print  ("brightness before was %i%%, now is %i%% max is %i%%\n", value, brightness, max);
+		g_object_unref (control);
+		goto out;
+	}
+
+	/* get named control */
+	if (control_name == NULL) {
+		g_print ("you need to specify a control name with --control\n");
+		show_device (device);
+	}
+	idx = gcm_get_vcp_index_from_description (control_name);
+	if (idx == GCM_VCP_ID_INVALID) {
+		const gchar *description;
+		g_warning ("Failed to match description, choose from:");
+		for (i=0; i<0xff; i++) {
+			description = gcm_get_vcp_description_from_index (i);
+			if (description != NULL)
+				g_print ("* %s\n", description);
+		}
+		goto out;
+	}
+
+	/* get control */
+	control = gcm_ddc_device_get_control_by_id (device, idx, &error);
+	if (control == NULL) {
+		g_warning ("Failed to get control: %s", error->message);
+		goto out;
+	}
+
+	/* get named control */
+	if (control_get) {
+		/* get old value */
+		ret = gcm_ddc_control_request (control, &value, &max, &error);
+		if (!ret) {
+			g_warning ("failed to read: %s", error->message);
+			goto out;
+		}
+
+		/* print what we got */
+		g_print  ("%s is %i, max is %i\n", control_name, value, max);
+	}
+
+	/* set named control */
+	if (control_set != -1) {
+
+		/* set new value */
+		ret = gcm_ddc_control_set (control, control_set, &error);
+		if (!ret) {
+			g_warning ("failed to write: %s", error->message);
+			goto out;
+		}
+	}
+out:
+	g_clear_error (&error);
+	ret = gcm_ddc_client_close (client, &error);
+	if (!ret) {
+		g_warning ("failed to close: %s", error->message);
+		g_error_free (error);
+		goto out;
+	}
+	if (device != NULL)
+		g_object_unref (device);
+	if (control != NULL)
+		g_object_unref (control);
+	g_object_unref (client);
+	g_free (display_md5);
+	g_free (control_name);
+	return 0;
+}



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