Blob Blame History Raw
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/edid.h
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/edid.h	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,169 @@
+typedef unsigned char uchar;
+typedef struct MonitorInfo MonitorInfo;
+typedef struct Timing Timing;
+typedef struct DetailedTiming DetailedTiming;
+
+typedef enum
+{
+    UNDEFINED,
+    DVI,
+    HDMI_A,
+    HDMI_B,
+    MDDI,
+    DISPLAY_PORT
+} Interface;
+
+typedef enum
+{
+    UNDEFINED_COLOR,
+    MONOCHROME,
+    RGB,
+    OTHER_COLOR
+} ColorType;
+
+typedef enum
+{
+    NO_STEREO,
+    FIELD_RIGHT,
+    FIELD_LEFT,
+    TWO_WAY_RIGHT_ON_EVEN,
+    TWO_WAY_LEFT_ON_EVEN,
+    FOUR_WAY_INTERLEAVED,
+    SIDE_BY_SIDE
+} StereoType;
+
+struct Timing
+{
+    int width;
+    int height;
+    int frequency;
+};
+
+struct DisplayDescriptor
+{
+};
+
+struct DetailedTiming
+{
+    int		pixel_clock;
+    int		h_addr;
+    int		h_blank;
+    int		h_sync;
+    int		h_front_porch;
+    int		v_addr;
+    int		v_blank;
+    int		v_sync;
+    int		v_front_porch;
+    int		width_mm;
+    int		height_mm;
+    int		right_border;
+    int		top_border;
+    int		interlaced;
+    StereoType	stereo;
+
+    int		digital_sync;
+    union
+    {
+	struct
+	{
+	    int bipolar;
+	    int serrations;
+	    int sync_on_green;
+	} analog;
+
+	struct
+	{
+	    int composite;
+	    int serrations;
+	    int negative_vsync;
+	    int negative_hsync;
+	} digital;
+    };
+};
+
+struct MonitorInfo
+{
+    int			checksum;
+    char		manufacturer_code[4];
+    int			product_code;
+    unsigned int	serial_number;
+    
+    int			production_week;	/* -1 if not specified */
+    int			production_year;	/* -1 if not specified */
+    int			model_year;		/* -1 if not specified */
+
+    int			major_version;
+    int			minor_version;
+
+    int			is_digital;
+    
+    union
+    {
+	struct
+	{
+	    int		bits_per_primary;
+	    Interface	interface;
+	    int		rgb444;
+	    int		ycrcb444;
+	    int		ycrcb422;
+	} digital;
+
+	struct
+	{
+	    double	video_signal_level;
+	    double	sync_signal_level;
+	    double	total_signal_level;
+
+	    int		blank_to_black;
+
+	    int		separate_hv_sync;
+	    int		composite_sync_on_h;
+	    int		composite_sync_on_green;
+	    int		serration_on_vsync;
+	    ColorType	color_type;
+	} analog;
+    };
+
+    int			width_mm;		/* -1 if not specified */
+    int			height_mm;		/* -1 if not specified */
+    double		aspect_ratio;		/* -1.0 if not specififed */
+
+    double		gamma;			/* -1.0 if not specified */
+
+    int			standby;
+    int			suspend;
+    int			active_off;
+
+    int			srgb_is_standard;
+    int			preferred_timing_includes_native;
+    int			continuous_frequency;
+
+    double		red_x;
+    double		red_y;
+    double		green_x;
+    double		green_y;
+    double		blue_x;
+    double		blue_y;
+    double		white_x;
+    double		white_y;
+
+    Timing		established[24];	/* Terminated by 0x0x0 */
+    Timing		standard[8];
+    
+    int			n_detailed_timings;
+    DetailedTiming	detailed_timings[4];	/* If monitor has a preferred
+						 * mode, it is the first one
+						 * (whether it has, is
+						 * determined by the 
+						 * preferred_timing_includes
+						 * bit.
+						 */
+
+    /* Optional product description */
+    char		dsc_serial_number[14];
+    char		dsc_product_name[14];
+    char		dsc_string[14];		/* Unspecified ASCII data */
+};
+
+MonitorInfo *decode_edid (const uchar *data);
+char *       make_display_name (const MonitorInfo *info);
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/foo-marshal.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/foo-marshal.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,279 @@
+
+#include	<glib-object.h>
+
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* VOID:OBJECT,OBJECT (marshal.list:1) */
+void
+foo_marshal_VOID__OBJECT_OBJECT (GClosure     *closure,
+                                 GValue       *return_value G_GNUC_UNUSED,
+                                 guint         n_param_values,
+                                 const GValue *param_values,
+                                 gpointer      invocation_hint G_GNUC_UNUSED,
+                                 gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer     data1,
+                                                    gpointer     arg_1,
+                                                    gpointer     arg_2,
+                                                    gpointer     data2);
+  register GMarshalFunc_VOID__OBJECT_OBJECT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_object (param_values + 1),
+            g_marshal_value_peek_object (param_values + 2),
+            data2);
+}
+
+/* VOID:UINT,UINT,UINT,UINT (marshal.list:2) */
+void
+foo_marshal_VOID__UINT_UINT_UINT_UINT (GClosure     *closure,
+                                       GValue       *return_value G_GNUC_UNUSED,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint G_GNUC_UNUSED,
+                                       gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__UINT_UINT_UINT_UINT) (gpointer     data1,
+                                                          guint        arg_1,
+                                                          guint        arg_2,
+                                                          guint        arg_3,
+                                                          guint        arg_4,
+                                                          gpointer     data2);
+  register GMarshalFunc_VOID__UINT_UINT_UINT_UINT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 5);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__UINT_UINT_UINT_UINT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_uint (param_values + 1),
+            g_marshal_value_peek_uint (param_values + 2),
+            g_marshal_value_peek_uint (param_values + 3),
+            g_marshal_value_peek_uint (param_values + 4),
+            data2);
+}
+
+/* VOID:UINT,UINT (marshal.list:3) */
+void
+foo_marshal_VOID__UINT_UINT (GClosure     *closure,
+                             GValue       *return_value G_GNUC_UNUSED,
+                             guint         n_param_values,
+                             const GValue *param_values,
+                             gpointer      invocation_hint G_GNUC_UNUSED,
+                             gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__UINT_UINT) (gpointer     data1,
+                                                guint        arg_1,
+                                                guint        arg_2,
+                                                gpointer     data2);
+  register GMarshalFunc_VOID__UINT_UINT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__UINT_UINT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_uint (param_values + 1),
+            g_marshal_value_peek_uint (param_values + 2),
+            data2);
+}
+
+/* VOID:BOXED (marshal.list:4) */
+
+/* VOID:BOXED,BOXED (marshal.list:5) */
+void
+foo_marshal_VOID__BOXED_BOXED (GClosure     *closure,
+                               GValue       *return_value G_GNUC_UNUSED,
+                               guint         n_param_values,
+                               const GValue *param_values,
+                               gpointer      invocation_hint G_GNUC_UNUSED,
+                               gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__BOXED_BOXED) (gpointer     data1,
+                                                  gpointer     arg_1,
+                                                  gpointer     arg_2,
+                                                  gpointer     data2);
+  register GMarshalFunc_VOID__BOXED_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_boxed (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            data2);
+}
+
+/* VOID:POINTER,BOXED,POINTER (marshal.list:6) */
+void
+foo_marshal_VOID__POINTER_BOXED_POINTER (GClosure     *closure,
+                                         GValue       *return_value G_GNUC_UNUSED,
+                                         guint         n_param_values,
+                                         const GValue *param_values,
+                                         gpointer      invocation_hint G_GNUC_UNUSED,
+                                         gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__POINTER_BOXED_POINTER) (gpointer     data1,
+                                                            gpointer     arg_1,
+                                                            gpointer     arg_2,
+                                                            gpointer     arg_3,
+                                                            gpointer     data2);
+  register GMarshalFunc_VOID__POINTER_BOXED_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 4);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__POINTER_BOXED_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_pointer (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            g_marshal_value_peek_pointer (param_values + 3),
+            data2);
+}
+
+/* VOID:POINTER,POINTER (marshal.list:7) */
+void
+foo_marshal_VOID__POINTER_POINTER (GClosure     *closure,
+                                   GValue       *return_value G_GNUC_UNUSED,
+                                   guint         n_param_values,
+                                   const GValue *param_values,
+                                   gpointer      invocation_hint G_GNUC_UNUSED,
+                                   gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__POINTER_POINTER) (gpointer     data1,
+                                                      gpointer     arg_1,
+                                                      gpointer     arg_2,
+                                                      gpointer     data2);
+  register GMarshalFunc_VOID__POINTER_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_pointer (param_values + 1),
+            g_marshal_value_peek_pointer (param_values + 2),
+            data2);
+}
+
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/edid-parse.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/edid-parse.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2007 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* Author: Soren Sandmann <sandmann@redhat.com> */
+
+#include "edid.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#define TRUE 1
+#define FALSE 0
+
+static int
+get_bit (int in, int bit)
+{
+    return (in & (1 << bit)) >> bit;
+}
+
+static int
+get_bits (int in, int begin, int end)
+{
+    int mask = (1 << (end - begin + 1)) - 1;
+    
+    return (in >> begin) & mask;
+}
+
+static int
+decode_header (const uchar *edid)
+{
+    if (memcmp (edid, "\x00\xff\xff\xff\xff\xff\xff\x00", 8) == 0)
+	return TRUE;
+    return FALSE;
+}
+
+static int
+decode_vendor_and_product_identification (const uchar *edid, MonitorInfo *info)
+{
+    int is_model_year;
+    
+    /* Manufacturer Code */
+    info->manufacturer_code[0]  = get_bits (edid[0x08], 2, 6);
+    info->manufacturer_code[1]  = get_bits (edid[0x08], 0, 1) << 3;
+    info->manufacturer_code[1] |= get_bits (edid[0x09], 5, 7);
+    info->manufacturer_code[2]  = get_bits (edid[0x09], 0, 4);
+    info->manufacturer_code[3]  = '\0';
+    
+    info->manufacturer_code[0] += 'A' - 1;
+    info->manufacturer_code[1] += 'A' - 1;
+    info->manufacturer_code[2] += 'A' - 1;
+
+    /* Product Code */
+    info->product_code = edid[0x0b] << 8 | edid[0x0a];
+
+    /* Serial Number */
+    info->serial_number =
+	edid[0x0c] | edid[0x0d] << 8 | edid[0x0e] << 16 | edid[0x0f] << 24;
+
+    /* Week and Year */
+    is_model_year = FALSE;
+    switch (edid[0x10])
+    {
+    case 0x00:
+	info->production_week = -1;
+	break;
+
+    case 0xff:
+	info->production_week = -1;
+	is_model_year = TRUE;
+	break;
+
+    default:
+	info->production_week = edid[0x10];
+	break;
+    }
+
+    if (is_model_year)
+    {
+	info->production_year = -1;
+	info->model_year = 1990 + edid[0x11];
+    }
+    else
+    {
+	info->production_year = 1990 + edid[0x11];
+	info->model_year = -1;
+    }
+
+    return TRUE;
+}
+
+static int
+decode_edid_version (const uchar *edid, MonitorInfo *info)
+{
+    info->major_version = edid[0x12];
+    info->minor_version = edid[0x13];
+
+    return TRUE;
+}
+
+static int
+decode_display_parameters (const uchar *edid, MonitorInfo *info)
+{
+    /* Digital vs Analog */
+    info->is_digital = get_bit (edid[0x14], 7);
+
+    if (info->is_digital)
+    {
+	int bits;
+	
+	static const int bit_depth[8] =
+	{
+	    -1, 6, 8, 10, 12, 14, 16, -1
+	};
+
+	static const Interface interfaces[6] =
+	{
+	    UNDEFINED, DVI, HDMI_A, HDMI_B, MDDI, DISPLAY_PORT
+	};
+
+	bits = get_bits (edid[0x14], 4, 6);
+	info->digital.bits_per_primary = bit_depth[bits];
+
+	bits = get_bits (edid[0x14], 0, 3);
+	
+	if (bits <= 5)
+	    info->digital.interface = interfaces[bits];
+	else
+	    info->digital.interface = UNDEFINED;
+    }
+    else
+    {
+	int bits = get_bits (edid[0x14], 5, 6);
+	
+	static const double levels[][3] =
+	{
+	    { 0.7,   0.3,    1.0 },
+	    { 0.714, 0.286,  1.0 },
+	    { 1.0,   0.4,    1.4 },
+	    { 0.7,   0.0,    0.7 },
+	};
+
+	info->analog.video_signal_level = levels[bits][0];
+	info->analog.sync_signal_level = levels[bits][1];
+	info->analog.total_signal_level = levels[bits][2];
+
+	info->analog.blank_to_black = get_bit (edid[0x14], 4);
+
+	info->analog.separate_hv_sync = get_bit (edid[0x14], 3);
+	info->analog.composite_sync_on_h = get_bit (edid[0x14], 2);
+	info->analog.composite_sync_on_green = get_bit (edid[0x14], 1);
+
+	info->analog.serration_on_vsync = get_bit (edid[0x14], 0);
+    }
+
+    /* Screen Size / Aspect Ratio */
+    if (edid[0x15] == 0 && edid[0x16] == 0)
+    {
+	info->width_mm = -1;
+	info->height_mm = -1;
+	info->aspect_ratio = -1.0;
+    }
+    else if (edid[0x16] == 0)
+    {
+	info->width_mm = -1;
+	info->height_mm = -1; 
+	info->aspect_ratio = 100.0 / (edid[0x15] + 99);
+    }
+    else if (edid[0x15] == 0)
+    {
+	info->width_mm = -1;
+	info->height_mm = -1;
+	info->aspect_ratio = 100.0 / (edid[0x16] + 99);
+	info->aspect_ratio = 1/info->aspect_ratio; /* portrait */
+    }
+    else
+    {
+	info->width_mm = 10 * edid[0x15];
+	info->height_mm = 10 * edid[0x16];
+    }
+
+    /* Gamma */
+    if (edid[0x17] == 0xFF)
+	info->gamma = -1.0;
+    else
+	info->gamma = (edid[0x17] + 100.0) / 100.0;
+
+    /* Features */
+    info->standby = get_bit (edid[0x18], 7);
+    info->suspend = get_bit (edid[0x18], 6);
+    info->active_off = get_bit (edid[0x18], 5);
+
+    if (info->is_digital)
+    {
+	info->digital.rgb444 = TRUE;
+	if (get_bit (edid[0x18], 3))
+	    info->digital.ycrcb444 = 1;
+	if (get_bit (edid[0x18], 4))
+	    info->digital.ycrcb422 = 1;
+    }
+    else
+    {
+	int bits = get_bits (edid[0x18], 3, 4);
+	ColorType color_type[4] =
+	{
+	    MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR
+	};
+
+	info->analog.color_type = color_type[bits];
+    }
+
+    info->srgb_is_standard = get_bit (edid[0x18], 2);
+
+    /* In 1.3 this is called "has preferred timing" */
+    info->preferred_timing_includes_native = get_bit (edid[0x18], 1);
+
+    /* FIXME: In 1.3 this indicates whether the monitor accepts GTF */
+    info->continuous_frequency = get_bit (edid[0x18], 0);
+    return TRUE;
+}
+
+static double
+decode_fraction (int high, int low)
+{
+    double result = 0.0;
+    int i;
+
+    high = (high << 2) | low;
+
+    for (i = 0; i < 10; ++i)
+	result += get_bit (high, i) * pow (2, i - 10);
+
+    return result;
+}
+
+static int
+decode_color_characteristics (const uchar *edid, MonitorInfo *info)
+{
+    info->red_x = decode_fraction (edid[0x1b], get_bits (edid[0x19], 6, 7));
+    info->red_y = decode_fraction (edid[0x1c], get_bits (edid[0x19], 5, 4));
+    info->green_x = decode_fraction (edid[0x1d], get_bits (edid[0x19], 2, 3));
+    info->green_y = decode_fraction (edid[0x1e], get_bits (edid[0x19], 0, 1));
+    info->blue_x = decode_fraction (edid[0x1f], get_bits (edid[0x1a], 6, 7));
+    info->blue_y = decode_fraction (edid[0x20], get_bits (edid[0x1a], 4, 5));
+    info->white_x = decode_fraction (edid[0x21], get_bits (edid[0x1a], 2, 3));
+    info->white_y = decode_fraction (edid[0x22], get_bits (edid[0x1a], 0, 1));
+
+    return TRUE;
+}
+
+static int
+decode_established_timings (const uchar *edid, MonitorInfo *info)
+{
+    static const Timing established[][8] = 
+    {
+	{
+	    { 800, 600, 60 },
+	    { 800, 600, 56 },
+	    { 640, 480, 75 },
+	    { 640, 480, 72 },
+	    { 640, 480, 67 },
+	    { 640, 480, 60 },
+	    { 720, 400, 88 },
+	    { 720, 400, 70 }
+	},
+	{
+	    { 1280, 1024, 75 },
+	    { 1024, 768, 75 },
+	    { 1024, 768, 70 },
+	    { 1024, 768, 60 },
+	    { 1024, 768, 87 },
+	    { 832, 624, 75 },
+	    { 800, 600, 75 },
+	    { 800, 600, 72 }
+	},
+	{
+	    { 0, 0, 0 },
+	    { 0, 0, 0 },
+	    { 0, 0, 0 },
+	    { 0, 0, 0 },
+	    { 0, 0, 0 },
+	    { 0, 0, 0 },
+	    { 0, 0, 0 },
+	    { 1152, 870, 75 }
+	},
+    };
+
+    int i, j, idx;
+
+    idx = 0;
+    for (i = 0; i < 3; ++i)
+    {
+	for (j = 0; j < 8; ++j)
+	{
+	    int byte = edid[0x23 + i];
+
+	    if (get_bit (byte, j) && established[i][j].frequency != 0)
+		info->established[idx++] = established[i][j];
+	}
+    }
+    return TRUE;
+}
+
+static int
+decode_standard_timings (const uchar *edid, MonitorInfo *info)
+{
+    int i;
+    
+    for (i = 0; i < 8; i++)
+    {
+	int first = edid[0x26 + 2 * i];
+	int second = edid[0x27 + 2 * i];
+
+	if (first != 0x01 && second != 0x01)
+	{
+	    int w = 8 * (first + 31);
+	    int h;
+
+	    switch (get_bits (second, 6, 7))
+	    {
+	    case 0x00: h = (w / 16) * 10; break;
+	    case 0x01: h = (w / 4) * 3; break;
+	    case 0x02: h = (w / 5) * 4; break;
+	    case 0x03: h = (w / 16) * 9; break;
+	    }
+
+	    info->standard[i].width = w;
+	    info->standard[i].height = h;
+	    info->standard[i].frequency = get_bits (second, 0, 5) + 60;
+	}
+    }
+    
+    return TRUE;
+}
+
+static void
+decode_lf_string (const uchar *s, int n_chars, char *result)
+{
+    int i;
+    for (i = 0; i < n_chars; ++i)
+    {
+	if (s[i] == 0x0a)
+	{
+	    *result++ = '\0';
+	    break;
+	}
+	else if (s[i] == 0x00)
+	{
+	    /* Convert embedded 0's to spaces */
+	    *result++ = ' ';
+	}
+	else
+	{
+	    *result++ = s[i];
+	}
+    }
+}
+
+static void
+decode_display_descriptor (const uchar *desc,
+			   MonitorInfo *info)
+{
+    switch (desc[0x03])
+    {
+    case 0xFC:
+	decode_lf_string (desc + 5, 13, info->dsc_product_name);
+	break;
+    case 0xFF:
+	decode_lf_string (desc + 5, 13, info->dsc_serial_number);
+	break;
+    case 0xFE:
+	decode_lf_string (desc + 5, 13, info->dsc_string);
+	break;
+    case 0xFD:
+	/* Range Limits */
+	break;
+    case 0xFB:
+	/* Color Point */
+	break;
+    case 0xFA:
+	/* Timing Identifications */
+	break;
+    case 0xF9:
+	/* Color Management */
+	break;
+    case 0xF8:
+	/* Timing Codes */
+	break;
+    case 0xF7:
+	/* Established Timings */
+	break;
+    case 0x10:
+	break;
+    }
+}
+
+static void
+decode_detailed_timing (const uchar *timing,
+			DetailedTiming *detailed)
+{
+    int bits;
+    StereoType stereo[] =
+    {
+	NO_STEREO, NO_STEREO, FIELD_RIGHT, FIELD_LEFT,
+	TWO_WAY_RIGHT_ON_EVEN, TWO_WAY_LEFT_ON_EVEN,
+	FOUR_WAY_INTERLEAVED, SIDE_BY_SIDE
+    };
+    
+    detailed->pixel_clock = (timing[0x00] | timing[0x01] << 8) * 10000;
+    detailed->h_addr = timing[0x02] | ((timing[0x04] & 0xf0) << 4);
+    detailed->h_blank = timing[0x03] | ((timing[0x04] & 0x0f) << 8);
+    detailed->v_addr = timing[0x05] | ((timing[0x07] & 0xf0) << 4);
+    detailed->v_blank = timing[0x06] | ((timing[0x07] & 0x0f) << 8);
+    detailed->h_front_porch = timing[0x08] | get_bits (timing[0x0b], 6, 7) << 8;
+    detailed->h_sync = timing[0x09] | get_bits (timing[0x0b], 4, 5) << 8;
+    detailed->v_front_porch =
+	get_bits (timing[0x0a], 4, 7) | get_bits (timing[0x0b], 2, 3) << 4;
+    detailed->v_sync =
+	get_bits (timing[0x0a], 0, 3) | get_bits (timing[0x0b], 0, 1) << 4;
+    detailed->width_mm =  timing[0x0c] | get_bits (timing[0x0e], 4, 7) << 8;
+    detailed->height_mm = timing[0x0d] | get_bits (timing[0x0e], 0, 3) << 8;
+    detailed->right_border = timing[0x0f];
+    detailed->top_border = timing[0x10];
+
+    detailed->interlaced = get_bit (timing[0x11], 7);
+
+    /* Stereo */
+    bits = get_bits (timing[0x11], 5, 6) << 1 | get_bit (timing[0x11], 0);
+    detailed->stereo = stereo[bits];
+
+    /* Sync */
+    bits = timing[0x11];
+
+    detailed->digital_sync = get_bit (bits, 4);
+    if (detailed->digital_sync)
+    {
+	detailed->digital.composite = !get_bit (bits, 3);
+
+	if (detailed->digital.composite)
+	{
+	    detailed->digital.serrations = get_bit (bits, 2);
+	    detailed->digital.negative_vsync = FALSE;
+	}
+	else
+	{
+	    detailed->digital.serrations = FALSE;
+	    detailed->digital.negative_vsync = !get_bit (bits, 2);
+	}
+
+	detailed->digital.negative_hsync = !get_bit (bits, 0);
+    }
+    else
+    {
+	detailed->analog.bipolar = get_bit (bits, 3);
+	detailed->analog.serrations = get_bit (bits, 2);
+	detailed->analog.sync_on_green = !get_bit (bits, 1);
+    }
+}
+
+static int
+decode_descriptors (const uchar *edid, MonitorInfo *info)
+{
+    int i;
+    int timing_idx;
+    
+    timing_idx = 0;
+    
+    for (i = 0; i < 4; ++i)
+    {
+	int index = 0x36 + i * 18;
+
+	if (edid[index + 0] == 0x00 && edid[index + 1] == 0x00)
+	{
+	    decode_display_descriptor (edid + index, info);
+	}
+	else
+	{
+	    decode_detailed_timing (
+		edid + index, &(info->detailed_timings[timing_idx++]));
+	}
+    }
+
+    info->n_detailed_timings = timing_idx;
+
+    return TRUE;
+}
+
+static void
+decode_check_sum (const uchar *edid,
+		  MonitorInfo *info)
+{
+    int i;
+    uchar check = 0;
+
+    for (i = 0; i < 128; ++i)
+	check += edid[i];
+
+    info->checksum = check;
+}
+
+MonitorInfo *
+decode_edid (const uchar *edid)
+{
+    MonitorInfo *info = calloc (1, sizeof (MonitorInfo));
+
+    decode_check_sum (edid, info);
+    
+    if (!decode_header (edid))
+	return NULL;
+
+    if (!decode_vendor_and_product_identification (edid, info))
+	return NULL;
+
+    if (!decode_edid_version (edid, info))
+	return NULL;
+
+    if (!decode_display_parameters (edid, info))
+	return NULL;
+
+    if (!decode_color_characteristics (edid, info))
+	return NULL;
+
+    if (!decode_established_timings (edid, info))
+	return NULL;
+
+    if (!decode_standard_timings (edid, info))
+	return NULL;
+    
+    if (!decode_descriptors (edid, info))
+	return NULL;
+    
+    return info;
+}
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/foo-marshal.h
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/foo-marshal.h	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,63 @@
+
+#ifndef __foo_marshal_MARSHAL_H__
+#define __foo_marshal_MARSHAL_H__
+
+#include	<glib-object.h>
+
+G_BEGIN_DECLS
+
+/* VOID:OBJECT,OBJECT (marshal.list:1) */
+extern void foo_marshal_VOID__OBJECT_OBJECT (GClosure     *closure,
+                                             GValue       *return_value,
+                                             guint         n_param_values,
+                                             const GValue *param_values,
+                                             gpointer      invocation_hint,
+                                             gpointer      marshal_data);
+
+/* VOID:UINT,UINT,UINT,UINT (marshal.list:2) */
+extern void foo_marshal_VOID__UINT_UINT_UINT_UINT (GClosure     *closure,
+                                                   GValue       *return_value,
+                                                   guint         n_param_values,
+                                                   const GValue *param_values,
+                                                   gpointer      invocation_hint,
+                                                   gpointer      marshal_data);
+
+/* VOID:UINT,UINT (marshal.list:3) */
+extern void foo_marshal_VOID__UINT_UINT (GClosure     *closure,
+                                         GValue       *return_value,
+                                         guint         n_param_values,
+                                         const GValue *param_values,
+                                         gpointer      invocation_hint,
+                                         gpointer      marshal_data);
+
+/* VOID:BOXED (marshal.list:4) */
+#define foo_marshal_VOID__BOXED	g_cclosure_marshal_VOID__BOXED
+
+/* VOID:BOXED,BOXED (marshal.list:5) */
+extern void foo_marshal_VOID__BOXED_BOXED (GClosure     *closure,
+                                           GValue       *return_value,
+                                           guint         n_param_values,
+                                           const GValue *param_values,
+                                           gpointer      invocation_hint,
+                                           gpointer      marshal_data);
+
+/* VOID:POINTER,BOXED,POINTER (marshal.list:6) */
+extern void foo_marshal_VOID__POINTER_BOXED_POINTER (GClosure     *closure,
+                                                     GValue       *return_value,
+                                                     guint         n_param_values,
+                                                     const GValue *param_values,
+                                                     gpointer      invocation_hint,
+                                                     gpointer      marshal_data);
+
+/* VOID:POINTER,POINTER (marshal.list:7) */
+extern void foo_marshal_VOID__POINTER_POINTER (GClosure     *closure,
+                                               GValue       *return_value,
+                                               guint         n_param_values,
+                                               const GValue *param_values,
+                                               gpointer      invocation_hint,
+                                               gpointer      marshal_data);
+
+G_END_DECLS
+
+#endif /* __foo_marshal_MARSHAL_H__ */
+
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/scrollarea.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/scrollarea.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,1900 @@
+#include <gdk/gdkprivate.h> /* For GDK_PARENT_RELATIVE_BG */
+#include "scrollarea.h"
+#include "foo-marshal.h"
+
+G_DEFINE_TYPE (FooScrollArea, foo_scroll_area, GTK_TYPE_CONTAINER);
+
+static GtkWidgetClass *parent_class;
+
+typedef struct BackingStore BackingStore;
+
+typedef void (* ExposeFunc) (cairo_t *cr, GdkRegion *region, gpointer data);
+
+#if 0
+static void          backing_store_draw (BackingStore *store, 
+					 GdkDrawable *dest,
+					 GdkRegion *clip,
+					 int dest_x,
+					 int dest_y);
+static void          backing_store_scroll (BackingStore *store,
+					   int dx, int dy);
+static void          backing_store_invalidate_rect (BackingStore *store,
+						    GdkRectangle *rect);
+static void          backing_store_invalidate_region (BackingStore *store,
+						      GdkRegion *region);
+static void          backing_store_invalidate_all (BackingStore *store);
+static BackingStore *backing_store_new (GdkWindow *window,
+					int width, int height);
+static void          backing_store_resize (BackingStore *store,
+					   int width, int height);
+static void          backing_store_process_updates (BackingStore *store,
+						    ExposeFunc func,
+						    gpointer data);
+static void	     backing_store_free (BackingStore *store);
+#endif
+
+typedef struct InputPath InputPath;
+typedef struct InputRegion InputRegion;
+typedef struct AutoScrollInfo AutoScrollInfo;
+
+struct InputPath
+{
+    gboolean			is_stroke;
+    cairo_fill_rule_t		fill_rule;
+    double			line_width;
+    cairo_path_t	       *path;		/* In canvas coordinates */
+
+    FooScrollAreaEventFunc	func;
+    gpointer			data;
+
+    InputPath		       *next;
+};
+
+/* InputRegions are mutually disjoint */
+struct InputRegion
+{
+    GdkRegion *region;		/* the boundary of this area in canvas coordinates */
+    InputPath *paths;
+};
+
+struct AutoScrollInfo
+{
+    int				dx;
+    int				dy;
+    int				timeout_id;
+    int				begin_x;
+    int				begin_y;
+    double			res_x;
+    double			res_y;
+    GTimer		       *timer;
+};
+
+struct FooScrollAreaPrivate
+{
+    GdkWindow		       *input_window;
+    
+    int				width;
+    int				height;
+    
+    GtkAdjustment	       *hadj;
+    GtkAdjustment	       *vadj;
+    int			        x_offset;
+    int				y_offset;
+    
+    int				min_width;
+    int				min_height;
+
+    GPtrArray		       *input_regions;
+    
+    AutoScrollInfo	       *auto_scroll_info;
+    
+    /* During expose, this region is set to the region
+     * being exposed. At other times, it is NULL
+     *
+     * It is used for clipping of input areas
+     */
+    GdkRegion		       *expose_region;
+    InputRegion		       *current_input;
+    
+    gboolean			grabbed;
+    FooScrollAreaEventFunc	grab_func;
+    gpointer			grab_data;
+
+    GdkPixmap		       *pixmap;
+    GdkRegion		       *update_region;		/* In canvas coordinates */
+};
+
+enum
+{
+    VIEWPORT_CHANGED,
+    PAINT,
+    INPUT,
+    LAST_SIGNAL,
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void foo_scroll_area_size_request (GtkWidget *widget,
+					  GtkRequisition *requisition);
+static gboolean foo_scroll_area_expose (GtkWidget *widget,
+					GdkEventExpose *expose);
+static void foo_scroll_area_size_allocate (GtkWidget *widget,
+					   GtkAllocation *allocation);
+static void foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area,
+						    GtkAdjustment    *hadjustment,
+						    GtkAdjustment    *vadjustment);
+static void foo_scroll_area_realize (GtkWidget *widget);
+static void foo_scroll_area_unrealize (GtkWidget *widget);
+static void foo_scroll_area_map (GtkWidget *widget);
+static void foo_scroll_area_unmap (GtkWidget *widget);
+static gboolean foo_scroll_area_button_press (GtkWidget *widget,
+					      GdkEventButton *event);
+static gboolean foo_scroll_area_button_release (GtkWidget *widget,
+						GdkEventButton *event);
+static gboolean foo_scroll_area_motion (GtkWidget *widget,
+					GdkEventMotion *event);
+
+static void
+foo_scroll_area_map (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    GTK_WIDGET_CLASS (parent_class)->map (widget);
+    
+    if (area->priv->input_window)
+	gdk_window_show (area->priv->input_window);
+}
+
+static void
+foo_scroll_area_unmap (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    if (area->priv->input_window)
+	gdk_window_hide (area->priv->input_window);
+    
+    GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+foo_scroll_area_finalize (GObject *object)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (object);
+    
+    g_object_unref (scroll_area->priv->hadj);
+    g_object_unref (scroll_area->priv->vadj);
+    
+    g_ptr_array_free (scroll_area->priv->input_regions, TRUE);
+    
+    g_free (scroll_area->priv);
+
+    G_OBJECT_CLASS (foo_scroll_area_parent_class)->finalize (object);
+}
+
+static void
+foo_scroll_area_class_init (FooScrollAreaClass *class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (class);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+    
+    object_class->finalize = foo_scroll_area_finalize;
+    widget_class->size_request = foo_scroll_area_size_request;
+    widget_class->expose_event = foo_scroll_area_expose;
+    widget_class->size_allocate = foo_scroll_area_size_allocate;
+    widget_class->realize = foo_scroll_area_realize;
+    widget_class->unrealize = foo_scroll_area_unrealize;
+    widget_class->button_press_event = foo_scroll_area_button_press;
+    widget_class->button_release_event = foo_scroll_area_button_release;
+    widget_class->motion_notify_event = foo_scroll_area_motion;
+    widget_class->map = foo_scroll_area_map;
+    widget_class->unmap = foo_scroll_area_unmap;
+    
+    class->set_scroll_adjustments = foo_scroll_area_set_scroll_adjustments;
+    
+    parent_class = g_type_class_peek_parent (class);
+    
+    signals[VIEWPORT_CHANGED] =
+	g_signal_new ("viewport_changed",
+		      G_OBJECT_CLASS_TYPE (object_class),
+		      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		      G_STRUCT_OFFSET (FooScrollAreaClass,
+				       viewport_changed),
+		      NULL, NULL,
+		      foo_marshal_VOID__BOXED_BOXED,
+		      G_TYPE_NONE, 2,
+		      GDK_TYPE_RECTANGLE,
+		      GDK_TYPE_RECTANGLE);
+    
+    signals[PAINT] =
+	g_signal_new ("paint",
+		      G_OBJECT_CLASS_TYPE (object_class),
+		      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		      G_STRUCT_OFFSET (FooScrollAreaClass,
+				       paint),
+		      NULL, NULL,
+		      foo_marshal_VOID__POINTER_BOXED_POINTER,
+		      G_TYPE_NONE, 3,
+		      G_TYPE_POINTER,
+		      GDK_TYPE_RECTANGLE, 
+		      G_TYPE_POINTER);
+    
+    widget_class->set_scroll_adjustments_signal =
+	g_signal_new ("set_scroll_adjustments",
+		      G_OBJECT_CLASS_TYPE (object_class),
+		      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		      G_STRUCT_OFFSET (FooScrollAreaClass,
+				       set_scroll_adjustments),
+		      NULL, NULL,
+		      foo_marshal_VOID__OBJECT_OBJECT,
+		      G_TYPE_NONE, 2,
+		      GTK_TYPE_ADJUSTMENT,
+		      GTK_TYPE_ADJUSTMENT);
+}
+
+static GtkAdjustment *
+new_adjustment (void)
+{
+    return GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+}
+
+static void
+foo_scroll_area_init (FooScrollArea *scroll_area)
+{
+    GTK_WIDGET_SET_FLAGS (scroll_area, GTK_NO_WINDOW);
+    
+    gtk_widget_set_redraw_on_allocate (GTK_WIDGET (scroll_area), FALSE);
+    
+    scroll_area->priv = g_new0 (FooScrollAreaPrivate, 1);
+    scroll_area->priv->width = 0;
+    scroll_area->priv->height = 0;
+    scroll_area->priv->hadj = g_object_ref_sink (new_adjustment());
+    scroll_area->priv->vadj = g_object_ref_sink (new_adjustment());
+    scroll_area->priv->x_offset = 0.0;
+    scroll_area->priv->y_offset = 0.0;
+    scroll_area->priv->min_width = -1;
+    scroll_area->priv->min_height = -1;
+    scroll_area->priv->auto_scroll_info = NULL;
+    scroll_area->priv->input_regions = g_ptr_array_new ();
+    scroll_area->priv->pixmap = NULL;
+    scroll_area->priv->update_region = gdk_region_new ();
+
+    gtk_widget_set_double_buffered (GTK_WIDGET (scroll_area), FALSE);
+}
+
+static void
+translate_cairo_device (cairo_t       *cr,
+			int            x_offset,
+			int            y_offset)
+{
+    cairo_surface_t *surface = cairo_get_target (cr);
+    double dev_x;
+    double dev_y;
+    
+    cairo_surface_get_device_offset (surface, &dev_x, &dev_y);
+    dev_x += x_offset;
+    dev_y += y_offset;
+    cairo_surface_set_device_offset (surface, dev_x, dev_y);
+}
+
+#if 0
+static void
+print_region (const char *header, GdkRegion *region)
+{
+    GdkRectangle *rects;
+    int n_rects;
+    int i;
+    
+    g_print ("%s\n", header);
+    
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+    for (i = 0; i < n_rects; ++i)
+    {
+	GdkRectangle *rect = &(rects[i]);
+	g_print ("  %d %d %d %d\n",
+		 rect->x, rect->y, rect->width, rect->height);
+    }
+}
+#endif
+
+typedef void (* PathForeachFunc) (double  *x,
+				  double  *y,
+				  gpointer data);
+
+static void
+path_foreach_point (cairo_path_t     *path,
+		    PathForeachFunc   func,
+		    gpointer	      user_data)
+{
+    int i;
+    
+    for (i = 0; i < path->num_data; i += path->data[i].header.length)
+    {
+	cairo_path_data_t *data = &(path->data[i]);
+	
+	switch (data->header.type)
+	{
+	case CAIRO_PATH_MOVE_TO:
+	case CAIRO_PATH_LINE_TO:
+	    func (&(data[1].point.x), &(data[1].point.y), user_data);
+	    break;
+	    
+	case CAIRO_PATH_CURVE_TO:
+	    func (&(data[1].point.x), &(data[1].point.y), user_data);
+	    func (&(data[2].point.x), &(data[2].point.y), user_data);
+	    func (&(data[3].point.x), &(data[3].point.y), user_data);
+	    break;
+	    
+	case CAIRO_PATH_CLOSE_PATH:
+	    break;
+	}
+    }
+}
+
+typedef struct
+{
+    double x1, y1, x2, y2;
+} Box;
+
+#if 0
+static void
+update_box (double *x, double *y, gpointer data)
+{
+    Box *box = data;
+    
+    if (*x < box->x1)
+	box->x1 = *x;
+
+    if (*y < box->y1)
+	box->y1 = *y;
+
+    if (*y > box->y2)
+	box->y2 = *y;
+    
+    if (*x > box->x2)
+	box->x2 = *x;
+}
+#endif
+
+#if 0
+static void
+path_compute_extents (cairo_path_t *path,
+		      GdkRectangle *rect)
+{
+    if (rect)
+    {
+	Box box = { G_MAXDOUBLE, G_MAXDOUBLE, G_MINDOUBLE, G_MINDOUBLE };
+
+	path_foreach_point (path, update_box, &box);
+
+	rect->x = box.x1;
+	rect->y = box.y1;
+	rect->width = box.x2 - box.x1;
+	rect->height = box.y2 - box.y1;
+    }
+}
+#endif
+
+static void
+input_path_free_list (InputPath *paths)
+{
+    if (!paths)
+	return;
+
+    input_path_free_list (paths->next);
+    cairo_path_destroy (paths->path);
+    g_free (paths);
+}
+
+static void
+input_region_free (InputRegion *region)
+{
+    input_path_free_list (region->paths);
+    gdk_region_destroy (region->region);
+
+    g_free (region);
+}
+
+static void
+get_viewport (FooScrollArea *scroll_area,
+	      GdkRectangle  *viewport)
+{
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+    
+    viewport->x = scroll_area->priv->x_offset;
+    viewport->y = scroll_area->priv->y_offset;
+    viewport->width = widget->allocation.width;
+    viewport->height = widget->allocation.height;
+}
+
+static void
+allocation_to_canvas (FooScrollArea *area,
+		      int           *x,
+		      int           *y)
+{
+    *x += area->priv->x_offset;
+    *y += area->priv->y_offset;
+}
+
+static void
+clear_exposed_input_region (FooScrollArea *area,
+			    GdkRegion *exposed)	/* in canvas coordinates */
+{
+    int i;
+    GdkRegion *viewport;
+    GdkRectangle allocation;
+
+    allocation = GTK_WIDGET (area)->allocation;
+    allocation.x = 0;
+    allocation.y = 0;
+    allocation_to_canvas (area, &allocation.x, &allocation.y);
+    viewport = gdk_region_rectangle (&allocation);
+    gdk_region_subtract (viewport, exposed);
+    
+    for (i = 0; i < area->priv->input_regions->len; ++i)
+    {
+	InputRegion *region = area->priv->input_regions->pdata[i];
+
+	gdk_region_intersect (region->region, viewport);
+
+	if (gdk_region_empty (region->region))
+	{
+	    input_region_free (region);
+	    g_ptr_array_remove_index_fast (area->priv->input_regions, i--);
+	}
+    }
+
+    gdk_region_destroy (viewport);
+    
+#if 0
+	path = region->paths;
+	while (path != NULL)
+	{
+	    GdkRectangle rect;
+
+	    path_compute_extents (path->path, &rect);
+
+	    if (gdk_region_rect_in (area->priv->expose_region, &rect) == GDK_OVERLAP_RECTANGLE_IN)
+		g_print ("we would have deleted it\n");
+#if 0
+	    else
+		g_print ("nope (%d %d %d %d)\n", );
+#endif
+	    
+	    path = path->next;
+	}
+	
+	/* FIXME: we should also delete paths (and path segments)
+	 * completely contained in the expose_region
+	 */
+    }
+#endif
+}
+
+static void
+setup_background_cr (GdkWindow *window,
+		     cairo_t   *cr,
+		     int        x_offset,
+		     int        y_offset)
+{
+    GdkWindowObject *private = (GdkWindowObject *)window;
+    
+    if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG && private->parent)
+    {
+	x_offset += private->x;
+	y_offset += private->y;
+	
+	setup_background_cr (GDK_WINDOW (private->parent), cr, x_offset, y_offset);
+    }
+    else if (private->bg_pixmap &&
+	     private->bg_pixmap != GDK_PARENT_RELATIVE_BG &&
+	     private->bg_pixmap != GDK_NO_BG)
+    {
+	gdk_cairo_set_source_pixmap (cr, private->bg_pixmap, -x_offset, -y_offset);
+    }
+    else
+    {
+	gdk_cairo_set_source_color (cr, &private->bg_color);
+    }
+}
+
+static void
+initialize_background (GtkWidget *widget,
+		       cairo_t   *cr)
+{
+    setup_background_cr (widget->window, cr, 0, 0);
+
+    cairo_paint (cr);
+}
+
+static void
+clip_to_region (cairo_t *cr, GdkRegion *region)
+{
+    int n_rects;
+    GdkRectangle *rects;
+
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+
+    cairo_new_path (cr);
+    while (n_rects--)
+    {
+	GdkRectangle *rect = &(rects[n_rects]);
+
+	cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
+    }
+    cairo_clip (cr);
+
+    g_free (rects);
+}
+
+static void
+simple_draw_drawable (GdkDrawable *dst,
+		      GdkDrawable *src,
+		      int	   src_x,
+		      int	   src_y,
+		      int          dst_x,
+		      int          dst_y,
+		      int          width,
+		      int          height)
+{
+    GdkGC *gc = gdk_gc_new (dst);
+
+    gdk_draw_drawable (dst, gc, src, src_x, src_y, dst_x, dst_y, width, height);
+
+    g_object_unref (gc);
+}
+
+static gboolean
+foo_scroll_area_expose (GtkWidget *widget,
+			GdkEventExpose *expose)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
+    cairo_t *cr;
+    GdkRectangle extents;
+    GdkRegion *region;
+    int x_offset, y_offset;
+    GdkGC *gc;
+    
+    /* I don't think expose can ever recurse for the same area */
+    g_assert (!scroll_area->priv->expose_region);
+    
+    /* Note that this function can be called at a time
+     * where the adj->value is different from x_offset. 
+     * Ie., the GtkScrolledWindow changed the adj->value
+     * without emitting the value_changed signal. 
+     *
+     * Hence we must always use the value we got 
+     * the last time the signal was emitted, ie.,
+     * priv->{x,y}_offset.
+     */
+    
+    x_offset = scroll_area->priv->x_offset;
+    y_offset = scroll_area->priv->y_offset;
+    
+    scroll_area->priv->expose_region = expose->region;
+
+    /* Setup input areas */
+    clear_exposed_input_region (scroll_area, scroll_area->priv->update_region);
+    
+    scroll_area->priv->current_input = g_new0 (InputRegion, 1);
+    scroll_area->priv->current_input->region = gdk_region_copy (scroll_area->priv->update_region);
+    scroll_area->priv->current_input->paths = NULL;
+    g_ptr_array_add (scroll_area->priv->input_regions,
+		     scroll_area->priv->current_input);
+
+    region = scroll_area->priv->update_region;
+    scroll_area->priv->update_region = gdk_region_new ();
+    
+    /* Create cairo context */
+    cr = gdk_cairo_create (scroll_area->priv->pixmap);
+    translate_cairo_device (cr, -x_offset, -y_offset);
+    clip_to_region (cr, region);
+    initialize_background (widget, cr);
+
+    /* Create regions */
+    gdk_region_get_clipbox (region, &extents);
+
+    g_signal_emit (widget, signals[PAINT], 0, cr, &extents, region);
+
+    /* Destroy stuff */
+    cairo_destroy (cr);
+    
+    scroll_area->priv->expose_region = NULL;
+    scroll_area->priv->current_input = NULL;
+
+    /* Finally draw the backing pixmap */
+    gc = gdk_gc_new (widget->window);
+    
+    gdk_gc_set_clip_region (gc, expose->region);
+
+    gdk_draw_drawable (widget->window, gc, scroll_area->priv->pixmap,
+		       0, 0, widget->allocation.x, widget->allocation.y,
+		       widget->allocation.width, widget->allocation.height);
+
+    g_object_unref (gc);
+    gdk_region_destroy (region);
+    
+    return TRUE;
+}
+
+void
+foo_scroll_area_get_viewport (FooScrollArea *scroll_area,
+			      GdkRectangle  *viewport)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    
+    if (!viewport)
+	return;
+    
+    get_viewport (scroll_area, viewport);
+}
+
+static void
+process_event (FooScrollArea	       *scroll_area,
+	       FooScrollAreaEventType	input_type,
+	       int			x,
+	       int			y);
+
+static void
+emit_viewport_changed (FooScrollArea *scroll_area,
+		       GdkRectangle  *new_viewport,
+		       GdkRectangle  *old_viewport)
+{
+    int px, py;
+    g_signal_emit (scroll_area, signals[VIEWPORT_CHANGED], 0, 
+		   new_viewport, old_viewport);
+    
+    gdk_window_get_pointer (scroll_area->priv->input_window, &px, &py, NULL);
+    
+#if 0
+    g_print ("procc\n");
+#endif
+    
+    process_event (scroll_area, FOO_MOTION, px, py);
+}
+
+static void
+clamp_adjustment (GtkAdjustment *adj)
+{
+    double old_value = adj->value;
+    
+    if (adj->upper >= adj->page_size)
+	adj->value = CLAMP (adj->value, 0.0, adj->upper - adj->page_size);
+    else
+	adj->value = 0.0;
+    
+    if (old_value != adj->value)
+	gtk_adjustment_value_changed (adj);
+    
+    gtk_adjustment_changed (adj);
+}
+
+static gboolean
+set_adjustment_values (FooScrollArea *scroll_area)
+{
+    GtkAllocation *allocation = &GTK_WIDGET (scroll_area)->allocation;
+    
+    GtkAdjustment *hadj = scroll_area->priv->hadj;
+    GtkAdjustment *vadj = scroll_area->priv->vadj;
+    
+    /* Horizontal */
+    hadj->page_size = allocation->width;
+    hadj->step_increment = 0.1 * allocation->width;
+    hadj->page_increment = 0.9 * allocation->width;
+    hadj->lower = 0.0;
+    hadj->upper = scroll_area->priv->width;
+    
+    /* Vertical */
+    vadj->page_size = allocation->height;
+    vadj->step_increment = 0.1 * allocation->height;
+    vadj->page_increment = 0.9 * allocation->height;
+    vadj->lower = 0.0;
+    vadj->upper = scroll_area->priv->height;
+    
+    clamp_adjustment (hadj);
+    clamp_adjustment (vadj);
+    
+    return TRUE;
+}
+
+static void
+foo_scroll_area_realize (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    GdkWindowAttr attributes;
+    gint attributes_mask;
+    
+    GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+    
+    attributes.window_type = GDK_WINDOW_CHILD;
+    attributes.x = widget->allocation.x;
+    attributes.y = widget->allocation.y;
+    attributes.width = widget->allocation.width;
+    attributes.height = widget->allocation.height;
+    attributes.wclass = GDK_INPUT_ONLY;
+    attributes.event_mask = gtk_widget_get_events (widget);
+    attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+			      GDK_BUTTON_RELEASE_MASK |
+			      GDK_BUTTON1_MOTION_MASK |
+			      GDK_BUTTON2_MOTION_MASK |
+			      GDK_BUTTON3_MOTION_MASK |
+			      GDK_POINTER_MOTION_MASK |
+			      GDK_ENTER_NOTIFY_MASK |
+			      GDK_LEAVE_NOTIFY_MASK);
+    
+    attributes_mask = GDK_WA_X | GDK_WA_Y;
+    
+    widget->window = gtk_widget_get_parent_window (widget);
+    g_object_ref (widget->window);
+    
+    area->priv->input_window = gdk_window_new (widget->window,
+					       &attributes, attributes_mask);
+    area->priv->pixmap = gdk_pixmap_new (widget->window, 
+					 widget->allocation.width,
+					 widget->allocation.height,
+					 -1);
+    gdk_window_set_user_data (area->priv->input_window, area);
+    
+    widget->style = gtk_style_attach (widget->style, widget->window);
+}
+
+static void
+foo_scroll_area_unrealize (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    if (area->priv->input_window)
+    {
+	gdk_window_set_user_data (area->priv->input_window, NULL);
+	gdk_window_destroy (area->priv->input_window);
+	area->priv->input_window = NULL;
+    }
+    
+    GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static GdkPixmap *
+create_new_pixmap (GtkWidget *widget,
+		   GdkPixmap *old)
+{
+    GdkPixmap *new = gdk_pixmap_new (widget->window,
+				     widget->allocation.width,
+				     widget->allocation.height,
+				     -1);
+
+    /* Unfortunately we don't know in which direction we were resized,
+     * so we just assume we were dragged from the south-east corner.
+     *
+     * Although, maybe we could get the root coordinates of the input-window?
+     * That might just work, actually. We need to make sure metacity uses
+     * static gravity for the window before this will be useful.
+     */
+    simple_draw_drawable (new, old, 0, 0, 0, 0, -1, -1);
+
+    return new;
+}
+		   
+static void
+allocation_to_canvas_region (FooScrollArea *area,
+			     GdkRegion *region)
+{
+    gdk_region_offset (region, area->priv->x_offset, area->priv->y_offset);
+}
+			     
+
+static void
+foo_scroll_area_size_allocate (GtkWidget     *widget,
+			       GtkAllocation *allocation)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
+    GdkRectangle new_viewport;
+    GdkRectangle old_viewport;
+    GdkRegion *old_allocation;
+    GdkRegion *invalid;
+
+    get_viewport (scroll_area, &old_viewport);
+
+    old_allocation = gdk_region_rectangle (&widget->allocation);
+    gdk_region_offset (old_allocation,
+		       -widget->allocation.x, -widget->allocation.y);
+    invalid = gdk_region_rectangle (allocation);
+    gdk_region_offset (invalid, -allocation->x, -allocation->y);
+    gdk_region_subtract (invalid, old_allocation);
+    allocation_to_canvas_region (scroll_area, invalid);
+    foo_scroll_area_invalidate_region (scroll_area, invalid);
+    gdk_region_destroy (old_allocation);
+    gdk_region_destroy (invalid);
+
+    widget->allocation = *allocation;
+    
+    if (scroll_area->priv->input_window)
+    {
+	GdkPixmap *new_pixmap;
+	
+	gdk_window_move_resize (scroll_area->priv->input_window,
+				allocation->x, allocation->y,
+				allocation->width, allocation->height);
+
+	new_pixmap = create_new_pixmap (widget, scroll_area->priv->pixmap);
+
+	g_object_unref (scroll_area->priv->pixmap);
+
+	scroll_area->priv->pixmap = new_pixmap;
+    }
+    
+    get_viewport (scroll_area, &new_viewport);
+    
+    emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
+}
+
+static void
+emit_input (FooScrollArea *scroll_area,
+	    FooScrollAreaEventType type,
+	    int			   x,
+	    int			   y,
+	    FooScrollAreaEventFunc func,
+	    gpointer		data)
+{
+    FooScrollAreaEvent event;
+    
+    if (!func)
+	return;
+
+    if (type != FOO_MOTION)
+	emit_input (scroll_area, FOO_MOTION, x, y, func, data);
+    
+#if 0
+    x += scroll_area->priv->x_offset;
+    y += scroll_area->priv->y_offset;
+#endif
+    
+    event.type = type;
+    event.x = x;
+    event.y = y;
+    
+    func (scroll_area, &event, data);
+}
+
+#if 0
+static void
+print_path (const char *header,
+	    cairo_path_t *path)
+{
+    int i;
+
+    g_print ("%s\n", header);
+
+    for (i=0; i < path->num_data; i += path->data[i].header.length)
+    {
+	cairo_path_data_t *data = &(path->data[i]);
+	
+	switch (data->header.type)
+	{
+	case CAIRO_PATH_MOVE_TO:
+	    g_print ("move to:    %f, %f\n", data[1].point.x, data[1].point.y);
+	    break;
+	    
+	case CAIRO_PATH_LINE_TO:
+	    g_print ("line to:    %f, %f\n", data[1].point.x, data[1].point.y);
+	    break;
+	    
+	case CAIRO_PATH_CURVE_TO:
+	    g_print ("curve to:   %f, %f\n", data[1].point.x, data[1].point.y);
+	    g_print ("            %f, %f\n", data[1].point.x, data[1].point.y);
+	    g_print ("            %f, %f\n", data[1].point.x, data[1].point.y);
+	    break;
+	    
+	case CAIRO_PATH_CLOSE_PATH:
+	    break;
+	}
+    }
+}
+#endif
+
+static void
+process_event (FooScrollArea	       *scroll_area,
+	       FooScrollAreaEventType	input_type,
+	       int			x,
+	       int			y)
+{
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+    int i;
+
+    allocation_to_canvas (scroll_area, &x, &y);
+    
+    if (scroll_area->priv->grabbed)
+    {
+	emit_input (scroll_area, input_type, x, y,
+		    scroll_area->priv->grab_func,
+		    scroll_area->priv->grab_data);
+	return;
+    }
+
+    
+#if 0
+    x += widget->allocation.x;
+    y += widget->allocation.y;
+#endif
+
+#if 0
+    g_print ("number of input regions: %d\n", scroll_area->priv->input_regions->len);
+#endif
+    
+    for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
+    {
+	InputRegion *region = scroll_area->priv->input_regions->pdata[i];
+
+#if 0
+	g_print ("%d ", i);
+	print_region ("region:", region->region);
+#endif
+	
+	if (gdk_region_point_in (region->region, x, y))
+	{
+	    InputPath *path;
+
+	    path = region->paths;
+	    while (path)
+	    {
+		cairo_t *cr;
+		gboolean inside;
+
+		cr = gdk_cairo_create (widget->window);
+		cairo_set_fill_rule (cr, path->fill_rule);
+		cairo_set_line_width (cr, path->line_width);
+		cairo_append_path (cr, path->path);
+
+		if (path->is_stroke)
+		    inside = cairo_in_stroke (cr, x, y);
+		else
+		    inside = cairo_in_fill (cr, x, y);
+
+		cairo_destroy (cr);
+		
+		if (inside)
+		{
+		    emit_input (scroll_area, input_type,
+				x, y,
+				path->func,
+				path->data);
+		    return;
+		}
+		
+		path = path->next;
+	    }
+
+	    /* Since the regions are all disjoint, no other region
+	     * can match. Of course we could be clever and try and
+	     * sort the regions, but so far I have been unable to
+	     * make this loop show up on a profile.
+	     */
+	    return;
+	}
+    }
+}
+
+static void
+process_gdk_event (FooScrollArea *scroll_area,
+		   int		  x,
+		   int	          y,
+		   GdkEvent      *event)
+{
+    FooScrollAreaEventType input_type;
+    
+    if (event->type == GDK_BUTTON_PRESS)
+	input_type = FOO_BUTTON_PRESS;
+    else if (event->type == GDK_BUTTON_RELEASE)
+	input_type = FOO_BUTTON_RELEASE;
+    else if (event->type == GDK_MOTION_NOTIFY)
+	input_type = FOO_MOTION;
+    
+    process_event (scroll_area, input_type, x, y);
+}
+
+static gboolean
+foo_scroll_area_button_press (GtkWidget *widget,
+			      GdkEventButton *event)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
+    
+    return TRUE;
+}
+
+static gboolean
+foo_scroll_area_button_release (GtkWidget *widget,
+				GdkEventButton *event)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
+    
+    return FALSE;
+}
+
+static gboolean
+foo_scroll_area_motion (GtkWidget *widget,
+			GdkEventMotion *event)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
+    return TRUE;
+}
+
+void
+foo_scroll_area_set_size_fixed_y (FooScrollArea	       *scroll_area,
+				  int			width,
+				  int			height,
+				  int			old_y,
+				  int			new_y)
+{
+    int dy = new_y - old_y;
+    
+    scroll_area->priv->width = width;
+    scroll_area->priv->height = height;
+    
+#if 0
+    g_print ("diff: %d\n", new_y - old_y);
+#endif
+    
+    scroll_area->priv->vadj->value += dy;
+    
+    set_adjustment_values (scroll_area);
+    
+    if (dy != 0)
+	gtk_adjustment_value_changed  (scroll_area->priv->vadj);
+}
+
+void
+foo_scroll_area_set_size (FooScrollArea	       *scroll_area,
+			  int			width,
+			  int			height)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    
+    /* FIXME: Default scroll algorithm should probably be to
+     * keep the same *area* outside the screen as before.
+     *
+     * For wrapper widgets that will do something roughly
+     * right. For widgets that don't change size, it
+     * will do the right thing. Except for idle-layouting
+     * widgets.
+     *
+     * Maybe there should be some generic support for those
+     * widgets. Can that even be done?
+     *
+     * Should we have a version of this function using 
+     * fixed points?
+     */
+    
+    scroll_area->priv->width = width;
+    scroll_area->priv->height = height;
+    
+    set_adjustment_values (scroll_area);
+}
+
+static void
+foo_scroll_area_size_request (GtkWidget      *widget,
+			      GtkRequisition *requisition)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
+    
+    requisition->width = scroll_area->priv->min_width;
+    requisition->height = scroll_area->priv->min_height;
+    
+#if 0
+    g_print ("request %d %d\n", requisition->width, requisition->height);
+#endif
+}
+
+#if 0
+static void
+translate_point (double *x, double *y, gpointer data)
+{
+    int *translation = data;
+
+    *x += translation[0];
+    *y += translation[1];
+}
+#endif
+
+#if 0
+static void
+path_translate (cairo_path_t  *path,
+		int	       dx,
+		int	       dy)
+{
+    int translation[2] = {dx, dy};
+    
+    path_foreach_point (path, translate_point, translation);
+}
+#endif
+
+static void
+translate_input_regions (FooScrollArea *scroll_area,
+			 int		dx,
+			 int		dy)
+{
+#if 0
+    int i;
+
+    for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
+    {
+	InputRegion *region = scroll_area->priv->input_regions->pdata[i];
+	InputPath *path;
+	
+	gdk_region_offset (region->region, dx, dy);
+
+	path = region->paths;
+	while (path != NULL)
+	{
+	    path_translate (path->path, dx, dy);
+	    path = path->next;
+	}
+    }
+#endif
+}
+
+#if 0
+static void
+paint_region (FooScrollArea *area, GdkRegion *region)
+{
+    int n_rects;
+    GdkRectangle *rects;
+    region = gdk_region_copy (region);
+    
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+
+    gdk_region_offset (region,
+		       GTK_WIDGET (area)->allocation.x,
+		       GTK_WIDGET (area)->allocation.y);
+
+    GdkGC *gc = gdk_gc_new (GTK_WIDGET (area)->window);
+    gdk_gc_set_clip_region (gc, region);
+    gdk_draw_rectangle (GTK_WIDGET (area)->window, gc, TRUE, 0, 0, -1, -1);
+    g_object_unref (gc);
+    g_free (rects);
+}
+#endif
+
+static void
+foo_scroll_area_scroll (FooScrollArea *area,
+			gint dx, 
+			gint dy)
+{
+    GdkRectangle allocation = GTK_WIDGET (area)->allocation;
+    GdkRectangle src_area;
+    GdkRectangle move_area;
+    GdkRegion *invalid_region;
+
+    allocation.x = 0;
+    allocation.y = 0;
+
+    src_area = allocation;
+    src_area.x -= dx;
+    src_area.y -= dy;
+
+    invalid_region = gdk_region_rectangle (&allocation);
+    
+    if (gdk_rectangle_intersect (&allocation, &src_area, &move_area))
+    {
+	GdkRegion *move_region;
+
+#if 0
+	g_print ("scrolling %d %d %d %d (%d %d)\n",
+		 move_area.x, move_area.y,
+		 move_area.width, move_area.height,
+		 dx, dy);
+#endif
+	
+	simple_draw_drawable (area->priv->pixmap, area->priv->pixmap,
+			      move_area.x, move_area.y,
+			      move_area.x + dx, move_area.y + dy,
+			      move_area.width, move_area.height);
+	gtk_widget_queue_draw (GTK_WIDGET (area));
+	
+	move_region = gdk_region_rectangle (&move_area);
+	gdk_region_offset (move_region, dx, dy);
+	gdk_region_subtract (invalid_region, move_region);
+	gdk_region_destroy (move_region);
+    }
+
+#if 0
+    paint_region (area, invalid_region);
+#endif
+    
+    allocation_to_canvas_region (area, invalid_region);
+
+    foo_scroll_area_invalidate_region (area, invalid_region);
+    
+    gdk_region_destroy (invalid_region);
+}
+
+static void
+foo_scrollbar_adjustment_changed (GtkAdjustment *adj,
+				  FooScrollArea *scroll_area)
+{
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+    gint dx = 0;
+    gint dy = 0;
+    GdkRectangle old_viewport, new_viewport;
+    
+    get_viewport (scroll_area, &old_viewport);
+    
+    if (adj == scroll_area->priv->hadj)
+    {
+	/* FIXME: do we treat the offset as int or double, and,
+	 * if int, how do we round?
+	 */
+	dx = (int)adj->value - scroll_area->priv->x_offset;
+	scroll_area->priv->x_offset = adj->value;
+    }
+    else if (adj == scroll_area->priv->vadj)
+    {
+	dy = (int)adj->value - scroll_area->priv->y_offset;
+	scroll_area->priv->y_offset = adj->value;
+    }
+    else
+    {
+	g_assert_not_reached ();
+    }
+    
+    if (GTK_WIDGET_REALIZED (widget))
+    {
+	foo_scroll_area_scroll (scroll_area, -dx, -dy);
+    
+#if 0
+	window_scroll_area (widget->window, &widget->allocation, -dx, -dy);
+#endif
+	translate_input_regions (scroll_area, -dx, -dy);
+
+#if 0
+	gdk_window_process_updates (widget->window, TRUE);
+#endif
+    }
+    
+    get_viewport (scroll_area, &new_viewport);
+    
+    emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
+}
+
+static void
+set_one_adjustment (FooScrollArea *scroll_area,
+		    GtkAdjustment *adjustment,
+		    GtkAdjustment **location)
+{
+    g_return_if_fail (location != NULL);
+    
+    if (adjustment == *location)
+	return;
+    
+    if (!adjustment)
+	adjustment = new_adjustment ();
+    
+    g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+    
+    if (*location)
+    {
+	g_signal_handlers_disconnect_by_func (
+	    *location, foo_scrollbar_adjustment_changed, scroll_area);
+	
+	g_object_unref (*location);
+    }
+    
+    *location = adjustment;
+    
+    g_object_ref_sink (*location);
+    
+    g_signal_connect (*location, "value_changed",
+		      G_CALLBACK (foo_scrollbar_adjustment_changed),
+		      scroll_area);
+}
+
+static void
+foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area,
+					GtkAdjustment *hadjustment,
+					GtkAdjustment *vadjustment)
+{
+    set_one_adjustment (scroll_area, hadjustment, &scroll_area->priv->hadj);
+    set_one_adjustment (scroll_area, vadjustment, &scroll_area->priv->vadj);
+    
+    set_adjustment_values (scroll_area);
+}
+
+FooScrollArea *
+foo_scroll_area_new (void)
+{
+    return g_object_new (FOO_TYPE_SCROLL_AREA, NULL);
+}
+
+void
+foo_scroll_area_set_min_size (FooScrollArea *scroll_area,
+			      int		   min_width,
+			      int            min_height)
+{
+    scroll_area->priv->min_width = min_width;
+    scroll_area->priv->min_height = min_height;
+    
+    /* FIXME: think through invalidation.
+     *
+     * Goals: - no repainting everything on size_allocate(),
+     *        - make sure input boxes are invalidated when
+     *          needed
+     */
+    gtk_widget_queue_resize (GTK_WIDGET (scroll_area));
+}
+
+#if 0
+static void
+warn_about_adding_input_outside_expose (const char *func)
+{
+    static gboolean warned = FALSE;
+    
+    if (!warned)
+    {
+	g_warning ("%s() can only be called "
+		   "from the paint handler for the FooScrollArea\n", func);
+	
+	warned = TRUE;
+    }
+}
+#endif
+
+static void
+user_to_device (double *x, double *y,
+		gpointer data)
+{
+    cairo_t *cr = data;
+    
+    cairo_user_to_device (cr, x, y);
+}
+
+static InputPath *
+make_path (FooScrollArea *area,
+	   cairo_t *cr,
+	   gboolean is_stroke,
+	   FooScrollAreaEventFunc func,
+	   gpointer data)
+{
+    InputPath *path = g_new0 (InputPath, 1);
+
+    path->is_stroke = is_stroke;
+    path->fill_rule = cairo_get_fill_rule (cr);
+    path->line_width = cairo_get_line_width (cr);
+    path->path = cairo_copy_path (cr);
+    path_foreach_point (path->path, user_to_device, cr);
+    path->func = func;
+    path->data = data;
+    path->next = area->priv->current_input->paths;
+    area->priv->current_input->paths = path;
+    return path;
+}
+
+/* FIXME: we probably really want a
+ *
+ *	foo_scroll_area_add_input_from_fill (area, cr, ...);
+ * and
+ *      foo_scroll_area_add_input_from_stroke (area, cr, ...);
+ * as well.
+ */
+void
+foo_scroll_area_add_input_from_fill (FooScrollArea           *scroll_area,
+				     cairo_t	             *cr,
+				     FooScrollAreaEventFunc   func,
+				     gpointer                 data)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    g_return_if_fail (cr != NULL);
+    g_return_if_fail (scroll_area->priv->current_input);
+
+    make_path (scroll_area, cr, FALSE, func, data);
+}
+
+void
+foo_scroll_area_add_input_from_stroke (FooScrollArea           *scroll_area,
+				       cairo_t	                *cr,
+				       FooScrollAreaEventFunc   func,
+				       gpointer                 data)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    g_return_if_fail (cr != NULL);
+    g_return_if_fail (scroll_area->priv->current_input);
+
+    make_path (scroll_area, cr, TRUE, func, data);
+}
+
+void
+foo_scroll_area_invalidate (FooScrollArea *scroll_area)
+{
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+
+    foo_scroll_area_invalidate_rect (scroll_area,
+				     scroll_area->priv->x_offset, scroll_area->priv->y_offset,
+				     widget->allocation.width,
+				     widget->allocation.height);
+}
+
+static void
+canvas_to_window (FooScrollArea *area,
+		  GdkRegion *region)
+{
+    GtkWidget *widget = GTK_WIDGET (area);
+    
+    gdk_region_offset (region,
+		       -area->priv->x_offset + widget->allocation.x,
+		       -area->priv->y_offset + widget->allocation.y);
+}
+
+static void
+window_to_canvas (FooScrollArea *area,
+		  GdkRegion *region)
+{
+    GtkWidget *widget = GTK_WIDGET (area);
+
+    gdk_region_offset (region,
+		       area->priv->x_offset - widget->allocation.x,
+		       area->priv->y_offset - widget->allocation.y);
+}
+
+void
+foo_scroll_area_invalidate_region (FooScrollArea *area,
+				   GdkRegion     *region)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (area));
+    
+    gdk_region_union (area->priv->update_region, region);
+
+    if (GTK_WIDGET_REALIZED (area))
+    {
+	canvas_to_window (area, region);
+	
+	gdk_window_invalidate_region (GTK_WIDGET (area)->window, region, TRUE);
+	
+	window_to_canvas (area, region);
+    }
+}
+
+void
+foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area,
+				 int	        x,
+				 int	        y,
+				 int	        width,
+				 int	        height)
+{
+    GdkRectangle rect = { x, y, width, height };
+    GdkRegion *region;
+    
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+
+    region = gdk_region_rectangle (&rect);
+
+    foo_scroll_area_invalidate_region (scroll_area, region);
+
+    gdk_region_destroy (region);
+}
+
+void
+foo_scroll_area_begin_grab (FooScrollArea *scroll_area,
+			    FooScrollAreaEventFunc func,
+			    gpointer       input_data)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    g_return_if_fail (!scroll_area->priv->grabbed);
+    
+    scroll_area->priv->grabbed = TRUE;
+    scroll_area->priv->grab_func = func;
+    scroll_area->priv->grab_data = input_data;
+    
+    /* FIXME: we should probably take a server grab */
+    /* Also, maybe there should be support for setting the grab cursor */
+}
+
+void
+foo_scroll_area_end_grab (FooScrollArea *scroll_area)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    
+    scroll_area->priv->grabbed = FALSE;
+    scroll_area->priv->grab_func = NULL;
+    scroll_area->priv->grab_data = NULL;
+}
+
+gboolean
+foo_scroll_area_is_grabbed (FooScrollArea *scroll_area)
+{
+    return scroll_area->priv->grabbed;
+}
+
+void
+foo_scroll_area_set_viewport_pos (FooScrollArea  *scroll_area,
+				  int		  x,
+				  int		  y)
+{
+    int x_changed = scroll_area->priv->hadj->value != (double)x;
+    int y_changed = scroll_area->priv->vadj->value != (double)y;
+    
+    scroll_area->priv->hadj->value = x;
+    scroll_area->priv->vadj->value = y;
+    
+    set_adjustment_values (scroll_area);
+    
+    if (x_changed)
+	gtk_adjustment_value_changed (scroll_area->priv->hadj);
+    
+    if (y_changed)
+	gtk_adjustment_value_changed (scroll_area->priv->vadj);
+}
+
+static gboolean
+rect_contains (const GdkRectangle *rect, int x, int y)
+{
+    return (x >= rect->x		&&
+	    y >= rect->y		&&
+	    x  < rect->x + rect->width	&&
+	    y  < rect->y + rect->height);
+}
+
+static void
+stop_scrolling (FooScrollArea *area)
+{
+#if 0
+    g_print ("stop scrolling\n");
+#endif
+    if (area->priv->auto_scroll_info)
+    {
+	g_source_remove (area->priv->auto_scroll_info->timeout_id);
+	g_timer_destroy (area->priv->auto_scroll_info->timer);
+	g_free (area->priv->auto_scroll_info);
+	
+	area->priv->auto_scroll_info = NULL;
+    }
+}
+
+static gboolean
+scroll_idle (gpointer data)
+{
+    GdkRectangle viewport, new_viewport;
+    FooScrollArea *area = data;
+    AutoScrollInfo *info = area->priv->auto_scroll_info;
+#if 0
+    int dx, dy;
+#endif
+    int new_x, new_y;
+    double elapsed;
+    
+    get_viewport (area, &viewport);
+    
+#if 0
+    g_print ("old info: %d %d\n", info->dx, info->dy);
+    
+    g_print ("timeout (%d %d)\n", dx, dy);
+#endif
+    
+#if 0
+    viewport.x += info->dx;
+    viewport.y += info->dy;
+#endif
+    
+#if 0
+    g_print ("new info %d %d\n", info->dx, info->dy);
+#endif
+
+    elapsed = g_timer_elapsed (info->timer, NULL);
+
+    info->res_x = elapsed * info->dx / 0.2;
+    info->res_y = elapsed * info->dy / 0.2;
+
+#if 0
+    g_print ("%f %f\n", info->res_x, info->res_y);
+#endif
+    
+    new_x = viewport.x + info->res_x;
+    new_y = viewport.y + info->res_y;
+
+#if 0
+    g_print ("%f\n", elapsed * (info->dx / 0.2));
+#endif
+    
+#if 0
+    g_print ("new_x, new_y\n: %d %d\n", new_x, new_y);
+#endif
+    
+    foo_scroll_area_set_viewport_pos (area, new_x, new_y);
+#if 0
+				      viewport.x + info->dx,
+				      viewport.y + info->dy);
+#endif
+
+    get_viewport (area, &new_viewport);
+
+    if (viewport.x == new_viewport.x		&&
+	viewport.y == new_viewport.y		&&
+	(info->res_x > 1.0			||
+	 info->res_y > 1.0			||
+	 info->res_x < -1.0			||
+	 info->res_y < -1.0))
+    {
+	stop_scrolling (area);
+	
+	/* stop scrolling if it didn't have an effect */
+	return FALSE;
+    }
+    
+    return TRUE;
+}
+
+static void
+ensure_scrolling (FooScrollArea *area,
+		  int		 dx,
+		  int		 dy)
+{
+    if (!area->priv->auto_scroll_info)
+    {
+#if 0
+	g_print ("start scrolling\n");
+#endif
+	area->priv->auto_scroll_info = g_new0 (AutoScrollInfo, 1);
+	area->priv->auto_scroll_info->timeout_id =
+	    g_idle_add (scroll_idle, area);
+	area->priv->auto_scroll_info->timer = g_timer_new ();
+    }
+    
+#if 0
+    g_print ("setting scrolling to %d %d\n", dx, dy);
+#endif
+
+#if 0
+    g_print ("dx, dy: %d %d\n", dx, dy);
+#endif
+    
+    area->priv->auto_scroll_info->dx = dx;
+    area->priv->auto_scroll_info->dy = dy;
+}
+
+void
+foo_scroll_area_auto_scroll (FooScrollArea *scroll_area,
+			     FooScrollAreaEvent *event)
+{
+    GdkRectangle viewport;
+    
+    get_viewport (scroll_area, &viewport);
+    
+    if (rect_contains (&viewport, event->x, event->y))
+    {
+	stop_scrolling (scroll_area);
+    }
+    else
+    {
+	int dx, dy;
+	
+	dx = dy = 0;
+	
+	if (event->y < viewport.y)
+	{
+	    dy = event->y - viewport.y;
+	    dy = MIN (dy + 2, 0);
+	}
+	else if (event->y >= viewport.y + viewport.height)
+	{
+	    dy = event->y - (viewport.y + viewport.height - 1);
+	    dy = MAX (dy - 2, 0);
+	}
+	
+	if (event->x < viewport.x)
+	{
+	    dx = event->x - viewport.x;
+	    dx = MIN (dx + 2, 0);
+	}
+	else if (event->x >= viewport.x + viewport.width)
+	{
+	    dx = event->x - (viewport.x + viewport.width - 1);
+	    dx = MAX (dx - 2, 0);
+	}
+
+#if 0
+	g_print ("dx, dy: %d %d\n", dx, dy);
+#endif
+	
+	ensure_scrolling (scroll_area, dx, dy);
+    }
+}
+
+void
+foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area)
+{
+    /* noop  for now */
+}
+
+void
+foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area)
+{
+    stop_scrolling (scroll_area);
+}
+
+
+
+#if 0
+/*
+ * Backing Store
+ */
+struct BackingStore
+{
+    GdkPixmap *pixmap;
+    GdkRegion *update_region;
+    int width;
+    int height;
+};
+
+static BackingStore *
+backing_store_new (GdkWindow *window,
+		   int width, int height)
+{
+    BackingStore *store = g_new0 (BackingStore, 1);
+    GdkRectangle rect = { 0, 0, width, height };
+    
+    store->pixmap = gdk_pixmap_new (window, width, height, -1);
+    store->update_region = gdk_region_rectangle (&rect);
+    store->width = width;
+    store->height = height;
+
+    return store;
+}
+
+static void
+backing_store_free (BackingStore *store)
+{
+    g_object_unref (store->pixmap);
+    gdk_region_destroy (store->update_region);
+    g_free (store);
+}
+
+static void
+backing_store_draw (BackingStore  *store, 
+		    GdkDrawable   *dest,
+		    GdkRegion     *clip,
+		    int		   x,
+		    int            y)
+{
+    GdkGC *gc = gdk_gc_new (dest);
+    
+    gdk_gc_set_clip_region (gc, clip);
+
+    gdk_draw_drawable (dest, gc, store->pixmap,
+		       0, 0, x, y, store->width, store->height);
+
+    g_object_unref (gc);
+}
+
+static void
+backing_store_scroll (BackingStore *store,
+		      int           dx,
+		      int           dy)
+{
+    GdkGC *gc = gdk_gc_new (store->pixmap);
+    GdkRectangle rect;
+    
+    gdk_draw_drawable (store->pixmap, gc, store->pixmap,
+		       0, 0, dx, dy,
+		       store->width, store->height);
+
+    /* Invalidate vertically */
+    rect.x = 0;
+    rect.width = store->width;
+
+    if (dy > 0)
+    {
+	rect.y = 0;
+	rect.height = dy;
+    }
+    else
+    {
+	rect.y = store->height + dy;
+	rect.y = -dy;
+    }
+
+    gdk_region_union_with_rect (store->update_region, &rect);
+    
+    /* Invalidate horizontally */
+    rect.y = 0;
+    rect.height = store->height;
+
+    if (dx > 0)
+    {
+	rect.x = 0;
+	rect.width = dx;
+    }
+    else
+    {
+	rect.x = store->width + dx;
+	rect.width = -dx;
+    }
+
+    gdk_region_union_with_rect (store->update_region, &rect);
+}
+
+static void
+backing_store_invalidate_rect (BackingStore *store,
+			       GdkRectangle *rect)
+{
+    gdk_region_union_with_rect (store->update_region, rect);
+}
+
+static void
+backing_store_invalidate_region (BackingStore *store,
+				 GdkRegion *region)
+{
+    gdk_region_union (store->update_region, region);
+}
+
+static void
+backing_store_invalidate_all (BackingStore *store)
+{
+    GdkRectangle rect = { 0, 0, store->width, store->height };
+    gdk_region_destroy (store->update_region);
+    store->update_region = gdk_region_rectangle (&rect);
+}
+
+static void
+backing_store_resize (BackingStore *store,
+		      int           width,
+		      int           height)
+{
+    GdkPixmap *pixmap = gdk_pixmap_new (store->pixmap, width, height, -1);
+
+    /* Unfortunately we don't know in which direction we were resized,
+     * so we just assume we were dragged from the south-east corner.
+     *
+     * Although, maybe we could get the root coordinates of the input-window?
+     * That might just work, actually. We need to make sure metacity uses
+     * static gravity for the window before this will be useful.
+     */
+    simple_draw_drawable (pixmap, store->pixmap, 0, 0, 0, 0, -1, -1);
+
+    g_object_unref (store->pixmap);
+
+    store->pixmap = pixmap;
+
+    /* FIXME: invalidate uncovered strip only */
+
+    backing_store_invalidate_all (store);
+}
+
+static void
+cclip_to_region (cairo_t *cr, GdkRegion *region)
+{
+    int n_rects;
+    GdkRectangle *rects;
+
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+
+    cairo_new_path (cr);
+    while (n_rects--)
+    {
+	GdkRectangle *rect = &(rects[n_rects]);
+
+	cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
+    }
+    cairo_clip (cr);
+
+    g_free (rects);
+}
+
+static void
+backing_store_process_updates (BackingStore *store,
+			       ExposeFunc    func,
+			       gpointer      data)
+{
+    cairo_t *cr = gdk_cairo_create (store->pixmap);
+    GdkRegion *region = store->update_region;
+    store->update_region = gdk_region_new ();
+
+    cclip_to_region (cr, store->update_region);
+
+    func (cr, store->update_region, data);
+
+    gdk_region_destroy (region);
+    cairo_destroy (cr);
+}
+
+#endif
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/monitor-db.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/monitor-db.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,637 @@
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include "monitor-db.h"
+#include "edid.h"
+
+/* Code to load and save monitor configurations
+ *
+ * A monitor configuration is a list of monitors and associated
+ * outputs, along with information about mode and rotation.
+ *
+ * A monitor is identified by a string like EDID-SUN-0x0567
+ * which is decoded as "We got this information from EDID" - the manufacturer
+ * was "SUN" and the product code was 0x0567.
+ *
+ * Do we put this in gconf?
+ *
+ * The database itself is an XML file with entries like this:
+ *
+ *     <configuration>
+ *          <output name="VGA0">
+ *               <monitor>EDID-SUN-0x0567-12341234</monitor>
+ *               <mode>
+ *                      <state>on</state>
+ *                      <width>1024</width>
+ *                      <height>768</height>
+ *                      <rotation>0</rotation>
+ *               </mode>
+ *          </output>
+ *          <output name="LVDS">
+ *               <monitor>EDID-CMO-0x1210</monitor>
+ *               <mode>
+ *                    <...>
+ *               </mode>
+ *          <output>
+ *     </configuration>    
+ *         
+ */
+
+/* A helper wrapper around the GMarkup parser stuff */
+static gboolean parse_file_gmarkup (const gchar *file,
+				    const GMarkupParser *parser,
+				    gpointer data,
+				    GError **err);
+
+typedef struct Parser Parser;
+
+static void
+emit_configurations (Configuration **configs,
+		     GString        *string)
+{
+    int i;
+
+    for (i = 0; configs[i] != NULL; ++i)
+    {
+	Configuration *config = configs[i];
+	int j;
+
+	g_string_append_printf (string, "<configuration>\n");
+	
+	for (j = 0; config->outputs[j] != NULL; ++j)
+	{
+	    Output *output = config->outputs[j];
+	    
+	    g_string_append_printf (
+		string, "    <output name=\"%s\">\n", output->name);
+
+	    if (output->connected && *output->vendor != '\0')
+	    {
+		g_string_append_printf (
+		    string, "        <vendor>%s</vendor>\n", output->vendor);
+		g_string_append_printf (
+		    string, "        <product>0x%04x</product>\n", output->product);
+		g_string_append_printf (
+		    string, "        <serial>0x%08x</serial>\n", output->serial);
+	    }
+	    
+	    if (output->on)
+	    {
+		g_string_append_printf (
+		    string, "        <width>%d</width>\n", output->width);
+		g_string_append_printf (
+		    string, "        <height>%d</height>\n", output->height);
+		g_string_append_printf (
+		    string, "        <rate>%d</rate>\n", output->rate);
+		g_string_append_printf (
+		    string, "        <x>%d</x>\n", output->x);
+		g_string_append_printf (
+		    string, "        <y>%d</y>\n", output->y);
+	    }
+	    
+	    g_string_append_printf (string, "    </output>\n");
+	}
+	
+	g_string_append_printf (string, "</configuration>\n");
+    }
+}
+
+gboolean
+configurations_write (Configuration **configs,
+		      const gchar *filename,
+		      GError **error)
+{
+    GString *output = g_string_new ("");
+    gboolean result;
+
+    emit_configurations (configs, output);
+
+    result = g_file_set_contents (filename, output->str, -1, error);
+
+    g_string_free (output, TRUE);
+
+    return result;
+}
+
+void
+configurations_dump (Configuration **configs)
+{
+    GString *output = g_string_new ("");
+
+    emit_configurations (configs, output);
+
+    g_print ("%s\n", output->str);
+
+    g_string_free (output, TRUE);
+}
+
+/* Parser for monitor configurations */
+struct Parser
+{
+    Output *output;
+    Configuration *configuration;
+    GPtrArray *outputs;
+    GPtrArray *configurations;
+    GQueue *stack;
+};
+
+static int
+parse_int (const char *text)
+{
+    return strtol (text, NULL, 0);
+}
+
+static gboolean
+stack_is (Parser *parser,
+	  const char *s1,
+	  ...)
+{
+    GList *stack = NULL;
+    const char *s;
+    GList *l1, *l2;
+    va_list args;
+    
+    stack = g_list_prepend (stack, (gpointer)s1);
+    
+    va_start (args, s1);
+    
+    s = va_arg (args, const char *);
+    while (s)
+    {
+	stack = g_list_prepend (stack, (gpointer)s);
+	s = va_arg (args, const char *);
+    }
+	
+    l1 = stack;
+    l2 = parser->stack->head;
+    
+    while (l1 && l2)
+    {
+	if (strcmp (l1->data, l2->data) != 0)
+	{
+	    g_list_free (stack);
+	    return FALSE;
+	}
+	
+	l1 = l1->next;
+	l2 = l2->next;
+    }
+    
+    g_list_free (stack);
+    
+    return (!l1 && !l2);
+}
+
+static void
+handle_start_element (GMarkupParseContext *context,
+		      const gchar         *name,
+		      const gchar        **attr_names,
+		      const gchar        **attr_values,
+		      gpointer             user_data,
+		      GError             **err)
+{
+    Parser *parser = user_data;
+
+    if (strcmp (name, "output") == 0)
+    {
+	int i;
+	g_assert (parser->output == NULL);
+
+	parser->output = g_new0 (Output, 1);
+
+	for (i = 0; attr_names[i] != NULL; ++i)
+	{
+	    if (strcmp (attr_names[i], "name") == 0)
+	    {
+		parser->output->name = g_strdup (attr_values[i]);
+		break;
+	    }
+	}
+
+	if (!parser->output->name)
+	{
+	    /* This really shouldn't happen, but it's better to make
+	     * something up than to crash later.
+	     */
+	    g_warning ("Malformed monitor configuration file");
+	    
+	    parser->output->name = g_strdup ("VGA");
+	}	
+	parser->output->connected = FALSE;
+	parser->output->on = FALSE;
+    }
+    else if (strcmp (name, "configuration") == 0)
+    {
+	g_assert (parser->configuration == NULL);
+	
+	parser->configuration = g_new0 (Configuration, 1);
+    }
+
+    g_queue_push_tail (parser->stack, g_strdup (name));
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+		    const gchar         *name,
+		    gpointer             user_data,
+		    GError             **err)
+{
+    Parser *parser = user_data;
+    
+    if (strcmp (name, "output") == 0)
+    {
+	g_ptr_array_add (parser->outputs, parser->output);
+
+	g_print (
+	    "%s output %s\n", parser->output->connected?
+	    "Connected" : "Unconnected", parser->output->name);
+	
+	parser->output = NULL;
+    }
+    else if (strcmp (name, "configuration") == 0)
+    {
+	g_ptr_array_add (parser->outputs, NULL);
+	parser->configuration->outputs =
+	    (Output **)g_ptr_array_free (parser->outputs, FALSE);
+	parser->outputs = g_ptr_array_new ();
+	g_ptr_array_add (parser->configurations, parser->configuration);
+	parser->configuration = NULL;
+    }
+    
+    g_free (g_queue_pop_tail (parser->stack));
+}
+
+static void
+handle_text (GMarkupParseContext *context,
+	     const gchar         *text,
+	     gsize                text_len,
+	     gpointer             user_data,
+	     GError             **err)
+{
+    Parser *parser = user_data;
+    
+    if (stack_is (parser, "vendor", "output", "configuration", NULL))
+    {
+	parser->output->connected = TRUE;
+	
+	strncpy (parser->output->vendor, text, 3);
+	parser->output->vendor[3] = 0;
+    }
+    else if (stack_is (parser, "product", "output", "configuration", NULL))
+    {
+	parser->output->connected = TRUE;
+
+	parser->output->product = parse_int (text);
+    }
+    else if (stack_is (parser, "serial", "output", "configuration", NULL))
+    {
+	parser->output->connected = TRUE;
+
+	parser->output->serial = parse_int (text);
+    }
+    else if (stack_is (parser, "width", "output", "configuration", NULL))
+    {
+	parser->output->on = TRUE;
+
+	parser->output->width = parse_int (text);
+    }
+    else if (stack_is (parser, "x", "output", "configuration", NULL))
+    {
+	parser->output->on = TRUE;
+
+	parser->output->x = parse_int (text);
+    }
+    else if (stack_is (parser, "y", "output", "configuration", NULL))
+    {
+	parser->output->on = TRUE;
+
+	parser->output->y = parse_int (text);
+    }
+    else if (stack_is (parser, "height", "output", "configuration", NULL))
+    {
+	parser->output->on = TRUE;
+
+	parser->output->height = parse_int (text);
+    }
+    else if (stack_is (parser, "rate", "output", "configuration", NULL))
+    {
+	parser->output->on = TRUE;
+
+	parser->output->rate = parse_int (text);
+    }
+    else
+    {
+	/* Ignore other properties so we can expand the format in the future */
+    }
+}
+
+static void
+output_free (Output *output)
+{
+    g_free (output);
+}
+
+void
+configuration_free (Configuration *config)
+{
+    int i;
+
+    for (i = 0; config->outputs[i] != NULL; ++i)
+	output_free (config->outputs[i]);
+}
+
+Configuration **
+configurations_add (Configuration **configurations,
+		    Configuration *configuration)
+{
+    int i;
+    GPtrArray *a = g_ptr_array_new ();
+
+    for (i = 0; configurations[i] != NULL; ++i)
+	g_ptr_array_add (a, configurations[i]);
+
+    g_ptr_array_add (a, configuration);
+    
+    g_ptr_array_add (a, NULL);
+
+    g_free (configurations);
+
+    return (Configuration **)g_ptr_array_free (a, FALSE);
+}
+
+static void
+parser_free (Parser *parser)
+{
+    int i;
+    GList *list;
+
+    if (parser->output)
+	output_free (parser->output);
+
+    if (parser->configuration)
+	configuration_free (parser->configuration);
+
+    for (i = 0; i < parser->outputs->len; ++i)
+    {
+	Output *output = parser->outputs->pdata[i];
+
+	output_free (output);
+    }
+
+    g_ptr_array_free (parser->outputs, TRUE);
+
+    for (i = 0; i < parser->configurations->len; ++i)
+    {
+	Configuration *config = parser->configurations->pdata[i];
+
+	configuration_free (config);
+    }
+
+    g_ptr_array_free (parser->configurations, TRUE);
+
+    for (list = parser->stack->head; list; list = list->next)
+	g_free (list->data);
+    g_queue_free (parser->stack);
+    
+    g_free (parser);
+}
+
+Configuration **
+configurations_read (const gchar *filename, GError **error)
+{
+    Parser *parser = g_new0 (Parser, 1);
+    Configuration **result;
+    GMarkupParser callbacks = {
+	handle_start_element,
+	handle_end_element,
+	handle_text,
+	NULL, /* passthrough */
+	NULL, /* error */
+    };
+
+    parser->configurations = g_ptr_array_new ();
+    parser->outputs = g_ptr_array_new ();
+    parser->stack = g_queue_new ();
+    
+    if (!parse_file_gmarkup (filename, &callbacks, parser, error))
+    {
+	result = NULL;
+	goto out;
+    }
+
+    g_ptr_array_add (parser->configurations, NULL);
+    result = (Configuration **)g_ptr_array_free (parser->configurations, FALSE);
+    parser->configurations = g_ptr_array_new ();
+    
+out:
+    parser_free (parser);
+
+    return result;
+}
+
+static gboolean configuration_match (Configuration *c1,
+				     Configuration *c2);
+
+Configuration *
+configuration_new_current (RWScreen *screen)
+{
+    Configuration *config = g_new0 (Configuration, 1);
+    GPtrArray *a = g_ptr_array_new ();
+    int i;
+    RWOutput **rw_outputs = rw_screen_list_outputs (screen);
+
+    g_print ("New configuration:\n");
+	
+    for (i = 0; rw_outputs[i] != NULL; ++i)
+    {
+	RWOutput *rw_output = rw_outputs[i];
+	Output *output = g_new0 (Output, 1);
+	RWCrtc *crtc;
+	
+	output->name = g_strdup (rw_output_get_name (rw_output));
+
+	crtc = rw_output_get_crtc (rw_output);
+	if (crtc)
+	{
+	    RWMode *mode = rw_crtc_get_current_mode (crtc);
+	    
+	    output->on = TRUE;
+	    output->width = rw_mode_get_width (mode);
+	    output->height = rw_mode_get_height (mode);
+	    output->rate = rw_mode_get_freq (mode);
+	    
+	    rw_crtc_get_position (crtc, &output->x, &output->y);
+
+	    g_print ("new output %s %d %d\n", output->name, output->width, output->height);
+	}
+
+	output->connected = rw_output_is_connected (rw_output);
+
+	g_print ("  Output %s is %sconnected\n", output->name, output->connected? "" : "not ");
+	
+	if (output->connected)
+	{
+	    const guchar *edid_data = rw_output_get_edid_data (rw_output);
+	    
+	    if (edid_data)
+	    {
+		MonitorInfo *info = decode_edid (edid_data);
+		
+		memcpy (output->vendor, info->manufacturer_code,
+			sizeof (output->vendor));
+		
+		output->product = info->product_code;
+		output->serial = info->serial_number;
+
+		g_free (info);
+	    }
+	    else
+	    {
+		strcpy (output->vendor, "???");
+		output->product = 0;
+		output->serial = 0;
+	    }
+	}
+	else
+	{
+	    output->vendor[0] = '\0';
+	    output->serial = 0;
+	    output->product = 0;
+	}
+	
+	g_ptr_array_add (a, output);
+    }
+
+    g_ptr_array_add (a, NULL);
+    
+    config->outputs = (Output **)g_ptr_array_free (a, FALSE);
+
+    g_assert (configuration_match (config, config));
+    
+    return config;
+}
+
+void
+configurations_free (Configuration **configurations)
+{
+    int i;
+
+    for (i = 0; configurations[i] != NULL; ++i)
+	configuration_free (configurations[i]);
+
+    g_free (configurations);
+}
+
+static gboolean
+parse_file_gmarkup (const gchar *filename,
+		    const GMarkupParser *parser,
+		    gpointer data,
+		    GError **err)
+{
+    GMarkupParseContext *context = NULL;
+    gchar *contents = NULL;
+    gboolean result = TRUE;
+    guint len;
+
+    if (!g_file_get_contents (filename, &contents, &len, err))
+    {
+	result = FALSE;
+	goto out;
+    }
+    
+    context = g_markup_parse_context_new (parser, 0, data, NULL);
+
+    if (!g_markup_parse_context_parse (context, contents, len, err))
+    {
+	result = FALSE;
+	goto out;
+    }
+
+    if (!g_markup_parse_context_end_parse (context, err))
+    {
+	result = FALSE;
+	goto out;
+    }
+
+out:
+    if (contents)
+	g_free (contents);
+
+    if (context)
+	g_markup_parse_context_free (context);
+
+    return result;
+}
+
+static gboolean
+output_match (Output *output1, Output *output2)
+{
+    if (strcmp (output1->name, output2->name) != 0)
+	return FALSE;
+
+    if (strcmp (output1->vendor, output2->vendor) != 0)
+	return FALSE;
+
+    if (output1->product != output2->product)
+	return FALSE;
+
+    if (output1->serial != output2->serial)
+	return FALSE;
+
+    if (output1->connected != output2->connected)
+	return FALSE;
+
+    g_print ("matched %s to %s\n", output1->name, output2->name);
+    
+    return TRUE;
+}
+
+static Output *
+find_output (Configuration *config, const char *name)
+{
+    int i;
+
+    for (i = 0; config->outputs[i] != NULL; ++i)
+    {
+	Output *output = config->outputs[i];
+	
+	if (strcmp (name, output->name) == 0)
+	    return output;
+    }
+
+    return NULL;
+}
+
+static gboolean
+configuration_match (Configuration *c1,
+		     Configuration *c2)
+{
+    int i;
+
+    for (i = 0; c1->outputs[i] != NULL; ++i)
+    {
+	Output *output1 = c1->outputs[i];
+	Output *output2;
+
+	output2 = find_output (c2, output1->name);
+	if (!output2 || !output_match (output1, output2))
+	    return FALSE;
+    }
+
+    return TRUE;
+}
+
+Configuration *
+configuration_find (Configuration **haystack,
+		    Configuration  *needle)
+{
+    int i;
+
+    for (i = 0; haystack[i] != NULL; ++i)
+    {
+	if (configuration_match (haystack[i], needle))
+	    return haystack[i];
+    }
+
+    return NULL;
+}
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/monitor-db.h
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/monitor-db.h	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,53 @@
+#define I_KNOW_THIS_IS_UNSTABLE_AND_ONLY_IN_FEDORA
+#include <libgnomeui/randrwrap.h>
+#include <glib.h>
+
+typedef struct Output Output;
+typedef struct Configuration Configuration;
+
+struct Output
+{
+    char *name;
+
+    gboolean on;
+    int width;
+    int height;
+    int rate;
+    int x;
+    int y;
+
+    gboolean connected;
+    char vendor[4];
+    guint product;
+    guint serial;
+};
+
+struct Configuration
+{
+    Output **outputs;
+};
+
+Output         *output_new                (const char     *name,
+					   gboolean        on,
+					   int             width,
+					   int             height,
+					   int             rate,
+					   gboolean        connected,
+					   char            vendor[4],
+					   guint           product,
+					   guint           serial);
+Configuration  *configuration_new         (Output        **outputs);
+Configuration **configurations_read       (const gchar    *filename,
+					   GError        **error);
+gboolean        configurations_write      (Configuration **configs,
+					   const gchar    *filename,
+					   GError        **error);
+void            configuration_free        (Configuration  *configuration);
+Configuration **configurations_add        (Configuration **configurations,
+					   Configuration  *configuration);
+void            configurations_free       (Configuration **configurations);
+void            configurations_dump       (Configuration **configurations);
+Configuration  *configuration_new_current (RWScreen       *screen);
+Configuration  *configuration_find        (Configuration **haystack,
+					   Configuration  *needle);
+
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/crtc.h
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/crtc.h	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,21 @@
+#include <gtk/gtk.h>
+#define I_KNOW_THIS_IS_UNSTABLE_AND_ONLY_IN_FEDORA
+#include <libgnomeui/randrwrap.h>
+
+typedef struct Setting Setting;
+typedef struct CrtcInfo CrtcInfo;
+typedef struct CrtcAssignment CrtcAssignment;
+typedef struct Crtc Crtc;
+
+struct Setting
+{
+    RWOutput *output;
+    RWMode   *mode;
+    int       x;
+    int       y;
+};
+
+void            crtc_assignment_apply (CrtcAssignment *assign);
+CrtcAssignment *assign_crtcs          (RWScreen       *screen,
+				       Setting       **settings);
+void            crtc_assignment_free  (CrtcAssignment *assign);
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/apply.h
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/apply.h	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,2 @@
+/* This should go in g-s-d eventually */
+void apply_stored_configuration (RWScreen *screen);
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/xrandr-capplet.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/xrandr-capplet.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,809 @@
+/* Monitor Settings. A preference panel for configuring monitors
+ *
+ * Copyright (C) 2007, 2008  Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Author: Soren Sandmann <sandmann@redhat.com>
+ */
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <string.h>
+#include <stdlib.h>
+#include "scrollarea.h"
+#define I_KNOW_THIS_IS_UNSTABLE_AND_ONLY_IN_FEDORA
+#include <libgnomeui/randrwrap.h>
+#include "edid.h"
+#include "monitor-db.h"
+#include "crtc.h"
+#include "apply.h"
+
+typedef struct App App;
+
+struct App
+{
+    RWScreen       *screen;
+    Configuration  *current_configuration;
+    Configuration **all_configurations;
+    Output         *current_output;
+    
+    GtkWidget	   *dialog;
+    GtkWidget	   *resolution_combo;
+    GtkWidget	   *refresh_combo;
+    GtkWidget	   *rotation_combo;
+    GtkWidget	   *panel_checkbox;
+
+    GtkWidget      *area;
+    gboolean	    ignore_gui_changes;
+};
+
+static void rebuild_gui (App *app);
+
+static void
+show_error (const GError *err)
+{
+    if (!err)
+	return;
+    
+    GtkWidget *dialog = gtk_message_dialog_new (
+	NULL,
+	GTK_DIALOG_DESTROY_WITH_PARENT,
+	GTK_MESSAGE_WARNING,
+	GTK_BUTTONS_OK, err->message);
+    
+    gtk_window_set_title (GTK_WINDOW (dialog), "");
+    
+    gtk_dialog_run (GTK_DIALOG (dialog));
+    gtk_widget_destroy (dialog);
+}
+
+static gboolean
+init_app (App *app)
+{
+    return TRUE;
+}
+
+static gboolean
+do_free (gpointer data)
+{
+    g_free (data);
+    return FALSE;
+}
+
+static gchar *
+idle_free (gchar *s)
+{
+    g_idle_add (do_free, s);
+
+    return s;
+}
+
+static const gchar *
+get_filename (void)
+{
+    return idle_free (
+	g_build_filename (
+	    g_get_home_dir(), ".gnome2", "monitors.xml", NULL));
+}
+
+static int
+compare_outputs (const void *p1, const void *p2)
+{
+    Output *const *o1 = p1;
+    Output *const *o2 = p2;
+
+    return (**o1).x - (**o2).x;
+}
+
+static void
+on_screen_changed (RWScreen *scr,
+		   gpointer data)
+{
+    Configuration *current;
+    Configuration *found;
+    App *app = data;
+    int i;
+    Output *best;
+
+    g_print ("screen changed\n");
+    
+    current = configuration_new_current (app->screen);
+    
+    found = configuration_find (app->all_configurations, current);
+    if (found)
+    {
+	g_print ("config found\n");
+	
+	configuration_free (current);
+
+	current = found;
+    }
+    else
+    {
+	g_print ("config not found, adding\n");
+
+	app->all_configurations = configurations_add (
+	    app->all_configurations, current);
+    }
+
+    g_assert (configuration_find (app->all_configurations, current));
+
+    app->current_configuration = current;
+
+    /* Sort outputs according to X coordinate */
+    
+    for (i = 0; app->current_configuration->outputs[i] != NULL; ++i)
+	;
+	
+    qsort (app->current_configuration->outputs, i, sizeof (Output *),
+	   compare_outputs);
+
+    /* Select an output */
+    
+    best = NULL;
+    for (i = 0; app->current_configuration->outputs[i] != NULL; ++i)
+    {
+	Output *output = app->current_configuration->outputs[i];
+
+	if (output->connected)
+	{
+	    char *cur_name = app->current_output? app->current_output->name : NULL;
+
+	    if ((cur_name && strcmp (output->name, cur_name) == 0) || !best)
+		best = output;
+	}
+    }
+
+    app->current_output = best;
+
+    rebuild_gui (app);
+}
+
+static void
+on_viewport_changed (FooScrollArea *scroll_area,
+		     GdkRectangle  *old_viewport,
+		     GdkRectangle  *new_viewport)
+{
+    foo_scroll_area_set_size (scroll_area,
+			      new_viewport->width,
+			      new_viewport->height);
+
+    foo_scroll_area_invalidate (scroll_area);
+}
+
+static void
+layout_set_font (PangoLayout *layout, const char *font)
+{
+    PangoFontDescription *desc =
+	pango_font_description_from_string (font);
+
+    if (desc)
+    {
+	pango_layout_set_font_description (layout, desc);
+
+	pango_font_description_free (desc);
+    }
+}
+
+static void
+clear_combo (GtkWidget *widget)
+{
+    GtkComboBox *box = GTK_COMBO_BOX (widget);
+    GtkTreeModel *model = gtk_combo_box_get_model (box);
+    if (!model)
+    {
+	g_print ("asdf\n");
+	return;
+    }
+    GtkListStore *store = GTK_LIST_STORE (model);
+
+    gtk_list_store_clear (store);
+}
+
+static void
+add_keys (GtkWidget *widget,
+	  GHashTable *table,
+	  const char *selected)
+{
+    /* FIXME: sort the keys */
+    GtkComboBox *box = GTK_COMBO_BOX (widget);
+    GList *keys = g_hash_table_get_keys (table);
+    GList *values = g_hash_table_get_values (table);
+    GList *list;
+    gboolean did_select = FALSE;
+    GtkListStore *store = GTK_LIST_STORE (gtk_combo_box_get_model (box));
+
+    for (list = keys; list != NULL; list = list->next, values = values->next)
+    {
+	GtkTreeIter iter;
+	char *resolution = list->data;
+	RWMode *mode = values->data;
+	int w, h;
+	
+	w = rw_mode_get_width (mode);
+	h = rw_mode_get_height (mode);
+	
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter,
+			    0, resolution,
+			    1, w, 
+			    2, h,
+			    3, rw_mode_get_freq (mode),
+			    4, w * h,
+			    -1);
+
+	if (strcmp (selected, resolution) == 0)
+	{
+	    gtk_combo_box_set_active_iter (box, &iter);
+	    did_select = TRUE;
+	}
+    }
+
+    if (!did_select)
+	gtk_combo_box_set_active (box, 0);
+    
+    g_list_free (keys);
+}
+
+static RWMode **
+get_current_modes (App *app)
+{
+    RWOutput *output;
+    
+    if (!app->current_output)
+	return NULL;
+
+    output = rw_screen_get_output_by_name (
+	app->screen, app->current_output->name);
+
+    if (!output)
+	return NULL;
+
+    return rw_output_list_modes (output);
+}
+
+static void
+rebuild_rate_combo (App *app)
+{
+    GHashTable *rates = g_hash_table_new_full (
+	g_str_hash, g_str_equal, (GFreeFunc)g_free, NULL);
+    int i;
+    RWMode **modes;
+
+    clear_combo (app->refresh_combo);
+
+    if (!(modes = get_current_modes (app)))
+	return;
+    
+    for (i = 0; modes[i] != NULL; ++i)
+    {
+	RWMode *mode = modes[i];
+	
+	if (rw_mode_get_width (mode) == app->current_output->width &&
+	    rw_mode_get_height (mode) == app->current_output->height)
+	{
+	    char *rate;
+
+	    rate = g_strdup_printf ("%d Hz",
+				    rw_mode_get_freq (mode));
+	    
+	    g_hash_table_insert (rates, rate, mode);
+	}
+    }
+
+    add_keys (app->refresh_combo, rates, idle_free (
+		  g_strdup_printf ("%d Hz",
+				   app->current_output->rate)));
+
+    g_hash_table_destroy (rates);
+}
+
+static void
+rebuild_resolution_combo (App *app)
+{
+    GHashTable *resolutions;
+    int i;
+    RWMode **modes;
+
+    clear_combo (app->resolution_combo);
+    
+    if (!(modes = get_current_modes (app)))
+	return;
+    
+    resolutions = g_hash_table_new_full (
+	g_str_hash, g_str_equal, (GFreeFunc)g_free, NULL);
+    
+    for (i = 0; modes[i] != NULL; ++i)
+    {
+	RWMode *mode = modes[i];
+	char *resolution =
+	    g_strdup_printf ("%d x %d",
+			     rw_mode_get_width (mode),
+			     rw_mode_get_height (mode));
+	
+	g_hash_table_insert (resolutions, resolution, mode);
+    }
+    
+    /* Add resolutions */
+    add_keys (app->resolution_combo, resolutions, idle_free (
+		  g_strdup_printf ("%d x %d",
+				   app->current_output->width,
+				   app->current_output->height)));
+    
+    g_hash_table_destroy (resolutions);
+}
+
+static void
+rebuild_gui (App *app)
+{
+    gboolean sensitive;
+
+    /* We would break spectacularly if we recursed, so
+     * just assert if that happens
+     */
+    g_assert (app->ignore_gui_changes == FALSE);
+    
+    app->ignore_gui_changes = TRUE;
+
+    sensitive = app->current_output? TRUE : FALSE;
+
+    rebuild_resolution_combo (app);
+    rebuild_rate_combo (app);
+
+    gtk_widget_set_sensitive (app->resolution_combo, sensitive);
+    gtk_widget_set_sensitive (app->refresh_combo, sensitive);
+    gtk_widget_set_sensitive (app->rotation_combo, sensitive);
+    gtk_widget_set_sensitive (app->panel_checkbox, sensitive);
+
+    app->ignore_gui_changes = FALSE;
+}
+
+static gboolean
+get_mode (GtkWidget *widget, int *width, int *height, int *freq)
+{
+    GtkTreeIter iter;
+    GtkTreeModel *model;
+    GtkComboBox *box = GTK_COMBO_BOX (widget);
+    int dummy;
+
+    if (!gtk_combo_box_get_active_iter (box, &iter))
+	return FALSE;
+
+    if (!width)
+	width = &dummy;
+    if (!height)
+	height = &dummy;
+    if (!freq)
+	freq = &dummy;
+    
+    model = gtk_combo_box_get_model (box);
+    gtk_tree_model_get (model, &iter,
+			1, width,
+			2, height,
+			3, freq,
+			-1);
+
+    return TRUE;
+}
+
+static void
+on_rate_changed (GtkComboBox *box, gpointer data)
+{
+    App *app = data;
+    int rate;
+
+    if (!app->current_output)
+	return;
+
+    if (get_mode (app->refresh_combo, NULL, NULL, &rate))
+	app->current_output->rate = rate;
+
+    foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
+}
+
+static void
+on_resolution_changed (GtkComboBox *box, gpointer data)
+{
+    App *app = data;
+    int width;
+    int height;
+    int i;
+    int x;
+
+    if (!app->current_output)
+	return;
+
+    if (get_mode (app->resolution_combo, &width, &height, NULL))
+    {
+	app->current_output->width = width;
+	app->current_output->height = height;
+    }
+
+    if (app->current_configuration)
+    {
+	x = 0;
+	for (i = 0; app->current_configuration->outputs[i] != NULL; ++i)
+	{
+	    Output *output = app->current_configuration->outputs[i];
+	    
+	    if (output->connected)
+	    {
+		output->x = x;
+		
+		x += output->width;
+	    }
+	}
+    }
+    
+    rebuild_rate_combo (app);
+    
+    foo_scroll_area_invalidate (FOO_SCROLL_AREA (app->area));
+}
+
+static void
+on_output_event (FooScrollArea *area,
+		 FooScrollAreaEvent *event,
+		 gpointer data)
+{
+    Output *output = data;
+    App *app = g_object_get_data (G_OBJECT (area), "app");
+
+    if (event->type == FOO_BUTTON_PRESS)
+    {
+	app->current_output = output;
+
+	rebuild_gui (app);
+	
+	foo_scroll_area_invalidate (area);
+    }
+}
+
+#if 0
+static void
+on_canvas_event (FooScrollArea *area,
+		 FooScrollAreaEvent *event,
+		 gpointer data)
+{
+    App *app = g_object_get_data (G_OBJECT (area), "app");
+    
+    if (event->type == FOO_BUTTON_PRESS)
+    {
+	app->current_output = NULL;
+
+	rebuild_gui (app);
+
+	foo_scroll_area_invalidate (area);
+    }
+}
+#endif
+
+static PangoLayout *
+get_display_name (App *app,
+		  Output *output)
+{
+    RWOutput *rwout = rw_screen_get_output_by_name (
+	app->screen, output->name);
+    const char *text = "Unknown";
+
+    if (rwout)
+    {
+	const guint8 *edid = rw_output_get_edid_data (rwout);
+
+	if (edid)
+	{
+	    MonitorInfo *info = decode_edid (edid);
+
+	    text = idle_free (make_display_name (info));
+	}
+    }
+
+    return gtk_widget_create_pango_layout (GTK_WIDGET (app->area), text);
+}
+
+static void
+on_area_paint (FooScrollArea *area,
+	       cairo_t	     *cr,
+	       GdkRectangle  *extent,
+	       GdkRegion     *region,
+	       gpointer	      data)
+{
+    App *app = data;
+    GdkRectangle viewport;
+    int i;
+    int available;
+    int total_pixels;
+    int max_height;
+    double scale;
+    GList *connected_outputs = NULL;
+    GList *list;
+    int n_monitors;
+
+    foo_scroll_area_get_viewport (area, &viewport);
+
+    cairo_set_source_rgba (cr, 0.72, 0.78, 0.87, 1.0);
+
+    cairo_rectangle (cr,
+		     viewport.x, viewport.y,
+		     viewport.width, viewport.height);
+
+    cairo_fill_preserve (cr);
+
+#if 0
+    foo_scroll_area_add_input_from_fill (area, cr, on_canvas_event, NULL);
+#endif
+    
+    cairo_set_source_rgba (cr, 0.44, 0.59, 0.76, 1.0);
+
+    cairo_stroke (cr);
+
+    if (!app->current_configuration)
+	return;
+    
+#define SPACE 36
+#define MARGIN  24
+
+    for (i = 0; app->current_configuration->outputs[i] != NULL; ++i)
+    {
+	Output *output = app->current_configuration->outputs[i];
+
+	if (output->connected)
+	    connected_outputs = g_list_prepend (connected_outputs, output);
+    }
+
+    connected_outputs = g_list_reverse (connected_outputs);
+    
+    n_monitors = g_list_length (connected_outputs);
+
+    available = viewport.width - 2 * MARGIN - (n_monitors - 1) * SPACE;
+
+    total_pixels = 0;
+    max_height = 0;
+    for (list = connected_outputs; list != NULL; list = list->next)
+    {
+	Output *output = list->data;
+
+	total_pixels += output->width;
+	max_height = MAX (max_height, output->height);
+    }
+
+    scale = (double)available / total_pixels;
+
+    scale = MIN ((double)(viewport.height - 2 * MARGIN) / max_height, scale);
+    
+    int x = MARGIN;
+    for (list = connected_outputs; list != NULL; list = list->next)
+    {
+	Output *output = list->data;
+	int w = output->width * scale + 0.5;
+	PangoLayout *layout = get_display_name (app, output);
+
+	g_print ("%s (%p) geometry %d %d %d\n", output->name, output,
+		 output->width, output->height, output->rate);
+	
+	cairo_rectangle (cr, x, MARGIN, w, (int)(
+			     output->height * scale + 0.5));
+
+	cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+	cairo_fill_preserve (cr);
+
+	foo_scroll_area_add_input_from_fill (area, cr, on_output_event,
+					     output);
+	
+	if (output == app->current_output)
+	    cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 1.0);
+	else
+	    cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+	cairo_stroke (cr);
+
+	cairo_move_to (cr, x + 40, MARGIN + 40);
+	cairo_set_source_rgb (cr, 0.2, 0.2, 0.8);
+	layout_set_font (layout, "Sans Bold 18");
+	pango_cairo_show_layout (cr, layout);
+	g_object_unref (layout);
+	
+	x += w + SPACE;
+    }
+}
+
+static void
+make_text_combo (GtkWidget *widget, int column)
+{
+    GtkComboBox *box = GTK_COMBO_BOX (widget);
+    GtkListStore *store = gtk_list_store_new (
+	5,
+	G_TYPE_STRING,		/* Text */
+	G_TYPE_INT,		/* Width */	
+	G_TYPE_INT,		/* Height */
+	G_TYPE_INT,		/* Frequency */
+	G_TYPE_INT);		/* Width * Height */
+    GtkCellRenderer *cell;
+    
+    gtk_combo_box_set_model (box, GTK_TREE_MODEL (store));
+
+    cell = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box), cell, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (box), cell,
+				    "text", 0,
+				    NULL);
+
+    if (column != -1)
+    {
+	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+					      column,
+					      GTK_SORT_DESCENDING);
+    }
+}
+
+static void
+apply (App *app)
+{
+#if 0
+    GPtrArray *array = g_ptr_array_new ();
+#endif
+
+    g_print ("apply\n");
+ 
+    if (configurations_write (app->all_configurations,
+			      get_filename(), NULL))
+    {
+	g_print ("wrote\n");
+
+	configurations_dump (app->all_configurations);
+	
+	apply_stored_configuration (app->screen);
+
+	g_print ("done applying\n");
+    }
+    
+#if 0
+    g_print ("This is where we should actually set the mode, \n");
+    g_print ("and if accepted, write the configuration file\n");
+    g_print ("Well, we should check if the mode is possible\n");
+    g_print ("write the configuration file and signal gsd to set the mode\n");
+#endif
+}
+
+static gboolean
+refresh (gpointer data)
+{
+    App *app = data;
+    
+    rw_screen_refresh (app->screen);
+
+    return TRUE;
+}
+
+static void
+run_application (App *app)
+{
+#ifndef GLADEDIR
+#define GLADEDIR ""
+#endif
+#define GLADE_FILE GLADEDIR "/display-capplet.glade"
+    GladeXML *xml;
+    GtkWidget *align;
+
+    xml = glade_xml_new (GLADE_FILE, NULL, NULL);
+    if (!xml)
+    {
+	g_warning ("Could not open " GLADE_FILE);
+	return;
+    }
+    
+    app->screen = rw_screen_new (gdk_screen_get_default(),
+				 on_screen_changed, app);
+
+    app->dialog = glade_xml_get_widget (xml, "dialog");
+
+    app->resolution_combo = glade_xml_get_widget (xml, "resolution_combo");
+    g_signal_connect (app->resolution_combo, "changed",
+		      G_CALLBACK (on_resolution_changed), app);
+
+    app->refresh_combo = glade_xml_get_widget (xml, "refresh_combo");
+    g_signal_connect (app->refresh_combo, "changed",
+		      G_CALLBACK (on_rate_changed), app);
+
+    app->rotation_combo = glade_xml_get_widget (xml, "rotation_combo");
+
+
+    app->panel_checkbox = glade_xml_get_widget (xml, "panel_checkbox");
+
+    make_text_combo (app->resolution_combo, 4);
+    make_text_combo (app->refresh_combo, 3);
+    make_text_combo (app->rotation_combo, -1);
+    
+    g_assert (app->panel_checkbox);
+    
+    /* Scroll Area */
+    app->area = (GtkWidget *)foo_scroll_area_new ();
+
+    g_object_set_data (G_OBJECT (app->area), "app", app);
+    
+    /* FIXME: this should be computed dynamically */
+    foo_scroll_area_set_min_size (FOO_SCROLL_AREA (app->area), -1, 200);
+    gtk_widget_show (app->area);
+    g_signal_connect (app->area, "paint",
+		      G_CALLBACK (on_area_paint), app);
+    g_signal_connect (app->area, "viewport_changed",
+		      G_CALLBACK (on_viewport_changed), app);
+
+    align = glade_xml_get_widget (xml, "align");
+
+    gtk_container_add (GTK_CONTAINER (align), app->area);
+
+    app->all_configurations = configurations_read (get_filename(), NULL);
+    if (!app->all_configurations)
+    {
+	app->all_configurations = g_malloc (sizeof (gpointer));
+	*(app->all_configurations) = NULL;
+    }
+
+    g_timeout_add (3000, refresh, app);
+
+    on_screen_changed (app->screen, app);
+    rebuild_gui (app);
+    
+restart:
+    switch (gtk_dialog_run (GTK_DIALOG (app->dialog)))
+    {
+    default:
+	g_print ("Unknown response\n");
+	/* Fall Through */
+    case GTK_RESPONSE_DELETE_EVENT:
+    case GTK_RESPONSE_CLOSE:
+	g_print ("Close\n");
+	break;
+
+    case GTK_RESPONSE_HELP:
+	g_print ("Help\n");
+	goto restart;
+	break;
+
+    case GTK_RESPONSE_APPLY:
+	apply (app);
+	goto restart;
+	break;
+    }
+
+    gtk_widget_destroy (app->dialog);
+}
+
+int
+main (int argc, char **argv)
+{
+    App *app;
+    GError *err = NULL;
+    
+#if 0
+    bindtextdomain (GETTEXT_PACKAGE, DESKTOPEFFECTSLOCALEDIR);
+    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+    textdomain (GETTEXT_PACKAGE);
+#endif
+    
+    gtk_init (&argc, &argv);
+
+    app = g_new0 (App, 1);
+
+    if (!init_app (app))
+    {
+        show_error (err);
+        return 0;
+    }
+
+    run_application (app);
+    
+    return 0;
+}
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/crtc.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/crtc.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,291 @@
+#include <gtk/gtk.h>
+#define I_KNOW_THIS_IS_UNSTABLE_AND_ONLY_IN_FEDORA
+#include <libgnomeui/randrwrap.h>
+#include "crtc.h"
+
+struct CrtcInfo
+{
+    RWMode    *mode;
+    int        x;
+    int        y;
+    GPtrArray *outputs;
+};
+
+struct CrtcAssignment
+{
+    RWScreen *screen;
+    GHashTable *info;
+};
+
+static void
+mode_info_free (CrtcInfo *info)
+{
+    g_ptr_array_free (info->outputs, TRUE);
+    g_free (info);
+}
+
+static CrtcAssignment *
+crtc_assignment_new (void)
+{
+    CrtcAssignment *assign = g_new0 (CrtcAssignment, 1);
+
+    assign->info = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+					  NULL, (GFreeFunc)mode_info_free);
+
+    return assign;
+}
+
+static gboolean
+can_clone (CrtcInfo *info,
+	   RWOutput *output)
+{
+    int i;
+
+    for (i = 0; i < info->outputs->len; ++i)
+    {
+	RWOutput *clone = info->outputs->pdata[i];
+
+	if (!rw_output_can_clone (clone, output))
+	    return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment *assign,
+			RWCrtc         *crtc,
+			RWMode         *mode,
+			int             x,
+			int             y,
+			RWOutput       *output)
+{
+    CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+    if (!rw_crtc_can_drive_output (crtc, output) ||
+	!rw_output_supports_mode (output, mode))
+    {
+	return FALSE;
+    }
+
+    if (info)
+    {
+	if (info->mode == mode		&&
+	    info->x == x		&&
+	    info->y == y		&&
+	    can_clone (info, output))
+	{
+	    g_ptr_array_add (info->outputs, output);
+
+	    return TRUE;
+	}
+    }
+    else
+    {	
+	CrtcInfo *info = g_new (CrtcInfo, 1);
+	
+	info->mode = mode;
+	info->x = x;
+	info->y = y;
+	info->outputs = g_ptr_array_new ();
+	
+	g_ptr_array_add (info->outputs, output);
+	
+	g_hash_table_insert (assign->info, crtc, info);
+	    
+	return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+crtc_assignment_unassign (CrtcAssignment *assign,
+			  RWCrtc         *crtc,
+			  RWOutput       *output)
+{
+    CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+    if (info)
+    {
+	g_ptr_array_remove (info->outputs, output);
+
+	if (info->outputs->len == 0)
+	    g_hash_table_remove (assign->info, crtc);
+    }
+}
+
+void
+crtc_assignment_free (CrtcAssignment *assign)
+{
+    g_hash_table_destroy (assign->info);
+
+    g_free (assign);
+}
+
+static void
+configure_crtc (gpointer key,
+		gpointer value,
+		gpointer data)
+{
+    RWCrtc *crtc = key;
+    CrtcInfo *info = value;
+    GTimer *timer = g_timer_new ();
+
+
+    g_print ("Configuring crtc %x with ", rw_crtc_get_id (crtc));
+
+
+    if (info->mode)
+    {
+	int n_outputs = info->outputs->len;
+	
+	g_print ("mode %x and %d outputs (%d %d %d %d) ",
+		 rw_mode_get_id (info->mode),
+		 n_outputs,
+		 info->x, info->y,
+		 rw_mode_get_width (info->mode),
+		 rw_mode_get_height (info->mode));
+    }
+    else
+    {
+	g_print ("no mode ");
+    }
+    
+    if (rw_crtc_set_config (crtc,
+			    info->x, info->y,
+			    info->mode,
+			    (RWOutput **)info->outputs->pdata,
+			    info->outputs->len))
+    {
+	g_print ("succeeded\n");
+    }
+    else
+    {
+	g_print ("failed\n");
+    }
+
+    g_print (" - %f seconds\n", g_timer_elapsed (timer, NULL));
+}
+
+void
+crtc_assignment_apply (CrtcAssignment *assign)
+{
+    GList *crtcs = g_hash_table_get_keys (assign->info);
+    GList *list;
+    int width, height;
+    GTimer *timer;
+    
+    width = height = 0;
+    for (list = crtcs; list != NULL; list = list->next)
+    {
+	RWCrtc *crtc = list->data;
+	CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+	width = MAX (width, info->x + rw_mode_get_width (info->mode));
+	height = MAX (height, info->y + rw_mode_get_height (info->mode));
+    }
+
+    /* Turn off CRTC's that are displaying outside the new screen area */
+    for (list = crtcs; list != NULL; list = list->next)
+    {
+	RWCrtc *crtc = list->data;
+	RWMode *mode = rw_crtc_get_current_mode (crtc);
+	int x, y;
+
+	if (mode)
+	{
+	    rw_crtc_get_position (crtc, &x, &y);
+
+	    if (x + rw_mode_get_width (mode) > width		||
+		y + rw_mode_get_height (mode) > height)
+	    {
+		rw_crtc_set_config (crtc, 0, 0, NULL, NULL, 0);
+	    }
+	}
+    }
+
+    g_list_free (crtcs);
+    
+    /* FIXME: What do we do about physical size?
+     *
+     * As far as this function is concerned, probably just pass the problem up to
+     * the callers. There, the best possibility might be to pick the sum of physical
+     * sizes, but the reality is that the physical size of an X screen is a meaningless
+     * concept in a randr 1.2 enabled world.
+     */
+    timer = g_timer_new ();
+    rw_screen_set_size (assign->screen, width, height, 300, 230);
+
+    g_print ("time to set screen size: %f\n", g_timer_elapsed (timer, NULL));
+
+    g_timer_reset (timer);
+    
+    g_hash_table_foreach (assign->info, configure_crtc, NULL);
+
+    g_print ("time to configure crtc's: %f\n", g_timer_elapsed (timer, NULL));
+}
+
+/* Check whether the given set of settings can be used
+ * at the same time -- ie. whether there is an assignment
+ * of CRTC's to outputs.
+ *
+ * Brute force - the number of objects involved is small
+ * enough that it doesn't matter.
+ */
+static gboolean
+real_assign_crtcs (RWScreen *screen,
+		   Setting **settings,
+		   CrtcAssignment *assignment)
+{
+    RWCrtc **crtcs = rw_screen_list_crtcs (screen);
+    Setting *setting;
+    int i;
+
+    setting = *settings;
+    if (!setting)
+	return TRUE;
+
+    /* It is always allowed for an output to be turned off */
+    if (setting->mode == NULL)
+    {
+	return real_assign_crtcs (screen, settings + 1, assignment);
+    }
+    
+    for (i = 0; crtcs[i] != NULL; ++i)
+    {
+	RWCrtc *crtc = crtcs[i];
+	
+	if (crtc_assignment_assign (
+		assignment, crtc, setting->mode,
+		setting->x, setting->y,
+		setting->output))
+	{
+	    if (real_assign_crtcs (screen, settings + 1, assignment))
+		return TRUE;
+	    
+	    crtc_assignment_unassign (assignment, crtc, setting->output);
+	}
+    }
+
+    return FALSE;
+}
+
+CrtcAssignment *
+assign_crtcs (RWScreen *screen, Setting **settings)
+{
+    CrtcAssignment *assignment = crtc_assignment_new ();
+
+    if (real_assign_crtcs (screen, settings, assignment))
+    {
+	assignment->screen = screen;
+	
+	return assignment;
+    }
+    else
+    {
+	crtc_assignment_free (assignment);
+    
+	return NULL;
+    }
+}
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/display-capplet.glade
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/display-capplet.glade	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,363 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="dialog">
+  <property name="border_width">18</property>
+  <property name="title" translatable="yes"></property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+  <property name="has_separator">False</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="dialog-vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="dialog-action_area1">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="helpbutton1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-help</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-11</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="button1">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-apply</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-10</property>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="button2">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-close</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">-7</property>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVBox" id="vbox1">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">12</property>
+
+	  <child>
+	    <widget class="GtkLabel" id="label1">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">&lt;b&gt;Monitor Resolution Settings&lt;/b&gt;</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">True</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">False</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkAlignment" id="align">
+	      <property name="visible">True</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xscale">1</property>
+	      <property name="yscale">1</property>
+	      <property name="top_padding">0</property>
+	      <property name="bottom_padding">0</property>
+	      <property name="left_padding">0</property>
+	      <property name="right_padding">0</property>
+
+	      <child>
+		<placeholder/>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">True</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkAlignment" id="alignment1">
+	      <property name="visible">True</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xscale">1</property>
+	      <property name="yscale">1</property>
+	      <property name="top_padding">12</property>
+	      <property name="bottom_padding">24</property>
+	      <property name="left_padding">24</property>
+	      <property name="right_padding">24</property>
+
+	      <child>
+		<widget class="GtkTable" id="table1">
+		  <property name="visible">True</property>
+		  <property name="n_rows">2</property>
+		  <property name="n_columns">4</property>
+		  <property name="homogeneous">False</property>
+		  <property name="row_spacing">12</property>
+		  <property name="column_spacing">12</property>
+
+		  <child>
+		    <widget class="GtkLabel" id="label2">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">_Resolution</property>
+		      <property name="use_underline">True</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">0</property>
+		      <property name="right_attach">1</property>
+		      <property name="top_attach">0</property>
+		      <property name="bottom_attach">1</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options"></property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="label3">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">Re_fresh Rate:</property>
+		      <property name="use_underline">True</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">0</property>
+		      <property name="right_attach">1</property>
+		      <property name="top_attach">1</property>
+		      <property name="bottom_attach">2</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options"></property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkComboBox" id="resolution_combo">
+		      <property name="visible">True</property>
+		      <property name="add_tearoffs">False</property>
+		      <property name="focus_on_click">True</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">1</property>
+		      <property name="right_attach">2</property>
+		      <property name="top_attach">0</property>
+		      <property name="bottom_attach">1</property>
+		      <property name="y_options">fill</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkComboBox" id="refresh_combo">
+		      <property name="visible">True</property>
+		      <property name="add_tearoffs">False</property>
+		      <property name="focus_on_click">True</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">1</property>
+		      <property name="right_attach">2</property>
+		      <property name="top_attach">1</property>
+		      <property name="bottom_attach">2</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options">fill</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="label4">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">Include _Panel</property>
+		      <property name="use_underline">True</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="mnemonic_widget">panel_checkbox</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">2</property>
+		      <property name="right_attach">3</property>
+		      <property name="top_attach">0</property>
+		      <property name="bottom_attach">1</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options"></property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="label5">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">R_otation</property>
+		      <property name="use_underline">True</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">2</property>
+		      <property name="right_attach">3</property>
+		      <property name="top_attach">1</property>
+		      <property name="bottom_attach">2</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options"></property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkComboBox" id="rotation_combo">
+		      <property name="visible">True</property>
+		      <property name="items" translatable="yes">Normal
+Left
+Right
+Upside-down
+</property>
+		      <property name="add_tearoffs">False</property>
+		      <property name="focus_on_click">True</property>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">3</property>
+		      <property name="right_attach">4</property>
+		      <property name="top_attach">1</property>
+		      <property name="bottom_attach">2</property>
+		      <property name="y_options">fill</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkCheckButton" id="panel_checkbox">
+		      <property name="visible">True</property>
+		      <property name="can_focus">True</property>
+		      <property name="relief">GTK_RELIEF_NORMAL</property>
+		      <property name="focus_on_click">True</property>
+		      <property name="active">False</property>
+		      <property name="inconsistent">False</property>
+		      <property name="draw_indicator">True</property>
+
+		      <child>
+			<placeholder/>
+		      </child>
+		    </widget>
+		    <packing>
+		      <property name="left_attach">3</property>
+		      <property name="right_attach">4</property>
+		      <property name="top_attach">0</property>
+		      <property name="bottom_attach">1</property>
+		      <property name="x_options">fill</property>
+		      <property name="y_options"></property>
+		    </packing>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/scrollarea.h
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/scrollarea.h	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,107 @@
+#include <cairo/cairo.h>
+#include <gtk/gtk.h>
+
+#define FOO_TYPE_SCROLL_AREA            (foo_scroll_area_get_type ())
+#define FOO_SCROLL_AREA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), FOO_TYPE_SCROLL_AREA, FooScrollArea))
+#define FOO_SCROLL_AREA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  FOO_TYPE_SCROLL_AREA, FooScrollAreaClass))
+#define FOO_IS_SCROLL_AREA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FOO_TYPE_SCROLL_AREA))
+#define FOO_IS_SCROLL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  FOO_TYPE_SCROLL_AREA))
+#define FOO_SCROLL_AREA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  FOO_TYPE_SCROLL_AREA, FooScrollAreaClass))
+
+typedef struct FooScrollArea FooScrollArea;
+typedef struct FooScrollAreaClass FooScrollAreaClass;
+typedef struct FooScrollAreaPrivate FooScrollAreaPrivate;
+typedef struct FooScrollAreaEvent FooScrollAreaEvent;
+
+typedef enum
+{
+    FOO_BUTTON_PRESS,
+    FOO_BUTTON_RELEASE,
+    FOO_MOTION
+} FooScrollAreaEventType;
+
+struct FooScrollAreaEvent
+{
+    FooScrollAreaEventType	type;
+    int				x;
+    int				y;
+};
+
+typedef void (* FooScrollAreaEventFunc) (FooScrollArea      *area,
+					 FooScrollAreaEvent *event,
+					 gpointer            data);
+
+struct FooScrollArea
+{
+    GtkContainer parent_instance;
+
+    FooScrollAreaPrivate *priv;
+};
+
+struct FooScrollAreaClass
+{
+    GtkContainerClass parent_class;
+
+    void (*set_scroll_adjustments) (FooScrollArea *scroll_area,
+				    GtkAdjustment *hadjustment,
+				    GtkAdjustment *vadjustment);
+
+    void (*viewport_changed) (FooScrollArea *scroll_area,
+			      GdkRectangle  *old_viewport,
+			      GdkRectangle  *new_viewport);
+
+    void (*paint) (FooScrollArea *scroll_area,
+		   cairo_t       *cr,
+		   GdkRectangle  *extents,
+		   GdkRegion     *region);
+};
+
+GType foo_scroll_area_get_type (void);
+
+FooScrollArea *foo_scroll_area_new (void);
+
+/* Set the requisition for the widget. */
+void	      foo_scroll_area_set_min_size (FooScrollArea *scroll_area,
+					    int		   min_width,
+					    int            min_height);
+
+/* Set how much of the canvas can be scrolled into view */
+void	      foo_scroll_area_set_size (FooScrollArea	       *scroll_area,
+					int			width,
+					int			height);
+void	      foo_scroll_area_set_size_fixed_y (FooScrollArea  *scroll_area,
+						int		width,
+						int		height,
+						int		old_y,
+						int		new_y);
+void	      foo_scroll_area_set_viewport_pos (FooScrollArea  *scroll_area,
+						int		x,
+						int		y);
+void	      foo_scroll_area_get_viewport (FooScrollArea *scroll_area,
+					    GdkRectangle  *viewport);
+void          foo_scroll_area_add_input_from_stroke (FooScrollArea           *scroll_area,
+						     cairo_t	                *cr,
+						     FooScrollAreaEventFunc   func,
+						     gpointer                 data);
+void          foo_scroll_area_add_input_from_fill (FooScrollArea *scroll_area,
+						      cairo_t	      *cr,
+						      FooScrollAreaEventFunc func,
+						      gpointer       data);
+void          foo_scroll_area_invalidate_region (FooScrollArea *area,
+						 GdkRegion     *region);
+void	      foo_scroll_area_invalidate (FooScrollArea *scroll_area);
+void	      foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area,
+					       int	      x,
+					       int	      y,
+					       int	      width,
+					       int	      height);
+void foo_scroll_area_begin_grab (FooScrollArea *scroll_area,
+				 FooScrollAreaEventFunc func,
+				 gpointer       input_data);
+void foo_scroll_area_end_grab (FooScrollArea *scroll_area);
+gboolean foo_scroll_area_is_grabbed (FooScrollArea *scroll_area);
+
+void foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area);
+void foo_scroll_area_auto_scroll (FooScrollArea *scroll_area,
+				  FooScrollAreaEvent *event);
+void foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area);
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/display-name.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/display-name.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2007 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* Author: Soren Sandmann <sandmann@redhat.com> */
+
+#include <stdlib.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include "edid.h"
+
+typedef struct Vendor Vendor;
+struct Vendor
+{
+    const char vendor_id[4];
+    const char vendor_name[28];
+};
+
+/* This list of vendor codes derived from lshw
+ * 
+ * http://ezix.org/project/wiki/HardwareLiSter
+ */
+static const struct Vendor vendors[] = 
+{
+    { "AIC", "AG Neovo" },
+    { "ACR", "Acer" },
+    { "DEL", "DELL" },
+    { "SAM", "SAMSUNG" },
+    { "SNY", "SONY" },
+    { "SEC", "Epson" },
+    { "WAC", "Wacom" },
+    { "NEC", "NEC" },
+    { "CMO", "CMO" },	/* Chi Mei */
+
+    { "ABP", "Advansys" },
+    { "ACC", "Accton" },
+    { "ACE", "Accton" },
+    { "ADP", "Adaptec" },
+    { "ADV", "AMD" },
+    { "AIR", "AIR" },
+    { "AMI", "AMI" },
+    { "ASU", "ASUS" },
+    { "ATI", "ATI" },
+    { "ATK", "Allied Telesyn" },
+    { "AZT", "Aztech" },
+    { "BAN", "Banya" },
+    { "BRI", "Boca Research" },
+    { "BUS", "Buslogic" },
+    { "CCI", "Cache Computers Inc." },
+    { "CHA", "Chase" },
+    { "CMD", "CMD Technology, Inc." },
+    { "COG", "Cogent" },
+    { "CPQ", "Compaq" },
+    { "CRS", "Crescendo" },
+    { "CSC", "Crystal" },
+    { "CSI", "CSI" },
+    { "CTL", "Creative Labs" },
+    { "DBI", "Digi" },
+    { "DEC", "Digital Equipment" },
+    { "DBK", "Databook" },
+    { "EGL", "Eagle Technology" },
+    { "ELS", "ELSA" },
+    { "ESS", "ESS" },
+    { "FAR", "Farallon" },
+    { "FDC", "Future Domain" },
+    { "HWP", "Hewlett-Packard" },
+    { "IBM", "IBM" },
+    { "INT", "Intel" },
+    { "ISA", "Iomega" },
+    { "MDG", "Madge" },
+    { "MDY", "Microdyne" },
+    { "MET", "Metheus" },
+    { "MIC", "Micronics" },
+    { "MLX", "Mylex" },
+    { "NVL", "Novell" },
+    { "OLC", "Olicom" },
+    { "PRO", "Proteon" },
+    { "RII", "Racal" },
+    { "RTL", "Realtek" },
+    { "SCM", "SCM" },
+    { "SKD", "SysKonnect" },
+    { "SGI", "SGI" },
+    { "SMC", "SMC" },
+    { "SNI", "Siemens Nixdorf" },
+    { "STL", "Stallion Technologies" },
+    { "SUN", "Sun" },
+    { "SUP", "SupraExpress" },
+    { "SVE", "SVEC" },
+    { "TCC", "Thomas-Conrad" },
+    { "TCI", "Tulip" },
+    { "TCM", "3Com" },
+    { "TCO", "Thomas-Conrad" },
+    { "TEC", "Tecmar" },
+    { "TRU", "Truevision" },
+    { "TOS", "Toshiba" },
+    { "TYN", "Tyan" },
+    { "UBI", "Ungermann-Bass" },
+    { "USC", "UltraStor" },
+    { "VDM", "Vadem" },
+    { "VMI", "Vermont" },
+    { "WDC", "Western Digital" },
+    { "ZDS", "Zeos" },
+    { "???", "Unknown" },
+};
+
+static const char *
+find_vendor (const char *code)
+{
+    int i;
+
+    for (i = 0; i < sizeof (vendors) / sizeof (vendors[0]); ++i)
+    {
+	const Vendor *v = &(vendors[i]);
+	
+	if (strcmp (v->vendor_id, code) == 0)
+	    return v->vendor_name;
+    }
+
+    return code;
+};
+
+char *
+make_display_name (const MonitorInfo *info)
+{
+    const char *vendor = find_vendor (info->manufacturer_code);
+    int len = 32 + strlen (vendor) + 1 + sizeof(int) * 10 + 1;
+    char *result = malloc (len);
+
+    if (!result)
+	return NULL;
+
+    if (strncmp (info->dsc_product_name, "EPSON PJ", 8) == 0)
+    {
+	sprintf (result, "Epson Projector");
+    }
+    else
+    {
+	sprintf (result, "%s", vendor);
+    }
+    
+    return result;
+}
diff -up gnome-control-center-2.21.5/capplets/display/Makefile.am.add-randr12-capplet gnome-control-center-2.21.5/capplets/display/Makefile.am
--- gnome-control-center-2.21.5/capplets/display/Makefile.am.add-randr12-capplet	2008-01-03 11:22:06.000000000 -0500
+++ gnome-control-center-2.21.5/capplets/display/Makefile.am	2008-01-29 16:47:04.000000000 -0500
@@ -1,9 +1,27 @@
 # This is used in GNOMECC_CAPPLETS_CFLAGS
 cappletname = display
 
