diff options
author | Marc-André Lureau <marcandre.lureau@redhat.com> | 2011-03-16 20:14:09 +0100 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@redhat.com> | 2011-03-17 11:57:53 +0100 |
commit | a2ad8d1bed0cb234e7689ff69d9729231ced34cd (patch) | |
tree | e9fe9d6216163e4274d2879a12752ef586601564 | |
parent | 07a3d7b2cd64686f17394b07a1fecce270005de0 (diff) |
gtk: import display configuration from gnome-desktop
-rw-r--r-- | configure.ac | 20 | ||||
-rw-r--r-- | gtk/Makefile.am | 25 | ||||
-rw-r--r-- | gtk/display/display-name.c | 299 | ||||
-rw-r--r-- | gtk/display/edid-parse.c | 540 | ||||
-rw-r--r-- | gtk/display/edid.h | 194 | ||||
-rw-r--r-- | gtk/display/gnome-rr-config.c | 1982 | ||||
-rw-r--r-- | gtk/display/gnome-rr-config.h | 150 | ||||
-rw-r--r-- | gtk/display/gnome-rr-output-info.c | 246 | ||||
-rw-r--r-- | gtk/display/gnome-rr-private.h | 80 | ||||
-rw-r--r-- | gtk/display/gnome-rr.c | 2121 | ||||
-rw-r--r-- | gtk/display/gnome-rr.h | 202 | ||||
-rw-r--r-- | gtk/spicy.c | 77 |
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); |