summaryrefslogtreecommitdiffstats
path: root/plugins/color/gcm-edid.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/color/gcm-edid.c450
1 files changed, 450 insertions, 0 deletions
diff --git a/plugins/color/gcm-edid.c b/plugins/color/gcm-edid.c
new file mode 100644
index 0000000..a00d2ce
--- /dev/null
+++ b/plugins/color/gcm-edid.c
@@ -0,0 +1,450 @@
+/* -*- 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 <libgnome-desktop/gnome-pnp-ids.h>
+
+#include "gcm-edid.h"
+
+static void gcm_edid_finalize (GObject *object);
+
+struct _GcmEdid
+{
+ GObject parent;
+
+ 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;
+ GnomePnpIds *pnp_ids;
+};
+
+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->monitor_name;
+}
+
+const gchar *
+gcm_edid_get_vendor_name (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+
+ if (edid->vendor_name == NULL)
+ edid->vendor_name = gnome_pnp_ids_get_pnp_id (edid->pnp_ids, edid->pnp_id);
+ return edid->vendor_name;
+}
+
+const gchar *
+gcm_edid_get_serial_number (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->serial_number;
+}
+
+const gchar *
+gcm_edid_get_eisa_id (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->eisa_id;
+}
+
+const gchar *
+gcm_edid_get_checksum (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->checksum;
+}
+
+const gchar *
+gcm_edid_get_pnp_id (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->pnp_id;
+}
+
+guint
+gcm_edid_get_width (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), 0);
+ return edid->width;
+}
+
+guint
+gcm_edid_get_height (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), 0);
+ return edid->height;
+}
+
+gfloat
+gcm_edid_get_gamma (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), 0.0f);
+ return edid->gamma;
+}
+
+const CdColorYxy *
+gcm_edid_get_red (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->red;
+}
+
+const CdColorYxy *
+gcm_edid_get_green (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->green;
+}
+
+const CdColorYxy *
+gcm_edid_get_blue (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->blue;
+}
+
+const CdColorYxy *
+gcm_edid_get_white (GcmEdid *edid)
+{
+ g_return_val_if_fail (GCM_IS_EDID (edid), NULL);
+ return edid->white;
+}
+
+void
+gcm_edid_reset (GcmEdid *edid)
+{
+ g_return_if_fail (GCM_IS_EDID (edid));
+
+ /* free old data */
+ g_free (edid->monitor_name);
+ g_free (edid->vendor_name);
+ g_free (edid->serial_number);
+ g_free (edid->eisa_id);
+ g_free (edid->checksum);
+
+ /* do not deallocate, just blank */
+ edid->pnp_id[0] = '\0';
+
+ /* set to default values */
+ edid->monitor_name = NULL;
+ edid->vendor_name = NULL;
+ edid->serial_number = NULL;
+ edid->eisa_id = NULL;
+ edid->checksum = NULL;
+ edid->width = 0;
+ edid->height = 0;
+ edid->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 13 bytes, but we can't guarantee it's null
+ * terminated or not junk. */
+ text = g_strndup ((const gchar *) data, 13);
+
+ /* 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;
+ 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 */
+ edid->pnp_id[0] = 'A' + ((data[GCM_EDID_OFFSET_PNPID+0] & 0x7c) / 4) - 1;
+ edid->pnp_id[1] = 'A' + ((data[GCM_EDID_OFFSET_PNPID+0] & 0x3) * 8) + ((data[GCM_EDID_OFFSET_PNPID+1] & 0xe0) / 32) - 1;
+ edid->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)
+ edid->serial_number = g_strdup_printf ("%" G_GUINT32_FORMAT, serial);
+
+ /* get the size */
+ edid->width = data[GCM_EDID_OFFSET_SIZE+0];
+ edid->height = data[GCM_EDID_OFFSET_SIZE+1];
+
+ /* we don't care about aspect */
+ if (edid->width == 0 || edid->height == 0) {
+ edid->width = 0;
+ edid->height = 0;
+ }
+
+ /* get gamma */
+ if (data[GCM_EDID_OFFSET_GAMMA] == 0xff) {
+ edid->gamma = 1.0f;
+ } else {
+ edid->gamma = ((gfloat) data[GCM_EDID_OFFSET_GAMMA] / 100) + 1;
+ }
+
+ /* get color red */
+ edid->red->x = gcm_edid_decode_fraction (data[0x1b], gcm_edid_get_bits (data[0x19], 6, 7));
+ edid->red->y = gcm_edid_decode_fraction (data[0x1c], gcm_edid_get_bits (data[0x19], 4, 5));
+
+ /* get color green */
+ edid->green->x = gcm_edid_decode_fraction (data[0x1d], gcm_edid_get_bits (data[0x19], 2, 3));
+ edid->green->y = gcm_edid_decode_fraction (data[0x1e], gcm_edid_get_bits (data[0x19], 0, 1));
+
+ /* get color blue */
+ edid->blue->x = gcm_edid_decode_fraction (data[0x1f], gcm_edid_get_bits (data[0x1a], 6, 7));
+ edid->blue->y = gcm_edid_decode_fraction (data[0x20], gcm_edid_get_bits (data[0x1a], 4, 5));
+
+ /* get color white */
+ edid->white->x = gcm_edid_decode_fraction (data[0x21], gcm_edid_get_bits (data[0x1a], 2, 3));
+ edid->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 (edid->monitor_name);
+ edid->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 (edid->serial_number);
+ edid->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 (edid->eisa_id);
+ edid->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 */
+ edid->gamma = ((gfloat) data[i+3+9] / 100) + 1;
+ }
+ if (data[i+3+14] != 0xff) {
+ /* extended EDID block(2) which contains
+ * a better gamma value */
+ edid->gamma = ((gfloat) data[i+3+9] / 100) + 1;
+ }
+ }
+ }
+
+ /* calculate checksum */
+ edid->checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5, data, length);
+out:
+ return ret;
+}
+
+static void
+gcm_edid_class_init (GcmEdidClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gcm_edid_finalize;
+}
+
+static void
+gcm_edid_init (GcmEdid *edid)
+{
+ edid->pnp_ids = gnome_pnp_ids_new ();
+ edid->pnp_id = g_new0 (gchar, 4);
+ edid->red = cd_color_yxy_new ();
+ edid->green = cd_color_yxy_new ();
+ edid->blue = cd_color_yxy_new ();
+ edid->white = cd_color_yxy_new ();
+}
+
+static void
+gcm_edid_finalize (GObject *object)
+{
+ GcmEdid *edid = GCM_EDID (object);
+
+ g_free (edid->monitor_name);
+ g_free (edid->vendor_name);
+ g_free (edid->serial_number);
+ g_free (edid->eisa_id);
+ g_free (edid->checksum);
+ g_free (edid->pnp_id);
+ cd_color_yxy_free (edid->white);
+ cd_color_yxy_free (edid->red);
+ cd_color_yxy_free (edid->green);
+ cd_color_yxy_free (edid->blue);
+ g_object_unref (edid->pnp_ids);
+
+ 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);
+}
+