+gladedir = $(pkgdatadir)/glade
+dist_glade_DATA = display-capplet.glade
+
 bin_PROGRAMS = gnome-display-properties
 
-gnome_display_properties_SOURCES = main.c
+gnome_display_properties_SOURCES = \
+	xrandr-capplet.c	   \
+	monitor-db.c		   \
+	display-name.c		   \
+        edid-parse.c	           \
+        scrollarea.c               \
+        crtc.c                     \
+        apply.c                    \
+        foo-marshal.c		   \
+	monitor-db.h		   \
+	edid.h		           \
+        scrollarea.h               \
+        crtc.h                     \
+        apply.h                    \
+        foo-marshal.h	
+
 gnome_display_properties_LDFLAGS = -export-dynamic
 gnome_display_properties_LDADD = \
 	$(DISPLAY_CAPPLET_LIBS) \
diff -up /dev/null gnome-control-center-2.21.5/capplets/display/apply.c
--- /dev/null	2008-01-26 11:28:38.229690233 -0500
+++ gnome-control-center-2.21.5/capplets/display/apply.c	2008-01-29 16:47:04.000000000 -0500
@@ -0,0 +1,178 @@
+#include "crtc.h"
+#include "monitor-db.h"
+
+/* This file should go in g-s-d eventually */
+
+static void
+push (GPtrArray *a,
+      RWOutput *output,
+      RWMode *mode,
+      int x, int y)
+{
+    Setting *setting = g_new0 (Setting, 1);
+
+    setting->output = output;
+    setting->mode = mode;
+    setting->x = x;
+    setting->y = y;
+    
+    g_ptr_array_add (a, setting);
+}
+
+static void
+pop (GPtrArray *a)
+{
+    g_free (g_ptr_array_remove_index (a, a->len - 1));
+}
+
+typedef gboolean (* ForeachFunc) (Setting **settings, gpointer data);
+
+static gboolean
+foreach_setting_list (RWScreen *screen,
+		      GPtrArray *settings,
+		      Output **outputs,
+		      ForeachFunc func,
+		      gpointer data)
+{
+    gboolean result;
+    
+    if (*outputs == NULL)
+    {
+	g_ptr_array_add (settings, NULL);
+	
+	result = func ((Setting **)settings->pdata, data);
+
+	g_ptr_array_remove_index (settings, settings->len - 1);
+    }
+    else
+    {
+	Output *output = *outputs;	
+	RWOutput *rw_output = rw_screen_get_output_by_name (
+	    screen, output->name);
+	RWMode **modes = rw_output_list_modes (rw_output);
+	int i;
+
+	if (!output->connected)
+	{
+	    g_print ("%s is not connected\n", output->name);
+	    push (settings, rw_output, NULL, 0, 0);
+
+	    result = foreach_setting_list (screen, settings, outputs + 1, func, data);
+
+	    pop (settings);
+	}
+	else
+	{
+	    result = FALSE;
+
+	    g_print ("%s modes (%p)\n", output->name, output);
+	    
+	    for (i = 0; modes[i] != NULL; ++i)
+	    {
+		RWMode *mode = modes[i];
+		
+		g_print ("%d x %d x %d   match mode   %d x %d x %d\n",
+			 output->width, output->height, output->rate,
+			 rw_mode_get_width (mode),
+			 rw_mode_get_height (mode),
+			 rw_mode_get_freq (mode));
+	    
+		if (rw_mode_get_width (mode) == output->width	&&
+		    rw_mode_get_height (mode) == output->height	&&
+		    rw_mode_get_freq (mode) == output->rate)
+		{
+		    push (settings, rw_output, mode, output->x, 0);
+
+		    result = foreach_setting_list (
+			screen, settings, outputs + 1, func, data);
+
+		    pop (settings);
+
+		    if (result)
+			break;
+		}
+	    }
+	}
+    }
+
+    g_print ("returning %d\n", result);
+    
+    return result;
+}
+
+static gboolean
+try_settings (Setting **settings, gpointer data)
+{
+    RWScreen *screen = data;
+    CrtcAssignment *assignment;
+    int i;
+
+    g_print ("found setting\n");
+    
+    assignment = assign_crtcs (screen, settings);
+
+    for (i = 0; settings[i] != NULL; ++i)
+    {
+	RWMode *mode = settings[i]->mode;
+	
+	g_print ("%s => ", rw_output_get_name (settings[i]->output));
+
+	if (mode)
+	{
+	    g_print ("%d x %d x %d\n",
+		     rw_mode_get_width (mode),
+		     rw_mode_get_height (mode),
+		     rw_mode_get_freq (mode));
+	}
+	else
+	{
+	    g_print ("off\n");
+	}
+    }
+    
+    if (assignment)
+    {
+	crtc_assignment_apply (assignment);
+	    
+	crtc_assignment_free (assignment);
+
+	return TRUE;
+    }
+    else
+    {
+	return FALSE;
+    }
+}
+
+/* This should go in g-s-d eventually */
+void
+apply_stored_configuration (RWScreen *screen)
+{
+    char *file = g_build_filename (
+	g_get_home_dir(), ".gnome2", "monitors.xml", NULL);
+    Configuration **configs = configurations_read (file, NULL);
+    Configuration *current = configuration_new_current (screen);
+    Configuration *found;
+
+    if ((found = configuration_find (configs, current)))
+    {
+	GPtrArray *array = g_ptr_array_new ();
+
+	g_print ("found\n");
+	
+	foreach_setting_list (
+	    screen, array, found->outputs,
+	    try_settings, screen);
+	
+	g_ptr_array_free (array, TRUE);
+    }
+    else
+    {
+	g_print ("Not found\n");
+    }
+
+    configuration_free (current);
+    configurations_free (configs);
+
+    g_free (file);
+}