summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@redhat.com>2011-03-16 20:14:09 +0100
committerMarc-André Lureau <marcandre.lureau@redhat.com>2011-03-17 11:57:53 +0100
commita2ad8d1bed0cb234e7689ff69d9729231ced34cd (patch)
treee9fe9d6216163e4274d2879a12752ef586601564
parent07a3d7b2cd64686f17394b07a1fecce270005de0 (diff)
gtk: import display configuration from gnome-desktop
-rw-r--r--configure.ac20
-rw-r--r--gtk/Makefile.am25
-rw-r--r--gtk/display/display-name.c299
-rw-r--r--gtk/display/edid-parse.c540
-rw-r--r--gtk/display/edid.h194
-rw-r--r--gtk/display/gnome-rr-config.c1982
-rw-r--r--gtk/display/gnome-rr-config.h150
-rw-r--r--gtk/display/gnome-rr-output-info.c246
-rw-r--r--gtk/display/gnome-rr-private.h80
-rw-r--r--gtk/display/gnome-rr.c2121
-rw-r--r--gtk/display/gnome-rr.h202
-rw-r--r--gtk/spicy.c77
12 files changed, 5926 insertions, 10 deletions
diff --git a/configure.ac b/configure.ac
index 27df5d7..3bcc2ea 100644
--- a/configure.ac
+++ b/configure.ac
@@ -166,7 +166,7 @@ if test "x$with_sasl" != "xno"; then
SASL_LIBS="$SASL_LIBS -lsasl"
else
AC_MSG_ERROR([You must install the Cyrus SASL development package in order to compile GTK-VNC])
- fi
+ fi
CFLAGS="$old_cflags"
LIBS="$old_libs"
if test "x$with_sasl2" = "xyes" -o "x$with_sasl" = "xyes" ; then
@@ -211,6 +211,24 @@ PKG_CHECK_MODULES(GTK, gtk+-$GTK_API_VERSION >= $GTK_REQUIRED)
AC_SUBST(GTK_CFLAGS)
AC_SUBST(GTK_LIBS)
+PKG_CHECK_MODULES(XRANDR, x11 xrandr)
+AC_SUBST(XRANDR_CFLAGS)
+AC_SUBST(XRANDR_LIBS)
+AC_DEFINE(HAVE_RANDR, 1, [Define if the xrandr library is present])
+AC_ARG_WITH(pnp-ids-path,
+ [AC_HELP_STRING([--with-pnp-ids-path],
+ [Specify the path to pnp.ids @<:@default=(internal)@:>@])],,
+ [with_pnp_ids_path="\${pnpdatadir}/pnp.ids"])
+
+AM_CONDITIONAL(USE_INTERNAL_PNP_IDS, test "x$with_pnp_ids_path" = "x\${pnpdatadir}/pnp.ids")
+PNP_IDS=$with_pnp_ids_path
+AC_SUBST(PNP_IDS)
+if test "x$with_pnp_ids_path" = "x\${pnpdatadir}/pnp.ids"; then
+ EXTERNAL_PNP_IDS="no (internal)"
+else
+ EXTERNAL_PNP_IDS="$with_pnp_ids_path"
+fi
+
PKG_CHECK_MODULES(GLIB2, glib-2.0 >= 2.22)
AC_SUBST(GLIB2_CFLAGS)
AC_SUBST(GLIB2_LIBS)
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 7ce8a65..9e761d6 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -43,6 +43,7 @@ SPICE_COMMON_CPPFLAGS = \
-DG_LOG_DOMAIN=\"GSpice\" \
-DSW_CANVAS_CACHE \
-DSPICE_GTK_LOCALEDIR=\"${SPICE_GTK_LOCALEDIR}\" \
+ -DPNP_IDS=\""$(PNP_IDS)"\"\
\
-I$(COMMON_DIR) \
-I$(CLIENT_DIR) \
@@ -253,15 +254,31 @@ libspice_client_glibinclude_HEADERS = \
$(NULL)
-spicy_SOURCES = \
- spicy.c \
- spice-cmdline.h \
- spice-cmdline.c \
+spicy_SOURCES = \
+ spicy.c \
+ display/edid.h \
+ display/edid-parse.c \
+ display/display-name.c \
+ display/gnome-rr-config.c \
+ display/gnome-rr-config.h \
+ display/gnome-rr-output-info.c \
+ display/gnome-rr-output-info.h \
+ display/gnome-rr-private.h \
+ display/gnome-rr.c \
+ display/gnome-rr.h \
+ spice-cmdline.h \
+ spice-cmdline.c \
$(NULL)
spicy_LDADD = \
libspice-client-gtk-$(SPICE_GTK_API_VERSION).la \
libspice-client-glib-2.0.la \
+ $(XRANDR_LIBS) \
+ $(NULL)
+
+spicy_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(XRANDR_CFLAGS) \
$(NULL)
diff --git a/gtk/display/display-name.c b/gtk/display/display-name.c
new file mode 100644
index 0000000..d38eb2f
--- /dev/null
+++ b/gtk/display/display-name.c
@@ -0,0 +1,299 @@
+/*
+ * 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 <config.h>
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <glib.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
+ *
+ * Note: we now prefer to use data coming from hwdata (and shipped with
+ * gnome-desktop). See
+ * http://git.fedorahosted.org/git/?p=hwdata.git;a=blob_plain;f=pnp.ids;hb=HEAD
+ * All contributions to the list of vendors should go there.
+ */
+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 */
+ { "BNQ", "BenQ" },
+
+ { "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" },
+ { "LEN", "Lenovo" },
+ { "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" },
+
+ /* From http://faydoc.tripod.com/structures/01/0136.htm */
+ { "ACT", "Targa" },
+ { "ADI", "ADI" },
+ { "AOC", "AOC Intl" },
+ { "API", "Acer America" },
+ { "APP", "Apple Computer" },
+ { "ART", "ArtMedia" },
+ { "AST", "AST Research" },
+ { "CPL", "Compal" },
+ { "CTX", "Chuntex Electronic Co." },
+ { "DPC", "Delta Electronics" },
+ { "DWE", "Daewoo" },
+ { "ECS", "ELITEGROUP" },
+ { "EIZ", "EIZO" },
+ { "FCM", "Funai" },
+ { "GSM", "LG Electronics" },
+ { "GWY", "Gateway 2000" },
+ { "HEI", "Hyundai" },
+ { "HIT", "Hitachi" },
+ { "HSL", "Hansol" },
+ { "HTC", "Hitachi" },
+ { "ICL", "Fujitsu ICL" },
+ { "IVM", "Idek Iiyama" },
+ { "KFC", "KFC Computek" },
+ { "LKM", "ADLAS" },
+ { "LNK", "LINK Tech" },
+ { "LTN", "Lite-On" },
+ { "MAG", "MAG InnoVision" },
+ { "MAX", "Maxdata" },
+ { "MEI", "Panasonic" },
+ { "MEL", "Mitsubishi" },
+ { "MIR", "miro" },
+ { "MTC", "MITAC" },
+ { "NAN", "NANAO" },
+ { "NEC", "NEC Tech" },
+ { "NOK", "Nokia" },
+ { "OQI", "OPTIQUEST" },
+ { "PBN", "Packard Bell" },
+ { "PGS", "Princeton" },
+ { "PHL", "Philips" },
+ { "REL", "Relisys" },
+ { "SDI", "Samtron" },
+ { "SMI", "Smile" },
+ { "SPT", "Sceptre" },
+ { "SRC", "Shamrock Technology" },
+ { "STP", "Sceptre" },
+ { "TAT", "Tatung" },
+ { "TRL", "Royal Information Company" },
+ { "TSB", "Toshiba, Inc." },
+ { "UNM", "Unisys" },
+ { "VSC", "ViewSonic" },
+ { "WTC", "Wen Tech" },
+ { "ZCM", "Zenith Data Systems" },
+
+ { "???", "Unknown" },
+};
+
+static GHashTable *pnp_ids = NULL;
+
+static void
+read_pnp_ids (void)
+{
+ gchar *contents;
+ gchar **lines;
+ gchar *line;
+ gchar *code, *name;
+ gint i;
+
+ if (pnp_ids)
+ return;
+
+ pnp_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ if (g_file_get_contents (PNP_IDS, &contents, NULL, NULL))
+ {
+ lines = g_strsplit (contents, "\n", -1);
+ for (i = 0; lines[i]; i++)
+ {
+ line = lines[i];
+ if (line[0] && line[1] && line[2] && line[3] == '\t' && line[4])
+ {
+ code = line;
+ line[3] = '\0';
+ name = line + 4;
+ g_hash_table_insert (pnp_ids, code, name);
+ }
+ }
+ g_free (lines);
+ g_free (contents);
+ }
+}
+
+
+static const char *
+find_vendor (const char *code)
+{
+ const char *vendor_name;
+ int i;
+
+ read_pnp_ids ();
+
+ vendor_name = g_hash_table_lookup (pnp_ids, code);
+
+ if (vendor_name)
+ return vendor_name;
+
+ 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;
+ int width_mm, height_mm, inches;
+
+ if (info)
+ {
+ vendor = find_vendor (info->manufacturer_code);
+ }
+ else
+ {
+ /* Translators: "Unknown" here is used to identify a monitor for which
+ * we don't know the vendor. When a vendor is known, the name of the
+ * vendor is used. */
+ vendor = C_("Monitor vendor", "Unknown");
+ }
+
+ if (info && info->width_mm != -1 && info->height_mm)
+ {
+ width_mm = info->width_mm;
+ height_mm = info->height_mm;
+ }
+ else if (info && info->n_detailed_timings)
+ {
+ width_mm = info->detailed_timings[0].width_mm;
+ height_mm = info->detailed_timings[0].height_mm;
+ }
+ else
+ {
+ width_mm = -1;
+ height_mm = -1;
+ }
+
+ if (width_mm != -1 && height_mm != -1)
+ {
+ double d = sqrt (width_mm * width_mm + height_mm * height_mm);
+
+ inches = (int)(d / 25.4 + 0.5);
+ }
+ else
+ {
+ inches = -1;
+ }
+
+ if (inches > 0)
+ return g_strdup_printf ("%s %d\"", vendor, inches);
+ else
+ return g_strdup (vendor);
+}
diff --git a/gtk/display/edid-parse.c b/gtk/display/edid-parse.c
new file mode 100644
index 0000000..512c568
--- /dev/null
+++ b/gtk/display/edid-parse.c
@@ -0,0 +1,540 @@
+/*
+ * 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>
+#include <glib.h>
+
+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->connector.digital.bits_per_primary = bit_depth[bits];
+
+ bits = get_bits (edid[0x14], 0, 3);
+
+ if (bits <= 5)
+ info->connector.digital.interface = interfaces[bits];
+ else
+ info->connector.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->connector.analog.video_signal_level = levels[bits][0];
+ info->connector.analog.sync_signal_level = levels[bits][1];
+ info->connector.analog.total_signal_level = levels[bits][2];
+
+ info->connector.analog.blank_to_black = get_bit (edid[0x14], 4);
+
+ info->connector.analog.separate_hv_sync = get_bit (edid[0x14], 3);
+ info->connector.analog.composite_sync_on_h = get_bit (edid[0x14], 2);
+ info->connector.analog.composite_sync_on_green = get_bit (edid[0x14], 1);
+
+ info->connector.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->connector.digital.rgb444 = TRUE;
+ if (get_bit (edid[0x18], 3))
+ info->connector.digital.ycrcb444 = 1;
+ if (get_bit (edid[0x18], 4))
+ info->connector.digital.ycrcb422 = 1;
+ }
+ else
+ {
+ int bits = get_bits (edid[0x18], 3, 4);
+ ColorType color_type[4] =
+ {
+ MONOCHROME, RGB, OTHER_COLOR, UNDEFINED_COLOR
+ };
+
+ info->connector.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 = 0;
+
+ 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->connector.digital.composite = !get_bit (bits, 3);
+
+ if (detailed->connector.digital.composite)
+ {
+ detailed->connector.digital.serrations = get_bit (bits, 2);
+ detailed->connector.digital.negative_vsync = FALSE;
+ }
+ else
+ {
+ detailed->connector.digital.serrations = FALSE;
+ detailed->connector.digital.negative_vsync = !get_bit (bits, 2);
+ }
+
+ detailed->connector.digital.negative_hsync = !get_bit (bits, 0);
+ }
+ else
+ {
+ detailed->connector.analog.bipolar = get_bit (bits, 3);
+ detailed->connector.analog.serrations = get_bit (bits, 2);
+ detailed->connector.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 = g_new0 (MonitorInfo, 1);
+
+ decode_check_sum (edid, info);
+
+ if (decode_header (edid)
+ && decode_vendor_and_product_identification (edid, info)
+ && decode_edid_version (edid, info)
+ && decode_display_parameters (edid, info)
+ && decode_color_characteristics (edid, info)
+ && decode_established_timings (edid, info)
+ && decode_standard_timings (edid, info)
+ && decode_descriptors (edid, info))
+ {
+ return info;
+ }
+ else
+ {
+ g_free (info);
+ return NULL;
+ }
+}
diff --git a/gtk/display/edid.h b/gtk/display/edid.h
new file mode 100644
index 0000000..1fa0d1a
--- /dev/null
+++ b/gtk/display/edid.h
@@ -0,0 +1,194 @@
+/* edid.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. 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>
+ */
+
+#ifndef EDID_H
+#define EDID_H
+
+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 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;
+ } connector;
+};
+
+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;
+ } connector;
+
+ 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);
+
+#endif
diff --git a/gtk/display/gnome-rr-config.c b/gtk/display/gnome-rr-config.c
new file mode 100644
index 0000000..f070df3
--- /dev/null
+++ b/gtk/display/gnome-rr-config.c
@@ -0,0 +1,1982 @@
+/* gnome-rr-config.c
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. 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>
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+
+#include "gnome-rr-config.h"
+
+#include "edid.h"
+#include "gnome-rr-private.h"
+
+#define CONFIG_INTENDED_BASENAME "monitors.xml"
+#define CONFIG_BACKUP_BASENAME "monitors.xml.backup"
+
+/* In version 0 of the config file format, we had several <configuration>
+ * toplevel elements and no explicit version number. So, the filed looked
+ * like
+ *
+ * <configuration>
+ * ...
+ * </configuration>
+ * <configuration>
+ * ...
+ * </configuration>
+ *
+ * Since version 1 of the config file, the file has a toplevel <monitors>
+ * element to group all the configurations. That element has a "version"
+ * attribute which is an integer. So, the file looks like this:
+ *
+ * <monitors version="1">
+ * <configuration>
+ * ...
+ * </configuration>
+ * <configuration>
+ * ...
+ * </configuration>
+ * </monitors>
+ */
+
+/* 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 CrtcAssignment CrtcAssignment;
+
+static gboolean crtc_assignment_apply (CrtcAssignment *assign,
+ guint32 timestamp,
+ GError **error);
+static CrtcAssignment *crtc_assignment_new (GnomeRRScreen *screen,
+ GnomeRROutputInfo **outputs,
+ GError **error);
+static void crtc_assignment_free (CrtcAssignment *assign);
+
+enum {
+ PROP_0,
+ PROP_SCREEN,
+ PROP_LAST
+};
+
+G_DEFINE_TYPE (GnomeRRConfig, gnome_rr_config, G_TYPE_OBJECT)
+
+typedef struct Parser Parser;
+
+/* Parser for monitor configurations */
+struct Parser
+{
+ int config_file_version;
+ GnomeRROutputInfo * output;
+ GnomeRRConfig * configuration;
+ GPtrArray * outputs;
+ GPtrArray * configurations;
+ GQueue * stack;
+};
+
+static int
+parse_int (const char *text)
+{
+ return strtol (text, NULL, 0);
+}
+
+static guint
+parse_uint (const char *text)
+{
+ return strtoul (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_object_new (GNOME_TYPE_RR_OUTPUT_INFO, NULL);
+ parser->output->priv->rotation = 0;
+
+ for (i = 0; attr_names[i] != NULL; ++i)
+ {
+ if (strcmp (attr_names[i], "name") == 0)
+ {
+ parser->output->priv->name = g_strdup (attr_values[i]);
+ break;
+ }
+ }
+
+ if (!parser->output->priv->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->priv->name = g_strdup ("default");
+ }
+ parser->output->priv->connected = FALSE;
+ parser->output->priv->on = FALSE;
+ parser->output->priv->primary = FALSE;
+ }
+ else if (strcmp (name, "configuration") == 0)
+ {
+ g_assert (parser->configuration == NULL);
+
+ parser->configuration = g_object_new (GNOME_TYPE_RR_CONFIG, NULL);
+ parser->configuration->priv->clone = FALSE;
+ parser->configuration->priv->outputs = NULL;
+ }
+ else if (strcmp (name, "monitors") == 0)
+ {
+ int i;
+
+ for (i = 0; attr_names[i] != NULL; i++)
+ {
+ if (strcmp (attr_names[i], "version") == 0)
+ {
+ parser->config_file_version = parse_int (attr_values[i]);
+ break;
+ }
+ }
+ }
+
+ 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)
+ {
+ /* If no rotation properties were set, just use GNOME_RR_ROTATION_0 */
+ if (parser->output->priv->rotation == 0)
+ parser->output->priv->rotation = GNOME_RR_ROTATION_0;
+
+ g_ptr_array_add (parser->outputs, parser->output);
+
+ parser->output = NULL;
+ }
+ else if (strcmp (name, "configuration") == 0)
+ {
+ g_ptr_array_add (parser->outputs, NULL);
+ parser->configuration->priv->outputs =
+ (GnomeRROutputInfo **)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));
+}
+
+#define TOPLEVEL_ELEMENT (parser->config_file_version > 0 ? "monitors" : NULL)
+
+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", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->connected = TRUE;
+
+ strncpy ((gchar*) parser->output->priv->vendor, text, 3);
+ parser->output->priv->vendor[3] = 0;
+ }
+ else if (stack_is (parser, "clone", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ parser->configuration->priv->clone = TRUE;
+ }
+ else if (stack_is (parser, "product", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->connected = TRUE;
+
+ parser->output->priv->product = parse_int (text);
+ }
+ else if (stack_is (parser, "serial", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->connected = TRUE;
+
+ parser->output->priv->serial = parse_uint (text);
+ }
+ else if (stack_is (parser, "width", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->on = TRUE;
+
+ parser->output->priv->width = parse_int (text);
+ }
+ else if (stack_is (parser, "x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->on = TRUE;
+
+ parser->output->priv->x = parse_int (text);
+ }
+ else if (stack_is (parser, "y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->on = TRUE;
+
+ parser->output->priv->y = parse_int (text);
+ }
+ else if (stack_is (parser, "height", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->on = TRUE;
+
+ parser->output->priv->height = parse_int (text);
+ }
+ else if (stack_is (parser, "rate", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->priv->on = TRUE;
+
+ parser->output->priv->rate = parse_int (text);
+ }
+ else if (stack_is (parser, "rotation", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "normal") == 0)
+ {
+ parser->output->priv->rotation |= GNOME_RR_ROTATION_0;
+ }
+ else if (strcmp (text, "left") == 0)
+ {
+ parser->output->priv->rotation |= GNOME_RR_ROTATION_90;
+ }
+ else if (strcmp (text, "upside_down") == 0)
+ {
+ parser->output->priv->rotation |= GNOME_RR_ROTATION_180;
+ }
+ else if (strcmp (text, "right") == 0)
+ {
+ parser->output->priv->rotation |= GNOME_RR_ROTATION_270;
+ }
+ }
+ else if (stack_is (parser, "reflect_x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->priv->rotation |= GNOME_RR_REFLECT_X;
+ }
+ }
+ else if (stack_is (parser, "reflect_y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->priv->rotation |= GNOME_RR_REFLECT_Y;
+ }
+ }
+ else if (stack_is (parser, "primary", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->priv->primary = TRUE;
+ }
+ }
+ else
+ {
+ /* Ignore other properties so we can expand the format in the future */
+ }
+}
+
+static void
+parser_free (Parser *parser)
+{
+ int i;
+ GList *list;
+
+ g_assert (parser != NULL);
+
+ if (parser->output)
+ g_object_unref (parser->output);
+
+ if (parser->configuration)
+ g_object_unref (parser->configuration);
+
+ for (i = 0; i < parser->outputs->len; ++i)
+ {
+ GnomeRROutputInfo *output = parser->outputs->pdata[i];
+
+ g_object_unref (output);
+ }
+
+ g_ptr_array_free (parser->outputs, TRUE);
+
+ for (i = 0; i < parser->configurations->len; ++i)
+ {
+ GnomeRRConfig *config = parser->configurations->pdata[i];
+
+ g_object_unref (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);
+}
+
+static GnomeRRConfig **
+configurations_read_from_file (const gchar *filename, GError **error)
+{
+ Parser *parser = g_new0 (Parser, 1);
+ GnomeRRConfig **result;
+ GMarkupParser callbacks = {
+ handle_start_element,
+ handle_end_element,
+ handle_text,
+ NULL, /* passthrough */
+ NULL, /* error */
+ };
+
+ parser->config_file_version = 0;
+ 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;
+
+ g_assert (parser->outputs);
+ goto out;
+ }
+
+ g_assert (parser->outputs);
+
+ g_ptr_array_add (parser->configurations, NULL);
+ result = (GnomeRRConfig **)g_ptr_array_free (parser->configurations, FALSE);
+ parser->configurations = g_ptr_array_new ();
+
+ g_assert (parser->outputs);
+out:
+ parser_free (parser);
+
+ return result;
+}
+
+static void
+gnome_rr_config_init (GnomeRRConfig *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GNOME_TYPE_RR_CONFIG, GnomeRRConfigPrivate);
+
+ self->priv->clone = FALSE;
+ self->priv->screen = NULL;
+ self->priv->outputs = NULL;
+}
+
+static void
+gnome_rr_config_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *property)
+{
+ GnomeRRConfig *self = GNOME_RR_CONFIG (gobject);
+
+ switch (property_id) {
+ case PROP_SCREEN:
+ self->priv->screen = g_value_dup_object (value);
+ return;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+ }
+}
+
+static void
+gnome_rr_config_finalize (GObject *gobject)
+{
+ GnomeRRConfig *self = GNOME_RR_CONFIG (gobject);
+
+ if (self->priv->screen)
+ g_object_unref (self->priv->screen);
+
+ if (self->priv->outputs) {
+ int i;
+
+ for (i = 0; self->priv->outputs[i] != NULL; i++) {
+ GnomeRROutputInfo *output = self->priv->outputs[i];
+ g_object_unref (output);
+ }
+ g_free (self->priv->outputs);
+ }
+
+ G_OBJECT_CLASS (gnome_rr_config_parent_class)->finalize (gobject);
+}
+
+gboolean
+gnome_rr_config_load_current (GnomeRRConfig *config, GError **error)
+{
+ GPtrArray *a;
+ GnomeRROutput **rr_outputs;
+ int i;
+ int clone_width = -1;
+ int clone_height = -1;
+ int last_x;
+
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (config), FALSE);
+
+ a = g_ptr_array_new ();
+ rr_outputs = gnome_rr_screen_list_outputs (config->priv->screen);
+
+ config->priv->clone = FALSE;
+
+ for (i = 0; rr_outputs[i] != NULL; ++i)
+ {
+ GnomeRROutput *rr_output = rr_outputs[i];
+ GnomeRROutputInfo *output = g_object_new (GNOME_TYPE_RR_OUTPUT_INFO, NULL);
+ GnomeRRMode *mode = NULL;
+ const guint8 *edid_data = gnome_rr_output_get_edid_data (rr_output);
+ GnomeRRCrtc *crtc;
+
+ output->priv->name = g_strdup (gnome_rr_output_get_name (rr_output));
+ output->priv->connected = gnome_rr_output_is_connected (rr_output);
+
+ if (!output->priv->connected)
+ {
+ output->priv->x = -1;
+ output->priv->y = -1;
+ output->priv->width = -1;
+ output->priv->height = -1;
+ output->priv->rate = -1;
+ output->priv->rotation = GNOME_RR_ROTATION_0;
+ }
+ else
+ {
+ MonitorInfo *info = NULL;
+
+ if (edid_data)
+ info = decode_edid (edid_data);
+
+ if (info)
+ {
+ memcpy (output->priv->vendor, info->manufacturer_code,
+ sizeof (output->priv->vendor));
+
+ output->priv->product = info->product_code;
+ output->priv->serial = info->serial_number;
+ output->priv->aspect = info->aspect_ratio;
+ }
+ else
+ {
+ strcpy (output->priv->vendor, "???");
+ output->priv->product = 0;
+ output->priv->serial = 0;
+ }
+
+ if (gnome_rr_output_is_laptop (rr_output))
+ output->priv->display_name = g_strdup (_("Laptop"));
+ else
+ output->priv->display_name = make_display_name (info);
+
+ g_free (info);
+
+ crtc = gnome_rr_output_get_crtc (rr_output);
+ mode = crtc? gnome_rr_crtc_get_current_mode (crtc) : NULL;
+
+ if (crtc && mode)
+ {
+ output->priv->on = TRUE;
+
+ gnome_rr_crtc_get_position (crtc, &output->priv->x, &output->priv->y);
+ output->priv->width = gnome_rr_mode_get_width (mode);
+ output->priv->height = gnome_rr_mode_get_height (mode);
+ output->priv->rate = gnome_rr_mode_get_freq (mode);
+ output->priv->rotation = gnome_rr_crtc_get_current_rotation (crtc);
+
+ if (output->priv->x == 0 && output->priv->y == 0) {
+ if (clone_width == -1) {
+ clone_width = output->priv->width;
+ clone_height = output->priv->height;
+ } else if (clone_width == output->priv->width &&
+ clone_height == output->priv->height) {
+ config->priv->clone = TRUE;
+ }
+ }
+ }
+ else
+ {
+ output->priv->on = FALSE;
+ config->priv->clone = FALSE;
+ }
+
+ /* Get preferred size for the monitor */
+ mode = gnome_rr_output_get_preferred_mode (rr_output);
+
+ if (!mode)
+ {
+ GnomeRRMode **modes = gnome_rr_output_list_modes (rr_output);
+
+ /* FIXME: we should pick the "best" mode here, where best is
+ * sorted wrt
+ *
+ * - closest aspect ratio
+ * - mode area
+ * - refresh rate
+ * - We may want to extend randrwrap so that get_preferred
+ * returns that - although that could also depend on
+ * the crtc.
+ */
+ if (modes[0])
+ mode = modes[0];
+ }
+
+ if (mode)
+ {
+ output->priv->pref_width = gnome_rr_mode_get_width (mode);
+ output->priv->pref_height = gnome_rr_mode_get_height (mode);
+ }
+ else
+ {
+ /* Pick some random numbers. This should basically never happen */
+ output->priv->pref_width = 1024;
+ output->priv->pref_height = 768;
+ }
+ }
+
+ output->priv->primary = gnome_rr_output_get_is_primary (rr_output);
+
+ g_ptr_array_add (a, output);
+ }
+
+ g_ptr_array_add (a, NULL);
+
+ config->priv->outputs = (GnomeRROutputInfo **)g_ptr_array_free (a, FALSE);
+
+ /* Walk the outputs computing the right-most edge of all
+ * lit-up displays
+ */
+ last_x = 0;
+ for (i = 0; config->priv->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutputInfo *output = config->priv->outputs[i];
+
+ if (output->priv->on)
+ {
+ last_x = MAX (last_x, output->priv->x + output->priv->width);
+ }
+ }
+
+ /* Now position all off displays to the right of the
+ * on displays
+ */
+ for (i = 0; config->priv->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutputInfo *output = config->priv->outputs[i];
+
+ if (output->priv->connected && !output->priv->on)
+ {
+ output->priv->x = last_x;
+ last_x = output->priv->x + output->priv->width;
+ }
+ }
+
+ g_assert (gnome_rr_config_match (config, config));
+
+ return TRUE;
+}
+
+gboolean
+gnome_rr_config_load_filename (GnomeRRConfig *result, const char *filename, GError **error)
+{
+ GnomeRRConfig *current;
+ GnomeRRConfig **configs;
+ gboolean found = FALSE;
+
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (result), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ current = gnome_rr_config_new_current (result->priv->screen, error);
+
+ configs = configurations_read_from_file (filename, error);
+
+ if (configs)
+ {
+ int i;
+
+ for (i = 0; configs[i] != NULL; ++i)
+ {
+ if (gnome_rr_config_match (configs[i], current))
+ {
+ int j;
+ GPtrArray *array;
+ result->priv->clone = configs[i]->priv->clone;
+
+ array = g_ptr_array_new ();
+ for (j = 0; configs[i]->priv->outputs[j] != NULL; j++) {
+ g_object_ref (configs[i]->priv->outputs[j]);
+ g_ptr_array_add (array, configs[i]->priv->outputs[j]);
+ }
+ g_ptr_array_add (array, NULL);
+ result->priv->outputs = (GnomeRROutputInfo **) g_ptr_array_free (array, FALSE);
+
+ found = TRUE;
+ break;
+ }
+ g_object_unref (configs[i]);
+ }
+ g_free (configs);
+
+ if (!found)
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_NO_MATCHING_CONFIG,
+ _("none of the saved display configurations matched the active configuration"));
+ }
+
+ g_object_unref (current);
+ return found;
+}
+
+static void
+gnome_rr_config_class_init (GnomeRRConfigClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GnomeRROutputInfoPrivate));
+
+ gobject_class->set_property = gnome_rr_config_set_property;
+ gobject_class->finalize = gnome_rr_config_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_SCREEN,
+ g_param_spec_object ("screen", "Screen", "The GnomeRRScreen this config applies to", GNOME_TYPE_RR_SCREEN,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
+}
+
+GnomeRRConfig *
+gnome_rr_config_new_current (GnomeRRScreen *screen, GError **error)
+{
+ GnomeRRConfig *self = g_object_new (GNOME_TYPE_RR_CONFIG, "screen", screen, NULL);
+
+ if (gnome_rr_config_load_current (self, error))
+ return self;
+ else
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+}
+
+GnomeRRConfig *
+gnome_rr_config_new_stored (GnomeRRScreen *screen, GError **error)
+{
+ GnomeRRConfig *self = g_object_new (GNOME_TYPE_RR_CONFIG, "screen", screen, NULL);
+ char *filename;
+ gboolean success;
+
+ filename = gnome_rr_config_get_intended_filename ();
+
+ success = gnome_rr_config_load_filename (self, filename, error);
+
+ g_free (filename);
+
+ if (success)
+ return self;
+ else
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+}
+
+static gboolean
+parse_file_gmarkup (const gchar *filename,
+ const GMarkupParser *parser,
+ gpointer data,
+ GError **err)
+{
+ GMarkupParseContext *context = NULL;
+ gchar *contents = NULL;
+ gboolean result = TRUE;
+ gsize 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 (GnomeRROutputInfo *output1, GnomeRROutputInfo *output2)
+{
+ g_assert (GNOME_IS_RR_OUTPUT_INFO (output1));
+ g_assert (GNOME_IS_RR_OUTPUT_INFO (output2));
+
+ if (strcmp (output1->priv->name, output2->priv->name) != 0)
+ return FALSE;
+
+ if (strcmp (output1->priv->vendor, output2->priv->vendor) != 0)
+ return FALSE;
+
+ if (output1->priv->product != output2->priv->product)
+ return FALSE;
+
+ if (output1->priv->serial != output2->priv->serial)
+ return FALSE;
+
+ if (output1->priv->connected != output2->priv->connected)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+output_equal (GnomeRROutputInfo *output1, GnomeRROutputInfo *output2)
+{
+ g_assert (GNOME_IS_RR_OUTPUT_INFO (output1));
+ g_assert (GNOME_IS_RR_OUTPUT_INFO (output2));
+
+ if (!output_match (output1, output2))
+ return FALSE;
+
+ if (output1->priv->on != output2->priv->on)
+ return FALSE;
+
+ if (output1->priv->on)
+ {
+ if (output1->priv->width != output2->priv->width)
+ return FALSE;
+
+ if (output1->priv->height != output2->priv->height)
+ return FALSE;
+
+ if (output1->priv->rate != output2->priv->rate)
+ return FALSE;
+
+ if (output1->priv->x != output2->priv->x)
+ return FALSE;
+
+ if (output1->priv->y != output2->priv->y)
+ return FALSE;
+
+ if (output1->priv->rotation != output2->priv->rotation)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GnomeRROutputInfo *
+find_output (GnomeRRConfig *config, const char *name)
+{
+ int i;
+
+ for (i = 0; config->priv->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutputInfo *output = config->priv->outputs[i];
+
+ if (strcmp (name, output->priv->name) == 0)
+ return output;
+ }
+
+ return NULL;
+}
+
+/* Match means "these configurations apply to the same hardware
+ * setups"
+ */
+gboolean
+gnome_rr_config_match (GnomeRRConfig *c1, GnomeRRConfig *c2)
+{
+ int i;
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (c1), FALSE);
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (c2), FALSE);
+
+ for (i = 0; c1->priv->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutputInfo *output1 = c1->priv->outputs[i];
+ GnomeRROutputInfo *output2;
+
+ output2 = find_output (c2, output1->priv->name);
+ if (!output2 || !output_match (output1, output2))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Equal means "the configurations will result in the same
+ * modes being set on the outputs"
+ */
+gboolean
+gnome_rr_config_equal (GnomeRRConfig *c1,
+ GnomeRRConfig *c2)
+{
+ int i;
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (c1), FALSE);
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (c2), FALSE);
+
+ for (i = 0; c1->priv->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutputInfo *output1 = c1->priv->outputs[i];
+ GnomeRROutputInfo *output2;
+
+ output2 = find_output (c2, output1->priv->name);
+ if (!output2 || !output_equal (output1, output2))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GnomeRROutputInfo **
+make_outputs (GnomeRRConfig *config)
+{
+ GPtrArray *outputs;
+ GnomeRROutputInfo *first_on;
+ int i;
+
+ outputs = g_ptr_array_new ();
+
+ first_on = NULL;
+
+ for (i = 0; config->priv->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutputInfo *old = config->priv->outputs[i];
+ GnomeRROutputInfo *new = g_object_new (GNOME_TYPE_RR_OUTPUT_INFO, NULL);
+ *(new->priv) = *(old->priv);
+ if (old->priv->name)
+ new->priv->name = g_strdup (old->priv->name);
+ if (old->priv->display_name)
+ new->priv->display_name = g_strdup (old->priv->display_name);
+
+ if (old->priv->on && !first_on)
+ first_on = old;
+
+ if (config->priv->clone && new->priv->on)
+ {
+ g_assert (first_on);
+
+ new->priv->width = first_on->priv->width;
+ new->priv->height = first_on->priv->height;
+ new->priv->rotation = first_on->priv->rotation;
+ new->priv->x = 0;
+ new->priv->y = 0;
+ }
+
+ g_ptr_array_add (outputs, new);
+ }
+
+ g_ptr_array_add (outputs, NULL);
+
+ return (GnomeRROutputInfo **)g_ptr_array_free (outputs, FALSE);
+}
+
+gboolean
+gnome_rr_config_applicable (GnomeRRConfig *configuration,
+ GnomeRRScreen *screen,
+ GError **error)
+{
+ GnomeRROutputInfo **outputs;
+ CrtcAssignment *assign;
+ gboolean result;
+ int i;
+
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (configuration), FALSE);
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ outputs = make_outputs (configuration);
+ assign = crtc_assignment_new (screen, outputs, error);
+
+ if (assign)
+ {
+ result = TRUE;
+ crtc_assignment_free (assign);
+ }
+ else
+ {
+ result = FALSE;
+ }
+
+ for (i = 0; outputs[i] != NULL; i++) {
+ g_object_unref (outputs[i]);
+ }
+
+ return result;
+}
+
+/* Database management */
+
+static void
+ensure_config_directory (void)
+{
+ g_mkdir_with_parents (g_get_user_config_dir (), 0700);
+}
+
+char *
+gnome_rr_config_get_backup_filename (void)
+{
+ ensure_config_directory ();
+ return g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
+}
+
+char *
+gnome_rr_config_get_intended_filename (void)
+{
+ ensure_config_directory ();
+ return g_build_filename (g_get_user_config_dir (), CONFIG_INTENDED_BASENAME, NULL);
+}
+
+static const char *
+get_rotation_name (GnomeRRRotation r)
+{
+ if (r & GNOME_RR_ROTATION_0)
+ return "normal";
+ if (r & GNOME_RR_ROTATION_90)
+ return "left";
+ if (r & GNOME_RR_ROTATION_180)
+ return "upside_down";
+ if (r & GNOME_RR_ROTATION_270)
+ return "right";
+
+ return "normal";
+}
+
+static const char *
+yes_no (int x)
+{
+ return x? "yes" : "no";
+}
+
+static const char *
+get_reflect_x (GnomeRRRotation r)
+{
+ return yes_no (r & GNOME_RR_REFLECT_X);
+}
+
+static const char *
+get_reflect_y (GnomeRRRotation r)
+{
+ return yes_no (r & GNOME_RR_REFLECT_Y);
+}
+
+static void
+emit_configuration (GnomeRRConfig *config,
+ GString *string)
+{
+ int j;
+
+ g_string_append_printf (string, " <configuration>\n");
+
+ g_string_append_printf (string, " <clone>%s</clone>\n", yes_no (config->priv->clone));
+
+ for (j = 0; config->priv->outputs[j] != NULL; ++j)
+ {
+ GnomeRROutputInfo *output = config->priv->outputs[j];
+
+ g_string_append_printf (
+ string, " <output name=\"%s\">\n", output->priv->name);
+
+ if (output->priv->connected && *output->priv->vendor != '\0')
+ {
+ g_string_append_printf (
+ string, " <vendor>%s</vendor>\n", output->priv->vendor);
+ g_string_append_printf (
+ string, " <product>0x%04x</product>\n", output->priv->product);
+ g_string_append_printf (
+ string, " <serial>0x%08x</serial>\n", output->priv->serial);
+ }
+
+ /* An unconnected output which is on does not make sense */
+ if (output->priv->connected && output->priv->on)
+ {
+ g_string_append_printf (
+ string, " <width>%d</width>\n", output->priv->width);
+ g_string_append_printf (
+ string, " <height>%d</height>\n", output->priv->height);
+ g_string_append_printf (
+ string, " <rate>%d</rate>\n", output->priv->rate);
+ g_string_append_printf (
+ string, " <x>%d</x>\n", output->priv->x);
+ g_string_append_printf (
+ string, " <y>%d</y>\n", output->priv->y);
+ g_string_append_printf (
+ string, " <rotation>%s</rotation>\n", get_rotation_name (output->priv->rotation));
+ g_string_append_printf (
+ string, " <reflect_x>%s</reflect_x>\n", get_reflect_x (output->priv->rotation));
+ g_string_append_printf (
+ string, " <reflect_y>%s</reflect_y>\n", get_reflect_y (output->priv->rotation));
+ g_string_append_printf (
+ string, " <primary>%s</primary>\n", yes_no (output->priv->primary));
+ }
+
+ g_string_append_printf (string, " </output>\n");
+ }
+
+ g_string_append_printf (string, " </configuration>\n");
+}
+
+void
+gnome_rr_config_sanitize (GnomeRRConfig *config)
+{
+ int i;
+ int x_offset, y_offset;
+ gboolean found;
+
+ /* Offset everything by the top/left-most coordinate to
+ * make sure the configuration starts at (0, 0)
+ */
+ x_offset = y_offset = G_MAXINT;
+ for (i = 0; config->priv->outputs[i]; ++i)
+ {
+ GnomeRROutputInfo *output = config->priv->outputs[i];
+
+ if (output->priv->on)
+ {
+ x_offset = MIN (x_offset, output->priv->x);
+ y_offset = MIN (y_offset, output->priv->y);
+ }
+ }
+
+ for (i = 0; config->priv->outputs[i]; ++i)
+ {
+ GnomeRROutputInfo *output = config->priv->outputs[i];
+
+ if (output->priv->on)
+ {
+ output->priv->x -= x_offset;
+ output->priv->y -= y_offset;
+ }
+ }
+
+ /* Only one primary, please */
+ found = FALSE;
+ for (i = 0; config->priv->outputs[i]; ++i)
+ {
+ if (config->priv->outputs[i]->priv->primary)
+ {
+ if (found)
+ {
+ config->priv->outputs[i]->priv->primary = FALSE;
+ }
+ else
+ {
+ found = TRUE;
+ }
+ }
+ }
+}
+
+static gboolean
+output_info_is_laptop (GnomeRROutputInfo *info)
+{
+ if (info->priv->name
+ && (strstr (info->priv->name, "lvds") || /* Most drivers use an "LVDS" prefix... */
+ strstr (info->priv->name, "LVDS") ||
+ strstr (info->priv->name, "Lvds") ||
+ strstr (info->priv->name, "LCD"))) /* ... but fglrx uses "LCD" in some versions. Shoot me now, kthxbye. */
+ return TRUE;
+
+ return FALSE;
+}
+
+gboolean
+gnome_rr_config_ensure_primary (GnomeRRConfig *configuration)
+{
+ int i;
+ GnomeRROutputInfo *laptop;
+ GnomeRROutputInfo *top_left;
+ gboolean found;
+ GnomeRRConfigPrivate *priv;
+
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (configuration), FALSE);
+
+ laptop = NULL;
+ top_left = NULL;
+ found = FALSE;
+ priv = configuration->priv;
+
+ for (i = 0; priv->outputs[i] != NULL; ++i) {
+ GnomeRROutputInfo *info = priv->outputs[i];
+
+ if (!info->priv->on) {
+ info->priv->primary = FALSE;
+ continue;
+ }
+
+ /* ensure only one */
+ if (info->priv->primary) {
+ if (found) {
+ info->priv->primary = FALSE;
+ } else {
+ found = TRUE;
+ }
+ }
+
+ if (top_left == NULL
+ || (info->priv->x < top_left->priv->x
+ && info->priv->y < top_left->priv->y)) {
+ top_left = info;
+ }
+ if (laptop == NULL
+ && output_info_is_laptop (info)) {
+ /* shame we can't find the connector type
+ as with gnome_rr_output_is_laptop */
+ laptop = info;
+ }
+ }
+
+ if (!found) {
+ if (laptop != NULL) {
+ laptop->priv->primary = TRUE;
+ } else {
+ top_left->priv->primary = TRUE;
+ }
+ }
+
+ return !found;
+}
+
+gboolean
+gnome_rr_config_save (GnomeRRConfig *configuration, GError **error)
+{
+ GnomeRRConfig **configurations;
+ GString *output;
+ int i;
+ gchar *intended_filename;
+ gchar *backup_filename;
+ gboolean result;
+
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (configuration), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ output = g_string_new ("");
+
+ backup_filename = gnome_rr_config_get_backup_filename ();
+ intended_filename = gnome_rr_config_get_intended_filename ();
+
+ configurations = configurations_read_from_file (intended_filename, NULL); /* NULL-GError */
+
+ g_string_append_printf (output, "<monitors version=\"1\">\n");
+
+ if (configurations)
+ {
+ for (i = 0; configurations[i] != NULL; ++i)
+ {
+ if (!gnome_rr_config_match (configurations[i], configuration))
+ emit_configuration (configurations[i], output);
+ g_object_unref (configurations[i]);
+ }
+
+ g_free (configurations);
+ }
+
+ emit_configuration (configuration, output);
+
+ g_string_append_printf (output, "</monitors>\n");
+
+ /* backup the file first */
+ rename (intended_filename, backup_filename); /* no error checking because the intended file may not even exist */
+
+ result = g_file_set_contents (intended_filename, output->str, -1, error);
+
+ if (!result)
+ rename (backup_filename, intended_filename); /* no error checking because the backup may not even exist */
+
+ g_free (backup_filename);
+ g_free (intended_filename);
+
+ return result;
+}
+
+gboolean
+gnome_rr_config_apply_with_time (GnomeRRConfig *config,
+ GnomeRRScreen *screen,
+ guint32 timestamp,
+ GError **error)
+{
+ CrtcAssignment *assignment;
+ GnomeRROutputInfo **outputs;
+ gboolean result = FALSE;
+ int i;
+
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (config), FALSE);
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), FALSE);
+
+ outputs = make_outputs (config);
+
+ assignment = crtc_assignment_new (screen, outputs, error);
+
+ for (i = 0; outputs[i] != NULL; i++)
+ g_object_unref (outputs[i]);
+ g_free (outputs);
+
+ if (assignment)
+ {
+ if (crtc_assignment_apply (assignment, timestamp, error))
+ result = TRUE;
+
+ crtc_assignment_free (assignment);
+
+ gdk_flush ();
+ }
+
+ return result;
+}
+
+/* gnome_rr_config_apply_from_filename_with_time:
+ * @screen: A #GnomeRRScreen
+ * @filename: Path of the file to look in for stored RANDR configurations.
+ * @timestamp: X server timestamp from the event that causes the screen configuration to change (a user's button press, for example)
+ * @error: Location to store error, or %NULL
+ *
+ * First, this function refreshes the @screen to match the current RANDR
+ * configuration from the X server. Then, it tries to load the file in
+ * @filename and looks for suitable matching RANDR configurations in the file;
+ * if one is found, that configuration will be applied to the current set of
+ * RANDR outputs.
+ *
+ * Typically, @filename is the result of gnome_rr_config_get_intended_filename() or
+ * gnome_rr_config_get_backup_filename().
+ *
+ * Returns: TRUE if the RANDR configuration was loaded and applied from
+ * $(XDG_CONFIG_HOME)/monitors.xml, or FALSE otherwise:
+ *
+ * If the current RANDR configuration could not be refreshed, the @error will
+ * have a domain of #GNOME_RR_ERROR and a corresponding error code.
+ *
+ * If the file in question is loaded successfully but the configuration cannot
+ * be applied, the @error will have a domain of #GNOME_RR_ERROR. Note that an
+ * error code of #GNOME_RR_ERROR_NO_MATCHING_CONFIG is not a real error; it
+ * simply means that there were no stored configurations that match the current
+ * set of RANDR outputs.
+ *
+ * If the file in question cannot be loaded, the @error will have a domain of
+ * #G_FILE_ERROR. Note that an error code of G_FILE_ERROR_NOENT is not really
+ * an error, either; it means that there was no stored configuration file and so
+ * nothing is changed.
+ */
+gboolean
+gnome_rr_config_apply_from_filename_with_time (GnomeRRScreen *screen, const char *filename, guint32 timestamp, GError **error)
+{
+ GnomeRRConfig *stored;
+ GError *my_error;
+
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ my_error = NULL;
+ if (!gnome_rr_screen_refresh (screen, &my_error)) {
+ if (my_error) {
+ g_propagate_error (error, my_error);
+ return FALSE; /* This is a genuine error */
+ }
+
+ /* This means the screen didn't change, so just proceed */
+ }
+
+ stored = g_object_new (GNOME_TYPE_RR_CONFIG, "screen", screen, NULL);
+
+ if (gnome_rr_config_load_filename (stored, filename, error))
+ {
+ gboolean result;
+
+ gnome_rr_config_ensure_primary (stored);
+ result = gnome_rr_config_apply_with_time (stored, screen, timestamp, error);
+
+ g_object_unref (stored);
+ return result;
+ }
+ else
+ {
+ g_object_unref (stored);
+ return FALSE;
+ }
+}
+
+/**
+ * gnome_rr_config_get_outputs:
+ *
+ * Returns: (array zero-terminated=1) (element-type GnomeDesktop.RROutputInfo) (transfer none): the output configuration for this #GnomeRRConfig
+ */
+GnomeRROutputInfo **
+gnome_rr_config_get_outputs (GnomeRRConfig *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (self), NULL);
+
+ return self->priv->outputs;
+}
+
+/**
+ * gnome_rr_config_get_clone:
+ *
+ * Returns: whether at least two outputs are at (0, 0) offset and they
+ * have the same width/height. Those outputs are of course connected and on
+ * (i.e. they have a CRTC assigned).
+ */
+gboolean
+gnome_rr_config_get_clone (GnomeRRConfig *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_CONFIG (self), FALSE);
+
+ return self->priv->clone;
+}
+
+void
+gnome_rr_config_set_clone (GnomeRRConfig *self, gboolean clone)
+{
+ g_return_if_fail (GNOME_IS_RR_CONFIG (self));
+
+ self->priv->clone = clone;
+}
+
+/*
+ * CRTC assignment
+ */
+typedef struct CrtcInfo CrtcInfo;
+
+struct CrtcInfo
+{
+ GnomeRRMode *mode;
+ int x;
+ int y;
+ GnomeRRRotation rotation;
+ GPtrArray *outputs;
+};
+
+struct CrtcAssignment
+{
+ GnomeRRScreen *screen;
+ GHashTable *info;
+ GnomeRROutput *primary;
+};
+
+static gboolean
+can_clone (CrtcInfo *info,
+ GnomeRROutput *output)
+{
+ int i;
+
+ for (i = 0; i < info->outputs->len; ++i)
+ {
+ GnomeRROutput *clone = info->outputs->pdata[i];
+
+ if (!gnome_rr_output_can_clone (clone, output))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment *assign,
+ GnomeRRCrtc *crtc,
+ GnomeRRMode *mode,
+ int x,
+ int y,
+ GnomeRRRotation rotation,
+ gboolean primary,
+ GnomeRROutput *output,
+ GError **error)
+{
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+ guint32 crtc_id;
+ const char *output_name;
+
+ crtc_id = gnome_rr_crtc_get_id (crtc);
+ output_name = gnome_rr_output_get_name (output);
+
+ if (!gnome_rr_crtc_can_drive_output (crtc, output))
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("CRTC %d cannot drive output %s"), crtc_id, output_name);
+ return FALSE;
+ }
+
+ if (!gnome_rr_output_supports_mode (output, mode))
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("output %s does not support mode %dx%d@%dHz"),
+ output_name,
+ gnome_rr_mode_get_width (mode),
+ gnome_rr_mode_get_height (mode),
+ gnome_rr_mode_get_freq (mode));
+ return FALSE;
+ }
+
+ if (!gnome_rr_crtc_supports_rotation (crtc, rotation))
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("CRTC %d does not support rotation=%s"),
+ crtc_id,
+ get_rotation_name (rotation));
+ return FALSE;
+ }
+
+ if (info)
+ {
+ if (!(info->mode == mode &&
+ info->x == x &&
+ info->y == y &&
+ info->rotation == rotation))
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("output %s does not have the same parameters as another cloned output:\n"
+ "existing mode = %d, new mode = %d\n"
+ "existing coordinates = (%d, %d), new coordinates = (%d, %d)\n"
+ "existing rotation = %s, new rotation = %s"),
+ output_name,
+ gnome_rr_mode_get_id (info->mode), gnome_rr_mode_get_id (mode),
+ info->x, info->y,
+ x, y,
+ get_rotation_name (info->rotation), get_rotation_name (rotation));
+ return FALSE;
+ }
+
+ if (!can_clone (info, output))
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("cannot clone to output %s"),
+ output_name);
+ return FALSE;
+ }
+
+ g_ptr_array_add (info->outputs, output);
+
+ if (primary && !assign->primary)
+ {
+ assign->primary = output;
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ CrtcInfo *info = g_new0 (CrtcInfo, 1);
+
+ info->mode = mode;
+ info->x = x;
+ info->y = y;
+ info->rotation = rotation;
+ info->outputs = g_ptr_array_new ();
+
+ g_ptr_array_add (info->outputs, output);
+
+ g_hash_table_insert (assign->info, crtc, info);
+
+ if (primary && !assign->primary)
+ {
+ assign->primary = output;
+ }
+
+ return TRUE;
+ }
+}
+
+static void
+crtc_assignment_unassign (CrtcAssignment *assign,
+ GnomeRRCrtc *crtc,
+ GnomeRROutput *output)
+{
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+
+ if (info)
+ {
+ g_ptr_array_remove (info->outputs, output);
+
+ if (assign->primary == output)
+ {
+ assign->primary = NULL;
+ }
+
+ if (info->outputs->len == 0)
+ g_hash_table_remove (assign->info, crtc);
+ }
+}
+
+static void
+crtc_assignment_free (CrtcAssignment *assign)
+{
+ g_hash_table_destroy (assign->info);
+
+ g_free (assign);
+}
+
+typedef struct {
+ guint32 timestamp;
+ gboolean has_error;
+ GError **error;
+} ConfigureCrtcState;
+
+static void
+configure_crtc (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GnomeRRCrtc *crtc = key;
+ CrtcInfo *info = value;
+ ConfigureCrtcState *state = data;
+
+ if (state->has_error)
+ return;
+
+ if (!gnome_rr_crtc_set_config_with_time (crtc,
+ state->timestamp,
+ info->x, info->y,
+ info->mode,
+ info->rotation,
+ (GnomeRROutput **)info->outputs->pdata,
+ info->outputs->len,
+ state->error))
+ state->has_error = TRUE;
+}
+
+static gboolean
+mode_is_rotated (CrtcInfo *info)
+{
+ if ((info->rotation & GNOME_RR_ROTATION_270) ||
+ (info->rotation & GNOME_RR_ROTATION_90))
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+crtc_is_rotated (GnomeRRCrtc *crtc)
+{
+ GnomeRRRotation r = gnome_rr_crtc_get_current_rotation (crtc);
+
+ if ((r & GNOME_RR_ROTATION_270) ||
+ (r & GNOME_RR_ROTATION_90))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+accumulate_error (GString *accumulated_error, GError *error)
+{
+ g_string_append_printf (accumulated_error, " %s\n", error->message);
+ g_error_free (error);
+}
+
+/* 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 (GnomeRRScreen *screen,
+ GnomeRROutputInfo **outputs,
+ CrtcAssignment *assignment,
+ GError **error)
+{
+ GnomeRRCrtc **crtcs = gnome_rr_screen_list_crtcs (screen);
+ GnomeRROutputInfo *output;
+ int i;
+ gboolean tried_mode;
+ GError *my_error;
+ GString *accumulated_error;
+ gboolean success;
+
+ output = *outputs;
+ if (!output)
+ return TRUE;
+
+ /* It is always allowed for an output to be turned off */
+ if (!output->priv->on)
+ {
+ return real_assign_crtcs (screen, outputs + 1, assignment, error);
+ }
+
+ success = FALSE;
+ tried_mode = FALSE;
+ accumulated_error = g_string_new (NULL);
+
+ for (i = 0; crtcs[i] != NULL; ++i)
+ {
+ GnomeRRCrtc *crtc = crtcs[i];
+ int crtc_id = gnome_rr_crtc_get_id (crtc);
+ int pass;
+
+ g_string_append_printf (accumulated_error,
+ _("Trying modes for CRTC %d\n"),
+ crtc_id);
+
+ /* Make two passes, one where frequencies must match, then
+ * one where they don't have to
+ */
+ for (pass = 0; pass < 2; ++pass)
+ {
+ GnomeRROutput *gnome_rr_output = gnome_rr_screen_get_output_by_name (screen, output->priv->name);
+ GnomeRRMode **modes = gnome_rr_output_list_modes (gnome_rr_output);
+ int j;
+
+ for (j = 0; modes[j] != NULL; ++j)
+ {
+ GnomeRRMode *mode = modes[j];
+ int mode_width;
+ int mode_height;
+ int mode_freq;
+
+ mode_width = gnome_rr_mode_get_width (mode);
+ mode_height = gnome_rr_mode_get_height (mode);
+ mode_freq = gnome_rr_mode_get_freq (mode);
+
+ g_string_append_printf (accumulated_error,
+ _("CRTC %d: trying mode %dx%d@%dHz with output at %dx%d@%dHz (pass %d)\n"),
+ crtc_id,
+ mode_width, mode_height, mode_freq,
+ output->priv->width, output->priv->height, output->priv->rate,
+ pass);
+
+ if (mode_width == output->priv->width &&
+ mode_height == output->priv->height &&
+ (pass == 1 || mode_freq == output->priv->rate))
+ {
+ tried_mode = TRUE;
+
+ my_error = NULL;
+ if (crtc_assignment_assign (
+ assignment, crtc, modes[j],
+ output->priv->x, output->priv->y,
+ output->priv->rotation,
+ output->priv->primary,
+ gnome_rr_output,
+ &my_error))
+ {
+ my_error = NULL;
+ if (real_assign_crtcs (screen, outputs + 1, assignment, &my_error)) {
+ success = TRUE;
+ goto out;
+ } else
+ accumulate_error (accumulated_error, my_error);
+
+ crtc_assignment_unassign (assignment, crtc, gnome_rr_output);
+ } else
+ accumulate_error (accumulated_error, my_error);
+ }
+ }
+ }
+ }
+
+out:
+
+ if (success)
+ g_string_free (accumulated_error, TRUE);
+ else {
+ char *str;
+
+ str = g_string_free (accumulated_error, FALSE);
+
+ if (tried_mode)
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("could not assign CRTCs to outputs:\n%s"),
+ str);
+ else
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_CRTC_ASSIGNMENT,
+ _("none of the selected modes were compatible with the possible modes:\n%s"),
+ str);
+
+ g_free (str);
+ }
+
+ return success;
+}
+
+static void
+crtc_info_free (CrtcInfo *info)
+{
+ g_ptr_array_free (info->outputs, TRUE);
+ g_free (info);
+}
+
+static void
+get_required_virtual_size (CrtcAssignment *assign, int *width, int *height)
+{
+ GList *active_crtcs = g_hash_table_get_keys (assign->info);
+ GList *list;
+ int d;
+
+ if (!width)
+ width = &d;
+ if (!height)
+ height = &d;
+
+ /* Compute size of the screen */
+ *width = *height = 1;
+ for (list = active_crtcs; list != NULL; list = list->next)
+ {
+ GnomeRRCrtc *crtc = list->data;
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+ int w, h;
+
+ w = gnome_rr_mode_get_width (info->mode);
+ h = gnome_rr_mode_get_height (info->mode);
+
+ if (mode_is_rotated (info))
+ {
+ int tmp = h;
+ h = w;
+ w = tmp;
+ }
+
+ *width = MAX (*width, info->x + w);
+ *height = MAX (*height, info->y + h);
+ }
+
+ g_list_free (active_crtcs);
+}
+
+static CrtcAssignment *
+crtc_assignment_new (GnomeRRScreen *screen, GnomeRROutputInfo **outputs, GError **error)
+{
+ CrtcAssignment *assignment = g_new0 (CrtcAssignment, 1);
+
+ assignment->info = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal, NULL, (GFreeFunc)crtc_info_free);
+
+ if (real_assign_crtcs (screen, outputs, assignment, error))
+ {
+ int width, height;
+ int min_width, max_width, min_height, max_height;
+ int required_pixels, min_pixels, max_pixels;
+
+ get_required_virtual_size (assignment, &width, &height);
+
+ gnome_rr_screen_get_ranges (
+ screen, &min_width, &max_width, &min_height, &max_height);
+
+ required_pixels = width * height;
+ min_pixels = min_width * min_height;
+ max_pixels = max_width * max_height;
+
+ if (required_pixels < min_pixels || required_pixels > max_pixels)
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_BOUNDS_ERROR,
+ /* Translators: the "requested", "minimum", and
+ * "maximum" words here are not keywords; please
+ * translate them as usual. */
+ _("required virtual size does not fit available size: "
+ "requested=(%d, %d), minimum=(%d, %d), maximum=(%d, %d)"),
+ width, height,
+ min_width, min_height,
+ max_width, max_height);
+ goto fail;
+ }
+
+ assignment->screen = screen;
+
+ return assignment;
+ }
+
+fail:
+ crtc_assignment_free (assignment);
+
+ return NULL;
+}
+
+static gboolean
+crtc_assignment_apply (CrtcAssignment *assign, guint32 timestamp, GError **error)
+{
+ GnomeRRCrtc **all_crtcs = gnome_rr_screen_list_crtcs (assign->screen);
+ int width, height;
+ int i;
+ int min_width, max_width, min_height, max_height;
+ int width_mm, height_mm;
+ gboolean success = TRUE;
+
+ /* Compute size of the screen */
+ get_required_virtual_size (assign, &width, &height);
+
+ gnome_rr_screen_get_ranges (
+ assign->screen, &min_width, &max_width, &min_height, &max_height);
+
+ /* We should never get here if the dimensions don't fit in the virtual size,
+ * but just in case we do, fix it up.
+ */
+ width = MAX (min_width, width);
+ width = MIN (max_width, width);
+ height = MAX (min_height, height);
+ height = MIN (max_height, height);
+
+ /* FMQ: do we need to check the sizes instead of clamping them? */
+
+ /* Grab the server while we fiddle with the CRTCs and the screen, so that
+ * apps that listen for RANDR notifications will only receive the final
+ * status.
+ */
+
+ gdk_x11_display_grab (gdk_screen_get_display (assign->screen->priv->gdk_screen));
+
+ /* Turn off all crtcs that are currently displaying outside the new screen,
+ * or are not used in the new setup
+ */
+ for (i = 0; all_crtcs[i] != NULL; ++i)
+ {
+ GnomeRRCrtc *crtc = all_crtcs[i];
+ GnomeRRMode *mode = gnome_rr_crtc_get_current_mode (crtc);
+ int x, y;
+
+ if (mode)
+ {
+ int w, h;
+ gnome_rr_crtc_get_position (crtc, &x, &y);
+
+ w = gnome_rr_mode_get_width (mode);
+ h = gnome_rr_mode_get_height (mode);
+
+ if (crtc_is_rotated (crtc))
+ {
+ int tmp = h;
+ h = w;
+ w = tmp;
+ }
+
+ if (x + w > width || y + h > height || !g_hash_table_lookup (assign->info, crtc))
+ {
+ if (!gnome_rr_crtc_set_config_with_time (crtc, timestamp, 0, 0, NULL, GNOME_RR_ROTATION_0, NULL, 0, error))
+ {
+ success = FALSE;
+ break;
+ }
+
+ }
+ }
+ }
+
+ /* The 'physical size' of an X screen is meaningless if that screen
+ * can consist of many monitors. So just pick a size that make the
+ * dpi 96.
+ *
+ * Firefox and Evince apparently believe what X tells them.
+ */
+ width_mm = (width / 96.0) * 25.4 + 0.5;
+ height_mm = (height / 96.0) * 25.4 + 0.5;
+
+ if (success)
+ {
+ ConfigureCrtcState state;
+
+ gnome_rr_screen_set_size (assign->screen, width, height, width_mm, height_mm);
+
+ state.timestamp = timestamp;
+ state.has_error = FALSE;
+ state.error = error;
+
+ g_hash_table_foreach (assign->info, configure_crtc, &state);
+
+ success = !state.has_error;
+ }
+
+ gnome_rr_screen_set_primary_output (assign->screen, assign->primary);
+
+ gdk_x11_display_ungrab (gdk_screen_get_display (assign->screen->priv->gdk_screen));
+
+ return success;
+}
diff --git a/gtk/display/gnome-rr-config.h b/gtk/display/gnome-rr-config.h
new file mode 100644
index 0000000..896d47c
--- /dev/null
+++ b/gtk/display/gnome-rr-config.h
@@ -0,0 +1,150 @@
+/* gnome-rr-config.h
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. 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>
+ */
+#ifndef GNOME_RR_CONFIG_H
+#define GNOME_RR_CONFIG_H
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error gnome-rr-config.h is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnome-rr-config.h
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gnome-rr.h"
+
+typedef struct GnomeRROutputInfoPrivate GnomeRROutputInfoPrivate;
+typedef struct GnomeRRConfigPrivate GnomeRRConfigPrivate;
+
+typedef struct
+{
+ GObject parent;
+
+ /*< private >*/
+ GnomeRROutputInfoPrivate *priv;
+} GnomeRROutputInfo;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} GnomeRROutputInfoClass;
+
+#define GNOME_TYPE_RR_OUTPUT_INFO (gnome_rr_output_info_get_type())
+#define GNOME_RR_OUTPUT_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_RR_OUTPUT_INFO, GnomeRROutputInfo))
+#define GNOME_IS_RR_OUTPUT_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_RR_OUTPUT_INFO))
+#define GNOME_RR_OUTPUT_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_RR_OUTPUT_INFO, GnomeRROutputInfoClass))
+#define GNOME_IS_RR_OUTPUT_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_RR_OUTPUT_INFO))
+#define GNOME_RR_OUTPUT_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_RR_OUTPUT_INFO, GnomeRROutputInfoClass))
+
+GType gnome_rr_output_info_get_type (void);
+
+char *gnome_rr_output_info_get_name (GnomeRROutputInfo *self);
+
+gboolean gnome_rr_output_info_is_active (GnomeRROutputInfo *self);
+void gnome_rr_output_info_set_active (GnomeRROutputInfo *self, gboolean active);
+
+void gnome_rr_output_info_get_geometry (GnomeRROutputInfo *self, int *x, int *y, int *width, int *height);
+void gnome_rr_output_info_set_geometry (GnomeRROutputInfo *self, int x, int y, int width, int height);
+
+int gnome_rr_output_info_get_refresh_rate (GnomeRROutputInfo *self);
+void gnome_rr_output_info_set_refresh_rate (GnomeRROutputInfo *self, int rate);
+
+GnomeRRRotation gnome_rr_output_info_get_rotation (GnomeRROutputInfo *self);
+void gnome_rr_output_info_set_rotation (GnomeRROutputInfo *self, GnomeRRRotation rotation);
+
+gboolean gnome_rr_output_info_is_connected (GnomeRROutputInfo *self);
+void gnome_rr_output_info_get_vendor (GnomeRROutputInfo *self, gchar* vendor);
+guint gnome_rr_output_info_get_product (GnomeRROutputInfo *self);
+guint gnome_rr_output_info_get_serial (GnomeRROutputInfo *self);
+double gnome_rr_output_info_get_aspect_ratio (GnomeRROutputInfo *self);
+char *gnome_rr_output_info_get_display_name (GnomeRROutputInfo *self);
+
+gboolean gnome_rr_output_info_get_primary (GnomeRROutputInfo *self);
+void gnome_rr_output_info_set_primary (GnomeRROutputInfo *self, gboolean primary);
+
+int gnome_rr_output_info_get_preferred_width (GnomeRROutputInfo *self);
+int gnome_rr_output_info_get_preferred_height (GnomeRROutputInfo *self);
+
+typedef struct
+{
+ GObject parent;
+
+ /*< private >*/
+ GnomeRRConfigPrivate *priv;
+} GnomeRRConfig;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} GnomeRRConfigClass;
+
+#define GNOME_TYPE_RR_CONFIG (gnome_rr_config_get_type())
+#define GNOME_RR_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_RR_CONFIG, GnomeRRConfig))
+#define GNOME_IS_RR_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_RR_CONFIG))
+#define GNOME_RR_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_RR_CONFIG, GnomeRRConfigClass))
+#define GNOME_IS_RR_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_RR_CONFIG))
+#define GNOME_RR_CONFIG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_RR_CONFIG, GnomeRRConfigClass))
+
+GType gnome_rr_config_get_type (void);
+
+GnomeRRConfig *gnome_rr_config_new_current (GnomeRRScreen *screen,
+ GError **error);
+GnomeRRConfig *gnome_rr_config_new_stored (GnomeRRScreen *screen,
+ GError **error);
+gboolean gnome_rr_config_load_current (GnomeRRConfig *self,
+ GError **error);
+gboolean gnome_rr_config_load_filename (GnomeRRConfig *self,
+ const gchar *filename,
+ GError **error);
+gboolean gnome_rr_config_match (GnomeRRConfig *config1,
+ GnomeRRConfig *config2);
+gboolean gnome_rr_config_equal (GnomeRRConfig *config1,
+ GnomeRRConfig *config2);
+gboolean gnome_rr_config_save (GnomeRRConfig *configuration,
+ GError **error);
+void gnome_rr_config_sanitize (GnomeRRConfig *configuration);
+gboolean gnome_rr_config_ensure_primary (GnomeRRConfig *configuration);
+
+gboolean gnome_rr_config_apply_with_time (GnomeRRConfig *configuration,
+ GnomeRRScreen *screen,
+ guint32 timestamp,
+ GError **error);
+
+gboolean gnome_rr_config_apply_from_filename_with_time (GnomeRRScreen *screen,
+ const char *filename,
+ guint32 timestamp,
+ GError **error);
+
+gboolean gnome_rr_config_applicable (GnomeRRConfig *configuration,
+ GnomeRRScreen *screen,
+ GError **error);
+
+gboolean gnome_rr_config_get_clone (GnomeRRConfig *configuration);
+void gnome_rr_config_set_clone (GnomeRRConfig *configuration, gboolean clone);
+GnomeRROutputInfo **gnome_rr_config_get_outputs (GnomeRRConfig *configuration);
+
+char *gnome_rr_config_get_backup_filename (void);
+char *gnome_rr_config_get_intended_filename (void);
+
+#endif
diff --git a/gtk/display/gnome-rr-output-info.c b/gtk/display/gnome-rr-output-info.c
new file mode 100644
index 0000000..8936eed
--- /dev/null
+++ b/gtk/display/gnome-rr-output-info.c
@@ -0,0 +1,246 @@
+/* gnome-rr-output-info.c
+ * -*- c-basic-offset: 4 -*-
+ *
+ * Copyright 2010 Giovanni Campagna
+ *
+ * This file is part of the Gnome Desktop Library.
+ *
+ * The Gnome Desktop Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Desktop Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include <config.h>
+
+#include "gnome-rr-config.h"
+
+#include "edid.h"
+#include "gnome-rr-private.h"
+
+G_DEFINE_TYPE (GnomeRROutputInfo, gnome_rr_output_info, G_TYPE_OBJECT)
+
+static void
+gnome_rr_output_info_init (GnomeRROutputInfo *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GNOME_TYPE_RR_OUTPUT_INFO, GnomeRROutputInfoPrivate);
+
+ self->priv->name = NULL;
+ self->priv->on = FALSE;
+ self->priv->display_name = NULL;
+}
+
+static void
+gnome_rr_output_info_finalize (GObject *gobject)
+{
+ GnomeRROutputInfo *self = GNOME_RR_OUTPUT_INFO (gobject);
+
+ g_free (self->priv->name);
+ g_free (self->priv->display_name);
+
+ G_OBJECT_CLASS (gnome_rr_output_info_parent_class)->finalize (gobject);
+}
+
+static void
+gnome_rr_output_info_class_init (GnomeRROutputInfoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GnomeRROutputInfoPrivate));
+
+ gobject_class->finalize = gnome_rr_output_info_finalize;
+}
+
+/**
+ * gnome_rr_output_info_get_name:
+ *
+ * Returns: (transfer none): the output name
+ */
+char *gnome_rr_output_info_get_name (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), NULL);
+
+ return self->priv->name;
+}
+
+/**
+ * gnome_rr_output_info_is_active:
+ *
+ * Returns: whether there is a CRTC assigned to this output (i.e. a signal is being sent to it)
+ */
+gboolean gnome_rr_output_info_is_active (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), FALSE);
+
+ return self->priv->on;
+}
+
+void gnome_rr_output_info_set_active (GnomeRROutputInfo *self, gboolean active)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+
+ self->priv->on = active;
+}
+
+/**
+ * gnome_rr_output_info_get_geometry:
+ *
+ * @self: a #GnomeRROutputInfo
+ * @x: (out) (allow-none):
+ * @y: (out) (allow-none):
+ * @width: (out) (allow-none):
+ * @height: (out) (allow-none):
+ */
+void gnome_rr_output_info_get_geometry (GnomeRROutputInfo *self, int *x, int *y, int *width, int *height)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+
+ if (x)
+ *x = self->priv->x;
+ if (y)
+ *y = self->priv->y;
+ if (width)
+ *width = self->priv->width;
+ if (height)
+ *height = self->priv->height;
+}
+
+void gnome_rr_output_info_set_geometry (GnomeRROutputInfo *self, int x, int y, int width, int height)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+
+ self->priv->x = x;
+ self->priv->y = y;
+ self->priv->width = width;
+ self->priv->height = height;
+}
+
+int gnome_rr_output_info_get_refresh_rate (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), 0);
+
+ return self->priv->rate;
+}
+
+void gnome_rr_output_info_set_refresh_rate (GnomeRROutputInfo *self, int rate)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+
+ self->priv->rate = rate;
+}
+
+GnomeRRRotation gnome_rr_output_info_get_rotation (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), GNOME_RR_ROTATION_0);
+
+ return self->priv->rotation;
+}
+
+void gnome_rr_output_info_set_rotation (GnomeRROutputInfo *self, GnomeRRRotation rotation)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+
+ self->priv->rotation = rotation;
+}
+
+/**
+ * gnome_rr_output_info_is_connected:
+ *
+ * Returns: whether the output is physically connected to a monitor
+ */
+gboolean gnome_rr_output_info_is_connected (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), FALSE);
+
+ return self->priv->connected;
+}
+
+/**
+ * gnome_rr_output_info_get_vendor:
+ *
+ * @self: a #GnomeRROutputInfo
+ * @vendor: (out caller-allocates) (array fixed-size=4):
+ */
+void gnome_rr_output_info_get_vendor (GnomeRROutputInfo *self, gchar* vendor)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+ g_return_if_fail (vendor != NULL);
+
+ vendor[0] = self->priv->vendor[0];
+ vendor[1] = self->priv->vendor[1];
+ vendor[2] = self->priv->vendor[2];
+ vendor[3] = self->priv->vendor[3];
+}
+
+guint gnome_rr_output_info_get_product (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), 0);
+
+ return self->priv->product;
+}
+
+guint gnome_rr_output_info_get_serial (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), 0);
+
+ return self->priv->serial;
+}
+
+double gnome_rr_output_info_get_aspect_ratio (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), 0);
+
+ return self->priv->aspect;
+}
+
+/**
+ * gnome_rr_output_info_get_display_name:
+ *
+ * Returns: (transfer none): the display name of this output
+ */
+char *gnome_rr_output_info_get_display_name (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), NULL);
+
+ return self->priv->display_name;
+}
+
+gboolean gnome_rr_output_info_get_primary (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), FALSE);
+
+ return self->priv->primary;
+}
+
+void gnome_rr_output_info_set_primary (GnomeRROutputInfo *self, gboolean primary)
+{
+ g_return_if_fail (GNOME_IS_RR_OUTPUT_INFO (self));
+
+ self->priv->primary = primary;
+}
+
+int gnome_rr_output_info_get_preferred_width (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), 0);
+
+ return self->priv->pref_width;
+}
+
+int gnome_rr_output_info_get_preferred_height (GnomeRROutputInfo *self)
+{
+ g_return_val_if_fail (GNOME_IS_RR_OUTPUT_INFO (self), 0);
+
+ return self->priv->pref_height;
+}
diff --git a/gtk/display/gnome-rr-private.h b/gtk/display/gnome-rr-private.h
new file mode 100644
index 0000000..d9eb776
--- /dev/null
+++ b/gtk/display/gnome-rr-private.h
@@ -0,0 +1,80 @@
+#ifndef GNOME_RR_PRIVATE_H
+#define GNOME_RR_PRIVATE_H
+
+#ifdef HAVE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif
+
+typedef struct ScreenInfo ScreenInfo;
+
+struct ScreenInfo
+{
+ int min_width;
+ int max_width;
+ int min_height;
+ int max_height;
+
+#ifdef HAVE_RANDR
+ XRRScreenResources *resources;
+#endif
+
+ GnomeRROutput ** outputs;
+ GnomeRRCrtc ** crtcs;
+ GnomeRRMode ** modes;
+
+ GnomeRRScreen * screen;
+
+ GnomeRRMode ** clone_modes;
+
+#ifdef HAVE_RANDR
+ RROutput primary;
+#endif
+};
+
+struct GnomeRRScreenPrivate
+{
+ GdkScreen * gdk_screen;
+ GdkWindow * gdk_root;
+ Display * xdisplay;
+ Screen * xscreen;
+ Window xroot;
+ ScreenInfo * info;
+
+ int randr_event_base;
+ int rr_major_version;
+ int rr_minor_version;
+
+ Atom connector_type_atom;
+};
+
+struct GnomeRROutputInfoPrivate
+{
+ char * name;
+
+ gboolean on;
+ int width;
+ int height;
+ int rate;
+ int x;
+ int y;
+ GnomeRRRotation rotation;
+
+ gboolean connected;
+ gchar vendor[4];
+ guint product;
+ guint serial;
+ double aspect;
+ int pref_width;
+ int pref_height;
+ char * display_name;
+ gboolean primary;
+};
+
+struct GnomeRRConfigPrivate
+{
+ gboolean clone;
+ GnomeRRScreen *screen;
+ GnomeRROutputInfo **outputs;
+};
+
+#endif
diff --git a/gtk/display/gnome-rr.c b/gtk/display/gnome-rr.c
new file mode 100644
index 0000000..9aca024
--- /dev/null
+++ b/gtk/display/gnome-rr.c
@@ -0,0 +1,2121 @@
+/* gnome-rr.c
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. 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>
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+
+#ifdef HAVE_RANDR
+#include <X11/extensions/Xrandr.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+
+#undef GNOME_DISABLE_DEPRECATED
+#include "gnome-rr.h"
+#include "gnome-rr-config.h"
+
+#include "gnome-rr-private.h"
+
+#define DISPLAY(o) ((o)->info->screen->priv->xdisplay)
+
+#ifndef HAVE_RANDR
+/* This is to avoid a ton of ifdefs wherever we use a type from libXrandr */
+typedef int RROutput;
+typedef int RRCrtc;
+typedef int RRMode;
+typedef int Rotation;
+#define RR_Rotate_0 1
+#define RR_Rotate_90 2
+#define RR_Rotate_180 4
+#define RR_Rotate_270 8
+#define RR_Reflect_X 16
+#define RR_Reflect_Y 32
+#endif
+
+#ifdef HAVE_RANDR
+#define RANDR_LIBRARY_IS_AT_LEAST_1_3 (RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3))
+#else
+#define RANDR_LIBRARY_IS_AT_LEAST_1_3 0
+#endif
+
+#define SERVERS_RANDR_IS_AT_LEAST_1_3(priv) (priv->rr_major_version > 1 || (priv->rr_major_version == 1 && priv->rr_minor_version >= 3))
+
+enum {
+ SCREEN_PROP_0,
+ SCREEN_PROP_GDK_SCREEN,
+ SCREEN_PROP_LAST,
+};
+
+enum {
+ SCREEN_CHANGED,
+ SCREEN_SIGNAL_LAST,
+};
+
+gint screen_signals[SCREEN_SIGNAL_LAST];
+
+struct GnomeRROutput
+{
+ ScreenInfo * info;
+ RROutput id;
+
+ char * name;
+ GnomeRRCrtc * current_crtc;
+ gboolean connected;
+ gulong width_mm;
+ gulong height_mm;
+ GnomeRRCrtc ** possible_crtcs;
+ GnomeRROutput ** clones;
+ GnomeRRMode ** modes;
+ int n_preferred;
+ guint8 * edid_data;
+ int edid_size;
+ char * connector_type;
+};
+
+struct GnomeRROutputWrap
+{
+ RROutput id;
+};
+
+struct GnomeRRCrtc
+{
+ ScreenInfo * info;
+ RRCrtc id;
+
+ GnomeRRMode * current_mode;
+ GnomeRROutput ** current_outputs;
+ GnomeRROutput ** possible_outputs;
+ int x;
+ int y;
+
+ GnomeRRRotation current_rotation;
+ GnomeRRRotation rotations;
+ int gamma_size;
+};
+
+struct GnomeRRMode
+{
+ ScreenInfo * info;
+ RRMode id;
+ char * name;
+ int width;
+ int height;
+ int freq; /* in mHz */
+};
+
+/* GnomeRRCrtc */
+static GnomeRRCrtc * crtc_new (ScreenInfo *info,
+ RRCrtc id);
+static GnomeRRCrtc * crtc_copy (const GnomeRRCrtc *from);
+static void crtc_free (GnomeRRCrtc *crtc);
+
+#ifdef HAVE_RANDR
+static gboolean crtc_initialize (GnomeRRCrtc *crtc,
+ XRRScreenResources *res,
+ GError **error);
+#endif
+
+/* GnomeRROutput */
+static GnomeRROutput *output_new (ScreenInfo *info,
+ RROutput id);
+
+#ifdef HAVE_RANDR
+static gboolean output_initialize (GnomeRROutput *output,
+ XRRScreenResources *res,
+ GError **error);
+#endif
+
+static GnomeRROutput *output_copy (const GnomeRROutput *from);
+static void output_free (GnomeRROutput *output);
+
+/* GnomeRRMode */
+static GnomeRRMode * mode_new (ScreenInfo *info,
+ RRMode id);
+
+#ifdef HAVE_RANDR
+static void mode_initialize (GnomeRRMode *mode,
+ XRRModeInfo *info);
+#endif
+
+static GnomeRRMode * mode_copy (const GnomeRRMode *from);
+static void mode_free (GnomeRRMode *mode);
+
+static void gnome_rr_screen_finalize (GObject*);
+static void gnome_rr_screen_set_property (GObject*, guint, const GValue*, GParamSpec*);
+static void gnome_rr_screen_get_property (GObject*, guint, GValue*, GParamSpec*);
+static gboolean gnome_rr_screen_initable_init (GInitable*, GCancellable*, GError**);
+static void gnome_rr_screen_initable_iface_init (GInitableIface *iface);
+G_DEFINE_TYPE_WITH_CODE (GnomeRRScreen, gnome_rr_screen, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gnome_rr_screen_initable_iface_init))
+
+G_DEFINE_BOXED_TYPE (GnomeRRCrtc, gnome_rr_crtc, crtc_copy, crtc_free)
+G_DEFINE_BOXED_TYPE (GnomeRROutput, gnome_rr_output, output_copy, output_free)
+G_DEFINE_BOXED_TYPE (GnomeRRMode, gnome_rr_mode, mode_copy, mode_free)
+
+/* Errors */
+
+/**
+ * gnome_rr_error_quark:
+ *
+ * Returns the #GQuark that will be used for #GError values returned by the
+ * GnomeRR API.
+ *
+ * Return value: a #GQuark used to identify errors coming from the GnomeRR API.
+ */
+GQuark
+gnome_rr_error_quark (void)
+{
+ return g_quark_from_static_string ("gnome-rr-error-quark");
+}
+
+/* Screen */
+static GnomeRROutput *
+gnome_rr_output_by_id (ScreenInfo *info, RROutput id)
+{
+ GnomeRROutput **output;
+
+ g_assert (info != NULL);
+
+ for (output = info->outputs; *output; ++output)
+ {
+ if ((*output)->id == id)
+ return *output;
+ }
+
+ return NULL;
+}
+
+static GnomeRRCrtc *
+crtc_by_id (ScreenInfo *info, RRCrtc id)
+{
+ GnomeRRCrtc **crtc;
+
+ if (!info)
+ return NULL;
+
+ for (crtc = info->crtcs; *crtc; ++crtc)
+ {
+ if ((*crtc)->id == id)
+ return *crtc;
+ }
+
+ return NULL;
+}
+
+static GnomeRRMode *
+mode_by_id (ScreenInfo *info, RRMode id)
+{
+ GnomeRRMode **mode;
+
+ g_assert (info != NULL);
+
+ for (mode = info->modes; *mode; ++mode)
+ {
+ if ((*mode)->id == id)
+ return *mode;
+ }
+
+ return NULL;
+}
+
+static void
+screen_info_free (ScreenInfo *info)
+{
+ GnomeRROutput **output;
+ GnomeRRCrtc **crtc;
+ GnomeRRMode **mode;
+
+ g_assert (info != NULL);
+
+#ifdef HAVE_RANDR
+ if (info->resources)
+ {
+ XRRFreeScreenResources (info->resources);
+
+ info->resources = NULL;
+ }
+#endif
+
+ if (info->outputs)
+ {
+ for (output = info->outputs; *output; ++output)
+ output_free (*output);
+ g_free (info->outputs);
+ }
+
+ if (info->crtcs)
+ {
+ for (crtc = info->crtcs; *crtc; ++crtc)
+ crtc_free (*crtc);
+ g_free (info->crtcs);
+ }
+
+ if (info->modes)
+ {
+ for (mode = info->modes; *mode; ++mode)
+ mode_free (*mode);
+ g_free (info->modes);
+ }
+
+ if (info->clone_modes)
+ {
+ /* The modes themselves were freed above */
+ g_free (info->clone_modes);
+ }
+
+ g_free (info);
+}
+
+static gboolean
+has_similar_mode (GnomeRROutput *output, GnomeRRMode *mode)
+{
+ int i;
+ GnomeRRMode **modes = gnome_rr_output_list_modes (output);
+ int width = gnome_rr_mode_get_width (mode);
+ int height = gnome_rr_mode_get_height (mode);
+
+ for (i = 0; modes[i] != NULL; ++i)
+ {
+ GnomeRRMode *m = modes[i];
+
+ if (gnome_rr_mode_get_width (m) == width &&
+ gnome_rr_mode_get_height (m) == height)
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gather_clone_modes (ScreenInfo *info)
+{
+ int i;
+ GPtrArray *result = g_ptr_array_new ();
+
+ for (i = 0; info->outputs[i] != NULL; ++i)
+ {
+ int j;
+ GnomeRROutput *output1, *output2;
+
+ output1 = info->outputs[i];
+
+ if (!output1->connected)
+ continue;
+
+ for (j = 0; output1->modes[j] != NULL; ++j)
+ {
+ GnomeRRMode *mode = output1->modes[j];
+ gboolean valid;
+ int k;
+
+ valid = TRUE;
+ for (k = 0; info->outputs[k] != NULL; ++k)
+ {
+ output2 = info->outputs[k];
+
+ if (!output2->connected)
+ continue;
+
+ if (!has_similar_mode (output2, mode))
+ {
+ valid = FALSE;
+ break;
+ }
+ }
+
+ if (valid)
+ g_ptr_array_add (result, mode);
+ }
+ }
+
+ g_ptr_array_add (result, NULL);
+
+ info->clone_modes = (GnomeRRMode **)g_ptr_array_free (result, FALSE);
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+fill_screen_info_from_resources (ScreenInfo *info,
+ XRRScreenResources *resources,
+ GError **error)
+{
+ int i;
+ GPtrArray *a;
+ GnomeRRCrtc **crtc;
+ GnomeRROutput **output;
+
+ info->resources = resources;
+
+ /* We create all the structures before initializing them, so
+ * that they can refer to each other.
+ */
+ a = g_ptr_array_new ();
+ for (i = 0; i < resources->ncrtc; ++i)
+ {
+ GnomeRRCrtc *crtc = crtc_new (info, resources->crtcs[i]);
+
+ g_ptr_array_add (a, crtc);
+ }
+ g_ptr_array_add (a, NULL);
+ info->crtcs = (GnomeRRCrtc **)g_ptr_array_free (a, FALSE);
+
+ a = g_ptr_array_new ();
+ for (i = 0; i < resources->noutput; ++i)
+ {
+ GnomeRROutput *output = output_new (info, resources->outputs[i]);
+
+ g_ptr_array_add (a, output);
+ }
+ g_ptr_array_add (a, NULL);
+ info->outputs = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
+
+ a = g_ptr_array_new ();
+ for (i = 0; i < resources->nmode; ++i)
+ {
+ GnomeRRMode *mode = mode_new (info, resources->modes[i].id);
+
+ g_ptr_array_add (a, mode);
+ }
+ g_ptr_array_add (a, NULL);
+ info->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
+
+ /* Initialize */
+ for (crtc = info->crtcs; *crtc; ++crtc)
+ {
+ if (!crtc_initialize (*crtc, resources, error))
+ return FALSE;
+ }
+
+ for (output = info->outputs; *output; ++output)
+ {
+ if (!output_initialize (*output, resources, error))
+ return FALSE;
+ }
+
+ for (i = 0; i < resources->nmode; ++i)
+ {
+ GnomeRRMode *mode = mode_by_id (info, resources->modes[i].id);
+
+ mode_initialize (mode, &(resources->modes[i]));
+ }
+
+ gather_clone_modes (info);
+
+ return TRUE;
+}
+#endif /* HAVE_RANDR */
+
+#if !GTK_CHECK_VERSION (2, 91, 0)
+#define gdk_x11_window_get_xid gdk_x11_drawable_get_xid
+#define gdk_error_trap_pop_ignored gdk_error_trap_pop
+#endif
+
+static gboolean
+fill_out_screen_info (Display *xdisplay,
+ Window xroot,
+ ScreenInfo *info,
+ gboolean needs_reprobe,
+ GError **error)
+{
+#ifdef HAVE_RANDR
+ XRRScreenResources *resources;
+ GnomeRRScreenPrivate *priv;
+
+ g_assert (xdisplay != NULL);
+ g_assert (info != NULL);
+
+ priv = info->screen->priv;
+
+ /* First update the screen resources */
+
+ if (needs_reprobe)
+ resources = XRRGetScreenResources (xdisplay, xroot);
+ else
+ {
+ /* XRRGetScreenResourcesCurrent is less expensive than
+ * XRRGetScreenResources, however it is available only
+ * in RandR 1.3 or higher
+ */
+#if RANDR_LIBRARY_IS_AT_LEAST_1_3
+ if (SERVERS_RANDR_IS_AT_LEAST_1_3 (priv))
+ resources = XRRGetScreenResourcesCurrent (xdisplay, xroot);
+ else
+ resources = XRRGetScreenResources (xdisplay, xroot);
+#else
+ resources = XRRGetScreenResources (xdisplay, xroot);
+#endif
+ }
+
+ if (resources)
+ {
+ if (!fill_screen_info_from_resources (info, resources, error))
+ return FALSE;
+ }
+ else
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_RANDR_ERROR,
+ /* Translators: a CRTC is a CRT Controller (this is X terminology). */
+ _("could not get the screen resources (CRTCs, outputs, modes)"));
+ return FALSE;
+ }
+
+ /* Then update the screen size range. We do this after XRRGetScreenResources() so that
+ * the X server will already have an updated view of the outputs.
+ */
+
+ if (needs_reprobe) {
+ gboolean success;
+
+ gdk_error_trap_push ();
+ success = XRRGetScreenSizeRange (xdisplay, xroot,
+ &(info->min_width),
+ &(info->min_height),
+ &(info->max_width),
+ &(info->max_height));
+ gdk_flush ();
+ if (gdk_error_trap_pop ()) {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_UNKNOWN,
+ _("unhandled X error while getting the range of screen sizes"));
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_RANDR_ERROR,
+ _("could not get the range of screen sizes"));
+ return FALSE;
+ }
+ }
+ else
+ {
+ gnome_rr_screen_get_ranges (info->screen,
+ &(info->min_width),
+ &(info->max_width),
+ &(info->min_height),
+ &(info->max_height));
+ }
+
+ info->primary = None;
+#if RANDR_LIBRARY_IS_AT_LEAST_1_3
+ if (SERVERS_RANDR_IS_AT_LEAST_1_3 (priv)) {
+ gdk_error_trap_push ();
+ info->primary = XRRGetOutputPrimary (xdisplay, xroot);
+ gdk_error_trap_pop_ignored ();
+ }
+#endif
+
+ return TRUE;
+#else
+ return FALSE;
+#endif /* HAVE_RANDR */
+}
+
+static ScreenInfo *
+screen_info_new (GnomeRRScreen *screen, gboolean needs_reprobe, GError **error)
+{
+ ScreenInfo *info = g_new0 (ScreenInfo, 1);
+ GnomeRRScreenPrivate *priv;
+
+ g_assert (screen != NULL);
+
+ priv = screen->priv;
+
+ info->outputs = NULL;
+ info->crtcs = NULL;
+ info->modes = NULL;
+ info->screen = screen;
+
+ if (fill_out_screen_info (priv->xdisplay, priv->xroot, info, needs_reprobe, error))
+ {
+ return info;
+ }
+ else
+ {
+ screen_info_free (info);
+ return NULL;
+ }
+}
+
+static gboolean
+screen_update (GnomeRRScreen *screen, gboolean force_callback, gboolean needs_reprobe, GError **error)
+{
+ ScreenInfo *info;
+ gboolean changed = FALSE;
+
+ g_assert (screen != NULL);
+
+ info = screen_info_new (screen, needs_reprobe, error);
+ if (!info)
+ return FALSE;
+
+#ifdef HAVE_RANDR
+ if (info->resources->configTimestamp != screen->priv->info->resources->configTimestamp)
+ changed = TRUE;
+#endif
+
+ screen_info_free (screen->priv->info);
+
+ screen->priv->info = info;
+
+ if (changed || force_callback)
+ g_signal_emit (G_OBJECT (screen), screen_signals[SCREEN_CHANGED], 0);
+
+ return changed;
+}
+
+static GdkFilterReturn
+screen_on_event (GdkXEvent *xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+#ifdef HAVE_RANDR
+ GnomeRRScreen *screen = data;
+ GnomeRRScreenPrivate *priv = screen->priv;
+ XEvent *e = xevent;
+ int event_num;
+
+ if (!e)
+ return GDK_FILTER_CONTINUE;
+
+ event_num = e->type - priv->randr_event_base;
+
+ if (event_num == RRScreenChangeNotify) {
+ /* We don't reprobe the hardware; we just fetch the X server's latest
+ * state. The server already knows the new state of the outputs; that's
+ * why it sent us an event!
+ */
+ screen_update (screen, TRUE, FALSE, NULL); /* NULL-GError */
+#if 0
+ /* Enable this code to get a dialog showing the RANDR timestamps, for debugging purposes */
+ {
+ GtkWidget *dialog;
+ XRRScreenChangeNotifyEvent *rr_event;
+ static int dialog_num;
+
+ rr_event = (XRRScreenChangeNotifyEvent *) e;
+
+ dialog = gtk_message_dialog_new (NULL,
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ "RRScreenChangeNotify timestamps (%d):\n"
+ "event change: %u\n"
+ "event config: %u\n"
+ "event serial: %lu\n"
+ "----------------------"
+ "screen change: %u\n"
+ "screen config: %u\n",
+ dialog_num++,
+ (guint32) rr_event->timestamp,
+ (guint32) rr_event->config_timestamp,
+ rr_event->serial,
+ (guint32) priv->info->resources->timestamp,
+ (guint32) priv->info->resources->configTimestamp);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_widget_show (dialog);
+ }
+#endif
+ }
+#if 0
+ /* WHY THIS CODE IS DISABLED:
+ *
+ * Note that in gnome_rr_screen_new(), we only select for
+ * RRScreenChangeNotifyMask. We used to select for other values in
+ * RR*NotifyMask, but we weren't really doing anything useful with those
+ * events. We only care about "the screens changed in some way or another"
+ * for now.
+ *
+ * If we ever run into a situtation that could benefit from processing more
+ * detailed events, we can enable this code again.
+ *
+ * Note that the X server sends RRScreenChangeNotify in conjunction with the
+ * more detailed events from RANDR 1.2 - see xserver/randr/randr.c:TellChanged().
+ */
+ else if (event_num == RRNotify)
+ {
+ /* Other RandR events */
+
+ XRRNotifyEvent *event = (XRRNotifyEvent *)e;
+
+ /* Here we can distinguish between RRNotify events supported
+ * since RandR 1.2 such as RRNotify_OutputProperty. For now, we
+ * don't have anything special to do for particular subevent types, so
+ * we leave this as an empty switch().
+ */
+ switch (event->subtype)
+ {
+ default:
+ break;
+ }
+
+ /* No need to reprobe hardware here */
+ screen_update (screen, TRUE, FALSE, NULL); /* NULL-GError */
+ }
+#endif
+
+#endif /* HAVE_RANDR */
+
+ /* Pass the event on to GTK+ */
+ return GDK_FILTER_CONTINUE;
+}
+
+static gboolean
+gnome_rr_screen_initable_init (GInitable *initable, GCancellable *canc, GError **error)
+{
+ GnomeRRScreen *self = GNOME_RR_SCREEN (initable);
+ GnomeRRScreenPrivate *priv = self->priv;
+ Display *dpy = GDK_SCREEN_XDISPLAY (self->priv->gdk_screen);
+ int event_base;
+ int ignore;
+
+ priv->connector_type_atom = XInternAtom (dpy, "ConnectorType", FALSE);
+
+#ifdef HAVE_RANDR
+ if (XRRQueryExtension (dpy, &event_base, &ignore))
+ {
+ priv->randr_event_base = event_base;
+
+ XRRQueryVersion (dpy, &priv->rr_major_version, &priv->rr_minor_version);
+ if (priv->rr_major_version < 1 || (priv->rr_major_version == 1 && priv->rr_minor_version < 2)) {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_NO_RANDR_EXTENSION,
+ "RANDR extension is too old (must be at least 1.2)");
+ return FALSE;
+ }
+
+ priv->info = screen_info_new (self, TRUE, error);
+
+ if (!priv->info) {
+ return FALSE;
+ }
+
+ XRRSelectInput (priv->xdisplay,
+ priv->xroot,
+ RRScreenChangeNotifyMask);
+ gdk_x11_register_standard_event_type (gdk_screen_get_display (priv->gdk_screen),
+ event_base,
+ RRNotify + 1);
+ gdk_window_add_filter (priv->gdk_root, screen_on_event, self);
+
+ return TRUE;
+ }
+ else
+ {
+#endif /* HAVE_RANDR */
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_NO_RANDR_EXTENSION,
+ _("RANDR extension is not present"));
+
+ return FALSE;
+#ifdef HAVE_RANDR
+ }
+#endif
+}
+
+void
+gnome_rr_screen_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = gnome_rr_screen_initable_init;
+}
+
+void
+gnome_rr_screen_finalize (GObject *gobject)
+{
+ GnomeRRScreen *screen = GNOME_RR_SCREEN (gobject);
+
+ gdk_window_remove_filter (screen->priv->gdk_root, screen_on_event, screen);
+
+ screen_info_free (screen->priv->info);
+
+ G_OBJECT_CLASS (gnome_rr_screen_parent_class)->finalize (gobject);
+}
+
+void
+gnome_rr_screen_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *property)
+{
+ GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
+ GnomeRRScreenPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case SCREEN_PROP_GDK_SCREEN:
+ priv->gdk_screen = g_value_get_object (value);
+ priv->gdk_root = gdk_screen_get_root_window (priv->gdk_screen);
+ priv->xroot = gdk_x11_window_get_xid (priv->gdk_root);
+ priv->xdisplay = GDK_SCREEN_XDISPLAY (priv->gdk_screen);
+ priv->xscreen = gdk_x11_screen_get_xscreen (priv->gdk_screen);
+ return;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+ return;
+ }
+}
+
+void
+gnome_rr_screen_get_property (GObject *gobject, guint property_id, GValue *value, GParamSpec *property)
+{
+ GnomeRRScreen *self = GNOME_RR_SCREEN (gobject);
+ GnomeRRScreenPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case SCREEN_PROP_GDK_SCREEN:
+ g_value_set_object (value, priv->gdk_screen);
+ return;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, property);
+ return;
+ }
+}
+
+void
+gnome_rr_screen_class_init (GnomeRRScreenClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ g_type_class_add_private (klass, sizeof (GnomeRRScreenPrivate));
+
+ gobject_class->set_property = gnome_rr_screen_set_property;
+ gobject_class->get_property = gnome_rr_screen_get_property;
+ gobject_class->finalize = gnome_rr_screen_finalize;
+
+ g_object_class_install_property(
+ gobject_class,
+ SCREEN_PROP_GDK_SCREEN,
+ g_param_spec_object (
+ "gdk-screen",
+ "GDK Screen",
+ "The GDK Screen represented by this GnomeRRScreen",
+ GDK_TYPE_SCREEN,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS)
+ );
+
+ screen_signals[SCREEN_CHANGED] = g_signal_new("changed",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ G_STRUCT_OFFSET (GnomeRRScreenClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+void
+gnome_rr_screen_init (GnomeRRScreen *self)
+{
+ GnomeRRScreenPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GNOME_TYPE_RR_SCREEN, GnomeRRScreenPrivate);
+ self->priv = priv;
+
+ priv->gdk_screen = NULL;
+ priv->gdk_root = NULL;
+ priv->xdisplay = NULL;
+ priv->xroot = None;
+ priv->xscreen = NULL;
+ priv->info = NULL;
+ priv->rr_major_version = 0;
+ priv->rr_minor_version = 0;
+}
+
+/**
+ * gnome_rr_screen_new:
+ * Creates a new #GnomeRRScreen instance
+ *
+ * @screen: the #GdkScreen on which to operate
+ * @error: will be set if XRandR is not supported
+ *
+ * Returns: a new #GnomeRRScreen instance or NULL if screen could not be created,
+ * for instance if the driver does not support Xrandr 1.2
+ */
+GnomeRRScreen *
+gnome_rr_screen_new (GdkScreen *screen,
+ GError **error)
+{
+ /* _gnome_desktop_init_i18n (); */
+ return g_initable_new (GNOME_TYPE_RR_SCREEN, NULL, error, "gdk-screen", screen, NULL);
+}
+
+void
+gnome_rr_screen_set_size (GnomeRRScreen *screen,
+ int width,
+ int height,
+ int mm_width,
+ int mm_height)
+{
+ g_return_if_fail (GNOME_IS_RR_SCREEN (screen));
+
+#ifdef HAVE_RANDR
+ gdk_error_trap_push ();
+ XRRSetScreenSize (screen->priv->xdisplay, screen->priv->xroot,
+ width, height, mm_width, mm_height);
+ gdk_error_trap_pop_ignored ();
+#endif
+}
+
+/**
+ * gnome_rr_screen_get_ranges:
+ *
+ * Get the ranges of the screen
+ * @screen: a #GnomeRRScreen
+ * @min_width: (out): the minimum width
+ * @max_width: (out): the maximum width
+ * @min_height: (out): the minimum height
+ * @max_height: (out): the maximum height
+ */
+void
+gnome_rr_screen_get_ranges (GnomeRRScreen *screen,
+ int *min_width,
+ int *max_width,
+ int *min_height,
+ int *max_height)
+{
+ GnomeRRScreenPrivate *priv;
+
+ g_return_if_fail (GNOME_IS_RR_SCREEN (screen));
+
+ priv = screen->priv;
+
+ if (min_width)
+ *min_width = priv->info->min_width;
+
+ if (max_width)
+ *max_width = priv->info->max_width;
+
+ if (min_height)
+ *min_height = priv->info->min_height;
+
+ if (max_height)
+ *max_height = priv->info->max_height;
+}
+
+/**
+ * gnome_rr_screen_get_timestamps:
+ * @screen: a #GnomeRRScreen
+ * @change_timestamp_ret: (out): Location in which to store the timestamp at which the RANDR configuration was last changed
+ * @config_timestamp_ret: (out): Location in which to store the timestamp at which the RANDR configuration was last obtained
+ *
+ * Queries the two timestamps that the X RANDR extension maintains. The X
+ * server will prevent change requests for stale configurations, those whose
+ * timestamp is not equal to that of the latest request for configuration. The
+ * X server will also prevent change requests that have an older timestamp to
+ * the latest change request.
+ */
+void
+gnome_rr_screen_get_timestamps (GnomeRRScreen *screen,
+ guint32 *change_timestamp_ret,
+ guint32 *config_timestamp_ret)
+{
+ GnomeRRScreenPrivate *priv;
+
+ g_return_if_fail (GNOME_IS_RR_SCREEN (screen));
+
+ priv = screen->priv;
+
+#ifdef HAVE_RANDR
+ if (change_timestamp_ret)
+ *change_timestamp_ret = priv->info->resources->timestamp;
+
+ if (config_timestamp_ret)
+ *config_timestamp_ret = priv->info->resources->configTimestamp;
+#endif
+}
+
+static gboolean
+force_timestamp_update (GnomeRRScreen *screen)
+{
+#ifdef HAVE_RANDR
+ GnomeRRScreenPrivate *priv = screen->priv;
+ GnomeRRCrtc *crtc;
+ XRRCrtcInfo *current_info;
+ Status status;
+ gboolean timestamp_updated;
+
+ timestamp_updated = FALSE;
+
+ crtc = priv->info->crtcs[0];
+
+ if (crtc == NULL)
+ goto out;
+
+ current_info = XRRGetCrtcInfo (priv->xdisplay,
+ priv->info->resources,
+ crtc->id);
+
+ if (current_info == NULL)
+ goto out;
+
+ gdk_error_trap_push ();
+ status = XRRSetCrtcConfig (priv->xdisplay,
+ priv->info->resources,
+ crtc->id,
+ current_info->timestamp,
+ current_info->x,
+ current_info->y,
+ current_info->mode,
+ current_info->rotation,
+ current_info->outputs,
+ current_info->noutput);
+
+ XRRFreeCrtcInfo (current_info);
+
+ gdk_flush ();
+ if (gdk_error_trap_pop ())
+ goto out;
+
+ if (status == RRSetConfigSuccess)
+ timestamp_updated = TRUE;
+out:
+ return timestamp_updated;
+#else
+ return FALSE;
+#endif
+}
+
+/**
+ * gnome_rr_screen_refresh:
+ * @screen: a #GnomeRRScreen
+ * @error: location to store error, or %NULL
+ *
+ * Refreshes the screen configuration, and calls the screen's callback if it
+ * exists and if the screen's configuration changed.
+ *
+ * Return value: TRUE if the screen's configuration changed; otherwise, the
+ * function returns FALSE and a NULL error if the configuration didn't change,
+ * or FALSE and a non-NULL error if there was an error while refreshing the
+ * configuration.
+ */
+gboolean
+gnome_rr_screen_refresh (GnomeRRScreen *screen,
+ GError **error)
+{
+ gboolean refreshed;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ gdk_x11_display_grab (gdk_screen_get_display (screen->priv->gdk_screen));
+
+ refreshed = screen_update (screen, FALSE, TRUE, error);
+ force_timestamp_update (screen); /* this is to keep other clients from thinking that the X server re-detected things by itself - bgo#621046 */
+
+ gdk_x11_display_ungrab (gdk_screen_get_display (screen->priv->gdk_screen));
+
+ return refreshed;
+}
+
+/**
+ * gnome_rr_screen_list_modes:
+ *
+ * List available XRandR modes
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+GnomeRRMode **
+gnome_rr_screen_list_modes (GnomeRRScreen *screen)
+{
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ return screen->priv->info->modes;
+}
+
+/**
+ * gnome_rr_screen_list_clone_modes:
+ *
+ * List available XRandR clone modes
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+GnomeRRMode **
+gnome_rr_screen_list_clone_modes (GnomeRRScreen *screen)
+{
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ return screen->priv->info->clone_modes;
+}
+
+/**
+ * gnome_rr_screen_list_crtcs:
+ *
+ * List all CRTCs
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+GnomeRRCrtc **
+gnome_rr_screen_list_crtcs (GnomeRRScreen *screen)
+{
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ return screen->priv->info->crtcs;
+}
+
+/**
+ * gnome_rr_screen_list_outputs:
+ *
+ * List all outputs
+ *
+ * Returns: (array zero-terminated=1) (transfer none):
+ */
+GnomeRROutput **
+gnome_rr_screen_list_outputs (GnomeRRScreen *screen)
+{
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ return screen->priv->info->outputs;
+}
+
+/**
+ * gnome_rr_screen_get_crtc_by_id:
+ *
+ * Returns: (transfer none): the CRTC identified by @id
+ */
+GnomeRRCrtc *
+gnome_rr_screen_get_crtc_by_id (GnomeRRScreen *screen,
+ guint32 id)
+{
+ GnomeRRCrtc **crtcs;
+ int i;
+
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ crtcs = screen->priv->info->crtcs;
+
+ for (i = 0; crtcs[i] != NULL; ++i)
+ {
+ if (crtcs[i]->id == id)
+ return crtcs[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * gnome_rr_screen_get_output_by_id:
+ *
+ * Returns: (transfer none): the output identified by @id
+ */
+GnomeRROutput *
+gnome_rr_screen_get_output_by_id (GnomeRRScreen *screen,
+ guint32 id)
+{
+ GnomeRROutput **outputs;
+ int i;
+
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ outputs = screen->priv->info->outputs;
+
+ for (i = 0; outputs[i] != NULL; ++i)
+ {
+ if (outputs[i]->id == id)
+ return outputs[i];
+ }
+
+ return NULL;
+}
+
+/* GnomeRROutput */
+static GnomeRROutput *
+output_new (ScreenInfo *info, RROutput id)
+{
+ GnomeRROutput *output = g_slice_new0 (GnomeRROutput);
+
+ output->id = id;
+ output->info = info;
+
+ return output;
+}
+
+static guint8 *
+get_property (Display *dpy,
+ RROutput output,
+ Atom atom,
+ int *len)
+{
+#ifdef HAVE_RANDR
+ unsigned char *prop;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ Atom actual_type;
+ guint8 *result;
+
+ XRRGetOutputProperty (dpy, output, atom,
+ 0, 100, False, False,
+ AnyPropertyType,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after, &prop);
+
+ if (actual_type == XA_INTEGER && actual_format == 8)
+ {
+ result = g_memdup (prop, nitems);
+ if (len)
+ *len = nitems;
+ }
+ else
+ {
+ result = NULL;
+ }
+
+ XFree (prop);
+
+ return result;
+#else
+ return NULL;
+#endif /* HAVE_RANDR */
+}
+
+static guint8 *
+read_edid_data (GnomeRROutput *output, int *len)
+{
+ Atom edid_atom;
+ guint8 *result;
+
+ edid_atom = XInternAtom (DISPLAY (output), "EDID", FALSE);
+ result = get_property (DISPLAY (output),
+ output->id, edid_atom, len);
+
+ if (!result)
+ {
+ edid_atom = XInternAtom (DISPLAY (output), "EDID_DATA", FALSE);
+ result = get_property (DISPLAY (output),
+ output->id, edid_atom, len);
+ }
+
+ if (result)
+ {
+ if (*len % 128 == 0)
+ return result;
+ else
+ g_free (result);
+ }
+
+ return NULL;
+}
+
+static char *
+get_connector_type_string (GnomeRROutput *output)
+{
+#ifdef HAVE_RANDR
+ char *result;
+ unsigned char *prop;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ Atom actual_type;
+ Atom connector_type;
+ char *connector_type_str;
+
+ result = NULL;
+
+ if (XRRGetOutputProperty (DISPLAY (output), output->id, output->info->screen->priv->connector_type_atom,
+ 0, 100, False, False,
+ AnyPropertyType,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after, &prop) != Success)
+ return NULL;
+
+ if (!(actual_type == XA_ATOM && actual_format == 32 && nitems == 1))
+ goto out;
+
+ connector_type = *((Atom *) prop);
+
+ connector_type_str = XGetAtomName (DISPLAY (output), connector_type);
+ if (connector_type_str) {
+ result = g_strdup (connector_type_str); /* so the caller can g_free() it */
+ XFree (connector_type_str);
+ }
+
+out:
+
+ XFree (prop);
+
+ return result;
+#else
+ return NULL;
+#endif
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+output_initialize (GnomeRROutput *output, XRRScreenResources *res, GError **error)
+{
+ XRROutputInfo *info = XRRGetOutputInfo (
+ DISPLAY (output), res, output->id);
+ GPtrArray *a;
+ int i;
+
+#if 0
+ g_print ("Output %lx Timestamp: %u\n", output->id, (guint32)info->timestamp);
+#endif
+
+ if (!info || !output->info)
+ {
+ /* FIXME: see the comment in crtc_initialize() */
+ /* Translators: here, an "output" is a video output */
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_RANDR_ERROR,
+ _("could not get information about output %d"),
+ (int) output->id);
+ return FALSE;
+ }
+
+ output->name = g_strdup (info->name); /* FIXME: what is nameLen used for? */
+ output->current_crtc = crtc_by_id (output->info, info->crtc);
+ output->width_mm = info->mm_width;
+ output->height_mm = info->mm_height;
+ output->connected = (info->connection == RR_Connected);
+ output->connector_type = get_connector_type_string (output);
+
+ /* Possible crtcs */
+ a = g_ptr_array_new ();
+
+ for (i = 0; i < info->ncrtc; ++i)
+ {
+ GnomeRRCrtc *crtc = crtc_by_id (output->info, info->crtcs[i]);
+
+ if (crtc)
+ g_ptr_array_add (a, crtc);
+ }
+ g_ptr_array_add (a, NULL);
+ output->possible_crtcs = (GnomeRRCrtc **)g_ptr_array_free (a, FALSE);
+
+ /* Clones */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->nclone; ++i)
+ {
+ GnomeRROutput *gnome_rr_output = gnome_rr_output_by_id (output->info, info->clones[i]);
+
+ if (gnome_rr_output)
+ g_ptr_array_add (a, gnome_rr_output);
+ }
+ g_ptr_array_add (a, NULL);
+ output->clones = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
+
+ /* Modes */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->nmode; ++i)
+ {
+ GnomeRRMode *mode = mode_by_id (output->info, info->modes[i]);
+
+ if (mode)
+ g_ptr_array_add (a, mode);
+ }
+ g_ptr_array_add (a, NULL);
+ output->modes = (GnomeRRMode **)g_ptr_array_free (a, FALSE);
+
+ output->n_preferred = info->npreferred;
+
+ /* Edid data */
+ output->edid_data = read_edid_data (output, &output->edid_size);
+
+ XRRFreeOutputInfo (info);
+
+ return TRUE;
+}
+#endif /* HAVE_RANDR */
+
+static GnomeRROutput*
+output_copy (const GnomeRROutput *from)
+{
+ GPtrArray *array;
+ GnomeRRCrtc **p_crtc;
+ GnomeRROutput **p_output;
+ GnomeRRMode **p_mode;
+ GnomeRROutput *output = g_slice_new0 (GnomeRROutput);
+
+ output->id = from->id;
+ output->info = from->info;
+ output->name = g_strdup (from->name);
+ output->current_crtc = from->current_crtc;
+ output->width_mm = from->width_mm;
+ output->height_mm = from->height_mm;
+ output->connected = from->connected;
+ output->n_preferred = from->n_preferred;
+ output->connector_type = g_strdup (from->connector_type);
+
+ array = g_ptr_array_new ();
+ for (p_crtc = from->possible_crtcs; *p_crtc != NULL; p_crtc++)
+ {
+ g_ptr_array_add (array, *p_crtc);
+ }
+ output->possible_crtcs = (GnomeRRCrtc**) g_ptr_array_free (array, FALSE);
+
+ array = g_ptr_array_new ();
+ for (p_output = from->clones; *p_output != NULL; p_output++)
+ {
+ g_ptr_array_add (array, *p_output);
+ }
+ output->clones = (GnomeRROutput**) g_ptr_array_free (array, FALSE);
+
+ array = g_ptr_array_new ();
+ for (p_mode = from->modes; *p_mode != NULL; p_mode++)
+ {
+ g_ptr_array_add (array, *p_mode);
+ }
+ output->modes = (GnomeRRMode**) g_ptr_array_free (array, FALSE);
+
+ output->edid_size = from->edid_size;
+ output->edid_data = g_memdup (from->edid_data, from->edid_size);
+
+ return output;
+}
+
+static void
+output_free (GnomeRROutput *output)
+{
+ g_free (output->clones);
+ g_free (output->modes);
+ g_free (output->possible_crtcs);
+ g_free (output->edid_data);
+ g_free (output->name);
+ g_free (output->connector_type);
+ g_slice_free (GnomeRROutput, output);
+}
+
+guint32
+gnome_rr_output_get_id (GnomeRROutput *output)
+{
+ g_assert(output != NULL);
+
+ return output->id;
+}
+
+const guint8 *
+gnome_rr_output_get_edid_data (GnomeRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+
+ return output->edid_data;
+}
+
+/**
+ * gnome_rr_screen_get_output_by_id:
+ *
+ * Returns: (transfer none): the output identified by @name
+ */
+GnomeRROutput *
+gnome_rr_screen_get_output_by_name (GnomeRRScreen *screen,
+ const char *name)
+{
+ int i;
+
+ g_return_val_if_fail (GNOME_IS_RR_SCREEN (screen), NULL);
+ g_return_val_if_fail (screen->priv->info != NULL, NULL);
+
+ for (i = 0; screen->priv->info->outputs[i] != NULL; ++i)
+ {
+ GnomeRROutput *output = screen->priv->info->outputs[i];
+
+ if (strcmp (output->name, name) == 0)
+ return output;
+ }
+
+ return NULL;
+}
+
+GnomeRRCrtc *
+gnome_rr_output_get_crtc (GnomeRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+
+ return output->current_crtc;
+}
+
+/* Returns NULL if the ConnectorType property is not available */
+const char *
+gnome_rr_output_get_connector_type (GnomeRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+
+ return output->connector_type;
+}
+
+gboolean
+gnome_rr_output_is_laptop (GnomeRROutput *output)
+{
+ const char *connector_type;
+
+ g_return_val_if_fail (output != NULL, FALSE);
+
+ if (!output->connected)
+ return FALSE;
+
+ /* The ConnectorType property is present in RANDR 1.3 and greater */
+
+ connector_type = gnome_rr_output_get_connector_type (output);
+ if (connector_type && strcmp (connector_type, GNOME_RR_CONNECTOR_TYPE_PANEL) == 0)
+ return TRUE;
+
+ /* Older versions of RANDR - this is a best guess, as @#$% RANDR doesn't have standard output names,
+ * so drivers can use whatever they like.
+ */
+
+ if (output->name
+ && (strstr (output->name, "lvds") || /* Most drivers use an "LVDS" prefix... */
+ strstr (output->name, "LVDS") ||
+ strstr (output->name, "Lvds") ||
+ strstr (output->name, "LCD"))) /* ... but fglrx uses "LCD" in some versions. Shoot me now, kthxbye. */
+ return TRUE;
+
+ return FALSE;
+}
+
+GnomeRRMode *
+gnome_rr_output_get_current_mode (GnomeRROutput *output)
+{
+ GnomeRRCrtc *crtc;
+
+ g_return_val_if_fail (output != NULL, NULL);
+
+ if ((crtc = gnome_rr_output_get_crtc (output)))
+ return gnome_rr_crtc_get_current_mode (crtc);
+
+ return NULL;
+}
+
+void
+gnome_rr_output_get_position (GnomeRROutput *output,
+ int *x,
+ int *y)
+{
+ GnomeRRCrtc *crtc;
+
+ g_return_if_fail (output != NULL);
+
+ if ((crtc = gnome_rr_output_get_crtc (output)))
+ gnome_rr_crtc_get_position (crtc, x, y);
+}
+
+const char *
+gnome_rr_output_get_name (GnomeRROutput *output)
+{
+ g_assert (output != NULL);
+ return output->name;
+}
+
+int
+gnome_rr_output_get_width_mm (GnomeRROutput *output)
+{
+ g_assert (output != NULL);
+ return output->width_mm;
+}
+
+int
+gnome_rr_output_get_height_mm (GnomeRROutput *output)
+{
+ g_assert (output != NULL);
+ return output->height_mm;
+}
+
+GnomeRRMode *
+gnome_rr_output_get_preferred_mode (GnomeRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+ if (output->n_preferred)
+ return output->modes[0];
+
+ return NULL;
+}
+
+GnomeRRMode **
+gnome_rr_output_list_modes (GnomeRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+ return output->modes;
+}
+
+gboolean
+gnome_rr_output_is_connected (GnomeRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, FALSE);
+ return output->connected;
+}
+
+gboolean
+gnome_rr_output_supports_mode (GnomeRROutput *output,
+ GnomeRRMode *mode)
+{
+ int i;
+
+ g_return_val_if_fail (output != NULL, FALSE);
+ g_return_val_if_fail (mode != NULL, FALSE);
+
+ for (i = 0; output->modes[i] != NULL; ++i)
+ {
+ if (output->modes[i] == mode)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gnome_rr_output_can_clone (GnomeRROutput *output,
+ GnomeRROutput *clone)
+{
+ int i;
+
+ g_return_val_if_fail (output != NULL, FALSE);
+ g_return_val_if_fail (clone != NULL, FALSE);
+
+ for (i = 0; output->clones[i] != NULL; ++i)
+ {
+ if (output->clones[i] == clone)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gnome_rr_output_get_is_primary (GnomeRROutput *output)
+{
+#ifdef HAVE_RANDR
+ return output->info->primary == output->id;
+#else
+ return FALSE;
+#endif
+}
+
+void
+gnome_rr_screen_set_primary_output (GnomeRRScreen *screen,
+ GnomeRROutput *output)
+{
+ GnomeRRScreenPrivate *priv;
+
+ g_return_if_fail (GNOME_IS_RR_SCREEN (screen));
+
+ priv = screen->priv;
+
+#if RANDR_LIBRARY_IS_AT_LEAST_1_3
+ RROutput id;
+
+ if (output)
+ id = output->id;
+ else
+ id = None;
+
+ if (SERVERS_RANDR_IS_AT_LEAST_1_3 (priv))
+ XRRSetOutputPrimary (priv->xdisplay, priv->xroot, id);
+#endif
+}
+
+/* GnomeRRCrtc */
+typedef struct
+{
+ Rotation xrot;
+ GnomeRRRotation rot;
+} RotationMap;
+
+static const RotationMap rotation_map[] =
+{
+ { RR_Rotate_0, GNOME_RR_ROTATION_0 },
+ { RR_Rotate_90, GNOME_RR_ROTATION_90 },
+ { RR_Rotate_180, GNOME_RR_ROTATION_180 },
+ { RR_Rotate_270, GNOME_RR_ROTATION_270 },
+ { RR_Reflect_X, GNOME_RR_REFLECT_X },
+ { RR_Reflect_Y, GNOME_RR_REFLECT_Y },
+};
+
+static GnomeRRRotation
+gnome_rr_rotation_from_xrotation (Rotation r)
+{
+ int i;
+ GnomeRRRotation result = 0;
+
+ for (i = 0; i < G_N_ELEMENTS (rotation_map); ++i)
+ {
+ if (r & rotation_map[i].xrot)
+ result |= rotation_map[i].rot;
+ }
+
+ return result;
+}
+
+static Rotation
+xrotation_from_rotation (GnomeRRRotation r)
+{
+ int i;
+ Rotation result = 0;
+
+ for (i = 0; i < G_N_ELEMENTS (rotation_map); ++i)
+ {
+ if (r & rotation_map[i].rot)
+ result |= rotation_map[i].xrot;
+ }
+
+ return result;
+}
+
+#ifndef GNOME_DISABLE_DEPRECATED_SOURCE
+gboolean
+gnome_rr_crtc_set_config (GnomeRRCrtc *crtc,
+ int x,
+ int y,
+ GnomeRRMode *mode,
+ GnomeRRRotation rotation,
+ GnomeRROutput **outputs,
+ int n_outputs,
+ GError **error)
+{
+ return gnome_rr_crtc_set_config_with_time (crtc, GDK_CURRENT_TIME, x, y, mode, rotation, outputs, n_outputs, error);
+}
+#endif
+
+gboolean
+gnome_rr_crtc_set_config_with_time (GnomeRRCrtc *crtc,
+ guint32 timestamp,
+ int x,
+ int y,
+ GnomeRRMode *mode,
+ GnomeRRRotation rotation,
+ GnomeRROutput **outputs,
+ int n_outputs,
+ GError **error)
+{
+#ifdef HAVE_RANDR
+ ScreenInfo *info;
+ GArray *output_ids;
+ Status status;
+ gboolean result;
+ int i;
+
+ g_return_val_if_fail (crtc != NULL, FALSE);
+ g_return_val_if_fail (mode != NULL || outputs == NULL || n_outputs == 0, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ info = crtc->info;
+
+ if (mode)
+ {
+ if (x + mode->width > info->max_width
+ || y + mode->height > info->max_height)
+ {
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_BOUNDS_ERROR,
+ /* Translators: the "position", "size", and "maximum"
+ * words here are not keywords; please translate them
+ * as usual. A CRTC is a CRT Controller (this is X terminology) */
+ _("requested position/size for CRTC %d is outside the allowed limit: "
+ "position=(%d, %d), size=(%d, %d), maximum=(%d, %d)"),
+ (int) crtc->id,
+ x, y,
+ mode->width, mode->height,
+ info->max_width, info->max_height);
+ return FALSE;
+ }
+ }
+
+ output_ids = g_array_new (FALSE, FALSE, sizeof (RROutput));
+
+ if (outputs)
+ {
+ for (i = 0; i < n_outputs; ++i)
+ g_array_append_val (output_ids, outputs[i]->id);
+ }
+
+ status = XRRSetCrtcConfig (DISPLAY (crtc), info->resources, crtc->id,
+ timestamp,
+ x, y,
+ mode ? mode->id : None,
+ xrotation_from_rotation (rotation),
+ (RROutput *)output_ids->data,
+ output_ids->len);
+
+ g_array_free (output_ids, TRUE);
+
+ if (status == RRSetConfigSuccess)
+ result = TRUE;
+ else {
+ result = FALSE;
+ /* Translators: CRTC is a CRT Controller (this is X terminology).
+ * It is *very* unlikely that you'll ever get this error, so it is
+ * only listed for completeness. */
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_RANDR_ERROR,
+ _("could not set the configuration for CRTC %d"),
+ (int) crtc->id);
+ }
+
+ return result;
+#else
+ return FALSE;
+#endif /* HAVE_RANDR */
+}
+
+GnomeRRMode *
+gnome_rr_crtc_get_current_mode (GnomeRRCrtc *crtc)
+{
+ g_return_val_if_fail (crtc != NULL, NULL);
+
+ return crtc->current_mode;
+}
+
+guint32
+gnome_rr_crtc_get_id (GnomeRRCrtc *crtc)
+{
+ g_return_val_if_fail (crtc != NULL, 0);
+
+ return crtc->id;
+}
+
+gboolean
+gnome_rr_crtc_can_drive_output (GnomeRRCrtc *crtc,
+ GnomeRROutput *output)
+{
+ int i;
+
+ g_return_val_if_fail (crtc != NULL, FALSE);
+ g_return_val_if_fail (output != NULL, FALSE);
+
+ for (i = 0; crtc->possible_outputs[i] != NULL; ++i)
+ {
+ if (crtc->possible_outputs[i] == output)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* FIXME: merge with get_mode()? */
+void
+gnome_rr_crtc_get_position (GnomeRRCrtc *crtc,
+ int *x,
+ int *y)
+{
+ g_return_if_fail (crtc != NULL);
+
+ if (x)
+ *x = crtc->x;
+
+ if (y)
+ *y = crtc->y;
+}
+
+/* FIXME: merge with get_mode()? */
+GnomeRRRotation
+gnome_rr_crtc_get_current_rotation (GnomeRRCrtc *crtc)
+{
+ g_assert(crtc != NULL);
+ return crtc->current_rotation;
+}
+
+GnomeRRRotation
+gnome_rr_crtc_get_rotations (GnomeRRCrtc *crtc)
+{
+ g_assert(crtc != NULL);
+ return crtc->rotations;
+}
+
+gboolean
+gnome_rr_crtc_supports_rotation (GnomeRRCrtc * crtc,
+ GnomeRRRotation rotation)
+{
+ g_return_val_if_fail (crtc != NULL, FALSE);
+ return (crtc->rotations & rotation);
+}
+
+static GnomeRRCrtc *
+crtc_new (ScreenInfo *info, RROutput id)
+{
+ GnomeRRCrtc *crtc = g_slice_new0 (GnomeRRCrtc);
+
+ crtc->id = id;
+ crtc->info = info;
+
+ return crtc;
+}
+
+static GnomeRRCrtc *
+crtc_copy (const GnomeRRCrtc *from)
+{
+ GnomeRROutput **p_output;
+ GPtrArray *array;
+ GnomeRRCrtc *to = g_slice_new0 (GnomeRRCrtc);
+
+ to->info = from->info;
+ to->id = from->id;
+ to->current_mode = from->current_mode;
+ to->x = from->x;
+ to->y = from->y;
+ to->current_rotation = from->current_rotation;
+ to->rotations = from->rotations;
+ to->gamma_size = from->gamma_size;
+
+ array = g_ptr_array_new ();
+ for (p_output = from->current_outputs; *p_output != NULL; p_output++)
+ {
+ g_ptr_array_add (array, *p_output);
+ }
+ to->current_outputs = (GnomeRROutput**) g_ptr_array_free (array, FALSE);
+
+ array = g_ptr_array_new ();
+ for (p_output = from->possible_outputs; *p_output != NULL; p_output++)
+ {
+ g_ptr_array_add (array, *p_output);
+ }
+ to->possible_outputs = (GnomeRROutput**) g_ptr_array_free (array, FALSE);
+
+ return to;
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+crtc_initialize (GnomeRRCrtc *crtc,
+ XRRScreenResources *res,
+ GError **error)
+{
+ XRRCrtcInfo *info = XRRGetCrtcInfo (DISPLAY (crtc), res, crtc->id);
+ GPtrArray *a;
+ int i;
+
+#if 0
+ g_print ("CRTC %lx Timestamp: %u\n", crtc->id, (guint32)info->timestamp);
+#endif
+
+ if (!info)
+ {
+ /* FIXME: We need to reaquire the screen resources */
+ /* FIXME: can we actually catch BadRRCrtc, and does it make sense to emit that? */
+
+ /* Translators: CRTC is a CRT Controller (this is X terminology).
+ * It is *very* unlikely that you'll ever get this error, so it is
+ * only listed for completeness. */
+ g_set_error (error, GNOME_RR_ERROR, GNOME_RR_ERROR_RANDR_ERROR,
+ _("could not get information about CRTC %d"),
+ (int) crtc->id);
+ return FALSE;
+ }
+
+ /* GnomeRRMode */
+ crtc->current_mode = mode_by_id (crtc->info, info->mode);
+
+ crtc->x = info->x;
+ crtc->y = info->y;
+
+ /* Current outputs */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->noutput; ++i)
+ {
+ GnomeRROutput *output = gnome_rr_output_by_id (crtc->info, info->outputs[i]);
+
+ if (output)
+ g_ptr_array_add (a, output);
+ }
+ g_ptr_array_add (a, NULL);
+ crtc->current_outputs = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
+
+ /* Possible outputs */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->npossible; ++i)
+ {
+ GnomeRROutput *output = gnome_rr_output_by_id (crtc->info, info->possible[i]);
+
+ if (output)
+ g_ptr_array_add (a, output);
+ }
+ g_ptr_array_add (a, NULL);
+ crtc->possible_outputs = (GnomeRROutput **)g_ptr_array_free (a, FALSE);
+
+ /* Rotations */
+ crtc->current_rotation = gnome_rr_rotation_from_xrotation (info->rotation);
+ crtc->rotations = gnome_rr_rotation_from_xrotation (info->rotations);
+
+ XRRFreeCrtcInfo (info);
+
+ /* get an store gamma size */
+ crtc->gamma_size = XRRGetCrtcGammaSize (DISPLAY (crtc), crtc->id);
+
+ return TRUE;
+}
+#endif
+
+static void
+crtc_free (GnomeRRCrtc *crtc)
+{
+ g_free (crtc->current_outputs);
+ g_free (crtc->possible_outputs);
+ g_slice_free (GnomeRRCrtc, crtc);
+}
+
+/* GnomeRRMode */
+static GnomeRRMode *
+mode_new (ScreenInfo *info, RRMode id)
+{
+ GnomeRRMode *mode = g_slice_new0 (GnomeRRMode);
+
+ mode->id = id;
+ mode->info = info;
+
+ return mode;
+}
+
+guint32
+gnome_rr_mode_get_id (GnomeRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return mode->id;
+}
+
+guint
+gnome_rr_mode_get_width (GnomeRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return mode->width;
+}
+
+int
+gnome_rr_mode_get_freq (GnomeRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return (mode->freq) / 1000;
+}
+
+guint
+gnome_rr_mode_get_height (GnomeRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return mode->height;
+}
+
+#ifdef HAVE_RANDR
+static void
+mode_initialize (GnomeRRMode *mode, XRRModeInfo *info)
+{
+ g_assert (mode != NULL);
+ g_assert (info != NULL);
+
+ mode->name = g_strdup (info->name);
+ mode->width = info->width;
+ mode->height = info->height;
+ mode->freq = ((info->dotClock / (double)info->hTotal) / info->vTotal + 0.5) * 1000;
+}
+#endif /* HAVE_RANDR */
+
+static GnomeRRMode *
+mode_copy (const GnomeRRMode *from)
+{
+ GnomeRRMode *to = g_slice_new0 (GnomeRRMode);
+
+ to->id = from->id;
+ to->info = from->info;
+ to->name = g_strdup (from->name);
+ to->width = from->width;
+ to->height = from->height;
+ to->freq = from->freq;
+
+ return to;
+}
+
+static void
+mode_free (GnomeRRMode *mode)
+{
+ g_free (mode->name);
+ g_slice_free (GnomeRRMode, mode);
+}
+
+void
+gnome_rr_crtc_set_gamma (GnomeRRCrtc *crtc, int size,
+ unsigned short *red,
+ unsigned short *green,
+ unsigned short *blue)
+{
+#ifdef HAVE_RANDR
+ int copy_size;
+ XRRCrtcGamma *gamma;
+
+ g_return_if_fail (crtc != NULL);
+ g_return_if_fail (red != NULL);
+ g_return_if_fail (green != NULL);
+ g_return_if_fail (blue != NULL);
+
+ if (size != crtc->gamma_size)
+ return;
+
+ gamma = XRRAllocGamma (crtc->gamma_size);
+
+ copy_size = crtc->gamma_size * sizeof (unsigned short);
+ memcpy (gamma->red, red, copy_size);
+ memcpy (gamma->green, green, copy_size);
+ memcpy (gamma->blue, blue, copy_size);
+
+ XRRSetCrtcGamma (DISPLAY (crtc), crtc->id, gamma);
+ XRRFreeGamma (gamma);
+#endif /* HAVE_RANDR */
+}
+
+gboolean
+gnome_rr_crtc_get_gamma (GnomeRRCrtc *crtc, int *size,
+ unsigned short **red, unsigned short **green,
+ unsigned short **blue)
+{
+#ifdef HAVE_RANDR
+ int copy_size;
+ unsigned short *r, *g, *b;
+ XRRCrtcGamma *gamma;
+
+ g_return_val_if_fail (crtc != NULL, FALSE);
+
+ gamma = XRRGetCrtcGamma (DISPLAY (crtc), crtc->id);
+ if (!gamma)
+ return FALSE;
+
+ copy_size = crtc->gamma_size * sizeof (unsigned short);
+
+ if (red) {
+ r = g_new0 (unsigned short, crtc->gamma_size);
+ memcpy (r, gamma->red, copy_size);
+ *red = r;
+ }
+
+ if (green) {
+ g = g_new0 (unsigned short, crtc->gamma_size);
+ memcpy (g, gamma->green, copy_size);
+ *green = g;
+ }
+
+ if (blue) {
+ b = g_new0 (unsigned short, crtc->gamma_size);
+ memcpy (b, gamma->blue, copy_size);
+ *blue = b;
+ }
+
+ XRRFreeGamma (gamma);
+
+ if (size)
+ *size = crtc->gamma_size;
+
+ return TRUE;
+#else
+ return FALSE;
+#endif /* HAVE_RANDR */
+}
+
diff --git a/gtk/display/gnome-rr.h b/gtk/display/gnome-rr.h
new file mode 100644
index 0000000..f4e944c
--- /dev/null
+++ b/gtk/display/gnome-rr.h
@@ -0,0 +1,202 @@
+/* gnome-rr.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. 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>
+ */
+#ifndef GNOME_RR_H
+#define GNOME_RR_H
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnomerr.h
+#endif
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+typedef struct GnomeRRScreenPrivate GnomeRRScreenPrivate;
+typedef struct GnomeRROutput GnomeRROutput;
+typedef struct GnomeRRCrtc GnomeRRCrtc;
+typedef struct GnomeRRMode GnomeRRMode;
+
+typedef struct {
+ GObject parent;
+
+ GnomeRRScreenPrivate* priv;
+} GnomeRRScreen;
+
+typedef struct {
+ GObjectClass parent_class;
+
+ void (* changed) (void);
+} GnomeRRScreenClass;
+
+typedef enum
+{
+ GNOME_RR_ROTATION_0 = (1 << 0),
+ GNOME_RR_ROTATION_90 = (1 << 1),
+ GNOME_RR_ROTATION_180 = (1 << 2),
+ GNOME_RR_ROTATION_270 = (1 << 3),
+ GNOME_RR_REFLECT_X = (1 << 4),
+ GNOME_RR_REFLECT_Y = (1 << 5)
+} GnomeRRRotation;
+
+/* Error codes */
+
+#define GNOME_RR_ERROR (gnome_rr_error_quark ())
+
+GQuark gnome_rr_error_quark (void);
+
+typedef enum {
+ GNOME_RR_ERROR_UNKNOWN, /* generic "fail" */
+ GNOME_RR_ERROR_NO_RANDR_EXTENSION, /* RANDR extension is not present */
+ GNOME_RR_ERROR_RANDR_ERROR, /* generic/undescribed error from the underlying XRR API */
+ GNOME_RR_ERROR_BOUNDS_ERROR, /* requested bounds of a CRTC are outside the maximum size */
+ GNOME_RR_ERROR_CRTC_ASSIGNMENT, /* could not assign CRTCs to outputs */
+ GNOME_RR_ERROR_NO_MATCHING_CONFIG, /* none of the saved configurations matched the current configuration */
+} GnomeRRError;
+
+#define GNOME_RR_CONNECTOR_TYPE_PANEL "Panel" /* This is a laptop's built-in LCD */
+
+#define GNOME_TYPE_RR_SCREEN (gnome_rr_screen_get_type())
+#define GNOME_RR_SCREEN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_RR_SCREEN, GnomeRRScreen))
+#define GNOME_IS_RR_SCREEN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_RR_SCREEN))
+#define GNOME_RR_SCREEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_RR_SCREEN, GnomeRRScreenClass))
+#define GNOME_IS_RR_SCREEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_RR_SCREEN))
+#define GNOME_RR_SCREEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_RR_SCREEN, GnomeRRScreenClass))
+
+#define GNOME_TYPE_RR_OUTPUT (gnome_rr_output_get_type())
+#define GNOME_TYPE_RR_CRTC (gnome_rr_crtc_get_type())
+#define GNOME_TYPE_RR_MODE (gnome_rr_mode_get_type())
+
+GType gnome_rr_screen_get_type (void);
+GType gnome_rr_output_get_type (void);
+GType gnome_rr_crtc_get_type (void);
+GType gnome_rr_mode_get_type (void);
+
+/* GnomeRRScreen */
+GnomeRRScreen * gnome_rr_screen_new (GdkScreen *screen,
+ GError **error);
+GnomeRROutput **gnome_rr_screen_list_outputs (GnomeRRScreen *screen);
+GnomeRRCrtc ** gnome_rr_screen_list_crtcs (GnomeRRScreen *screen);
+GnomeRRMode ** gnome_rr_screen_list_modes (GnomeRRScreen *screen);
+GnomeRRMode ** gnome_rr_screen_list_clone_modes (GnomeRRScreen *screen);
+void gnome_rr_screen_set_size (GnomeRRScreen *screen,
+ int width,
+ int height,
+ int mm_width,
+ int mm_height);
+GnomeRRCrtc * gnome_rr_screen_get_crtc_by_id (GnomeRRScreen *screen,
+ guint32 id);
+gboolean gnome_rr_screen_refresh (GnomeRRScreen *screen,
+ GError **error);
+GnomeRROutput * gnome_rr_screen_get_output_by_id (GnomeRRScreen *screen,
+ guint32 id);
+GnomeRROutput * gnome_rr_screen_get_output_by_name (GnomeRRScreen *screen,
+ const char *name);
+void gnome_rr_screen_get_ranges (GnomeRRScreen *screen,
+ int *min_width,
+ int *max_width,
+ int *min_height,
+ int *max_height);
+void gnome_rr_screen_get_timestamps (GnomeRRScreen *screen,
+ guint32 *change_timestamp_ret,
+ guint32 *config_timestamp_ret);
+
+void gnome_rr_screen_set_primary_output (GnomeRRScreen *screen,
+ GnomeRROutput *output);
+
+GnomeRRMode **gnome_rr_screen_create_clone_modes (GnomeRRScreen *screen);
+
+/* GnomeRROutput */
+guint32 gnome_rr_output_get_id (GnomeRROutput *output);
+const char * gnome_rr_output_get_name (GnomeRROutput *output);
+gboolean gnome_rr_output_is_connected (GnomeRROutput *output);
+int gnome_rr_output_get_size_inches (GnomeRROutput *output);
+int gnome_rr_output_get_width_mm (GnomeRROutput *outout);
+int gnome_rr_output_get_height_mm (GnomeRROutput *output);
+const guint8 * gnome_rr_output_get_edid_data (GnomeRROutput *output);
+GnomeRRCrtc ** gnome_rr_output_get_possible_crtcs (GnomeRROutput *output);
+GnomeRRMode * gnome_rr_output_get_current_mode (GnomeRROutput *output);
+GnomeRRCrtc * gnome_rr_output_get_crtc (GnomeRROutput *output);
+const char * gnome_rr_output_get_connector_type (GnomeRROutput *output);
+gboolean gnome_rr_output_is_laptop (GnomeRROutput *output);
+void gnome_rr_output_get_position (GnomeRROutput *output,
+ int *x,
+ int *y);
+gboolean gnome_rr_output_can_clone (GnomeRROutput *output,
+ GnomeRROutput *clone);
+GnomeRRMode ** gnome_rr_output_list_modes (GnomeRROutput *output);
+GnomeRRMode * gnome_rr_output_get_preferred_mode (GnomeRROutput *output);
+gboolean gnome_rr_output_supports_mode (GnomeRROutput *output,
+ GnomeRRMode *mode);
+gboolean gnome_rr_output_get_is_primary (GnomeRROutput *output);
+
+/* GnomeRRMode */
+guint32 gnome_rr_mode_get_id (GnomeRRMode *mode);
+guint gnome_rr_mode_get_width (GnomeRRMode *mode);
+guint gnome_rr_mode_get_height (GnomeRRMode *mode);
+int gnome_rr_mode_get_freq (GnomeRRMode *mode);
+
+/* GnomeRRCrtc */
+guint32 gnome_rr_crtc_get_id (GnomeRRCrtc *crtc);
+
+#ifndef GNOME_DISABLE_DEPRECATED
+gboolean gnome_rr_crtc_set_config (GnomeRRCrtc *crtc,
+ int x,
+ int y,
+ GnomeRRMode *mode,
+ GnomeRRRotation rotation,
+ GnomeRROutput **outputs,
+ int n_outputs,
+ GError **error);
+#endif
+
+gboolean gnome_rr_crtc_set_config_with_time (GnomeRRCrtc *crtc,
+ guint32 timestamp,
+ int x,
+ int y,
+ GnomeRRMode *mode,
+ GnomeRRRotation rotation,
+ GnomeRROutput **outputs,
+ int n_outputs,
+ GError **error);
+gboolean gnome_rr_crtc_can_drive_output (GnomeRRCrtc *crtc,
+ GnomeRROutput *output);
+GnomeRRMode * gnome_rr_crtc_get_current_mode (GnomeRRCrtc *crtc);
+void gnome_rr_crtc_get_position (GnomeRRCrtc *crtc,
+ int *x,
+ int *y);
+GnomeRRRotation gnome_rr_crtc_get_current_rotation (GnomeRRCrtc *crtc);
+GnomeRRRotation gnome_rr_crtc_get_rotations (GnomeRRCrtc *crtc);
+gboolean gnome_rr_crtc_supports_rotation (GnomeRRCrtc *crtc,
+ GnomeRRRotation rotation);
+
+gboolean gnome_rr_crtc_get_gamma (GnomeRRCrtc *crtc,
+ int *size,
+ unsigned short **red,
+ unsigned short **green,
+ unsigned short **blue);
+void gnome_rr_crtc_set_gamma (GnomeRRCrtc *crtc,
+ int size,
+ unsigned short *red,
+ unsigned short *green,
+ unsigned short *blue);
+#endif /* GNOME_RR_H */
diff --git a/gtk/spicy.c b/gtk/spicy.c
index cd9784d..d1423cc 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -22,11 +22,18 @@
#include <glib/gi18n.h>
#include <sys/stat.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API 2
+#include "display/gnome-rr.h"
+#include "display/gnome-rr-config.h"
+
#include "spice-widget.h"
#include "spice-audio.h"
#include "spice-common.h"
#include "spice-cmdline.h"
+#include "spice-cmdline.h"
+
/* config */
static gboolean fullscreen = false;
static gboolean version = false;
@@ -52,6 +59,7 @@ struct spice_window {
GtkUIManager *ui;
bool fullscreen;
bool mouse_grabbed;
+ SpiceChannel *channel;
};
struct spice_connection {
@@ -67,6 +75,8 @@ struct spice_connection {
static GMainLoop *mainloop;
static int connections;
static GKeyFile *keyfile;
+static GnomeRRScreen *rrscreen;
+static GnomeRRConfig *rrsaved;
static spice_connection *connection_new(void);
static void connection_connect(spice_connection *conn);
@@ -369,7 +379,6 @@ static gboolean window_state_cb(GtkWidget *widget, GdkEventWindowState *event,
gpointer data)
{
struct spice_window *win = data;
-
if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
win->fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
if (win->fullscreen) {
@@ -618,7 +627,38 @@ static void recent_item_activated_cb(GtkRecentChooser *chooser, gpointer data)
connection_connect(conn);
}
-static spice_window *create_spice_window(spice_connection *conn, int id)
+static void resolution_change(struct spice_window *win)
+{
+}
+
+static void resolution_restore(struct spice_window *win)
+{
+}
+
+static gboolean configure_event_cb(GtkWidget *widget,
+ GdkEventConfigure *event,
+ gpointer data)
+{
+ gboolean resize_guest;
+ struct spice_window *win = data;
+ guint w, h;
+
+ g_object_get(win->spice, "resize-guest", &resize_guest, NULL);
+ if (resize_guest)
+ return FALSE;
+
+ g_object_get(win->channel, "width", &w, "height", &h, NULL);
+ g_message("test: %d %d win %d %d %d", w, h, event->width, event->height, win->fullscreen);
+ if (win->fullscreen) {
+ resolution_change(win);
+ } else {
+ resolution_restore(win);
+ }
+
+ return FALSE;
+}
+
+static spice_window *create_spice_window(spice_connection *conn, int id, SpiceChannel *channel)
{
char title[32];
struct spice_window *win;
@@ -636,6 +676,7 @@ static spice_window *create_spice_window(spice_connection *conn, int id)
memset(win,0,sizeof(*win));
win->id = id;
win->conn = conn;
+ win->channel = channel;
g_message("create window (#%d)", win->id);
/* toplevel */
@@ -682,6 +723,7 @@ static spice_window *create_spice_window(spice_connection *conn, int id)
/* spice display */
win->spice = GTK_WIDGET(spice_display_new(conn->session, id));
+ g_signal_connect(win->spice, "configure-event", G_CALLBACK(configure_event_cb), win);
seq = spice_grab_sequence_new_from_string("Shift+F12");
spice_display_set_grab_keys(SPICE_DISPLAY(win->spice), seq);
spice_grab_sequence_free(seq);
@@ -905,7 +947,7 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
if (conn->wins[id] != NULL)
return;
SPICE_DEBUG("new display channel (#%d)", id);
- conn->wins[id] = create_spice_window(conn, id);
+ conn->wins[id] = create_spice_window(conn, id, channel);
}
if (SPICE_IS_INPUTS_CHANNEL(channel)) {
@@ -1013,6 +1055,28 @@ static void connection_destroy(spice_connection *conn)
g_main_loop_quit(mainloop);
}
+static void
+on_screen_changed(GnomeRRScreen *scr, gpointer data)
+{
+ GError *error = NULL;
+
+ rrsaved = gnome_rr_config_new_current(rrscreen, &error);
+ if (!rrsaved) {
+ g_warning("Can't get current display config: %s", error->message);
+ goto end;
+ }
+ g_clear_error(&error);
+
+ if (!gnome_rr_config_apply_with_time(rrsaved, rrscreen,
+ gtk_get_current_event_time (), &error)) {
+ g_warning("Can't restore display config: %s", error->message);
+ }
+ g_clear_error(&error);
+
+end:
+ g_clear_error(&error);
+}
+
/* ------------------------------------------------------------------ */
static GOptionEntry cmd_entries[] = {
@@ -1058,8 +1122,7 @@ int main(int argc, char *argv[])
if (!g_key_file_load_from_file(keyfile, conf_file,
G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error)) {
SPICE_DEBUG("Couldn't load configuration: %s", error->message);
- g_error_free(error);
- error = NULL;
+ g_clear_error(&error);
}
/* parse opts */
@@ -1082,6 +1145,10 @@ int main(int argc, char *argv[])
g_type_init();
mainloop = g_main_loop_new(NULL, false);
+ rrscreen = gnome_rr_screen_new(gdk_screen_get_default (), &error);
+ g_warn_if_fail(rrscreen != NULL);
+ g_signal_connect(rrscreen, "changed", G_CALLBACK(on_screen_changed), NULL);
+ on_screen_changed(rrscreen, NULL);
conn = connection_new();
spice_cmdline_session_setup(conn->session);