diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/color/gcm-edid.c | 450 |
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); +} + |