summaryrefslogtreecommitdiff
path: root/libmate-desktop
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-12-01 22:07:25 -0300
committerPerberos <[email protected]>2011-12-01 22:07:25 -0300
commitd00aab12b6ace2c3dda3efbc2aaa2646d78a9099 (patch)
tree8b0ca776e9234f7eabf3446f12df9a81abd466d0 /libmate-desktop
downloadmate-desktop-d00aab12b6ace2c3dda3efbc2aaa2646d78a9099.tar.bz2
mate-desktop-d00aab12b6ace2c3dda3efbc2aaa2646d78a9099.tar.xz
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libmate-desktop')
-rw-r--r--libmate-desktop/Makefile.am67
-rw-r--r--libmate-desktop/display-name.c302
-rw-r--r--libmate-desktop/edid-parse.c536
-rw-r--r--libmate-desktop/edid.h184
-rw-r--r--libmate-desktop/libmate/Makefile.am6
-rw-r--r--libmate-desktop/libmate/mate-desktop-item.h312
-rw-r--r--libmate-desktop/libmate/mate-desktop-utils.h51
-rw-r--r--libmate-desktop/libmateui/Makefile.am10
-rw-r--r--libmate-desktop/libmateui/mate-bg-crossfade.h78
-rw-r--r--libmate-desktop/libmateui/mate-bg.h139
-rw-r--r--libmate-desktop/libmateui/mate-desktop-thumbnail.h114
-rw-r--r--libmate-desktop/libmateui/mate-rr-config.h131
-rw-r--r--libmate-desktop/libmateui/mate-rr-labeler.h53
-rw-r--r--libmate-desktop/libmateui/mate-rr.h177
-rw-r--r--libmate-desktop/mate-bg-crossfade.c542
-rw-r--r--libmate-desktop/mate-bg.c2930
-rw-r--r--libmate-desktop/mate-desktop-2.0-uninstalled.pc.in11
-rw-r--r--libmate-desktop/mate-desktop-2.0.pc.in11
-rw-r--r--libmate-desktop/mate-desktop-item.c3872
-rw-r--r--libmate-desktop/mate-desktop-thumbnail.c1302
-rw-r--r--libmate-desktop/mate-desktop-utils.c178
-rw-r--r--libmate-desktop/mate-rr-config.c1913
-rw-r--r--libmate-desktop/mate-rr-labeler.c317
-rw-r--r--libmate-desktop/mate-rr-private.h53
-rw-r--r--libmate-desktop/mate-rr.c1824
-rw-r--r--libmate-desktop/mate-thumbnail-pixbuf-utils.c172
-rw-r--r--libmate-desktop/pnp.ids2029
-rw-r--r--libmate-desktop/private.h38
-rw-r--r--libmate-desktop/test-ditem.c160
29 files changed, 17512 insertions, 0 deletions
diff --git a/libmate-desktop/Makefile.am b/libmate-desktop/Makefile.am
new file mode 100644
index 0000000..322963e
--- /dev/null
+++ b/libmate-desktop/Makefile.am
@@ -0,0 +1,67 @@
+SUBDIRS = libmate libmateui
+
+INCLUDES = \
+ -DMATELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale\"" \
+ -DPNP_IDS=\""$(PNP_IDS)"\" \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED) \
+ $(XLIB_CFLAGS) \
+ $(MATE_DESKTOP_CFLAGS)
+
+lib_LTLIBRARIES = libmate-desktop-2.la
+
+noinst_PROGRAMS = test-ditem
+
+libmate_desktop_2_la_SOURCES = \
+ mate-desktop-item.c \
+ mate-desktop-utils.c \
+ mate-desktop-thumbnail.c \
+ mate-thumbnail-pixbuf-utils.c \
+ mate-bg.c \
+ mate-bg-crossfade.c \
+ display-name.c \
+ mate-rr.c \
+ mate-rr-config.c \
+ mate-rr-labeler.c \
+ mate-rr-private.h \
+ edid-parse.c \
+ edid.h \
+ private.h
+
+libmate_desktop_2_la_LIBADD = \
+ $(XLIB_LIBS) \
+ $(MATE_DESKTOP_LIBS)
+
+libmate_desktop_2_la_LDFLAGS = \
+ -version-info $(LT_VERSION) \
+ -no-undefined
+
+test_ditem_SOURCES = \
+ test-ditem.c
+
+test_ditem_LDADD = \
+ libmate-desktop-2.la \
+ $(XLIB_LIBS) \
+ $(MATE_DESKTOP_LIBS)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = mate-desktop-2.0.pc
+
+pnpdata_DATA_dist = pnp.ids
+if USE_INTERNAL_PNP_IDS
+pnpdatadir = $(datadir)/libmate-desktop
+pnpdata_DATA = pnp.ids
+endif
+
+check:
+ test -s $(top_srcdir)/libmate-desktop/pnp.ids
+
+EXTRA_DIST = \
+ mate-desktop-2.0.pc.in \
+ mate-desktop-2.0-uninstalled.pc.in \
+ $(pnpdata_DATA_dist)
+
+MAINTAINERCLEANFILES = \
+ pnp.ids
+
+-include $(top_srcdir)/git.mk
diff --git a/libmate-desktop/display-name.c b/libmate-desktop/display-name.c
new file mode 100644
index 0000000..cbd59d9
--- /dev/null
+++ b/libmate-desktop/display-name.c
@@ -0,0 +1,302 @@
+/*
+ * 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 <[email protected]> */
+
+#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
+ * mate-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[3] == '\t')
+ {
+ 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/libmate-desktop/edid-parse.c b/libmate-desktop/edid-parse.c
new file mode 100644
index 0000000..842e327
--- /dev/null
+++ b/libmate-desktop/edid-parse.c
@@ -0,0 +1,536 @@
+/*
+ * 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 <[email protected]> */
+
+#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;
+
+ 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/libmate-desktop/edid.h b/libmate-desktop/edid.h
new file mode 100644
index 0000000..ec0793e
--- /dev/null
+++ b/libmate-desktop/edid.h
@@ -0,0 +1,184 @@
+/* edid.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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 <[email protected]>
+ */
+
+#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 /* !EDID_H */
diff --git a/libmate-desktop/libmate/Makefile.am b/libmate-desktop/libmate/Makefile.am
new file mode 100644
index 0000000..6c080f7
--- /dev/null
+++ b/libmate-desktop/libmate/Makefile.am
@@ -0,0 +1,6 @@
+libmate_desktopdir = $(includedir)/mate-desktop-2.0/libmate
+libmate_desktop_HEADERS = \
+ mate-desktop-utils.h \
+ mate-desktop-item.h
+
+-include $(top_srcdir)/git.mk
diff --git a/libmate-desktop/libmate/mate-desktop-item.h b/libmate-desktop/libmate/mate-desktop-item.h
new file mode 100644
index 0000000..683db13
--- /dev/null
+++ b/libmate-desktop/libmate/mate-desktop-item.h
@@ -0,0 +1,312 @@
+/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* mate-ditem.h - MATE Desktop File Representation
+
+ Copyright (C) 1999, 2000 Red Hat Inc.
+ Copyright (C) 2001 Sid Vicious
+ All rights reserved.
+
+ This file is part of the Mate Library.
+
+ The Mate 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 Mate 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 Mate 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. */
+/*
+ @NOTATION@
+ */
+
+#ifndef MATE_DITEM_H
+#define MATE_DITEM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ MATE_DESKTOP_ITEM_TYPE_NULL = 0 /* This means its NULL, that is, not
+ * set */,
+ MATE_DESKTOP_ITEM_TYPE_OTHER /* This means it's not one of the below
+ strings types, and you must get the
+ Type attribute. */,
+
+ /* These are the standard compliant types: */
+ MATE_DESKTOP_ITEM_TYPE_APPLICATION,
+ MATE_DESKTOP_ITEM_TYPE_LINK,
+ MATE_DESKTOP_ITEM_TYPE_FSDEVICE,
+ MATE_DESKTOP_ITEM_TYPE_MIME_TYPE,
+ MATE_DESKTOP_ITEM_TYPE_DIRECTORY,
+ MATE_DESKTOP_ITEM_TYPE_SERVICE,
+ MATE_DESKTOP_ITEM_TYPE_SERVICE_TYPE
+} MateDesktopItemType;
+
+typedef enum {
+ MATE_DESKTOP_ITEM_UNCHANGED = 0,
+ MATE_DESKTOP_ITEM_CHANGED = 1,
+ MATE_DESKTOP_ITEM_DISAPPEARED = 2
+} MateDesktopItemStatus;
+
+#define MATE_TYPE_DESKTOP_ITEM (mate_desktop_item_get_type ())
+GType mate_desktop_item_get_type (void);
+
+typedef struct _MateDesktopItem MateDesktopItem;
+
+/* standard */
+#define MATE_DESKTOP_ITEM_ENCODING "Encoding" /* string */
+#define MATE_DESKTOP_ITEM_VERSION "Version" /* numeric */
+#define MATE_DESKTOP_ITEM_NAME "Name" /* localestring */
+#define MATE_DESKTOP_ITEM_GENERIC_NAME "GenericName" /* localestring */
+#define MATE_DESKTOP_ITEM_TYPE "Type" /* string */
+#define MATE_DESKTOP_ITEM_FILE_PATTERN "FilePattern" /* regexp(s) */
+#define MATE_DESKTOP_ITEM_TRY_EXEC "TryExec" /* string */
+#define MATE_DESKTOP_ITEM_NO_DISPLAY "NoDisplay" /* boolean */
+#define MATE_DESKTOP_ITEM_COMMENT "Comment" /* localestring */
+#define MATE_DESKTOP_ITEM_EXEC "Exec" /* string */
+#define MATE_DESKTOP_ITEM_ACTIONS "Actions" /* strings */
+#define MATE_DESKTOP_ITEM_ICON "Icon" /* string */
+#define MATE_DESKTOP_ITEM_MINI_ICON "MiniIcon" /* string */
+#define MATE_DESKTOP_ITEM_HIDDEN "Hidden" /* boolean */
+#define MATE_DESKTOP_ITEM_PATH "Path" /* string */
+#define MATE_DESKTOP_ITEM_TERMINAL "Terminal" /* boolean */
+#define MATE_DESKTOP_ITEM_TERMINAL_OPTIONS "TerminalOptions" /* string */
+#define MATE_DESKTOP_ITEM_SWALLOW_TITLE "SwallowTitle" /* string */
+#define MATE_DESKTOP_ITEM_SWALLOW_EXEC "SwallowExec" /* string */
+#define MATE_DESKTOP_ITEM_MIME_TYPE "MimeType" /* regexp(s) */
+#define MATE_DESKTOP_ITEM_PATTERNS "Patterns" /* regexp(s) */
+#define MATE_DESKTOP_ITEM_DEFAULT_APP "DefaultApp" /* string */
+#define MATE_DESKTOP_ITEM_DEV "Dev" /* string */
+#define MATE_DESKTOP_ITEM_FS_TYPE "FSType" /* string */
+#define MATE_DESKTOP_ITEM_MOUNT_POINT "MountPoint" /* string */
+#define MATE_DESKTOP_ITEM_READ_ONLY "ReadOnly" /* boolean */
+#define MATE_DESKTOP_ITEM_UNMOUNT_ICON "UnmountIcon" /* string */
+#define MATE_DESKTOP_ITEM_SORT_ORDER "SortOrder" /* strings */
+#define MATE_DESKTOP_ITEM_URL "URL" /* string */
+#define MATE_DESKTOP_ITEM_DOC_PATH "X-MATE-DocPath" /* string */
+
+/* The vfolder proposal */
+#define MATE_DESKTOP_ITEM_CATEGORIES "Categories" /* string */
+#define MATE_DESKTOP_ITEM_ONLY_SHOW_IN "OnlyShowIn" /* string */
+
+typedef enum {
+ /* Use the TryExec field to determine if this should be loaded */
+ MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS = 1<<0,
+ MATE_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS = 1<<1
+} MateDesktopItemLoadFlags;
+
+typedef enum {
+ /* Never launch more instances even if the app can only
+ * handle one file and we have passed many */
+ MATE_DESKTOP_ITEM_LAUNCH_ONLY_ONE = 1<<0,
+ /* Use current directory instead of home directory */
+ MATE_DESKTOP_ITEM_LAUNCH_USE_CURRENT_DIR = 1<<1,
+ /* Append the list of URIs to the command if no Exec
+ * parameter is specified, instead of launching the
+ * app without parameters. */
+ MATE_DESKTOP_ITEM_LAUNCH_APPEND_URIS = 1<<2,
+ /* Same as above but instead append local paths */
+ MATE_DESKTOP_ITEM_LAUNCH_APPEND_PATHS = 1<<3,
+ /* Don't automatically reap child process. */
+ MATE_DESKTOP_ITEM_LAUNCH_DO_NOT_REAP_CHILD = 1<<4
+} MateDesktopItemLaunchFlags;
+
+typedef enum {
+ /* Don't check the kde directories */
+ MATE_DESKTOP_ITEM_ICON_NO_KDE = 1<<0
+} MateDesktopItemIconFlags;
+
+typedef enum {
+ MATE_DESKTOP_ITEM_ERROR_NO_FILENAME /* No filename set or given on save */,
+ MATE_DESKTOP_ITEM_ERROR_UNKNOWN_ENCODING /* Unknown encoding of the file */,
+ MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN /* Cannot open file */,
+ MATE_DESKTOP_ITEM_ERROR_NO_EXEC_STRING /* Cannot launch due to no execute string */,
+ MATE_DESKTOP_ITEM_ERROR_BAD_EXEC_STRING /* Cannot launch due to bad execute string */,
+ MATE_DESKTOP_ITEM_ERROR_NO_URL /* No URL on a url entry*/,
+ MATE_DESKTOP_ITEM_ERROR_NOT_LAUNCHABLE /* Not a launchable type of item */,
+ MATE_DESKTOP_ITEM_ERROR_INVALID_TYPE /* Not of type application/x-mate-app-info */
+} MateDesktopItemError;
+
+/* Note that functions can also return the G_FILE_ERROR_* errors */
+
+#define MATE_DESKTOP_ITEM_ERROR mate_desktop_item_error_quark ()
+GQuark mate_desktop_item_error_quark (void);
+
+/* Returned item from new*() and copy() methods have a refcount of 1 */
+MateDesktopItem * mate_desktop_item_new (void);
+MateDesktopItem * mate_desktop_item_new_from_file (const char *file,
+ MateDesktopItemLoadFlags flags,
+ GError **error);
+MateDesktopItem * mate_desktop_item_new_from_uri (const char *uri,
+ MateDesktopItemLoadFlags flags,
+ GError **error);
+MateDesktopItem * mate_desktop_item_new_from_string (const char *uri,
+ const char *string,
+ gssize length,
+ MateDesktopItemLoadFlags flags,
+ GError **error);
+MateDesktopItem * mate_desktop_item_new_from_basename (const char *basename,
+ MateDesktopItemLoadFlags flags,
+ GError **error);
+MateDesktopItem * mate_desktop_item_copy (const MateDesktopItem *item);
+
+/* if under is NULL save in original location */
+gboolean mate_desktop_item_save (MateDesktopItem *item,
+ const char *under,
+ gboolean force,
+ GError **error);
+MateDesktopItem * mate_desktop_item_ref (MateDesktopItem *item);
+void mate_desktop_item_unref (MateDesktopItem *item);
+int mate_desktop_item_launch (const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ GError **error);
+int mate_desktop_item_launch_with_env (const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ char **envp,
+ GError **error);
+
+int mate_desktop_item_launch_on_screen (const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ GdkScreen *screen,
+ int workspace,
+ GError **error);
+
+/* A list of files or urls dropped onto an icon */
+int mate_desktop_item_drop_uri_list (const MateDesktopItem *item,
+ const char *uri_list,
+ MateDesktopItemLaunchFlags flags,
+ GError **error);
+
+int mate_desktop_item_drop_uri_list_with_env (const MateDesktopItem *item,
+ const char *uri_list,
+ MateDesktopItemLaunchFlags flags,
+ char **envp,
+ GError **error);
+
+gboolean mate_desktop_item_exists (const MateDesktopItem *item);
+
+MateDesktopItemType mate_desktop_item_get_entry_type (const MateDesktopItem *item);
+/* You could also just use the set_string on the TYPE argument */
+void mate_desktop_item_set_entry_type (MateDesktopItem *item,
+ MateDesktopItemType type);
+
+/* Get current location on disk */
+const char * mate_desktop_item_get_location (const MateDesktopItem *item);
+void mate_desktop_item_set_location (MateDesktopItem *item,
+ const char *location);
+void mate_desktop_item_set_location_file (MateDesktopItem *item,
+ const char *file);
+MateDesktopItemStatus mate_desktop_item_get_file_status (const MateDesktopItem *item);
+
+/*
+ * Get the icon, this is not as simple as getting the Icon attr as it actually tries to find
+ * it and returns %NULL if it can't
+ */
+char * mate_desktop_item_get_icon (const MateDesktopItem *item,
+ GtkIconTheme *icon_theme);
+
+char * mate_desktop_item_find_icon (GtkIconTheme *icon_theme,
+ const char *icon,
+ /* size is only a suggestion */
+ int desired_size,
+ int flags);
+
+
+/*
+ * Reading/Writing different sections, NULL is the standard section
+ */
+gboolean mate_desktop_item_attr_exists (const MateDesktopItem *item,
+ const char *attr);
+
+/*
+ * String type
+ */
+const char * mate_desktop_item_get_string (const MateDesktopItem *item,
+ const char *attr);
+
+void mate_desktop_item_set_string (MateDesktopItem *item,
+ const char *attr,
+ const char *value);
+
+const char * mate_desktop_item_get_attr_locale (const MateDesktopItem *item,
+ const char *attr);
+
+/*
+ * LocaleString type
+ */
+const char * mate_desktop_item_get_localestring (const MateDesktopItem *item,
+ const char *attr);
+const char * mate_desktop_item_get_localestring_lang (const MateDesktopItem *item,
+ const char *attr,
+ const char *language);
+/* use g_list_free only */
+GList * mate_desktop_item_get_languages (const MateDesktopItem *item,
+ const char *attr);
+
+void mate_desktop_item_set_localestring (MateDesktopItem *item,
+ const char *attr,
+ const char *value);
+void mate_desktop_item_set_localestring_lang (MateDesktopItem *item,
+ const char *attr,
+ const char *language,
+ const char *value);
+void mate_desktop_item_clear_localestring(MateDesktopItem *item,
+ const char *attr);
+
+/*
+ * Strings, Regexps types
+ */
+
+/* use mate_desktop_item_free_string_list */
+char ** mate_desktop_item_get_strings (const MateDesktopItem *item,
+ const char *attr);
+
+void mate_desktop_item_set_strings (MateDesktopItem *item,
+ const char *attr,
+ char **strings);
+
+/*
+ * Boolean type
+ */
+gboolean mate_desktop_item_get_boolean (const MateDesktopItem *item,
+ const char *attr);
+
+void mate_desktop_item_set_boolean (MateDesktopItem *item,
+ const char *attr,
+ gboolean value);
+
+/*
+ * Xserver time of user action that caused the application launch to start.
+ */
+void mate_desktop_item_set_launch_time (MateDesktopItem *item,
+ guint32 timestamp);
+
+/*
+ * Clearing attributes
+ */
+#define mate_desktop_item_clear_attr(item,attr) \
+ mate_desktop_item_set_string(item,attr,NULL)
+void mate_desktop_item_clear_section (MateDesktopItem *item,
+ const char *section);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MATE_DITEM_H */
diff --git a/libmate-desktop/libmate/mate-desktop-utils.h b/libmate-desktop/libmate/mate-desktop-utils.h
new file mode 100644
index 0000000..f3904ba
--- /dev/null
+++ b/libmate-desktop/libmate/mate-desktop-utils.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* mate-ditem.h - Utilities for the MATE Desktop
+
+ Copyright (C) 1998 Tom Tromey
+ All rights reserved.
+
+ This file is part of the Mate Library.
+
+ The Mate 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 Mate 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 Mate 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. */
+/*
+ @NOTATION@
+ */
+
+#ifndef MATE_DESKTOP_UTILS_H
+#define MATE_DESKTOP_UTILS_H
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error mate-desktop-utils is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including mate-desktop-utils.h
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* prepend the terminal command to a vector */
+void mate_desktop_prepend_terminal_to_vector (int *argc, char ***argv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MATE_DITEM_H */
diff --git a/libmate-desktop/libmateui/Makefile.am b/libmate-desktop/libmateui/Makefile.am
new file mode 100644
index 0000000..ca42525
--- /dev/null
+++ b/libmate-desktop/libmateui/Makefile.am
@@ -0,0 +1,10 @@
+libmateui_desktopdir = $(includedir)/mate-desktop-2.0/libmateui
+libmateui_desktop_HEADERS = \
+ mate-bg.h \
+ mate-bg-crossfade.h \
+ mate-desktop-thumbnail.h \
+ mate-rr.h \
+ mate-rr-config.h \
+ mate-rr-labeler.h
+
+-include $(top_srcdir)/git.mk
diff --git a/libmate-desktop/libmateui/mate-bg-crossfade.h b/libmate-desktop/libmateui/mate-bg-crossfade.h
new file mode 100644
index 0000000..6da8795
--- /dev/null
+++ b/libmate-desktop/libmateui/mate-bg-crossfade.h
@@ -0,0 +1,78 @@
+/* mate-bg-crossfade.h - fade window background between two pixmaps
+
+ Copyright 2008, Red Hat, Inc.
+
+ This file is part of the Mate Library.
+
+ The Mate 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 Mate 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 Mate 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: Ray Strode <[email protected]>
+*/
+
+#ifndef __MATE_BG_CROSSFADE_H__
+#define __MATE_BG_CROSSFADE_H__
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error MateBGCrossfade is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including mate-bg-crossfade.h
+#endif
+
+#include <gdk/gdk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MATE_TYPE_BG_CROSSFADE (mate_bg_crossfade_get_type ())
+#define MATE_BG_CROSSFADE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MATE_TYPE_BG_CROSSFADE, MateBGCrossfade))
+#define MATE_BG_CROSSFADE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MATE_TYPE_BG_CROSSFADE, MateBGCrossfadeClass))
+#define MATE_IS_BG_CROSSFADE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MATE_TYPE_BG_CROSSFADE))
+#define MATE_IS_BG_CROSSFADE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MATE_TYPE_BG_CROSSFADE))
+#define MATE_BG_CROSSFADE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MATE_TYPE_BG_CROSSFADE, MateBGCrossfadeClass))
+
+typedef struct _MateBGCrossfadePrivate MateBGCrossfadePrivate;
+typedef struct _MateBGCrossfade MateBGCrossfade;
+typedef struct _MateBGCrossfadeClass MateBGCrossfadeClass;
+
+struct _MateBGCrossfade
+{
+ GObject parent_object;
+
+ MateBGCrossfadePrivate *priv;
+};
+
+struct _MateBGCrossfadeClass
+{
+ GObjectClass parent_class;
+
+ void (* finished) (MateBGCrossfade *fade, GdkWindow *window);
+};
+
+GType mate_bg_crossfade_get_type (void);
+MateBGCrossfade *mate_bg_crossfade_new (int width, int height);
+gboolean mate_bg_crossfade_set_start_pixmap (MateBGCrossfade *fade,
+ GdkPixmap *pixmap);
+gboolean mate_bg_crossfade_set_end_pixmap (MateBGCrossfade *fade,
+ GdkPixmap *pixmap);
+void mate_bg_crossfade_start (MateBGCrossfade *fade,
+ GdkWindow *window);
+gboolean mate_bg_crossfade_is_started (MateBGCrossfade *fade);
+void mate_bg_crossfade_stop (MateBGCrossfade *fade);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libmate-desktop/libmateui/mate-bg.h b/libmate-desktop/libmateui/mate-bg.h
new file mode 100644
index 0000000..0506d86
--- /dev/null
+++ b/libmate-desktop/libmateui/mate-bg.h
@@ -0,0 +1,139 @@
+/* mate-bg.h -
+
+ Copyright 2007, Red Hat, Inc.
+
+ This file is part of the Mate Library.
+
+ The Mate 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 Mate 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 Mate 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 <[email protected]>
+*/
+
+#ifndef __MATE_BG_H__
+#define __MATE_BG_H__
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error MateBG is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including mate-bg.h
+#endif
+
+#include <gdk/gdk.h>
+#include <mateconf/mateconf-client.h>
+#include <libmateui/mate-desktop-thumbnail.h>
+#include <libmateui/mate-bg-crossfade.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MATE_TYPE_BG (mate_bg_get_type ())
+#define MATE_BG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MATE_TYPE_BG, MateBG))
+#define MATE_BG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MATE_TYPE_BG, MateBGClass))
+#define MATE_IS_BG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MATE_TYPE_BG))
+#define MATE_IS_BG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MATE_TYPE_BG))
+#define MATE_BG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MATE_TYPE_BG, MateBGClass))
+
+#define MATE_BG_KEY_DIR "/desktop/mate/background"
+
+typedef struct _MateBG MateBG;
+typedef struct _MateBGClass MateBGClass;
+
+typedef enum {
+ MATE_BG_COLOR_SOLID,
+ MATE_BG_COLOR_H_GRADIENT,
+ MATE_BG_COLOR_V_GRADIENT
+} MateBGColorType;
+
+typedef enum {
+ MATE_BG_PLACEMENT_TILED,
+ MATE_BG_PLACEMENT_ZOOMED,
+ MATE_BG_PLACEMENT_CENTERED,
+ MATE_BG_PLACEMENT_SCALED,
+ MATE_BG_PLACEMENT_FILL_SCREEN,
+ MATE_BG_PLACEMENT_SPANNED
+} MateBGPlacement;
+
+GType mate_bg_get_type (void);
+MateBG * mate_bg_new (void);
+void mate_bg_load_from_preferences (MateBG *bg,
+ MateConfClient *client);
+void mate_bg_save_to_preferences (MateBG *bg,
+ MateConfClient *client);
+/* Setters */
+void mate_bg_set_filename (MateBG *bg,
+ const char *filename);
+void mate_bg_set_placement (MateBG *bg,
+ MateBGPlacement placement);
+void mate_bg_set_color (MateBG *bg,
+ MateBGColorType type,
+ GdkColor *primary,
+ GdkColor *secondary);
+/* Getters */
+MateBGPlacement mate_bg_get_placement (MateBG *bg);
+void mate_bg_get_color (MateBG *bg,
+ MateBGColorType *type,
+ GdkColor *primary,
+ GdkColor *secondary);
+const gchar * mate_bg_get_filename (MateBG *bg);
+
+/* Drawing and thumbnailing */
+void mate_bg_draw (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkScreen *screen,
+ gboolean is_root);
+GdkPixmap * mate_bg_create_pixmap (MateBG *bg,
+ GdkWindow *window,
+ int width,
+ int height,
+ gboolean root);
+gboolean mate_bg_get_image_size (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ int best_width,
+ int best_height,
+ int *width,
+ int *height);
+GdkPixbuf * mate_bg_create_thumbnail (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height);
+gboolean mate_bg_is_dark (MateBG *bg,
+ int dest_width,
+ int dest_height);
+gboolean mate_bg_has_multiple_sizes (MateBG *bg);
+gboolean mate_bg_changes_with_time (MateBG *bg);
+GdkPixbuf * mate_bg_create_frame_thumbnail (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height,
+ int frame_num);
+
+/* Set a pixmap as root - not a MateBG method. At some point
+ * if we decide to stabilize the API then we may want to make
+ * these object methods, drop mate_bg_create_pixmap, etc.
+ */
+void mate_bg_set_pixmap_as_root (GdkScreen *screen,
+ GdkPixmap *pixmap);
+
+MateBGCrossfade *mate_bg_set_pixmap_as_root_with_crossfade (GdkScreen *screen,
+ GdkPixmap *pixmap);
+GdkPixmap *mate_bg_get_pixmap_from_root (GdkScreen *screen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libmate-desktop/libmateui/mate-desktop-thumbnail.h b/libmate-desktop/libmateui/mate-desktop-thumbnail.h
new file mode 100644
index 0000000..cc59a10
--- /dev/null
+++ b/libmate-desktop/libmateui/mate-desktop-thumbnail.h
@@ -0,0 +1,114 @@
+/*
+ * mate-thumbnail.h: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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: Alexander Larsson <[email protected]>
+ */
+
+#ifndef MATE_DESKTOP_THUMBNAIL_H
+#define MATE_DESKTOP_THUMBNAIL_H
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error MateDesktopThumbnail is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including mate-desktop-thumbnail.h
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <time.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL,
+ MATE_DESKTOP_THUMBNAIL_SIZE_LARGE
+} MateDesktopThumbnailSize;
+
+#define MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY (mate_desktop_thumbnail_factory_get_type ())
+#define MATE_DESKTOP_THUMBNAIL_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY, MateDesktopThumbnailFactory))
+#define MATE_DESKTOP_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY, MateDesktopThumbnailFactoryClass))
+#define MATE_DESKTOP_IS_THUMBNAIL_FACTORY(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+#define MATE_DESKTOP_IS_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+
+typedef struct _MateDesktopThumbnailFactory MateDesktopThumbnailFactory;
+typedef struct _MateDesktopThumbnailFactoryClass MateDesktopThumbnailFactoryClass;
+typedef struct _MateDesktopThumbnailFactoryPrivate MateDesktopThumbnailFactoryPrivate;
+
+struct _MateDesktopThumbnailFactory {
+ GObject parent;
+
+ MateDesktopThumbnailFactoryPrivate *priv;
+};
+
+struct _MateDesktopThumbnailFactoryClass {
+ GObjectClass parent;
+};
+
+GType mate_desktop_thumbnail_factory_get_type (void);
+MateDesktopThumbnailFactory *mate_desktop_thumbnail_factory_new (MateDesktopThumbnailSize size);
+
+char * mate_desktop_thumbnail_factory_lookup (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+
+gboolean mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+gboolean mate_desktop_thumbnail_factory_can_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type,
+ time_t mtime);
+GdkPixbuf * mate_desktop_thumbnail_factory_generate_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type);
+void mate_desktop_thumbnail_factory_save_thumbnail (MateDesktopThumbnailFactory *factory,
+ GdkPixbuf *thumbnail,
+ const char *uri,
+ time_t original_mtime);
+void mate_desktop_thumbnail_factory_create_failed_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+
+
+/* Thumbnailing utils: */
+gboolean mate_desktop_thumbnail_has_uri (GdkPixbuf *pixbuf,
+ const char *uri);
+gboolean mate_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
+ const char *uri,
+ time_t mtime);
+char * mate_desktop_thumbnail_md5 (const char *uri);
+char * mate_desktop_thumbnail_path_for_uri (const char *uri,
+ MateDesktopThumbnailSize size);
+
+
+/* Pixbuf utils */
+
+GdkPixbuf *mate_desktop_thumbnail_scale_down_pixbuf (GdkPixbuf *pixbuf,
+ int dest_width,
+ int dest_height);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MATE_DESKTOP_THUMBNAIL_H */
diff --git a/libmate-desktop/libmateui/mate-rr-config.h b/libmate-desktop/libmateui/mate-rr-config.h
new file mode 100644
index 0000000..1ed31c8
--- /dev/null
+++ b/libmate-desktop/libmateui/mate-rr-config.h
@@ -0,0 +1,131 @@
+/* mate-rr-config.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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 <[email protected]>
+ */
+#ifndef MATE_RR_CONFIG_H
+#define MATE_RR_CONFIG_H
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error mate-rr-config.h is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including mate-rr-config.h
+#endif
+
+#include <libmateui/mate-rr.h>
+#include <glib.h>
+
+typedef struct MateOutputInfo MateOutputInfo;
+typedef struct MateRRConfig MateRRConfig;
+
+/* FIXME:
+ *
+ * This structure is a Frankenstein monster where all of the fields
+ * are generated by the system, but some of them can be changed by
+ * the client.
+ */
+
+struct MateOutputInfo
+{
+ char * name;
+
+ gboolean on; /* whether there is a CRTC assigned to this output (i.e. a signal is being sent to it) */
+ int width;
+ int height;
+ int rate;
+ int x;
+ int y;
+ MateRRRotation rotation;
+
+ gboolean connected; /* whether the output is physically connected to a monitor */
+ char vendor[4];
+ guint product;
+ guint serial;
+ double aspect;
+ int pref_width;
+ int pref_height;
+ char * display_name;
+ gboolean primary;
+
+ gpointer user_data;
+};
+
+struct MateRRConfig
+{
+ /* "clone" means that 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 clone;
+
+ MateOutputInfo ** outputs;
+};
+
+MateRRConfig *mate_rr_config_new_current (MateRRScreen *screen);
+MateRRConfig *mate_rr_config_new_stored (MateRRScreen *screen,
+ GError **error);
+void mate_rr_config_free (MateRRConfig *configuration);
+gboolean mate_rr_config_match (MateRRConfig *config1,
+ MateRRConfig *config2);
+gboolean mate_rr_config_equal (MateRRConfig *config1,
+ MateRRConfig *config2);
+gboolean mate_rr_config_save (MateRRConfig *configuration,
+ GError **error);
+void mate_rr_config_sanitize (MateRRConfig *configuration);
+
+#ifndef MATE_DISABLE_DEPRECATED
+gboolean mate_rr_config_apply (MateRRConfig *configuration,
+ MateRRScreen *screen,
+ GError **error);
+#endif
+
+gboolean mate_rr_config_apply_with_time (MateRRConfig *configuration,
+ MateRRScreen *screen,
+ guint32 timestamp,
+ GError **error);
+
+#ifndef MATE_DISABLE_DEPRECATED
+gboolean mate_rr_config_apply_stored (MateRRScreen *screen,
+ GError **error);
+#endif
+
+#ifndef MATE_DISABLE_DEPRECATED
+gboolean mate_rr_config_apply_from_filename (MateRRScreen *screen,
+ const char *filename,
+ GError **error);
+#endif
+
+gboolean mate_rr_config_apply_from_filename_with_time (MateRRScreen *screen,
+ const char *filename,
+ guint32 timestamp,
+ GError **error);
+
+gboolean mate_rr_config_applicable (MateRRConfig *configuration,
+ MateRRScreen *screen,
+ GError **error);
+
+char *mate_rr_config_get_backup_filename (void);
+char *mate_rr_config_get_intended_filename (void);
+
+/* A utility function that isn't really in the spirit of this file, but I don't
+ * don't know a better place for it.
+ */
+MateRRMode **mate_rr_create_clone_modes (MateRRScreen *screen);
+
+#endif
diff --git a/libmate-desktop/libmateui/mate-rr-labeler.h b/libmate-desktop/libmateui/mate-rr-labeler.h
new file mode 100644
index 0000000..0bcbe71
--- /dev/null
+++ b/libmate-desktop/libmateui/mate-rr-labeler.h
@@ -0,0 +1,53 @@
+/* mate-rr-labeler.h - Utility to label monitors to identify them
+ * while they are being configured.
+ *
+ * Copyright 2008, Novell, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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: Federico Mena-Quintero <[email protected]>
+ */
+
+#ifndef MATE_RR_LABELER_H
+#define MATE_RR_LABELER_H
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error MateRR is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including materr.h
+#endif
+
+#include <libmateui/mate-rr-config.h>
+
+#define MATE_TYPE_RR_LABELER (mate_rr_labeler_get_type ())
+#define MATE_RR_LABELER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MATE_TYPE_RR_LABELER, MateRRLabeler))
+#define MATE_RR_LABELER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MATE_TYPE_RR_LABELER, MateRRLabelerClass))
+#define MATE_IS_RR_LABELER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MATE_TYPE_RR_LABELER))
+#define MATE_IS_RR_LABELER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MATE_TYPE_RR_LABELER))
+#define MATE_RR_LABELER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MATE_TYPE_RR_LABELER, MateRRLabelerClass))
+
+typedef struct _MateRRLabeler MateRRLabeler;
+typedef struct _MateRRLabelerClass MateRRLabelerClass;
+
+GType mate_rr_labeler_get_type (void);
+
+MateRRLabeler *mate_rr_labeler_new (MateRRConfig *config);
+
+void mate_rr_labeler_hide (MateRRLabeler *labeler);
+
+void mate_rr_labeler_get_color_for_output (MateRRLabeler *labeler, MateOutputInfo *output, GdkColor *color_out);
+
+#endif
diff --git a/libmate-desktop/libmateui/mate-rr.h b/libmate-desktop/libmateui/mate-rr.h
new file mode 100644
index 0000000..ab780e0
--- /dev/null
+++ b/libmate-desktop/libmateui/mate-rr.h
@@ -0,0 +1,177 @@
+/* randrwrap.h
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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 <[email protected]>
+ */
+#ifndef RANDR_WRAP_H
+#define RANDR_WRAP_H
+
+#ifndef MATE_DESKTOP_USE_UNSTABLE_API
+#error MateRR is unstable API. You must define MATE_DESKTOP_USE_UNSTABLE_API before including materr.h
+#endif
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+typedef struct MateRRScreen MateRRScreen;
+typedef struct MateRROutput MateRROutput;
+typedef struct MateRRCrtc MateRRCrtc;
+typedef struct MateRRMode MateRRMode;
+
+typedef void (* MateRRScreenChanged) (MateRRScreen *screen, gpointer data);
+
+typedef enum
+{
+ MATE_RR_ROTATION_0 = (1 << 0),
+ MATE_RR_ROTATION_90 = (1 << 1),
+ MATE_RR_ROTATION_180 = (1 << 2),
+ MATE_RR_ROTATION_270 = (1 << 3),
+ MATE_RR_REFLECT_X = (1 << 4),
+ MATE_RR_REFLECT_Y = (1 << 5)
+} MateRRRotation;
+
+/* Error codes */
+
+#define MATE_RR_ERROR (mate_rr_error_quark ())
+
+GQuark mate_rr_error_quark (void);
+
+typedef enum {
+ MATE_RR_ERROR_UNKNOWN, /* generic "fail" */
+ MATE_RR_ERROR_NO_RANDR_EXTENSION, /* RANDR extension is not present */
+ MATE_RR_ERROR_RANDR_ERROR, /* generic/undescribed error from the underlying XRR API */
+ MATE_RR_ERROR_BOUNDS_ERROR, /* requested bounds of a CRTC are outside the maximum size */
+ MATE_RR_ERROR_CRTC_ASSIGNMENT, /* could not assign CRTCs to outputs */
+ MATE_RR_ERROR_NO_MATCHING_CONFIG, /* none of the saved configurations matched the current configuration */
+} MateRRError;
+
+#define MATE_RR_CONNECTOR_TYPE_PANEL "Panel" /* This is a laptop's built-in LCD */
+
+/* MateRRScreen */
+MateRRScreen * mate_rr_screen_new (GdkScreen *screen,
+ MateRRScreenChanged callback,
+ gpointer data,
+ GError **error);
+void mate_rr_screen_destroy (MateRRScreen *screen);
+MateRROutput **mate_rr_screen_list_outputs (MateRRScreen *screen);
+MateRRCrtc ** mate_rr_screen_list_crtcs (MateRRScreen *screen);
+MateRRMode ** mate_rr_screen_list_modes (MateRRScreen *screen);
+MateRRMode ** mate_rr_screen_list_clone_modes (MateRRScreen *screen);
+void mate_rr_screen_set_size (MateRRScreen *screen,
+ int width,
+ int height,
+ int mm_width,
+ int mm_height);
+MateRRCrtc * mate_rr_screen_get_crtc_by_id (MateRRScreen *screen,
+ guint32 id);
+gboolean mate_rr_screen_refresh (MateRRScreen *screen,
+ GError **error);
+MateRROutput * mate_rr_screen_get_output_by_id (MateRRScreen *screen,
+ guint32 id);
+MateRROutput * mate_rr_screen_get_output_by_name (MateRRScreen *screen,
+ const char *name);
+void mate_rr_screen_get_ranges (MateRRScreen *screen,
+ int *min_width,
+ int *max_width,
+ int *min_height,
+ int *max_height);
+void mate_rr_screen_get_timestamps (MateRRScreen *screen,
+ guint32 *change_timestamp_ret,
+ guint32 *config_timestamp_ret);
+
+void mate_rr_screen_set_primary_output (MateRRScreen *screen,
+ MateRROutput *output);
+
+/* MateRROutput */
+guint32 mate_rr_output_get_id (MateRROutput *output);
+const char * mate_rr_output_get_name (MateRROutput *output);
+gboolean mate_rr_output_is_connected (MateRROutput *output);
+int mate_rr_output_get_size_inches (MateRROutput *output);
+int mate_rr_output_get_width_mm (MateRROutput *outout);
+int mate_rr_output_get_height_mm (MateRROutput *output);
+const guint8 * mate_rr_output_get_edid_data (MateRROutput *output);
+MateRRCrtc ** mate_rr_output_get_possible_crtcs (MateRROutput *output);
+MateRRMode * mate_rr_output_get_current_mode (MateRROutput *output);
+MateRRCrtc * mate_rr_output_get_crtc (MateRROutput *output);
+const char * mate_rr_output_get_connector_type (MateRROutput *output);
+gboolean mate_rr_output_is_laptop (MateRROutput *output);
+void mate_rr_output_get_position (MateRROutput *output,
+ int *x,
+ int *y);
+gboolean mate_rr_output_can_clone (MateRROutput *output,
+ MateRROutput *clone);
+MateRRMode ** mate_rr_output_list_modes (MateRROutput *output);
+MateRRMode * mate_rr_output_get_preferred_mode (MateRROutput *output);
+gboolean mate_rr_output_supports_mode (MateRROutput *output,
+ MateRRMode *mode);
+gboolean mate_rr_output_get_is_primary (MateRROutput *output);
+
+/* MateRRMode */
+guint32 mate_rr_mode_get_id (MateRRMode *mode);
+guint mate_rr_mode_get_width (MateRRMode *mode);
+guint mate_rr_mode_get_height (MateRRMode *mode);
+int mate_rr_mode_get_freq (MateRRMode *mode);
+
+/* MateRRCrtc */
+guint32 mate_rr_crtc_get_id (MateRRCrtc *crtc);
+
+#ifndef MATE_DISABLE_DEPRECATED
+gboolean mate_rr_crtc_set_config (MateRRCrtc *crtc,
+ int x,
+ int y,
+ MateRRMode *mode,
+ MateRRRotation rotation,
+ MateRROutput **outputs,
+ int n_outputs,
+ GError **error);
+#endif
+
+gboolean mate_rr_crtc_set_config_with_time (MateRRCrtc *crtc,
+ guint32 timestamp,
+ int x,
+ int y,
+ MateRRMode *mode,
+ MateRRRotation rotation,
+ MateRROutput **outputs,
+ int n_outputs,
+ GError **error);
+gboolean mate_rr_crtc_can_drive_output (MateRRCrtc *crtc,
+ MateRROutput *output);
+MateRRMode * mate_rr_crtc_get_current_mode (MateRRCrtc *crtc);
+void mate_rr_crtc_get_position (MateRRCrtc *crtc,
+ int *x,
+ int *y);
+MateRRRotation mate_rr_crtc_get_current_rotation (MateRRCrtc *crtc);
+MateRRRotation mate_rr_crtc_get_rotations (MateRRCrtc *crtc);
+gboolean mate_rr_crtc_supports_rotation (MateRRCrtc *crtc,
+ MateRRRotation rotation);
+
+gboolean mate_rr_crtc_get_gamma (MateRRCrtc *crtc,
+ int *size,
+ unsigned short **red,
+ unsigned short **green,
+ unsigned short **blue);
+void mate_rr_crtc_set_gamma (MateRRCrtc *crtc,
+ int size,
+ unsigned short *red,
+ unsigned short *green,
+ unsigned short *blue);
+#endif
diff --git a/libmate-desktop/mate-bg-crossfade.c b/libmate-desktop/mate-bg-crossfade.c
new file mode 100644
index 0000000..5fa807f
--- /dev/null
+++ b/libmate-desktop/mate-bg-crossfade.c
@@ -0,0 +1,542 @@
+/* mate-bg-crossfade.h - fade window background between two pixmaps
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Ray Strode <[email protected]>
+*/
+
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+
+#include <gio/gio.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gtk/gtk.h>
+
+#include <cairo.h>
+#include <cairo-xlib.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <libmateui/mate-bg.h>
+#include "libmateui/mate-bg-crossfade.h"
+
+struct _MateBGCrossfadePrivate
+{
+ GdkWindow *window;
+ int width;
+ int height;
+ GdkPixmap *fading_pixmap;
+ GdkPixmap *end_pixmap;
+ gdouble start_time;
+ gdouble total_duration;
+ guint timeout_id;
+ guint is_first_frame : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+};
+
+enum {
+ FINISHED,
+ NUMBER_OF_SIGNALS
+};
+
+static guint signals[NUMBER_OF_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (MateBGCrossfade, mate_bg_crossfade, G_TYPE_OBJECT)
+#define MATE_BG_CROSSFADE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o),\
+ MATE_TYPE_BG_CROSSFADE,\
+ MateBGCrossfadePrivate))
+
+static void
+mate_bg_crossfade_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MateBGCrossfade *fade;
+
+ g_assert (MATE_IS_BG_CROSSFADE (object));
+
+ fade = MATE_BG_CROSSFADE (object);
+
+ switch (property_id)
+ {
+ case PROP_WIDTH:
+ fade->priv->width = g_value_get_int (value);
+ break;
+ case PROP_HEIGHT:
+ fade->priv->height = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+mate_bg_crossfade_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MateBGCrossfade *fade;
+
+ g_assert (MATE_IS_BG_CROSSFADE (object));
+
+ fade = MATE_BG_CROSSFADE (object);
+
+ switch (property_id)
+ {
+ case PROP_WIDTH:
+ g_value_set_int (value, fade->priv->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, fade->priv->height);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+mate_bg_crossfade_finalize (GObject *object)
+{
+ MateBGCrossfade *fade;
+
+ fade = MATE_BG_CROSSFADE (object);
+
+ mate_bg_crossfade_stop (fade);
+
+ if (fade->priv->fading_pixmap != NULL) {
+ g_object_unref (fade->priv->fading_pixmap);
+ fade->priv->fading_pixmap = NULL;
+ }
+
+ if (fade->priv->end_pixmap != NULL) {
+ g_object_unref (fade->priv->end_pixmap);
+ fade->priv->end_pixmap = NULL;
+ }
+}
+
+static void
+mate_bg_crossfade_class_init (MateBGCrossfadeClass *fade_class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (fade_class);
+
+ gobject_class->get_property = mate_bg_crossfade_get_property;
+ gobject_class->set_property = mate_bg_crossfade_set_property;
+ gobject_class->finalize = mate_bg_crossfade_finalize;
+
+ /**
+ * MateBGCrossfade:width:
+ *
+ * When a crossfade is running, this is width of the fading
+ * pixmap.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_WIDTH,
+ g_param_spec_int ("width",
+ "Window Width",
+ "Width of window to fade",
+ 0, G_MAXINT, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+ /**
+ * MateBGCrossfade:height:
+ *
+ * When a crossfade is running, this is height of the fading
+ * pixmap.
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_HEIGHT,
+ g_param_spec_int ("height", "Window Height",
+ "Height of window to fade on",
+ 0, G_MAXINT, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+ /**
+ * MateBGCrossfade::finished:
+ * @fade: the #MateBGCrossfade that received the signal
+ * @window: the #GdkWindow the crossfade happend on.
+ *
+ * When a crossfade finishes, @window will have a copy
+ * of the end pixmap as its background, and this signal will
+ * get emitted.
+ */
+ signals[FINISHED] = g_signal_new ("finished",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+ g_type_class_add_private (gobject_class, sizeof (MateBGCrossfadePrivate));
+}
+
+static void
+mate_bg_crossfade_init (MateBGCrossfade *fade)
+{
+ fade->priv = MATE_BG_CROSSFADE_GET_PRIVATE (fade);
+
+ fade->priv->fading_pixmap = NULL;
+ fade->priv->end_pixmap = NULL;
+ fade->priv->timeout_id = 0;
+}
+
+/**
+ * mate_bg_crossfade_new:
+ * @width: The width of the crossfading window
+ * @height: The height of the crossfading window
+ *
+ * Creates a new object to manage crossfading a
+ * window background between two #GdkPixmap drawables.
+ *
+ * Return value: the new #MateBGCrossfade
+ **/
+MateBGCrossfade *
+mate_bg_crossfade_new (int width,
+ int height)
+{
+ GObject *object;
+
+ object = g_object_new (MATE_TYPE_BG_CROSSFADE,
+ "width", width,
+ "height", height, NULL);
+
+ return (MateBGCrossfade *) object;
+}
+
+static GdkPixmap *
+tile_pixmap (GdkPixmap *pixmap,
+ int width,
+ int height)
+{
+ GdkPixmap *copy;
+ cairo_t *cr;
+
+ copy = gdk_pixmap_new (pixmap, width, height, pixmap == NULL? 24 : -1);
+
+ cr = gdk_cairo_create (copy);
+
+ if (pixmap != NULL) {
+ cairo_pattern_t *pattern;
+ gdk_cairo_set_source_pixmap (cr, pixmap, 0.0, 0.0);
+ pattern = cairo_get_source (cr);
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+ } else {
+ GtkStyle *style;
+ style = gtk_widget_get_default_style ();
+ gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
+ }
+
+ cairo_paint (cr);
+
+ if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
+ g_object_unref (copy);
+ copy = NULL;
+ }
+ cairo_destroy (cr);
+
+ return copy;
+}
+
+/**
+ * mate_bg_crossfade_set_start_pixmap:
+ * @fade: a #MateBGCrossfade
+ * @pixmap: The #GdkPixmap to fade from
+ *
+ * Before initiating a crossfade with mate_bg_crossfade_start()
+ * a start and end pixmap have to be set. This function sets
+ * the pixmap shown at the beginning of the crossfade effect.
+ *
+ * Return value: %TRUE if successful, or %FALSE if the pixmap
+ * could not be copied.
+ **/
+gboolean
+mate_bg_crossfade_set_start_pixmap (MateBGCrossfade *fade,
+ GdkPixmap *pixmap)
+{
+ g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE);
+
+ if (fade->priv->fading_pixmap != NULL) {
+ g_object_unref (fade->priv->fading_pixmap);
+ fade->priv->fading_pixmap = NULL;
+ }
+
+ fade->priv->fading_pixmap = tile_pixmap (pixmap,
+ fade->priv->width,
+ fade->priv->height);
+
+ return fade->priv->fading_pixmap != NULL;
+}
+
+static gdouble
+get_current_time (void)
+{
+ const double microseconds_per_second = (double) G_USEC_PER_SEC;
+ double timestamp;
+ GTimeVal now;
+
+ g_get_current_time (&now);
+
+ timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
+ microseconds_per_second;
+
+ return timestamp;
+}
+
+/**
+ * mate_bg_crossfade_set_end_pixmap:
+ * @fade: a #MateBGCrossfade
+ * @pixmap: The #GdkPixmap to fade to
+ *
+ * Before initiating a crossfade with mate_bg_crossfade_start()
+ * a start and end pixmap have to be set. This function sets
+ * the pixmap shown at the end of the crossfade effect.
+ *
+ * Return value: %TRUE if successful, or %FALSE if the pixmap
+ * could not be copied.
+ **/
+gboolean
+mate_bg_crossfade_set_end_pixmap (MateBGCrossfade *fade,
+ GdkPixmap *pixmap)
+{
+ g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE);
+
+ if (fade->priv->end_pixmap != NULL) {
+ g_object_unref (fade->priv->end_pixmap);
+ fade->priv->end_pixmap = NULL;
+ }
+
+ fade->priv->end_pixmap = tile_pixmap (pixmap,
+ fade->priv->width,
+ fade->priv->height);
+
+ /* Reset timer in case we're called while animating
+ */
+ fade->priv->start_time = get_current_time ();
+ return fade->priv->end_pixmap != NULL;
+}
+
+static gboolean
+animations_are_disabled (MateBGCrossfade *fade)
+{
+ GtkSettings *settings;
+ GdkScreen *screen;
+ gboolean are_enabled;
+
+ g_assert (fade->priv->window != NULL);
+
+ screen = gdk_drawable_get_screen (fade->priv->window);
+
+ settings = gtk_settings_get_for_screen (screen);
+
+ g_object_get (settings, "gtk-enable-animations", &are_enabled, NULL);
+
+ return !are_enabled;
+}
+
+static void
+draw_background (MateBGCrossfade *fade)
+{
+ if (GDK_WINDOW_TYPE (fade->priv->window) == GDK_WINDOW_ROOT) {
+ GdkDisplay *display;
+ display = gdk_drawable_get_display (fade->priv->window);
+ gdk_window_clear (fade->priv->window);
+ gdk_flush ();
+ } else {
+ gdk_window_invalidate_rect (fade->priv->window, NULL, FALSE);
+ gdk_window_process_updates (fade->priv->window, FALSE);
+ }
+}
+
+static gboolean
+on_tick (MateBGCrossfade *fade)
+{
+ gdouble now, percent_done;
+ cairo_t *cr;
+ cairo_status_t status;
+
+ g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE);
+
+ now = get_current_time ();
+
+ percent_done = (now - fade->priv->start_time) / fade->priv->total_duration;
+ percent_done = CLAMP (percent_done, 0.0, 1.0);
+
+ /* If it's taking a long time to get to the first frame,
+ * then lengthen the duration, so the user will get to see
+ * the effect.
+ */
+ if (fade->priv->is_first_frame && percent_done > .33) {
+ fade->priv->is_first_frame = FALSE;
+ fade->priv->total_duration *= 1.5;
+ return on_tick (fade);
+ }
+
+ if (fade->priv->fading_pixmap == NULL) {
+ return FALSE;
+ }
+
+ if (animations_are_disabled (fade)) {
+ return FALSE;
+ }
+
+ /* We accumulate the results in place for performance reasons.
+ *
+ * This means 1) The fade is exponential, not linear (looks good!)
+ * 2) The rate of fade is not independent of frame rate. Slower machines
+ * will get a slower fade (but never longer than .75 seconds), and
+ * even the fastest machines will get *some* fade because the framerate
+ * is capped.
+ */
+ cr = gdk_cairo_create (fade->priv->fading_pixmap);
+
+ gdk_cairo_set_source_pixmap (cr, fade->priv->end_pixmap,
+ 0.0, 0.0);
+ cairo_paint_with_alpha (cr, percent_done);
+
+ status = cairo_status (cr);
+ cairo_destroy (cr);
+
+ if (status == CAIRO_STATUS_SUCCESS) {
+ draw_background (fade);
+ }
+ return percent_done <= .99;
+}
+
+static void
+on_finished (MateBGCrossfade *fade)
+{
+ if (fade->priv->timeout_id == 0)
+ return;
+
+ g_assert (fade->priv->end_pixmap != NULL);
+
+ gdk_window_set_back_pixmap (fade->priv->window,
+ fade->priv->end_pixmap,
+ FALSE);
+ draw_background (fade);
+
+ g_object_unref (fade->priv->end_pixmap);
+ fade->priv->end_pixmap = NULL;
+
+ g_assert (fade->priv->fading_pixmap != NULL);
+
+ g_object_unref (fade->priv->fading_pixmap);
+ fade->priv->fading_pixmap = NULL;
+
+ fade->priv->timeout_id = 0;
+ g_signal_emit (fade, signals[FINISHED], 0, fade->priv->window);
+}
+
+/**
+ * mate_bg_crossfade_start:
+ * @fade: a #MateBGCrossfade
+ * @window: The #GdkWindow to draw crossfade on
+ *
+ * This function initiates a quick crossfade between two pixmaps on
+ * the background of @window. Before initiating the crossfade both
+ * mate_bg_crossfade_start() and mate_bg_crossfade_end() need to
+ * be called. If animations are disabled, the crossfade is skipped,
+ * and the window background is set immediately to the end pixmap.
+ **/
+void
+mate_bg_crossfade_start (MateBGCrossfade *fade,
+ GdkWindow *window)
+{
+ GSource *source;
+ GMainContext *context;
+
+ g_return_if_fail (MATE_IS_BG_CROSSFADE (fade));
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (fade->priv->fading_pixmap != NULL);
+ g_return_if_fail (fade->priv->end_pixmap != NULL);
+ g_return_if_fail (!mate_bg_crossfade_is_started (fade));
+ g_return_if_fail (GDK_WINDOW_TYPE (window) != GDK_WINDOW_FOREIGN);
+
+ source = g_timeout_source_new (1000 / 60.0);
+ g_source_set_callback (source,
+ (GSourceFunc) on_tick,
+ fade,
+ (GDestroyNotify) on_finished);
+ context = g_main_context_default ();
+ fade->priv->timeout_id = g_source_attach (source, context);
+ g_source_unref (source);
+
+ fade->priv->window = window;
+ gdk_window_set_back_pixmap (fade->priv->window,
+ fade->priv->fading_pixmap,
+ FALSE);
+ draw_background (fade);
+
+ fade->priv->is_first_frame = TRUE;
+ fade->priv->total_duration = .75;
+ fade->priv->start_time = get_current_time ();
+}
+
+
+/**
+ * mate_bg_crossfade_is_started:
+ * @fade: a #MateBGCrossfade
+ *
+ * This function reveals whether or not @fade is currently
+ * running on a window. See mate_bg_crossfade_start() for
+ * information on how to initiate a crossfade.
+ *
+ * Return value: %TRUE if fading, or %FALSE if not fading
+ **/
+gboolean
+mate_bg_crossfade_is_started (MateBGCrossfade *fade)
+{
+ g_return_val_if_fail (MATE_IS_BG_CROSSFADE (fade), FALSE);
+
+ return fade->priv->timeout_id != 0;
+}
+
+/**
+ * mate_bg_crossfade_stop:
+ * @fade: a #MateBGCrossfade
+ *
+ * This function stops any in progress crossfades that may be
+ * happening. It's harmless to call this function if @fade is
+ * already stopped.
+ **/
+void
+mate_bg_crossfade_stop (MateBGCrossfade *fade)
+{
+ g_return_if_fail (MATE_IS_BG_CROSSFADE (fade));
+
+ if (!mate_bg_crossfade_is_started (fade))
+ return;
+
+ g_assert (fade->priv->timeout_id != 0);
+ g_source_remove (fade->priv->timeout_id);
+ fade->priv->timeout_id = 0;
+}
diff --git a/libmate-desktop/mate-bg.c b/libmate-desktop/mate-bg.c
new file mode 100644
index 0000000..3b0f684
--- /dev/null
+++ b/libmate-desktop/mate-bg.c
@@ -0,0 +1,2930 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+matebg.c: Object for the desktop background.
+
+Copyright (C) 2000 Eazel, Inc.
+Copyright (C) 2007-2008 Red Hat, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this program; if not, write to the
+Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+
+Derived from eel-background.c and eel-gdk-pixbuf-extensions.c by
+Darin Adler <[email protected]> and Ramiro Estrugo <[email protected]>
+
+Author: Soren Sandmann <[email protected]>
+
+*/
+
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <cairo.h>
+
+#include <mateconf/mateconf-client.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <libmateui/mate-bg.h>
+#include <libmateui/mate-bg-crossfade.h>
+
+#define BG_KEY_DRAW_BACKGROUND MATE_BG_KEY_DIR "/draw_background"
+#define BG_KEY_PRIMARY_COLOR MATE_BG_KEY_DIR "/primary_color"
+#define BG_KEY_SECONDARY_COLOR MATE_BG_KEY_DIR "/secondary_color"
+#define BG_KEY_COLOR_TYPE MATE_BG_KEY_DIR "/color_shading_type"
+#define BG_KEY_PICTURE_PLACEMENT MATE_BG_KEY_DIR "/picture_options"
+#define BG_KEY_PICTURE_OPACITY MATE_BG_KEY_DIR "/picture_opacity"
+#define BG_KEY_PICTURE_FILENAME MATE_BG_KEY_DIR "/picture_filename"
+
+/* We keep the large pixbufs around if the next update
+ in the slideshow is less than 60 seconds away */
+#define KEEP_EXPENSIVE_CACHE_SECS 60
+
+typedef struct _SlideShow SlideShow;
+typedef struct _Slide Slide;
+
+struct _Slide {
+ double duration; /* in seconds */
+ gboolean fixed;
+
+ GSList* file1;
+ GSList* file2; /* NULL if fixed is TRUE */
+};
+
+typedef struct _FileSize FileSize;
+struct _FileSize {
+ gint width;
+ gint height;
+
+ char* file;
+};
+
+/* This is the size of the GdkRGB dither matrix, in order to avoid
+ * bad dithering when tiling the gradient
+ */
+#define GRADIENT_PIXMAP_TILE_SIZE 128
+
+typedef struct FileCacheEntry FileCacheEntry;
+#define CACHE_SIZE 4
+
+/*
+ * Implementation of the MateBG class
+ */
+struct _MateBG {
+ GObject parent_instance;
+ char* filename;
+ MateBGPlacement placement;
+ MateBGColorType color_type;
+ GdkColor primary;
+ GdkColor secondary;
+
+ gint last_pixmap_width;
+ gint last_pixmap_height;
+
+ GFileMonitor* file_monitor;
+
+ guint changed_id;
+ guint transitioned_id;
+ guint blow_caches_id;
+
+ /* Cached information, only access through cache accessor functions */
+ SlideShow* slideshow;
+ time_t file_mtime;
+ GdkPixbuf* pixbuf_cache;
+ int timeout_id;
+
+ GList* file_cache;
+};
+
+struct _MateBGClass {
+ GObjectClass parent_class;
+};
+
+enum {
+ CHANGED,
+ TRANSITIONED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = {0};
+
+G_DEFINE_TYPE(MateBG, mate_bg, G_TYPE_OBJECT)
+
+static GdkPixmap* make_root_pixmap(GdkScreen* screen, gint width, gint height);
+
+/* Pixbuf utils */
+static guint32 pixbuf_average_value (GdkPixbuf *pixbuf);
+static GdkPixbuf *pixbuf_scale_to_fit (GdkPixbuf *src,
+ int max_width,
+ int max_height);
+static GdkPixbuf *pixbuf_scale_to_min (GdkPixbuf *src,
+ int min_width,
+ int min_height);
+static void pixbuf_draw_gradient (GdkPixbuf *pixbuf,
+ gboolean horizontal,
+ GdkColor *c1,
+ GdkColor *c2,
+ GdkRectangle *rect);
+static void pixbuf_tile (GdkPixbuf *src,
+ GdkPixbuf *dest);
+static void pixbuf_blend (GdkPixbuf *src,
+ GdkPixbuf *dest,
+ int src_x,
+ int src_y,
+ int width,
+ int height,
+ int dest_x,
+ int dest_y,
+ double alpha);
+
+/* Thumbnail utilities */
+static GdkPixbuf *create_thumbnail_for_filename (MateDesktopThumbnailFactory *factory,
+ const char *filename);
+static gboolean get_thumb_annotations (GdkPixbuf *thumb,
+ int *orig_width,
+ int *orig_height);
+
+/* Cache */
+static GdkPixbuf *get_pixbuf_for_size (MateBG *bg,
+ int width,
+ int height);
+static void clear_cache (MateBG *bg);
+static gboolean is_different (MateBG *bg,
+ const char *filename);
+static time_t get_mtime (const char *filename);
+static GdkPixbuf *create_img_thumbnail (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height,
+ int frame_num);
+static SlideShow * get_as_slideshow (MateBG *bg,
+ const char *filename);
+static Slide * get_current_slide (SlideShow *show,
+ double *alpha);
+static gboolean slideshow_has_multiple_sizes (SlideShow *show);
+
+static SlideShow *read_slideshow_file (const char *filename,
+ GError **err);
+static SlideShow *slideshow_ref (SlideShow *show);
+static void slideshow_unref (SlideShow *show);
+
+static FileSize *find_best_size (GSList *sizes,
+ gint width,
+ gint height);
+
+static void
+color_from_string (const char *string,
+ GdkColor *colorp)
+{
+ /* If all else fails use black */
+ gdk_color_parse ("black", colorp);
+
+ if (!string)
+ return;
+
+ gdk_color_parse (string, colorp);
+}
+
+static char *
+color_to_string (const GdkColor *color)
+{
+ return g_strdup_printf ("#%02x%02x%02x",
+ color->red >> 8,
+ color->green >> 8,
+ color->blue >> 8);
+}
+
+static MateConfEnumStringPair placement_lookup[] = {
+ { MATE_BG_PLACEMENT_CENTERED, "centered" },
+ { MATE_BG_PLACEMENT_FILL_SCREEN, "stretched" },
+ { MATE_BG_PLACEMENT_SCALED, "scaled" },
+ { MATE_BG_PLACEMENT_ZOOMED, "zoom" },
+ { MATE_BG_PLACEMENT_TILED, "wallpaper" },
+ { MATE_BG_PLACEMENT_SPANNED, "spanned" },
+ { 0, NULL }
+};
+
+static MateConfEnumStringPair color_type_lookup[] = {
+ { MATE_BG_COLOR_SOLID, "solid" },
+ { MATE_BG_COLOR_H_GRADIENT, "horizontal-gradient" },
+ { MATE_BG_COLOR_V_GRADIENT, "vertical-gradient" },
+ { 0, NULL }
+};
+
+static void
+color_type_from_string (const char *string,
+ MateBGColorType *color_type)
+{
+ *color_type = MATE_BG_COLOR_SOLID;
+
+ if (string) {
+ mateconf_string_to_enum (color_type_lookup,
+ string, (int *)color_type);
+ }
+}
+
+static const char *
+color_type_to_string (MateBGColorType color_type)
+{
+ return mateconf_enum_to_string (color_type_lookup, color_type);
+}
+
+static void
+placement_from_string (const char *string,
+ MateBGPlacement *placement)
+{
+ *placement = MATE_BG_PLACEMENT_ZOOMED;
+
+ if (string) {
+ mateconf_string_to_enum (placement_lookup,
+ string, (int *)placement);
+ }
+}
+
+static const char *
+placement_to_string (MateBGPlacement placement)
+{
+ return mateconf_enum_to_string (placement_lookup, placement);
+}
+
+static gboolean
+do_changed (MateBG *bg)
+{
+ gboolean ignore_pending_change;
+ bg->changed_id = 0;
+
+ ignore_pending_change =
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (bg),
+ "ignore-pending-change"));
+
+ if (!ignore_pending_change) {
+ g_signal_emit (G_OBJECT (bg), signals[CHANGED], 0);
+ }
+
+ return FALSE;
+}
+
+static void
+queue_changed (MateBG *bg)
+{
+ if (bg->changed_id > 0) {
+ g_source_remove (bg->changed_id);
+ }
+
+ /* We unset this here to allow apps to set it if they don't want
+ to get the change event. This is used by caja when it
+ gets the pixmap from the bg (due to a reason other than the changed
+ event). Because if there is no other change after this time the
+ pending changed event will just uselessly cause us to recreate
+ the pixmap. */
+ g_object_set_data (G_OBJECT (bg), "ignore-pending-change",
+ GINT_TO_POINTER (FALSE));
+ bg->changed_id = g_timeout_add_full (G_PRIORITY_LOW,
+ 100,
+ (GSourceFunc)do_changed,
+ bg,
+ NULL);
+}
+
+static gboolean
+do_transitioned (MateBG *bg)
+{
+ bg->transitioned_id = 0;
+
+ if (bg->pixbuf_cache) {
+ g_object_unref (bg->pixbuf_cache);
+ bg->pixbuf_cache = NULL;
+ }
+
+ g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0);
+
+ return FALSE;
+}
+
+static void
+queue_transitioned (MateBG *bg)
+{
+ if (bg->transitioned_id > 0) {
+ g_source_remove (bg->transitioned_id);
+ }
+
+ bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW,
+ 100,
+ (GSourceFunc)do_transitioned,
+ bg,
+ NULL);
+}
+
+void
+mate_bg_load_from_preferences (MateBG *bg,
+ MateConfClient *client)
+{
+ char *tmp;
+ char *filename;
+ MateBGColorType ctype;
+ GdkColor c1, c2;
+ MateBGPlacement placement;
+
+ g_return_if_fail (MATE_IS_BG (bg));
+ g_return_if_fail (client != NULL);
+
+ /* Filename */
+ filename = NULL;
+ tmp = mateconf_client_get_string (client, BG_KEY_PICTURE_FILENAME, NULL);
+ if (tmp != NULL && *tmp != '\0') {
+ if (g_utf8_validate (tmp, -1, NULL) &&
+ g_file_test (tmp, G_FILE_TEST_EXISTS)) {
+ filename = g_strdup (tmp);
+ } else {
+ filename = g_filename_from_utf8 (tmp, -1, NULL, NULL, NULL);
+ }
+
+ /* Fall back to default background if filename was set
+ but no longer exists */
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ MateConfValue *default_value;
+
+ g_free (filename);
+ filename = NULL;
+
+ default_value =
+ mateconf_client_get_default_from_schema (client,
+ BG_KEY_PICTURE_FILENAME,
+ NULL);
+ if (default_value != NULL) {
+ filename = g_strdup (mateconf_value_get_string (default_value));
+ mateconf_value_free (default_value);
+ }
+ }
+ }
+ g_free (tmp);
+
+ /* Colors */
+ tmp = mateconf_client_get_string (client, BG_KEY_PRIMARY_COLOR, NULL);
+ color_from_string (tmp, &c1);
+ g_free (tmp);
+
+ tmp = mateconf_client_get_string (client, BG_KEY_SECONDARY_COLOR, NULL);
+ color_from_string (tmp, &c2);
+ g_free (tmp);
+
+ /* Color type */
+ tmp = mateconf_client_get_string (client, BG_KEY_COLOR_TYPE, NULL);
+ color_type_from_string (tmp, &ctype);
+ g_free (tmp);
+
+ /* Placement */
+ tmp = mateconf_client_get_string (client, BG_KEY_PICTURE_PLACEMENT, NULL);
+ placement_from_string (tmp, &placement);
+ g_free (tmp);
+
+ mate_bg_set_color (bg, ctype, &c1, &c2);
+ mate_bg_set_placement (bg, placement);
+ mate_bg_set_filename (bg, filename);
+
+ g_free (filename);
+}
+
+void
+mate_bg_save_to_preferences (MateBG *bg,
+ MateConfClient *client)
+{
+ const char *color_type;
+ const char *placement;
+ const gchar *filename;
+ gchar *primary;
+ gchar *secondary;
+
+ primary = color_to_string (&bg->primary);
+ secondary = color_to_string (&bg->secondary);
+
+ color_type = color_type_to_string (bg->color_type);
+
+ if (bg->filename) {
+ filename = bg->filename;
+ placement = placement_to_string (bg->placement);
+ }
+ else {
+ filename = "(none)";
+ placement = "none";
+ }
+
+ mateconf_client_set_string (client, BG_KEY_PICTURE_FILENAME, filename, NULL);
+ mateconf_client_set_string (client, BG_KEY_PRIMARY_COLOR, primary, NULL);
+ mateconf_client_set_string (client, BG_KEY_SECONDARY_COLOR, secondary, NULL);
+ mateconf_client_set_string (client, BG_KEY_COLOR_TYPE, color_type, NULL);
+ mateconf_client_set_string (client, BG_KEY_PICTURE_PLACEMENT, placement, NULL);
+
+ g_free (primary);
+ g_free (secondary);
+}
+
+
+static void
+mate_bg_init (MateBG *bg)
+{
+}
+
+static void
+mate_bg_dispose (GObject *object)
+{
+ MateBG *bg = MATE_BG (object);
+
+ if (bg->file_monitor) {
+ g_object_unref (bg->file_monitor);
+ bg->file_monitor = NULL;
+ }
+
+ clear_cache (bg);
+
+ G_OBJECT_CLASS (mate_bg_parent_class)->dispose (object);
+}
+
+static void
+mate_bg_finalize (GObject *object)
+{
+ MateBG *bg = MATE_BG (object);
+
+ if (bg->changed_id != 0) {
+ g_source_remove (bg->changed_id);
+ bg->changed_id = 0;
+ }
+
+ if (bg->transitioned_id != 0) {
+ g_source_remove (bg->transitioned_id);
+ bg->transitioned_id = 0;
+ }
+
+ if (bg->blow_caches_id != 0) {
+ g_source_remove (bg->blow_caches_id);
+ bg->blow_caches_id = 0;
+ }
+
+ if (bg->filename) {
+ g_free (bg->filename);
+ bg->filename = NULL;
+ }
+
+ G_OBJECT_CLASS (mate_bg_parent_class)->finalize (object);
+}
+
+static void
+mate_bg_class_init (MateBGClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = mate_bg_dispose;
+ object_class->finalize = mate_bg_finalize;
+
+ signals[CHANGED] = g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[TRANSITIONED] = g_signal_new ("transitioned",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+MateBG *
+mate_bg_new (void)
+{
+ return g_object_new (MATE_TYPE_BG, NULL);
+}
+
+void
+mate_bg_set_color (MateBG *bg,
+ MateBGColorType type,
+ GdkColor *primary,
+ GdkColor *secondary)
+{
+ g_return_if_fail (bg != NULL);
+
+ if (bg->color_type != type ||
+ !gdk_color_equal (&bg->primary, primary) ||
+ (secondary && !gdk_color_equal (&bg->secondary, secondary))) {
+
+ bg->color_type = type;
+ bg->primary = *primary;
+ if (secondary) {
+ bg->secondary = *secondary;
+ }
+
+ queue_changed (bg);
+ }
+}
+
+void
+mate_bg_set_placement (MateBG *bg,
+ MateBGPlacement placement)
+{
+ g_return_if_fail (bg != NULL);
+
+ if (bg->placement != placement) {
+ bg->placement = placement;
+
+ queue_changed (bg);
+ }
+}
+
+MateBGPlacement
+mate_bg_get_placement (MateBG *bg)
+{
+ g_return_val_if_fail (bg != NULL, -1);
+
+ return bg->placement;
+}
+
+void
+mate_bg_get_color (MateBG *bg,
+ MateBGColorType *type,
+ GdkColor *primary,
+ GdkColor *secondary)
+{
+ g_return_if_fail (bg != NULL);
+
+ if (type)
+ *type = bg->color_type;
+
+ if (primary)
+ *primary = bg->primary;
+
+ if (secondary)
+ *secondary = bg->secondary;
+}
+
+const gchar *
+mate_bg_get_filename (MateBG *bg)
+{
+ g_return_val_if_fail (bg != NULL, NULL);
+
+ return bg->filename;
+}
+
+static void
+file_changed (GFileMonitor *file_monitor,
+ GFile *child,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ MateBG *bg = MATE_BG (user_data);
+
+ clear_cache (bg);
+ queue_changed (bg);
+}
+
+void
+mate_bg_set_filename (MateBG *bg,
+ const char *filename)
+{
+ g_return_if_fail (bg != NULL);
+
+ if (is_different (bg, filename)) {
+ char *tmp = g_strdup (filename);
+
+ g_free (bg->filename);
+
+ bg->filename = tmp;
+ bg->file_mtime = get_mtime (bg->filename);
+
+ if (bg->file_monitor) {
+ g_object_unref (bg->file_monitor);
+ bg->file_monitor = NULL;
+ }
+
+ if (bg->filename) {
+ GFile *f = g_file_new_for_path (bg->filename);
+
+ bg->file_monitor = g_file_monitor_file (f, 0, NULL, NULL);
+ g_signal_connect (bg->file_monitor, "changed",
+ G_CALLBACK (file_changed), bg);
+
+ g_object_unref (f);
+ }
+
+ clear_cache (bg);
+
+ queue_changed (bg);
+ }
+}
+
+static void
+draw_color_area (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkRectangle *rect)
+{
+ guint32 pixel;
+ GdkRectangle extent;
+
+ extent.x = 0;
+ extent.y = 0;
+ extent.width = gdk_pixbuf_get_width (dest);
+ extent.height = gdk_pixbuf_get_height (dest);
+
+ gdk_rectangle_intersect (rect, &extent, rect);
+
+ switch (bg->color_type) {
+ case MATE_BG_COLOR_SOLID:
+ /* not really a big deal to ignore the area of interest */
+ pixel = ((bg->primary.red >> 8) << 24) |
+ ((bg->primary.green >> 8) << 16) |
+ ((bg->primary.blue >> 8) << 8) |
+ (0xff);
+
+ gdk_pixbuf_fill (dest, pixel);
+ break;
+
+ case MATE_BG_COLOR_H_GRADIENT:
+ pixbuf_draw_gradient (dest, TRUE, &(bg->primary), &(bg->secondary), rect);
+ break;
+
+ case MATE_BG_COLOR_V_GRADIENT:
+ pixbuf_draw_gradient (dest, FALSE, &(bg->primary), &(bg->secondary), rect);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+draw_color (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkScreen *screen)
+{
+ GdkRectangle rect;
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = gdk_pixbuf_get_width (dest);
+ rect.height = gdk_pixbuf_get_height (dest);
+ draw_color_area (bg, dest, &rect);
+}
+
+static void
+draw_color_each_monitor (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkScreen *screen)
+{
+ GdkRectangle rect;
+ gint num_monitors;
+ int monitor;
+
+ num_monitors = gdk_screen_get_n_monitors (screen);
+ for (monitor = 0; monitor < num_monitors; monitor++) {
+ gdk_screen_get_monitor_geometry (screen, monitor, &rect);
+ draw_color_area (bg, dest, &rect);
+ }
+}
+
+static GdkPixbuf *
+pixbuf_clip_to_fit (GdkPixbuf *src,
+ int max_width,
+ int max_height)
+{
+ int src_width, src_height;
+ int w, h;
+ int src_x, src_y;
+ GdkPixbuf *pixbuf;
+
+ src_width = gdk_pixbuf_get_width (src);
+ src_height = gdk_pixbuf_get_height (src);
+
+ if (src_width < max_width && src_height < max_height)
+ return g_object_ref (src);
+
+ w = MIN(src_width, max_width);
+ h = MIN(src_height, max_height);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (src),
+ 8, w, h);
+
+ src_x = (src_width - w) / 2;
+ src_y = (src_height - h) / 2;
+ gdk_pixbuf_copy_area (src,
+ src_x, src_y,
+ w, h,
+ pixbuf,
+ 0, 0);
+ return pixbuf;
+}
+
+static GdkPixbuf *
+get_scaled_pixbuf (MateBGPlacement placement,
+ GdkPixbuf *pixbuf,
+ int width, int height,
+ int *x, int *y,
+ int *w, int *h)
+{
+ GdkPixbuf *new;
+
+#if 0
+ g_print ("original_width: %d %d\n",
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+#endif
+
+ switch (placement) {
+ case MATE_BG_PLACEMENT_SPANNED:
+ new = pixbuf_scale_to_fit (pixbuf, width, height);
+ break;
+ case MATE_BG_PLACEMENT_ZOOMED:
+ new = pixbuf_scale_to_min (pixbuf, width, height);
+ break;
+
+ case MATE_BG_PLACEMENT_FILL_SCREEN:
+ new = gdk_pixbuf_scale_simple (pixbuf, width, height,
+ GDK_INTERP_BILINEAR);
+ break;
+
+ case MATE_BG_PLACEMENT_SCALED:
+ new = pixbuf_scale_to_fit (pixbuf, width, height);
+ break;
+
+ case MATE_BG_PLACEMENT_CENTERED:
+ case MATE_BG_PLACEMENT_TILED:
+ default:
+ new = pixbuf_clip_to_fit (pixbuf, width, height);
+ break;
+ }
+
+ *w = gdk_pixbuf_get_width (new);
+ *h = gdk_pixbuf_get_height (new);
+ *x = (width - *w) / 2;
+ *y = (height - *h) / 2;
+
+ return new;
+}
+
+static void
+draw_image_area (MateBGPlacement placement,
+ GdkPixbuf *pixbuf,
+ GdkPixbuf *dest,
+ GdkRectangle *area)
+{
+ int dest_width = area->width;
+ int dest_height = area->height;
+ int x, y, w, h;
+ GdkPixbuf *scaled;
+
+ if (!pixbuf)
+ return;
+
+ scaled = get_scaled_pixbuf (placement, pixbuf, dest_width, dest_height, &x, &y, &w, &h);
+
+ switch (placement) {
+ case MATE_BG_PLACEMENT_TILED:
+ pixbuf_tile (scaled, dest);
+ break;
+ case MATE_BG_PLACEMENT_ZOOMED:
+ case MATE_BG_PLACEMENT_CENTERED:
+ case MATE_BG_PLACEMENT_FILL_SCREEN:
+ case MATE_BG_PLACEMENT_SCALED:
+ pixbuf_blend (scaled, dest, 0, 0, w, h, x + area->x, y + area->y, 1.0);
+ break;
+ case MATE_BG_PLACEMENT_SPANNED:
+ pixbuf_blend (scaled, dest, 0, 0, w, h, x, y, 1.0);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_object_unref (scaled);
+}
+
+static void
+draw_image (MateBGPlacement placement,
+ GdkPixbuf *pixbuf,
+ GdkPixbuf *dest)
+{
+ GdkRectangle rect;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = gdk_pixbuf_get_width (dest);
+ rect.height = gdk_pixbuf_get_height (dest);
+
+ draw_image_area (placement, pixbuf, dest, &rect);
+}
+
+static void
+draw_once (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkScreen *screen)
+{
+ GdkRectangle rect;
+ GdkPixbuf *pixbuf;
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = gdk_pixbuf_get_width (dest);
+ rect.height = gdk_pixbuf_get_height (dest);
+
+ pixbuf = get_pixbuf_for_size (bg, gdk_pixbuf_get_width (dest), gdk_pixbuf_get_height (dest));
+ if (pixbuf) {
+ draw_image_area (bg->placement,
+ pixbuf,
+ dest,
+ &rect);
+ g_object_unref (pixbuf);
+ }
+}
+
+static void
+draw_each_monitor (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkScreen *screen)
+{
+ GdkRectangle rect;
+ gint num_monitors;
+ int monitor;
+
+ num_monitors = gdk_screen_get_n_monitors (screen);
+ for (monitor = 0; monitor < num_monitors; monitor++) {
+ GdkPixbuf *pixbuf;
+ gdk_screen_get_monitor_geometry (screen, monitor, &rect);
+ pixbuf = get_pixbuf_for_size (bg, rect.width, rect.height);
+ if (pixbuf) {
+ draw_image_area (bg->placement,
+ pixbuf,
+ dest, &rect);
+ g_object_unref (pixbuf);
+ }
+ }
+}
+
+void
+mate_bg_draw (MateBG *bg,
+ GdkPixbuf *dest,
+ GdkScreen *screen,
+ gboolean is_root)
+{
+ if (!bg)
+ return;
+
+ if (is_root && (bg->placement != MATE_BG_PLACEMENT_SPANNED)) {
+ draw_color_each_monitor (bg, dest, screen);
+ draw_each_monitor (bg, dest, screen);
+ } else {
+ draw_color (bg, dest, screen);
+ draw_once (bg, dest, screen);
+ }
+}
+
+gboolean
+mate_bg_has_multiple_sizes (MateBG *bg)
+{
+ SlideShow *show;
+ gboolean ret;
+
+ g_return_val_if_fail (bg != NULL, FALSE);
+
+ ret = FALSE;
+
+ show = get_as_slideshow (bg, bg->filename);
+ if (show) {
+ ret = slideshow_has_multiple_sizes (show);
+ slideshow_unref (show);
+ }
+
+ return ret;
+}
+
+static void
+mate_bg_get_pixmap_size (MateBG *bg,
+ int width,
+ int height,
+ int *pixmap_width,
+ int *pixmap_height)
+{
+ int dummy;
+
+ if (!pixmap_width)
+ pixmap_width = &dummy;
+ if (!pixmap_height)
+ pixmap_height = &dummy;
+
+ *pixmap_width = width;
+ *pixmap_height = height;
+
+ if (!bg->filename) {
+ switch (bg->color_type) {
+ case MATE_BG_COLOR_SOLID:
+ *pixmap_width = 1;
+ *pixmap_height = 1;
+ break;
+
+ case MATE_BG_COLOR_H_GRADIENT:
+ case MATE_BG_COLOR_V_GRADIENT:
+ break;
+ }
+
+ return;
+ }
+}
+
+/**
+ * mate_bg_get_pixmap:
+ * @bg: MateBG
+ * @window:
+ * @width:
+ * @height:
+ *
+ * Create a pixmap that can be set as background for @window. If @root is TRUE,
+ * the pixmap created will be created by a temporary X server connection so
+ * that if someone calls XKillClient on it, it won't affect the application who
+ * created it.
+ *
+ * Since: 2.20
+ **/
+GdkPixmap *
+mate_bg_create_pixmap (MateBG *bg,
+ GdkWindow *window,
+ int width,
+ int height,
+ gboolean is_root)
+{
+ int pm_width, pm_height;
+ GdkPixmap *pixmap;
+ cairo_t *cr;
+
+ g_return_val_if_fail (bg != NULL, NULL);
+ g_return_val_if_fail (window != NULL, NULL);
+
+ if (bg->last_pixmap_width != width ||
+ bg->last_pixmap_height != height) {
+ if (bg->pixbuf_cache) {
+ g_object_unref (bg->pixbuf_cache);
+ bg->pixbuf_cache = NULL;
+ }
+ }
+ bg->last_pixmap_width = width;
+ bg->last_pixmap_height = height;
+
+ /* has the side effect of loading and caching pixbuf only when in tile mode */
+ mate_bg_get_pixmap_size (bg, width, height, &pm_width, &pm_height);
+
+ if (is_root) {
+ pixmap = make_root_pixmap (gdk_drawable_get_screen (window),
+ pm_width, pm_height);
+ }
+ else {
+ pixmap = gdk_pixmap_new (window, pm_width, pm_height, -1);
+ }
+
+ cr = gdk_cairo_create (pixmap);
+ if (!bg->filename && bg->color_type == MATE_BG_COLOR_SOLID) {
+ gdk_cairo_set_source_color (cr, &(bg->primary));
+ }
+ else {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+ width, height);
+ mate_bg_draw (bg, pixbuf, gdk_drawable_get_screen (GDK_DRAWABLE (window)), is_root);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ g_object_unref (pixbuf);
+ }
+
+ cairo_paint (cr);
+
+ cairo_destroy (cr);
+
+ return pixmap;
+}
+
+
+/* determine if a background is darker or lighter than average, to help
+ * clients know what colors to draw on top with
+ */
+gboolean
+mate_bg_is_dark (MateBG *bg,
+ int width,
+ int height)
+{
+ GdkColor color;
+ int intensity;
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (bg != NULL, FALSE);
+
+ if (bg->color_type == MATE_BG_COLOR_SOLID) {
+ color = bg->primary;
+ } else {
+ color.red = (bg->primary.red + bg->secondary.red) / 2;
+ color.green = (bg->primary.green + bg->secondary.green) / 2;
+ color.blue = (bg->primary.blue + bg->secondary.blue) / 2;
+ }
+ pixbuf = get_pixbuf_for_size (bg, width, height);
+ if (pixbuf) {
+ guint32 argb = pixbuf_average_value (pixbuf);
+ guchar a = (argb >> 24) & 0xff;
+ guchar r = (argb >> 16) & 0xff;
+ guchar g = (argb >> 8) & 0xff;
+ guchar b = (argb >> 0) & 0xff;
+
+ color.red = (color.red * (0xFF - a) + r * 0x101 * a) / 0xFF;
+ color.green = (color.green * (0xFF - a) + g * 0x101 * a) / 0xFF;
+ color.blue = (color.blue * (0xFF - a) + b * 0x101 * a) / 0xFF;
+ g_object_unref (pixbuf);
+ }
+
+ intensity = (color.red * 77 +
+ color.green * 150 +
+ color.blue * 28) >> 16;
+
+ return intensity < 160; /* biased slightly to be dark */
+}
+
+/*
+ * Create a persistent pixmap. We create a separate display
+ * and set the closedown mode on it to RetainPermanent.
+ */
+static GdkPixmap *
+make_root_pixmap (GdkScreen *screen, gint width, gint height)
+{
+ Display *display;
+ const char *display_name;
+ Pixmap result;
+ GdkPixmap *gdk_pixmap;
+ int screen_num;
+ int depth;
+
+ screen_num = gdk_screen_get_number (screen);
+
+ gdk_flush ();
+
+ display_name = gdk_display_get_name (gdk_screen_get_display (screen));
+ display = XOpenDisplay (display_name);
+
+ if (display == NULL) {
+ g_warning ("Unable to open display '%s' when setting "
+ "background pixmap\n",
+ (display_name) ? display_name : "NULL");
+ return NULL;
+ }
+
+ /* Desktop background pixmap should be created from
+ * dummy X client since most applications will try to
+ * kill it with XKillClient later when changing pixmap
+ */
+
+ XSetCloseDownMode (display, RetainPermanent);
+
+ depth = DefaultDepth (display, screen_num);
+
+ result = XCreatePixmap (display,
+ RootWindow (display, screen_num),
+ width, height, depth);
+
+ XCloseDisplay (display);
+
+ gdk_pixmap = gdk_pixmap_foreign_new_for_screen (screen, result,
+ width, height, depth);
+
+ gdk_drawable_set_colormap (
+ GDK_DRAWABLE (gdk_pixmap),
+ gdk_drawable_get_colormap (gdk_screen_get_root_window (screen)));
+
+ return gdk_pixmap;
+}
+
+static gboolean
+get_original_size (const char *filename,
+ int *orig_width,
+ int *orig_height)
+{
+ gboolean result;
+
+ if (gdk_pixbuf_get_file_info (filename, orig_width, orig_height))
+ result = TRUE;
+ else
+ result = FALSE;
+
+ return result;
+}
+
+static const char *
+get_filename_for_size (MateBG *bg, gint best_width, gint best_height)
+{
+ SlideShow *show;
+ Slide *slide;
+ FileSize *size;
+
+ if (!bg->filename)
+ return NULL;
+
+ show = get_as_slideshow (bg, bg->filename);
+ if (!show) {
+ return bg->filename;
+ }
+
+ slide = get_current_slide (show, NULL);
+ slideshow_unref (show);
+ size = find_best_size (slide->file1, best_width, best_height);
+ return size->file;
+}
+
+gboolean
+mate_bg_get_image_size (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ int best_width,
+ int best_height,
+ int *width,
+ int *height)
+{
+ GdkPixbuf *thumb;
+ gboolean result = FALSE;
+ const gchar *filename;
+
+ g_return_val_if_fail (bg != NULL, FALSE);
+ g_return_val_if_fail (factory != NULL, FALSE);
+
+ if (!bg->filename)
+ return FALSE;
+
+ filename = get_filename_for_size (bg, best_width, best_height);
+ thumb = create_thumbnail_for_filename (factory, filename);
+ if (thumb) {
+ if (get_thumb_annotations (thumb, width, height))
+ result = TRUE;
+
+ g_object_unref (thumb);
+ }
+
+ if (!result) {
+ if (get_original_size (filename, width, height))
+ result = TRUE;
+ }
+
+ return result;
+}
+
+static double
+fit_factor (int from_width, int from_height,
+ int to_width, int to_height)
+{
+ return MIN (to_width / (double) from_width, to_height / (double) from_height);
+}
+
+GdkPixbuf *
+mate_bg_create_thumbnail (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height)
+{
+ GdkPixbuf *result;
+ GdkPixbuf *thumb;
+
+ g_return_val_if_fail (bg != NULL, NULL);
+
+ result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
+
+ draw_color (bg, result, screen);
+
+ thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, -1);
+
+ if (thumb) {
+ draw_image (bg->placement, thumb, result);
+ g_object_unref (thumb);
+ }
+
+ return result;
+}
+
+/**
+ * mate_bg_get_pixmap_from_root:
+ * @screen: a #GdkScreen
+ *
+ * This function queries the _XROOTPMAP_ID property from
+ * the root window associated with @screen to determine
+ * the current root window background pixmap and returns
+ * a copy of it. If the _XROOTPMAP_ID is not set, then
+ * a black pixmap is returned.
+ *
+ * Return value: a #GdkPixmap if successful or %NULL
+ **/
+GdkPixmap *
+mate_bg_get_pixmap_from_root (GdkScreen *screen)
+{
+ int result;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guchar *data;
+ Atom type;
+ Display *display;
+ int screen_num;
+ GdkPixmap *pixmap;
+ GdkPixmap *source_pixmap;
+ int width, height;
+ cairo_t *cr;
+ cairo_pattern_t *pattern;
+
+ display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+ screen_num = gdk_screen_get_number (screen);
+
+ result = XGetWindowProperty (display,
+ RootWindow (display, screen_num),
+ gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
+ 0L, 1L, False, XA_PIXMAP,
+ &type, &format, &nitems, &bytes_after,
+ &data);
+ pixmap = NULL;
+ source_pixmap = NULL;
+
+ if (result != Success || type != XA_PIXMAP ||
+ format != 32 || nitems != 1) {
+ XFree (data);
+ data = NULL;
+ }
+
+ if (data != NULL) {
+ gdk_error_trap_push ();
+ source_pixmap = gdk_pixmap_foreign_new (*(Pixmap *) data);
+ gdk_error_trap_pop ();
+
+ if (source_pixmap != NULL) {
+ gdk_drawable_set_colormap (source_pixmap,
+ gdk_screen_get_default_colormap (screen));
+ }
+ }
+
+ width = gdk_screen_get_width (screen);
+ height = gdk_screen_get_height (screen);
+
+ pixmap = gdk_pixmap_new (source_pixmap != NULL? source_pixmap :
+ gdk_screen_get_root_window (screen),
+ width, height, -1);
+
+ cr = gdk_cairo_create (pixmap);
+ if (source_pixmap != NULL) {
+ gdk_cairo_set_source_pixmap (cr, source_pixmap, 0, 0);
+ pattern = cairo_get_source (cr);
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+ } else {
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+ }
+ cairo_paint (cr);
+
+ if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
+ g_object_unref (pixmap);
+ pixmap = NULL;
+ }
+ cairo_destroy (cr);
+
+ if (source_pixmap != NULL)
+ g_object_unref (source_pixmap);
+
+ if (data != NULL)
+ XFree (data);
+
+ return pixmap;
+}
+
+static void
+mate_bg_set_root_pixmap_id (GdkScreen *screen,
+ GdkPixmap *pixmap)
+{
+ int result;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guchar *data_esetroot;
+ Pixmap pixmap_id;
+ Atom type;
+ Display *display;
+ int screen_num;
+
+ screen_num = gdk_screen_get_number (screen);
+ data_esetroot = NULL;
+
+ display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+
+ result = XGetWindowProperty (display,
+ RootWindow (display, screen_num),
+ gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"),
+ 0L, 1L, False, XA_PIXMAP,
+ &type, &format, &nitems,
+ &bytes_after,
+ &data_esetroot);
+
+ if (data_esetroot != NULL) {
+ if (result == Success && type == XA_PIXMAP &&
+ format == 32 &&
+ nitems == 1) {
+ gdk_error_trap_push ();
+ XKillClient (display, *(Pixmap *)data_esetroot);
+ gdk_flush ();
+ gdk_error_trap_pop ();
+ }
+ XFree (data_esetroot);
+ }
+
+ pixmap_id = GDK_WINDOW_XWINDOW (pixmap);
+
+ XChangeProperty (display, RootWindow (display, screen_num),
+ gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"),
+ XA_PIXMAP, 32, PropModeReplace,
+ (guchar *) &pixmap_id, 1);
+ XChangeProperty (display, RootWindow (display, screen_num),
+ gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"), XA_PIXMAP,
+ 32, PropModeReplace,
+ (guchar *) &pixmap_id, 1);
+}
+
+/**
+ * mate_bg_set_pixmap_as_root:
+ * @screen: the #GdkScreen to change root background on
+ * @pixmap: the #GdkPixmap to set root background from
+ *
+ * Set the root pixmap, and properties pointing to it. We
+ * do this atomically with a server grab to make sure that
+ * we won't leak the pixmap if somebody else it setting
+ * it at the same time. (This assumes that they follow the
+ * same conventions we do). @pixmap should come from a call
+ * to mate_bg_create_pixmap().
+ **/
+void
+mate_bg_set_pixmap_as_root (GdkScreen *screen, GdkPixmap *pixmap)
+{
+ Display *display;
+ int screen_num;
+
+ g_return_if_fail (screen != NULL);
+ g_return_if_fail (pixmap != NULL);
+
+ screen_num = gdk_screen_get_number (screen);
+
+ display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+
+ gdk_x11_display_grab (gdk_screen_get_display (screen));
+
+ mate_bg_set_root_pixmap_id (screen, pixmap);
+
+ XSetWindowBackgroundPixmap (display, RootWindow (display, screen_num),
+ GDK_PIXMAP_XID (pixmap));
+ XClearWindow (display, RootWindow (display, screen_num));
+
+ gdk_display_flush (gdk_screen_get_display (screen));
+ gdk_x11_display_ungrab (gdk_screen_get_display (screen));
+}
+
+/**
+ * mate_bg_set_pixmap_as_root_with_crossfade:
+ * @screen: the #GdkScreen to change root background on
+ * @pixmap: the #GdkPixmap to set root background from
+ * @context: a #GMainContext or %NULL
+ *
+ * Set the root pixmap, and properties pointing to it.
+ * This function differs from mate_bg_set_pixmap_as_root()
+ * in that it adds a subtle crossfade animation from the
+ * current root pixmap to the new one.
+ * same conventions we do).
+ *
+ * Return value: a #MateBGCrossfade object
+ **/
+MateBGCrossfade *
+mate_bg_set_pixmap_as_root_with_crossfade (GdkScreen *screen,
+ GdkPixmap *pixmap)
+{
+ GdkDisplay *display;
+ GdkWindow *root_window;
+ GdkPixmap *old_pixmap;
+ int width, height;
+ MateBGCrossfade *fade;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (pixmap != NULL, NULL);
+
+ root_window = gdk_screen_get_root_window (screen);
+
+ width = gdk_screen_get_width (screen);
+ height = gdk_screen_get_height (screen);
+
+ fade = mate_bg_crossfade_new (width, height);
+
+ display = gdk_screen_get_display (screen);
+ gdk_x11_display_grab (display);
+ old_pixmap = mate_bg_get_pixmap_from_root (screen);
+ mate_bg_set_root_pixmap_id (screen, pixmap);
+ mate_bg_crossfade_set_start_pixmap (fade, old_pixmap);
+ g_object_unref (old_pixmap);
+ mate_bg_crossfade_set_end_pixmap (fade, pixmap);
+ gdk_display_flush (display);
+ gdk_x11_display_ungrab (display);
+
+ mate_bg_crossfade_start (fade, root_window);
+
+ return fade;
+}
+
+/* Implementation of the pixbuf cache */
+struct _SlideShow
+{
+ gint ref_count;
+ double start_time;
+ double total_duration;
+
+ GQueue *slides;
+
+ gboolean has_multiple_sizes;
+
+ /* used during parsing */
+ struct tm start_tm;
+ GQueue *stack;
+};
+
+
+static double
+now (void)
+{
+ GTimeVal tv;
+
+ g_get_current_time (&tv);
+
+ return (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+}
+
+static Slide *
+get_current_slide (SlideShow *show,
+ double *alpha)
+{
+ double delta = fmod (now() - show->start_time, show->total_duration);
+ GList *list;
+ double elapsed;
+ int i;
+
+ if (delta < 0)
+ delta += show->total_duration;
+
+ elapsed = 0;
+ i = 0;
+ for (list = show->slides->head; list != NULL; list = list->next) {
+ Slide *slide = list->data;
+
+ if (elapsed + slide->duration > delta) {
+ if (alpha)
+ *alpha = (delta - elapsed) / (double)slide->duration;
+ return slide;
+ }
+
+ i++;
+ elapsed += slide->duration;
+ }
+
+ /* this should never happen since we have slides and we should always
+ * find a current slide for the elapsed time since beginning -- we're
+ * looping with fmod() */
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static GdkPixbuf *
+blend (GdkPixbuf *p1,
+ GdkPixbuf *p2,
+ double alpha)
+{
+ GdkPixbuf *result = gdk_pixbuf_copy (p1);
+ GdkPixbuf *tmp;
+
+ if (gdk_pixbuf_get_width (p2) != gdk_pixbuf_get_width (p1) ||
+ gdk_pixbuf_get_height (p2) != gdk_pixbuf_get_height (p1)) {
+ tmp = gdk_pixbuf_scale_simple (p2,
+ gdk_pixbuf_get_width (p1),
+ gdk_pixbuf_get_height (p1),
+ GDK_INTERP_BILINEAR);
+ }
+ else {
+ tmp = g_object_ref (p2);
+ }
+
+ pixbuf_blend (tmp, result, 0, 0, -1, -1, 0, 0, alpha);
+
+ g_object_unref (tmp);
+
+ return result;
+}
+
+typedef enum {
+ PIXBUF,
+ SLIDESHOW,
+ THUMBNAIL
+} FileType;
+
+struct FileCacheEntry
+{
+ FileType type;
+ char *filename;
+ union {
+ GdkPixbuf *pixbuf;
+ SlideShow *slideshow;
+ GdkPixbuf *thumbnail;
+ } u;
+};
+
+static void
+file_cache_entry_delete (FileCacheEntry *ent)
+{
+ g_free (ent->filename);
+
+ switch (ent->type) {
+ case PIXBUF:
+ g_object_unref (ent->u.pixbuf);
+ break;
+ case SLIDESHOW:
+ slideshow_unref (ent->u.slideshow);
+ break;
+ case THUMBNAIL:
+ g_object_unref (ent->u.thumbnail);
+ break;
+ }
+
+ g_free (ent);
+}
+
+static void
+bound_cache (MateBG *bg)
+{
+ while (g_list_length (bg->file_cache) >= CACHE_SIZE) {
+ GList *last_link = g_list_last (bg->file_cache);
+ FileCacheEntry *ent = last_link->data;
+
+ file_cache_entry_delete (ent);
+
+ bg->file_cache = g_list_delete_link (bg->file_cache, last_link);
+ }
+}
+
+static const FileCacheEntry *
+file_cache_lookup (MateBG *bg, FileType type, const char *filename)
+{
+ GList *list;
+
+ for (list = bg->file_cache; list != NULL; list = list->next) {
+ FileCacheEntry *ent = list->data;
+
+ if (ent && ent->type == type &&
+ strcmp (ent->filename, filename) == 0) {
+ return ent;
+ }
+ }
+
+ return NULL;
+}
+
+static FileCacheEntry *
+file_cache_entry_new (MateBG *bg,
+ FileType type,
+ const char *filename)
+{
+ FileCacheEntry *ent = g_new0 (FileCacheEntry, 1);
+
+ g_assert (!file_cache_lookup (bg, type, filename));
+
+ ent->type = type;
+ ent->filename = g_strdup (filename);
+
+ bg->file_cache = g_list_prepend (bg->file_cache, ent);
+
+ bound_cache (bg);
+
+ return ent;
+}
+
+static void
+file_cache_add_pixbuf (MateBG *bg,
+ const char *filename,
+ GdkPixbuf *pixbuf)
+{
+ FileCacheEntry *ent = file_cache_entry_new (bg, PIXBUF, filename);
+ ent->u.pixbuf = g_object_ref (pixbuf);
+}
+
+static void
+file_cache_add_thumbnail (MateBG *bg,
+ const char *filename,
+ GdkPixbuf *pixbuf)
+{
+ FileCacheEntry *ent = file_cache_entry_new (bg, THUMBNAIL, filename);
+ ent->u.thumbnail = g_object_ref (pixbuf);
+}
+
+static void
+file_cache_add_slide_show (MateBG *bg,
+ const char *filename,
+ SlideShow *show)
+{
+ FileCacheEntry *ent = file_cache_entry_new (bg, SLIDESHOW, filename);
+ ent->u.slideshow = slideshow_ref (show);
+}
+
+static GdkPixbuf *
+get_as_pixbuf_for_size (MateBG *bg,
+ const char *filename,
+ int best_width,
+ int best_height)
+{
+ const FileCacheEntry *ent;
+ if ((ent = file_cache_lookup (bg, PIXBUF, filename))) {
+ return g_object_ref (ent->u.pixbuf);
+ }
+ else {
+ GdkPixbufFormat *format;
+ GdkPixbuf *pixbuf;
+ gchar *tmp;
+
+ /* If scalable choose maximum size */
+ format = gdk_pixbuf_get_file_info (filename, NULL, NULL);
+ tmp = gdk_pixbuf_format_get_name (format);
+ if (format != NULL &&
+ strcmp (tmp, "svg") == 0 &&
+ (best_width > 0 && best_height > 0) &&
+ (bg->placement == MATE_BG_PLACEMENT_FILL_SCREEN ||
+ bg->placement == MATE_BG_PLACEMENT_SCALED ||
+ bg->placement == MATE_BG_PLACEMENT_ZOOMED))
+ pixbuf = gdk_pixbuf_new_from_file_at_size (filename, best_width, best_height, NULL);
+ else
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+ g_free (tmp);
+
+ if (pixbuf)
+ file_cache_add_pixbuf (bg, filename, pixbuf);
+
+ return pixbuf;
+ }
+}
+
+static SlideShow *
+get_as_slideshow (MateBG *bg, const char *filename)
+{
+ const FileCacheEntry *ent;
+ if ((ent = file_cache_lookup (bg, SLIDESHOW, filename))) {
+ return slideshow_ref (ent->u.slideshow);
+ }
+ else {
+ SlideShow *show = read_slideshow_file (filename, NULL);
+
+ if (show)
+ file_cache_add_slide_show (bg, filename, show);
+
+ return show;
+ }
+}
+
+static GdkPixbuf *
+get_as_thumbnail (MateBG *bg, MateDesktopThumbnailFactory *factory, const char *filename)
+{
+ const FileCacheEntry *ent;
+ if ((ent = file_cache_lookup (bg, THUMBNAIL, filename))) {
+ return g_object_ref (ent->u.thumbnail);
+ }
+ else {
+ GdkPixbuf *thumb = create_thumbnail_for_filename (factory, filename);
+
+ if (thumb)
+ file_cache_add_thumbnail (bg, filename, thumb);
+
+ return thumb;
+ }
+}
+
+static gboolean
+blow_expensive_caches (gpointer data)
+{
+ MateBG *bg = data;
+ GList *list, *next;
+
+ bg->blow_caches_id = 0;
+
+ for (list = bg->file_cache; list != NULL; list = next) {
+ FileCacheEntry *ent = list->data;
+ next = list->next;
+
+ if (ent->type == PIXBUF) {
+ file_cache_entry_delete (ent);
+ bg->file_cache = g_list_delete_link (bg->file_cache,
+ list);
+ }
+ }
+
+ if (bg->pixbuf_cache) {
+ g_object_unref (bg->pixbuf_cache);
+ bg->pixbuf_cache = NULL;
+ }
+
+ return FALSE;
+}
+
+static void
+blow_expensive_caches_in_idle (MateBG *bg)
+{
+ if (bg->blow_caches_id == 0) {
+ bg->blow_caches_id =
+ g_idle_add (blow_expensive_caches,
+ bg);
+ }
+}
+
+
+static gboolean
+on_timeout (gpointer data)
+{
+ MateBG *bg = data;
+
+ bg->timeout_id = 0;
+
+ queue_transitioned (bg);
+
+ return FALSE;
+}
+
+static double
+get_slide_timeout (Slide *slide)
+{
+ double timeout;
+ if (slide->fixed) {
+ timeout = slide->duration;
+ } else {
+ /* Maybe the number of steps should be configurable? */
+
+ /* In the worst case we will do a fade from 0 to 256, which mean
+ * we will never use more than 255 steps, however in most cases
+ * the first and last value are similar and users can't percieve
+ * changes in pixel values as small as 1/255th. So, lets not waste
+ * CPU cycles on transitioning to often.
+ *
+ * 64 steps is enough for each step to be just detectable in a 16bit
+ * color mode in the worst case, so we'll use this as an approximation
+ * of whats detectable.
+ */
+ timeout = slide->duration / 64.0;
+ }
+ return timeout;
+}
+
+static void
+ensure_timeout (MateBG *bg,
+ Slide *slide)
+{
+ if (!bg->timeout_id) {
+ double timeout = get_slide_timeout (slide);
+ bg->timeout_id = g_timeout_add_full (
+ G_PRIORITY_LOW,
+ timeout * 1000, on_timeout, bg, NULL);
+
+ }
+}
+
+static time_t
+get_mtime (const char *filename)
+{
+ GFile *file;
+ GFileInfo *info;
+ time_t mtime;
+
+ mtime = (time_t)-1;
+
+ if (filename) {
+ file = g_file_new_for_path (filename);
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE, NULL, NULL);
+ if (info) {
+ mtime = g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ g_object_unref (info);
+ }
+ g_object_unref (file);
+ }
+
+ return mtime;
+}
+
+static GdkPixbuf *
+scale_thumbnail (MateBGPlacement placement,
+ const char *filename,
+ GdkPixbuf *thumb,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height)
+{
+ int o_width;
+ int o_height;
+
+ if (placement != MATE_BG_PLACEMENT_TILED &&
+ placement != MATE_BG_PLACEMENT_CENTERED) {
+
+ /* In this case, the pixbuf will be scaled to fit the screen anyway,
+ * so just return the pixbuf here
+ */
+ return g_object_ref (thumb);
+ }
+
+ if (get_thumb_annotations (thumb, &o_width, &o_height) ||
+ (filename && get_original_size (filename, &o_width, &o_height))) {
+
+ int scr_height = gdk_screen_get_height (screen);
+ int scr_width = gdk_screen_get_width (screen);
+ int thumb_width = gdk_pixbuf_get_width (thumb);
+ int thumb_height = gdk_pixbuf_get_height (thumb);
+ double screen_to_dest = fit_factor (scr_width, scr_height,
+ dest_width, dest_height);
+ double thumb_to_orig = fit_factor (thumb_width, thumb_height,
+ o_width, o_height);
+ double f = thumb_to_orig * screen_to_dest;
+ int new_width, new_height;
+
+ new_width = floor (thumb_width * f + 0.5);
+ new_height = floor (thumb_height * f + 0.5);
+
+ if (placement == MATE_BG_PLACEMENT_TILED) {
+ /* Heuristic to make sure tiles don't become so small that
+ * they turn into a blur.
+ *
+ * This is strictly speaking incorrect, but the resulting
+ * thumbnail gives a much better idea what the background
+ * will actually look like.
+ */
+
+ if ((new_width < 32 || new_height < 32) &&
+ (new_width < o_width / 4 || new_height < o_height / 4)) {
+ new_width = o_width / 4;
+ new_height = o_height / 4;
+ }
+ }
+
+ thumb = gdk_pixbuf_scale_simple (thumb, new_width, new_height,
+ GDK_INTERP_BILINEAR);
+ }
+ else
+ g_object_ref (thumb);
+
+ return thumb;
+}
+
+/* frame_num determines which slide to thumbnail.
+ * -1 means 'current slide'.
+ */
+static GdkPixbuf *
+create_img_thumbnail (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height,
+ int frame_num)
+{
+ if (bg->filename) {
+ GdkPixbuf *thumb;
+
+ thumb = get_as_thumbnail (bg, factory, bg->filename);
+
+ if (thumb) {
+ GdkPixbuf *result;
+ result = scale_thumbnail (bg->placement,
+ bg->filename,
+ thumb,
+ screen,
+ dest_width,
+ dest_height);
+ g_object_unref (thumb);
+ return result;
+ }
+ else {
+ SlideShow *show = get_as_slideshow (bg, bg->filename);
+
+ if (show) {
+ double alpha;
+ Slide *slide;
+
+ if (frame_num == -1)
+ slide = get_current_slide (show, &alpha);
+ else
+ slide = g_queue_peek_nth (show->slides, frame_num);
+
+ if (slide->fixed) {
+ GdkPixbuf *tmp;
+ FileSize *fs;
+ fs = find_best_size (slide->file1, dest_width, dest_height);
+ tmp = get_as_thumbnail (bg, factory, fs->file);
+ if (tmp) {
+ thumb = scale_thumbnail (bg->placement,
+ fs->file,
+ tmp,
+ screen,
+ dest_width,
+ dest_height);
+ g_object_unref (tmp);
+ }
+ }
+ else {
+ FileSize *fs1, *fs2;
+ GdkPixbuf *p1, *p2;
+ fs1 = find_best_size (slide->file1, dest_width, dest_height);
+ p1 = get_as_thumbnail (bg, factory, fs1->file);
+
+ fs2 = find_best_size (slide->file2, dest_width, dest_height);
+ p2 = get_as_thumbnail (bg, factory, fs2->file);
+
+ if (p1 && p2) {
+ GdkPixbuf *thumb1, *thumb2;
+
+ thumb1 = scale_thumbnail (bg->placement,
+ fs1->file,
+ p1,
+ screen,
+ dest_width,
+ dest_height);
+
+ thumb2 = scale_thumbnail (bg->placement,
+ fs2->file,
+ p2,
+ screen,
+ dest_width,
+ dest_height);
+
+ thumb = blend (thumb1, thumb2, alpha);
+
+ g_object_unref (thumb1);
+ g_object_unref (thumb2);
+ }
+ if (p1)
+ g_object_unref (p1);
+ if (p2)
+ g_object_unref (p2);
+ }
+
+ ensure_timeout (bg, slide);
+
+ slideshow_unref (show);
+ }
+ }
+
+ return thumb;
+ }
+
+ return NULL;
+}
+
+/*
+ * Find the FileSize that best matches the given size.
+ * Do two passes; the first pass only considers FileSizes
+ * that are larger than the given size.
+ * We are looking for the image that best matches the aspect ratio.
+ * When two images have the same aspect ratio, prefer the one whose
+ * width is closer to the given width.
+ */
+static FileSize *
+find_best_size (GSList *sizes, gint width, gint height)
+{
+ GSList *s;
+ gdouble a, d, distance;
+ FileSize *best = NULL;
+ gint pass;
+
+ a = width/(gdouble)height;
+ distance = 10000.0;
+
+ for (pass = 0; pass < 2; pass++) {
+ for (s = sizes; s; s = s->next) {
+ FileSize *size = s->data;
+
+ if (pass == 0 && (size->width < width || size->height < height))
+ continue;
+
+ d = fabs (a - size->width/(gdouble)size->height);
+ if (d < distance) {
+ distance = d;
+ best = size;
+ }
+ else if (d == distance) {
+ if (abs (size->width - width) < abs (best->width - width)) {
+ best = size;
+ }
+ }
+ }
+
+ if (best)
+ break;
+ }
+
+ return best;
+}
+
+static GdkPixbuf *
+get_pixbuf_for_size (MateBG *bg,
+ gint best_width,
+ gint best_height)
+{
+ guint time_until_next_change;
+ gboolean hit_cache = FALSE;
+
+ /* only hit the cache if the aspect ratio matches */
+ if (bg->pixbuf_cache) {
+ int width, height;
+ width = gdk_pixbuf_get_width (bg->pixbuf_cache);
+ height = gdk_pixbuf_get_height (bg->pixbuf_cache);
+ hit_cache = 0.2 > fabs ((best_width / (double)best_height) - (width / (double)height));
+ if (!hit_cache) {
+ g_object_unref (bg->pixbuf_cache);
+ bg->pixbuf_cache = NULL;
+ }
+ }
+
+ if (!hit_cache && bg->filename) {
+ bg->file_mtime = get_mtime (bg->filename);
+
+ bg->pixbuf_cache = get_as_pixbuf_for_size (bg, bg->filename, best_width, best_height);
+ time_until_next_change = G_MAXUINT;
+ if (!bg->pixbuf_cache) {
+ SlideShow *show = get_as_slideshow (bg, bg->filename);
+
+ if (show) {
+ double alpha;
+ Slide *slide;
+
+ slideshow_ref (show);
+
+ slide = get_current_slide (show, &alpha);
+ time_until_next_change = (guint)get_slide_timeout (slide);
+ if (slide->fixed) {
+ FileSize *size;
+ size = find_best_size (slide->file1, best_width, best_height);
+ bg->pixbuf_cache = get_as_pixbuf_for_size (bg, size->file, best_width, best_height);
+ }
+ else {
+ FileSize *size;
+ GdkPixbuf *p1, *p2;
+ size = find_best_size (slide->file1, best_width, best_height);
+ p1 = get_as_pixbuf_for_size (bg, size->file, best_width, best_height);
+ size = find_best_size (slide->file2, best_width, best_height);
+ p2 = get_as_pixbuf_for_size (bg, size->file, best_width, best_height);
+
+ if (p1 && p2) {
+ bg->pixbuf_cache = blend (p1, p2, alpha);
+ }
+ if (p1)
+ g_object_unref (p1);
+ if (p2)
+ g_object_unref (p2);
+ }
+
+ ensure_timeout (bg, slide);
+
+ slideshow_unref (show);
+ }
+ }
+
+ /* If the next slideshow step is a long time away then
+ we blow away the expensive stuff (large pixbufs) from
+ the cache */
+ if (time_until_next_change > KEEP_EXPENSIVE_CACHE_SECS)
+ blow_expensive_caches_in_idle (bg);
+ }
+
+ if (bg->pixbuf_cache)
+ g_object_ref (bg->pixbuf_cache);
+
+ return bg->pixbuf_cache;
+}
+
+static gboolean
+is_different (MateBG *bg,
+ const char *filename)
+{
+ if (!filename && bg->filename) {
+ return TRUE;
+ }
+ else if (filename && !bg->filename) {
+ return TRUE;
+ }
+ else if (!filename && !bg->filename) {
+ return FALSE;
+ }
+ else {
+ time_t mtime = get_mtime (filename);
+
+ if (mtime != bg->file_mtime)
+ return TRUE;
+
+ if (strcmp (filename, bg->filename) != 0)
+ return TRUE;
+
+ return FALSE;
+ }
+}
+
+static void
+clear_cache (MateBG *bg)
+{
+ GList *list;
+
+ if (bg->file_cache) {
+ for (list = bg->file_cache; list != NULL; list = list->next) {
+ FileCacheEntry *ent = list->data;
+
+ file_cache_entry_delete (ent);
+ }
+ g_list_free (bg->file_cache);
+ bg->file_cache = NULL;
+ }
+
+ if (bg->pixbuf_cache) {
+ g_object_unref (bg->pixbuf_cache);
+
+ bg->pixbuf_cache = NULL;
+ }
+
+ if (bg->timeout_id) {
+ g_source_remove (bg->timeout_id);
+
+ bg->timeout_id = 0;
+ }
+}
+
+/* Pixbuf utilities */
+static guint32
+pixbuf_average_value (GdkPixbuf *pixbuf)
+{
+ guint64 a_total, r_total, g_total, b_total;
+ guint row, column;
+ int row_stride;
+ const guchar *pixels, *p;
+ int r, g, b, a;
+ guint64 dividend;
+ guint width, height;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ row_stride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ /* iterate through the pixbuf, counting up each component */
+ a_total = 0;
+ r_total = 0;
+ g_total = 0;
+ b_total = 0;
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf)) {
+ for (row = 0; row < height; row++) {
+ p = pixels + (row * row_stride);
+ for (column = 0; column < width; column++) {
+ r = *p++;
+ g = *p++;
+ b = *p++;
+ a = *p++;
+
+ a_total += a;
+ r_total += r * a;
+ g_total += g * a;
+ b_total += b * a;
+ }
+ }
+ dividend = height * width * 0xFF;
+ a_total *= 0xFF;
+ } else {
+ for (row = 0; row < height; row++) {
+ p = pixels + (row * row_stride);
+ for (column = 0; column < width; column++) {
+ r = *p++;
+ g = *p++;
+ b = *p++;
+
+ r_total += r;
+ g_total += g;
+ b_total += b;
+ }
+ }
+ dividend = height * width;
+ a_total = dividend * 0xFF;
+ }
+
+ return ((a_total + dividend / 2) / dividend) << 24
+ | ((r_total + dividend / 2) / dividend) << 16
+ | ((g_total + dividend / 2) / dividend) << 8
+ | ((b_total + dividend / 2) / dividend);
+}
+
+static GdkPixbuf *
+pixbuf_scale_to_fit (GdkPixbuf *src, int max_width, int max_height)
+{
+ double factor;
+ int src_width, src_height;
+ int new_width, new_height;
+
+ src_width = gdk_pixbuf_get_width (src);
+ src_height = gdk_pixbuf_get_height (src);
+
+ factor = MIN (max_width / (double) src_width, max_height / (double) src_height);
+
+ new_width = floor (src_width * factor + 0.5);
+ new_height = floor (src_height * factor + 0.5);
+
+ return gdk_pixbuf_scale_simple (src, new_width, new_height, GDK_INTERP_BILINEAR);
+}
+
+static GdkPixbuf *
+pixbuf_scale_to_min (GdkPixbuf *src, int min_width, int min_height)
+{
+ double factor;
+ int src_width, src_height;
+ int new_width, new_height;
+ GdkPixbuf *dest;
+
+ src_width = gdk_pixbuf_get_width (src);
+ src_height = gdk_pixbuf_get_height (src);
+
+ factor = MAX (min_width / (double) src_width, min_height / (double) src_height);
+
+ new_width = floor (src_width * factor + 0.5);
+ new_height = floor (src_height * factor + 0.5);
+
+ dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (src),
+ 8, min_width, min_height);
+ if (!dest)
+ return NULL;
+
+ /* crop the result */
+ gdk_pixbuf_scale (src, dest,
+ 0, 0,
+ min_width, min_height,
+ (new_width - min_width) / -2,
+ (new_height - min_height) / -2,
+ factor,
+ factor,
+ GDK_INTERP_BILINEAR);
+ return dest;
+}
+
+static guchar *
+create_gradient (const GdkColor *primary,
+ const GdkColor *secondary,
+ int n_pixels)
+{
+ guchar *result = g_malloc (n_pixels * 3);
+ int i;
+
+ for (i = 0; i < n_pixels; ++i) {
+ double ratio = (i + 0.5) / n_pixels;
+
+ result[3 * i + 0] = ((guint16) (primary->red * (1 - ratio) + secondary->red * ratio)) >> 8;
+ result[3 * i + 1] = ((guint16) (primary->green * (1 - ratio) + secondary->green * ratio)) >> 8;
+ result[3 * i + 2] = ((guint16) (primary->blue * (1 - ratio) + secondary->blue * ratio)) >> 8;
+ }
+
+ return result;
+}
+
+static void
+pixbuf_draw_gradient (GdkPixbuf *pixbuf,
+ gboolean horizontal,
+ GdkColor *primary,
+ GdkColor *secondary,
+ GdkRectangle *rect)
+{
+ int width;
+ int height;
+ int rowstride;
+ guchar *dst;
+ guchar *dst_limit;
+ int n_channels = 3;
+
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ width = rect->width;
+ height = rect->height;
+ dst = gdk_pixbuf_get_pixels (pixbuf) + rect->x * n_channels + rowstride * rect->y;
+ dst_limit = dst + height * rowstride;
+
+ if (horizontal) {
+ guchar *gradient = create_gradient (primary, secondary, width);
+ int copy_bytes_per_row = width * n_channels;
+ int i;
+
+ for (i = 0; i < height; i++) {
+ guchar *d;
+ d = dst + rowstride * i;
+ memcpy (d, gradient, copy_bytes_per_row);
+ }
+ g_free (gradient);
+ } else {
+ guchar *gb, *gradient;
+ int i;
+
+ gradient = create_gradient (primary, secondary, height);
+ for (i = 0; i < height; i++) {
+ int j;
+ guchar *d;
+
+ d = dst + rowstride * i;
+ gb = gradient + n_channels * i;
+ for (j = width; j > 0; j--) {
+ int k;
+
+ for (k = 0; k < n_channels; k++) {
+ *(d++) = gb[k];
+ }
+ }
+ }
+
+ g_free (gradient);
+ }
+}
+
+static void
+pixbuf_blend (GdkPixbuf *src,
+ GdkPixbuf *dest,
+ int src_x,
+ int src_y,
+ int src_width,
+ int src_height,
+ int dest_x,
+ int dest_y,
+ double alpha)
+{
+ int dest_width = gdk_pixbuf_get_width (dest);
+ int dest_height = gdk_pixbuf_get_height (dest);
+ int offset_x = dest_x - src_x;
+ int offset_y = dest_y - src_y;
+
+ if (src_width < 0)
+ src_width = gdk_pixbuf_get_width (src);
+
+ if (src_height < 0)
+ src_height = gdk_pixbuf_get_height (src);
+
+ if (dest_x < 0)
+ dest_x = 0;
+
+ if (dest_y < 0)
+ dest_y = 0;
+
+ if (dest_x + src_width > dest_width) {
+ src_width = dest_width - dest_x;
+ }
+
+ if (dest_y + src_height > dest_height) {
+ src_height = dest_height - dest_y;
+ }
+
+ gdk_pixbuf_composite (src, dest,
+ dest_x, dest_y,
+ src_width, src_height,
+ offset_x, offset_y,
+ 1, 1, GDK_INTERP_NEAREST,
+ alpha * 0xFF + 0.5);
+}
+
+static void
+pixbuf_tile (GdkPixbuf *src, GdkPixbuf *dest)
+{
+ int x, y;
+ int tile_width, tile_height;
+ int dest_width = gdk_pixbuf_get_width (dest);
+ int dest_height = gdk_pixbuf_get_height (dest);
+
+ tile_width = gdk_pixbuf_get_width (src);
+ tile_height = gdk_pixbuf_get_height (src);
+
+ for (y = 0; y < dest_height; y += tile_height) {
+ for (x = 0; x < dest_width; x += tile_width) {
+ pixbuf_blend (src, dest, 0, 0,
+ tile_width, tile_height, x, y, 1.0);
+ }
+ }
+}
+
+static gboolean stack_is (SlideShow *parser, const char *s1, ...);
+
+/* Parser for fading background */
+static void
+handle_start_element (GMarkupParseContext *context,
+ const gchar *name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer user_data,
+ GError **err)
+{
+ SlideShow *parser = user_data;
+ gint i;
+
+ if (strcmp (name, "static") == 0 || strcmp (name, "transition") == 0) {
+ Slide *slide = g_new0 (Slide, 1);
+
+ if (strcmp (name, "static") == 0)
+ slide->fixed = TRUE;
+
+ g_queue_push_tail (parser->slides, slide);
+ }
+ else if (strcmp (name, "size") == 0) {
+ Slide *slide = parser->slides->tail->data;
+ FileSize *size = g_new0 (FileSize, 1);
+ for (i = 0; attr_names[i]; i++) {
+ if (strcmp (attr_names[i], "width") == 0)
+ size->width = atoi (attr_values[i]);
+ else if (strcmp (attr_names[i], "height") == 0)
+ size->height = atoi (attr_values[i]);
+ }
+ if (parser->stack->tail &&
+ (strcmp (parser->stack->tail->data, "file") == 0 ||
+ strcmp (parser->stack->tail->data, "from") == 0)) {
+ slide->file1 = g_slist_prepend (slide->file1, size);
+ }
+ else if (parser->stack->tail &&
+ strcmp (parser->stack->tail->data, "to") == 0) {
+ slide->file2 = g_slist_prepend (slide->file2, size);
+ }
+ }
+ g_queue_push_tail (parser->stack, g_strdup (name));
+}
+
+static void
+handle_end_element (GMarkupParseContext *context,
+ const gchar *name,
+ gpointer user_data,
+ GError **err)
+{
+ SlideShow *parser = user_data;
+
+ g_free (g_queue_pop_tail (parser->stack));
+}
+
+static gboolean
+stack_is (SlideShow *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 int
+parse_int (const char *text)
+{
+ return strtol (text, NULL, 0);
+}
+
+static void
+handle_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **err)
+{
+ SlideShow *parser = user_data;
+ Slide *slide = parser->slides->tail? parser->slides->tail->data : NULL;
+ FileSize *fs;
+ gint i;
+
+ if (stack_is (parser, "year", "starttime", "background", NULL)) {
+ parser->start_tm.tm_year = parse_int (text) - 1900;
+ }
+ else if (stack_is (parser, "month", "starttime", "background", NULL)) {
+ parser->start_tm.tm_mon = parse_int (text) - 1;
+ }
+ else if (stack_is (parser, "day", "starttime", "background", NULL)) {
+ parser->start_tm.tm_mday = parse_int (text);
+ }
+ else if (stack_is (parser, "hour", "starttime", "background", NULL)) {
+ parser->start_tm.tm_hour = parse_int (text) - 1;
+ }
+ else if (stack_is (parser, "minute", "starttime", "background", NULL)) {
+ parser->start_tm.tm_min = parse_int (text);
+ }
+ else if (stack_is (parser, "second", "starttime", "background", NULL)) {
+ parser->start_tm.tm_sec = parse_int (text);
+ }
+ else if (stack_is (parser, "duration", "static", "background", NULL) ||
+ stack_is (parser, "duration", "transition", "background", NULL)) {
+ slide->duration = g_strtod (text, NULL);
+ parser->total_duration += slide->duration;
+ }
+ else if (stack_is (parser, "file", "static", "background", NULL) ||
+ stack_is (parser, "from", "transition", "background", NULL)) {
+ for (i = 0; text[i]; i++) {
+ if (!g_ascii_isspace (text[i]))
+ break;
+ }
+ if (text[i] == 0)
+ return;
+ fs = g_new (FileSize, 1);
+ fs->width = -1;
+ fs->height = -1;
+ fs->file = g_strdup (text);
+ slide->file1 = g_slist_prepend (slide->file1, fs);
+ if (slide->file1->next != NULL)
+ parser->has_multiple_sizes = TRUE;
+ }
+ else if (stack_is (parser, "size", "file", "static", "background", NULL) ||
+ stack_is (parser, "size", "from", "transition", "background", NULL)) {
+ fs = slide->file1->data;
+ fs->file = g_strdup (text);
+ if (slide->file1->next != NULL)
+ parser->has_multiple_sizes = TRUE;
+ }
+ else if (stack_is (parser, "to", "transition", "background", NULL)) {
+ for (i = 0; text[i]; i++) {
+ if (!g_ascii_isspace (text[i]))
+ break;
+ }
+ if (text[i] == 0)
+ return;
+ fs = g_new (FileSize, 1);
+ fs->width = -1;
+ fs->height = -1;
+ fs->file = g_strdup (text);
+ slide->file2 = g_slist_prepend (slide->file2, fs);
+ if (slide->file2->next != NULL)
+ parser->has_multiple_sizes = TRUE;
+ }
+ else if (stack_is (parser, "size", "to", "transition", "background", NULL)) {
+ fs = slide->file2->data;
+ fs->file = g_strdup (text);
+ if (slide->file2->next != NULL)
+ parser->has_multiple_sizes = TRUE;
+ }
+}
+
+static SlideShow *
+slideshow_ref (SlideShow *show)
+{
+ show->ref_count++;
+ return show;
+}
+
+static void
+slideshow_unref (SlideShow *show)
+{
+ GList *list;
+ GSList *slist;
+ FileSize *size;
+
+ show->ref_count--;
+ if (show->ref_count > 0)
+ return;
+
+ for (list = show->slides->head; list != NULL; list = list->next) {
+ Slide *slide = list->data;
+
+ for (slist = slide->file1; slist != NULL; slist = slist->next) {
+ size = slist->data;
+ g_free (size->file);
+ g_free (size);
+ }
+ g_slist_free (slide->file1);
+
+ for (slist = slide->file2; slist != NULL; slist = slist->next) {
+ size = slist->data;
+ g_free (size->file);
+ g_free (size);
+ }
+ g_slist_free (slide->file2);
+
+ g_free (slide);
+ }
+
+ g_queue_free (show->slides);
+
+ g_list_foreach (show->stack->head, (GFunc) g_free, NULL);
+ g_queue_free (show->stack);
+
+ g_free (show);
+}
+
+static void
+dump_bg (SlideShow *show)
+{
+#if 0
+ GList *list;
+ GSList *slist;
+
+ for (list = show->slides->head; list != NULL; list = list->next)
+ {
+ Slide *slide = list->data;
+
+ g_print ("\nSlide: %s\n", slide->fixed? "fixed" : "transition");
+ g_print ("duration: %f\n", slide->duration);
+ g_print ("File1:\n");
+ for (slist = slide->file1; slist != NULL; slist = slist->next) {
+ FileSize *size = slist->data;
+ g_print ("\t%s (%dx%d)\n",
+ size->file, size->width, size->height);
+ }
+ g_print ("File2:\n");
+ for (slist = slide->file2; slist != NULL; slist = slist->next) {
+ FileSize *size = slist->data;
+ g_print ("\t%s (%dx%d)\n",
+ size->file, size->width, size->height);
+ }
+ }
+#endif
+}
+
+static void
+threadsafe_localtime (time_t time, struct tm *tm)
+{
+ struct tm *res;
+
+ G_LOCK_DEFINE_STATIC (localtime_mutex);
+
+ G_LOCK (localtime_mutex);
+
+ res = localtime (&time);
+ if (tm) {
+ *tm = *res;
+ }
+
+ G_UNLOCK (localtime_mutex);
+}
+
+static SlideShow *
+read_slideshow_file (const char *filename,
+ GError **err)
+{
+ GMarkupParser parser = {
+ handle_start_element,
+ handle_end_element,
+ handle_text,
+ NULL, /* passthrough */
+ NULL, /* error */
+ };
+
+ GFile *file;
+ char *contents = NULL;
+ gsize len;
+ SlideShow *show = NULL;
+ GMarkupParseContext *context = NULL;
+ time_t t;
+
+ if (!filename)
+ return NULL;
+
+ file = g_file_new_for_path (filename);
+ if (!g_file_load_contents (file, NULL, &contents, &len, NULL, NULL)) {
+ g_object_unref (file);
+ return NULL;
+ }
+ g_object_unref (file);
+
+ show = g_new0 (SlideShow, 1);
+ show->ref_count = 1;
+ threadsafe_localtime ((time_t)0, &show->start_tm);
+ show->stack = g_queue_new ();
+ show->slides = g_queue_new ();
+
+ context = g_markup_parse_context_new (&parser, 0, show, NULL);
+
+ if (!g_markup_parse_context_parse (context, contents, len, err)) {
+ slideshow_unref (show);
+ show = NULL;
+ }
+
+
+ if (show) {
+ if (!g_markup_parse_context_end_parse (context, err)) {
+ slideshow_unref (show);
+ show = NULL;
+ }
+ }
+
+ g_markup_parse_context_free (context);
+
+ if (show) {
+ t = mktime (&show->start_tm);
+
+ show->start_time = (double)t;
+
+ dump_bg (show);
+
+ /* no slides, that's not a slideshow */
+ if (g_queue_get_length (show->slides) == 0) {
+ slideshow_unref (show);
+ show = NULL;
+ }
+ }
+
+ g_free (contents);
+
+ return show;
+}
+
+/* Thumbnail utilities */
+static GdkPixbuf *
+create_thumbnail_for_filename (MateDesktopThumbnailFactory *factory,
+ const char *filename)
+{
+ char *thumb;
+ time_t mtime;
+ GdkPixbuf *orig, *result = NULL;
+ char *uri;
+
+ mtime = get_mtime (filename);
+
+ if (mtime == (time_t)-1)
+ return NULL;
+
+ uri = g_filename_to_uri (filename, NULL, NULL);
+
+ thumb = mate_desktop_thumbnail_factory_lookup (factory, uri, mtime);
+
+ if (thumb) {
+ result = gdk_pixbuf_new_from_file (thumb, NULL);
+ g_free (thumb);
+ }
+ else {
+ orig = gdk_pixbuf_new_from_file (filename, NULL);
+ if (orig) {
+ int orig_width = gdk_pixbuf_get_width (orig);
+ int orig_height = gdk_pixbuf_get_height (orig);
+
+ result = pixbuf_scale_to_fit (orig, 128, 128);
+
+ g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-height",
+ g_strdup_printf ("%d", orig_height), g_free);
+ g_object_set_data_full (G_OBJECT (result), "mate-thumbnail-width",
+ g_strdup_printf ("%d", orig_width), g_free);
+
+ g_object_unref (orig);
+
+ mate_desktop_thumbnail_factory_save_thumbnail (factory, result, uri, mtime);
+ }
+ else {
+ mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, mtime);
+ }
+ }
+
+ g_free (uri);
+
+ return result;
+}
+
+static gboolean
+get_thumb_annotations (GdkPixbuf *thumb,
+ int *orig_width,
+ int *orig_height)
+{
+ char *end;
+ const char *wstr, *hstr;
+
+ wstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Width");
+ hstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Height");
+
+ if (hstr && wstr) {
+ *orig_width = strtol (wstr, &end, 10);
+ if (*end != 0)
+ return FALSE;
+
+ *orig_height = strtol (hstr, &end, 10);
+ if (*end != 0)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+slideshow_has_multiple_sizes (SlideShow *show)
+{
+ return show->has_multiple_sizes;
+}
+
+/*
+ * Returns whether the background is a slideshow.
+ */
+gboolean
+mate_bg_changes_with_time (MateBG *bg)
+{
+ SlideShow *show;
+
+ g_return_val_if_fail (bg != NULL, FALSE);
+
+ show = get_as_slideshow (bg, bg->filename);
+ if (show)
+ return g_queue_get_length (show->slides) > 1;
+
+ return FALSE;
+}
+
+/* Creates a thumbnail for a certain frame, where 'frame' is somewhat
+ * vaguely defined as 'suitable point to show while single-stepping
+ * through the slideshow'. Returns NULL if frame_num is out of bounds.
+ */
+GdkPixbuf *
+mate_bg_create_frame_thumbnail (MateBG *bg,
+ MateDesktopThumbnailFactory *factory,
+ GdkScreen *screen,
+ int dest_width,
+ int dest_height,
+ int frame_num)
+{
+ SlideShow *show;
+ GdkPixbuf *result;
+ GdkPixbuf *thumb;
+ GList *l;
+ int i, skipped;
+ gboolean found;
+
+ g_return_val_if_fail (bg != NULL, FALSE);
+
+ show = get_as_slideshow (bg, bg->filename);
+
+ if (!show)
+ return NULL;
+
+
+ if (frame_num < 0 || frame_num >= g_queue_get_length (show->slides))
+ return NULL;
+
+ i = 0;
+ skipped = 0;
+ found = FALSE;
+ for (l = show->slides->head; l; l = l->next) {
+ Slide *slide = l->data;
+ if (!slide->fixed) {
+ skipped++;
+ continue;
+ }
+ if (i == frame_num) {
+ found = TRUE;
+ break;
+ }
+ i++;
+ }
+ if (!found)
+ return NULL;
+
+
+ result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
+
+ draw_color (bg, result, screen);
+
+ thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, frame_num + skipped);
+
+ if (thumb) {
+ draw_image (bg->placement, thumb, result);
+ g_object_unref (thumb);
+ }
+
+ return result;
+}
+
diff --git a/libmate-desktop/mate-desktop-2.0-uninstalled.pc.in b/libmate-desktop/mate-desktop-2.0-uninstalled.pc.in
new file mode 100644
index 0000000..21520a5
--- /dev/null
+++ b/libmate-desktop/mate-desktop-2.0-uninstalled.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: mate-desktop-2.0
+Description: Utility library for loading .desktop files
+Requires: gtk+-2.0 libmateui-2.0 @STARTUP_NOTIFICATION_PACKAGE@
+Version: @VERSION@
+Libs: ${pc_top_builddir}/${pcfiledir}/libmate-desktop-2.la
+Cflags: -I${pc_top_builddir}/${pcfiledir}
diff --git a/libmate-desktop/mate-desktop-2.0.pc.in b/libmate-desktop/mate-desktop-2.0.pc.in
new file mode 100644
index 0000000..f72665e
--- /dev/null
+++ b/libmate-desktop/mate-desktop-2.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: mate-desktop-2.0
+Description: Utility library for loading .desktop files
+Requires: gtk+-2.0 @STARTUP_NOTIFICATION_PACKAGE@
+Version: @VERSION@
+Libs: -L${libdir} -lmate-desktop-2
+Cflags: -I${includedir}/mate-desktop-2.0
diff --git a/libmate-desktop/mate-desktop-item.c b/libmate-desktop/mate-desktop-item.c
new file mode 100644
index 0000000..cf86a6d
--- /dev/null
+++ b/libmate-desktop/mate-desktop-item.c
@@ -0,0 +1,3872 @@
+/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* mate-desktop-item.c - MATE Desktop File Representation
+
+ Copyright (C) 1999, 2000 Red Hat Inc.
+ Copyright (C) 2001 Sid Vicious
+ All rights reserved.
+
+ This file is part of the Mate Library.
+
+ Developed by Elliot Lee <[email protected]> and Sid Vicious
+
+ The Mate 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 Mate 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 Mate 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. */
+/*
+ @NOTATION@
+ */
+
+#include "config.h"
+
+#include <limits.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <time.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <locale.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+#define SN_API_NOT_YET_FROZEN
+#include <libsn/sn.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#endif
+
+#define sure_string(s) ((s)!=NULL?(s):"")
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#undef MATE_DISABLE_DEPRECATED
+#include <libmate/mate-desktop-item.h>
+#include <libmate/mate-desktop-utils.h>
+
+#include "private.h"
+
+struct _MateDesktopItem {
+ int refcount;
+
+ /* all languages used */
+ GList *languages;
+
+ MateDesktopItemType type;
+
+ /* `modified' means that the ditem has been
+ * modified since the last save. */
+ gboolean modified;
+
+ /* Keys of the main section only */
+ GList *keys;
+
+ GList *sections;
+
+ /* This includes ALL keys, including
+ * other sections, separated by '/' */
+ GHashTable *main_hash;
+
+ char *location;
+
+ time_t mtime;
+
+ guint32 launch_time;
+};
+
+/* If mtime is set to this, set_location won't update mtime,
+ * this is to be used internally only. */
+#define DONT_UPDATE_MTIME ((time_t)-2)
+
+typedef struct {
+ char *name;
+ GList *keys;
+} Section;
+
+typedef enum {
+ ENCODING_UNKNOWN,
+ ENCODING_UTF8,
+ ENCODING_LEGACY_MIXED
+} Encoding;
+
+/*
+ * IO reading utils, that look like the libc buffered io stuff
+ */
+
+#define READ_BUF_SIZE (32 * 1024)
+
+typedef struct {
+ GFile *file;
+ GFileInputStream *stream;
+ char *uri;
+ char *buf;
+ gboolean buf_needs_free;
+ gboolean past_first_read;
+ gboolean eof;
+ guint64 size;
+ gsize pos;
+} ReadBuf;
+
+static MateDesktopItem *ditem_load (ReadBuf *rb,
+ gboolean no_translations,
+ GError **error);
+static gboolean ditem_save (MateDesktopItem *item,
+ const char *uri,
+ GError **error);
+
+static void mate_desktop_item_set_location_gfile (MateDesktopItem *item,
+ GFile *file);
+
+static MateDesktopItem *mate_desktop_item_new_from_gfile (GFile *file,
+ MateDesktopItemLoadFlags flags,
+ GError **error);
+
+static int
+readbuf_getc (ReadBuf *rb)
+{
+ if (rb->eof)
+ return EOF;
+
+ if (rb->size == 0 ||
+ rb->pos == rb->size) {
+ gssize bytes_read;
+
+ if (rb->stream == NULL)
+ bytes_read = 0;
+ else
+ bytes_read = g_input_stream_read (G_INPUT_STREAM (rb->stream),
+ rb->buf,
+ READ_BUF_SIZE,
+ NULL, NULL);
+
+ /* FIXME: handle errors other than EOF */
+ if (bytes_read <= 0) {
+ rb->eof = TRUE;
+ return EOF;
+ }
+
+ if (rb->size != 0)
+ rb->past_first_read = TRUE;
+ rb->size = bytes_read;
+ rb->pos = 0;
+
+ }
+
+ return (guchar) rb->buf[rb->pos++];
+}
+
+/* Note, does not include the trailing \n */
+static char *
+readbuf_gets (char *buf, gsize bufsize, ReadBuf *rb)
+{
+ int c;
+ gsize pos;
+
+ g_return_val_if_fail (buf != NULL, NULL);
+ g_return_val_if_fail (rb != NULL, NULL);
+
+ pos = 0;
+ buf[0] = '\0';
+
+ do {
+ c = readbuf_getc (rb);
+ if (c == EOF || c == '\n')
+ break;
+ buf[pos++] = c;
+ } while (pos < bufsize-1);
+
+ if (c == EOF && pos == 0)
+ return NULL;
+
+ buf[pos++] = '\0';
+
+ return buf;
+}
+
+static ReadBuf *
+readbuf_open (GFile *file, GError **error)
+{
+ GError *local_error;
+ GFileInputStream *stream;
+ char *uri;
+ ReadBuf *rb;
+
+ g_return_val_if_fail (file != NULL, NULL);
+
+ uri = g_file_get_uri (file);
+ local_error = NULL;
+ stream = g_file_read (file, NULL, &local_error);
+
+ if (stream == NULL) {
+ g_set_error (error,
+ /* FIXME: better errors */
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN,
+ _("Error reading file '%s': %s"),
+ uri, local_error->message);
+ g_error_free (local_error);
+ g_free (uri);
+ return NULL;
+ }
+
+ rb = g_new0 (ReadBuf, 1);
+ rb->stream = stream;
+ rb->file = g_file_dup (file);
+ rb->uri = uri;
+ rb->buf = g_malloc (READ_BUF_SIZE);
+ rb->buf_needs_free = TRUE;
+ /* rb->past_first_read = FALSE; */
+ /* rb->eof = FALSE; */
+ /* rb->size = 0; */
+ /* rb->pos = 0; */
+
+ return rb;
+}
+
+static ReadBuf *
+readbuf_new_from_string (const char *uri, const char *string, gssize length)
+{
+ ReadBuf *rb;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (length >= 0, NULL);
+
+ rb = g_new0 (ReadBuf, 1);
+ /* rb->file = NULL; */
+ /* rb->stream = NULL; */
+ rb->uri = g_strdup (uri);
+ rb->buf = (char *) string;
+ /* rb->buf_needs_free = FALSE; */
+ /* rb->past_first_read = FALSE; */
+ /* rb->eof = FALSE; */
+ rb->size = length;
+ /* rb->pos = 0; */
+
+ return rb;
+}
+
+static gboolean
+readbuf_rewind (ReadBuf *rb, GError **error)
+{
+ GError *local_error;
+
+ rb->eof = FALSE;
+ rb->pos = 0;
+
+ if (!rb->past_first_read)
+ return TRUE;
+
+ rb->size = 0;
+
+ if (g_seekable_seek (G_SEEKABLE (rb->stream),
+ 0, G_SEEK_SET, NULL, NULL))
+ return TRUE;
+
+ g_object_unref (rb->stream);
+ local_error = NULL;
+ rb->stream = g_file_read (rb->file, NULL, &local_error);
+
+ if (rb->stream == NULL) {
+ g_set_error (
+ error, MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN,
+ _("Error rewinding file '%s': %s"),
+ rb->uri, local_error->message);
+ g_error_free (local_error);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+readbuf_close (ReadBuf *rb)
+{
+ if (rb->stream != NULL)
+ g_object_unref (rb->stream);
+ if (rb->file != NULL)
+ g_object_unref (rb->file);
+ g_free (rb->uri);
+ if (rb->buf_needs_free)
+ g_free (rb->buf);
+ g_free (rb);
+}
+
+static MateDesktopItemType
+type_from_string (const char *type)
+{
+ if (!type)
+ return MATE_DESKTOP_ITEM_TYPE_NULL;
+
+ switch (type [0]) {
+ case 'A':
+ if (!strcmp (type, "Application"))
+ return MATE_DESKTOP_ITEM_TYPE_APPLICATION;
+ break;
+ case 'L':
+ if (!strcmp (type, "Link"))
+ return MATE_DESKTOP_ITEM_TYPE_LINK;
+ break;
+ case 'F':
+ if (!strcmp (type, "FSDevice"))
+ return MATE_DESKTOP_ITEM_TYPE_FSDEVICE;
+ break;
+ case 'M':
+ if (!strcmp (type, "MimeType"))
+ return MATE_DESKTOP_ITEM_TYPE_MIME_TYPE;
+ break;
+ case 'D':
+ if (!strcmp (type, "Directory"))
+ return MATE_DESKTOP_ITEM_TYPE_DIRECTORY;
+ break;
+ case 'S':
+ if (!strcmp (type, "Service"))
+ return MATE_DESKTOP_ITEM_TYPE_SERVICE;
+
+ else if (!strcmp (type, "ServiceType"))
+ return MATE_DESKTOP_ITEM_TYPE_SERVICE_TYPE;
+ break;
+ default:
+ break;
+ }
+
+ return MATE_DESKTOP_ITEM_TYPE_OTHER;
+}
+
+/**
+ * mate_desktop_item_new:
+ *
+ * Creates a MateDesktopItem object. The reference count on the returned value is set to '1'.
+ *
+ * Returns: The new MateDesktopItem
+ */
+MateDesktopItem *
+mate_desktop_item_new (void)
+{
+ MateDesktopItem *retval;
+
+ _mate_desktop_init_i18n ();
+
+ retval = g_new0 (MateDesktopItem, 1);
+
+ retval->refcount++;
+
+ retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ /* These are guaranteed to be set */
+ mate_desktop_item_set_string (retval,
+ MATE_DESKTOP_ITEM_NAME,
+ /* Translators: the "name" mentioned
+ * here is the name of an application or
+ * a document */
+ _("No name"));
+ mate_desktop_item_set_string (retval,
+ MATE_DESKTOP_ITEM_ENCODING,
+ "UTF-8");
+ mate_desktop_item_set_string (retval,
+ MATE_DESKTOP_ITEM_VERSION,
+ "1.0");
+
+ retval->launch_time = 0;
+
+ return retval;
+}
+
+static Section *
+dup_section (Section *sec)
+{
+ GList *li;
+ Section *retval = g_new0 (Section, 1);
+
+ retval->name = g_strdup (sec->name);
+
+ retval->keys = g_list_copy (sec->keys);
+ for (li = retval->keys; li != NULL; li = li->next)
+ li->data = g_strdup (li->data);
+
+ return retval;
+}
+
+static void
+copy_string_hash (gpointer key, gpointer value, gpointer user_data)
+{
+ GHashTable *copy = user_data;
+ g_hash_table_replace (copy,
+ g_strdup (key),
+ g_strdup (value));
+}
+
+
+/**
+ * mate_desktop_item_copy:
+ * @item: The item to be copied
+ *
+ * Creates a copy of a MateDesktopItem. The new copy has a refcount of 1.
+ * Note: Section stack is NOT copied.
+ *
+ * Returns: The new copy
+ */
+MateDesktopItem *
+mate_desktop_item_copy (const MateDesktopItem *item)
+{
+ GList *li;
+ MateDesktopItem *retval;
+
+ g_return_val_if_fail (item != NULL, NULL);
+ g_return_val_if_fail (item->refcount > 0, NULL);
+
+ retval = mate_desktop_item_new ();
+
+ retval->type = item->type;
+ retval->modified = item->modified;
+ retval->location = g_strdup (item->location);
+ retval->mtime = item->mtime;
+ retval->launch_time = item->launch_time;
+
+ /* Languages */
+ retval->languages = g_list_copy (item->languages);
+ for (li = retval->languages; li != NULL; li = li->next)
+ li->data = g_strdup (li->data);
+
+ /* Keys */
+ retval->keys = g_list_copy (item->keys);
+ for (li = retval->keys; li != NULL; li = li->next)
+ li->data = g_strdup (li->data);
+
+ /* Sections */
+ retval->sections = g_list_copy (item->sections);
+ for (li = retval->sections; li != NULL; li = li->next)
+ li->data = dup_section (li->data);
+
+ retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ g_hash_table_foreach (item->main_hash,
+ copy_string_hash,
+ retval->main_hash);
+
+ return retval;
+}
+
+static void
+read_sort_order (MateDesktopItem *item, GFile *dir)
+{
+ GFile *child;
+ char buf[BUFSIZ];
+ GString *str;
+ ReadBuf *rb;
+
+ child = g_file_get_child (dir, ".order");
+
+ rb = readbuf_open (child, NULL);
+ g_object_unref (child);
+
+ if (rb == NULL)
+ return;
+
+ str = NULL;
+ while (readbuf_gets (buf, sizeof (buf), rb) != NULL) {
+ if (str == NULL)
+ str = g_string_new (buf);
+ else
+ g_string_append (str, buf);
+ g_string_append_c (str, ';');
+ }
+ readbuf_close (rb);
+ if (str != NULL) {
+ mate_desktop_item_set_string (item, MATE_DESKTOP_ITEM_SORT_ORDER,
+ str->str);
+ g_string_free (str, TRUE);
+ }
+}
+
+static MateDesktopItem *
+make_fake_directory (GFile *dir)
+{
+ MateDesktopItem *item;
+ GFile *child;
+
+ item = mate_desktop_item_new ();
+ mate_desktop_item_set_entry_type (item,
+ MATE_DESKTOP_ITEM_TYPE_DIRECTORY);
+
+
+ item->mtime = DONT_UPDATE_MTIME; /* it doesn't exist, we know that */
+ child = g_file_get_child (dir, ".directory");
+ mate_desktop_item_set_location_gfile (item, child);
+ item->mtime = 0;
+ g_object_unref (child);
+
+ read_sort_order (item, dir);
+
+ return item;
+}
+
+/**
+ * mate_desktop_item_new_from_file:
+ * @file: The filename or directory path to load the MateDesktopItem from
+ * @flags: Flags to influence the loading process
+ *
+ * This function loads 'file' and turns it into a MateDesktopItem.
+ *
+ * Returns: The newly loaded item.
+ */
+MateDesktopItem *
+mate_desktop_item_new_from_file (const char *file,
+ MateDesktopItemLoadFlags flags,
+ GError **error)
+{
+ MateDesktopItem *retval;
+ GFile *gfile;
+
+ g_return_val_if_fail (file != NULL, NULL);
+
+ gfile = g_file_new_for_path (file);
+ retval = mate_desktop_item_new_from_gfile (gfile, flags, error);
+ g_object_unref (gfile);
+
+ return retval;
+}
+
+/**
+ * mate_desktop_item_new_from_uri:
+ * @uri: URI to load the MateDesktopItem from
+ * @flags: Flags to influence the loading process
+ *
+ * This function loads 'uri' and turns it into a MateDesktopItem.
+ *
+ * Returns: The newly loaded item.
+ */
+MateDesktopItem *
+mate_desktop_item_new_from_uri (const char *uri,
+ MateDesktopItemLoadFlags flags,
+ GError **error)
+{
+ MateDesktopItem *retval;
+ GFile *file;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ file = g_file_new_for_uri (uri);
+ retval = mate_desktop_item_new_from_gfile (file, flags, error);
+ g_object_unref (file);
+
+ return retval;
+}
+
+static MateDesktopItem *
+mate_desktop_item_new_from_gfile (GFile *file,
+ MateDesktopItemLoadFlags flags,
+ GError **error)
+{
+ MateDesktopItem *retval;
+ GFile *subfn;
+ GFileInfo *info;
+ GFileType type;
+ GFile *parent;
+ time_t mtime = 0;
+ ReadBuf *rb;
+
+ g_return_val_if_fail (file != NULL, NULL);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE","G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE, NULL, error);
+ if (info == NULL)
+ return NULL;
+
+ type = g_file_info_get_file_type (info);
+
+ if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_DIRECTORY) {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ g_set_error (error,
+ /* FIXME: better errors */
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_INVALID_TYPE,
+ _("File '%s' is not a regular file or directory."),
+ uri);
+
+ g_free (uri);
+ g_object_unref (info);
+
+ return NULL;
+ }
+
+ mtime = g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ g_object_unref (info);
+
+ if (type == G_FILE_TYPE_DIRECTORY) {
+ GFile *child;
+ GFileInfo *child_info;
+
+ child = g_file_get_child (file, ".directory");
+ child_info = g_file_query_info (child,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ if (child_info == NULL) {
+ g_object_unref (child);
+
+ if (flags & MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS) {
+ return NULL;
+ } else {
+ return make_fake_directory (file);
+ }
+ }
+
+ mtime = g_file_info_get_attribute_uint64 (child_info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ g_object_unref (child_info);
+
+ subfn = child;
+ } else {
+ subfn = g_file_dup (file);
+ }
+
+ rb = readbuf_open (subfn, error);
+
+ if (rb == NULL) {
+ g_object_unref (subfn);
+ return NULL;
+ }
+
+ retval = ditem_load (rb,
+ (flags & MATE_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS) != 0,
+ error);
+
+ if (retval == NULL) {
+ g_object_unref (subfn);
+ return NULL;
+ }
+
+ if (flags & MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS &&
+ ! mate_desktop_item_exists (retval)) {
+ mate_desktop_item_unref (retval);
+ g_object_unref (subfn);
+ return NULL;
+ }
+
+ retval->mtime = DONT_UPDATE_MTIME;
+ mate_desktop_item_set_location_gfile (retval, subfn);
+ retval->mtime = mtime;
+
+ parent = g_file_get_parent (file);
+ if (parent != NULL) {
+ read_sort_order (retval, parent);
+ g_object_unref (parent);
+ }
+
+ g_object_unref (subfn);
+
+ return retval;
+}
+
+/**
+ * mate_desktop_item_new_from_string:
+ * @string: string to load the MateDesktopItem from
+ * @length: length of string, or -1 to use strlen
+ * @flags: Flags to influence the loading process
+ * @error: place to put errors
+ *
+ * This function turns the contents of the string into a MateDesktopItem.
+ *
+ * Returns: The newly loaded item.
+ */
+MateDesktopItem *
+mate_desktop_item_new_from_string (const char *uri,
+ const char *string,
+ gssize length,
+ MateDesktopItemLoadFlags flags,
+ GError **error)
+{
+ MateDesktopItem *retval;
+ ReadBuf *rb;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (length >= -1, NULL);
+
+ if (length == -1) {
+ length = strlen (string);
+ }
+
+ rb = readbuf_new_from_string (uri, string, length);
+
+ retval = ditem_load (rb,
+ (flags & MATE_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS) != 0,
+ error);
+
+ if (retval == NULL) {
+ return NULL;
+ }
+
+ /* FIXME: Sort order? */
+
+ return retval;
+}
+
+static char *
+lookup_desktop_file_in_data_dir (const char *desktop_file,
+ const char *data_dir)
+{
+ char *path;
+
+ path = g_build_filename (data_dir, "applications", desktop_file, NULL);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+ g_free (path);
+ return NULL;
+ }
+ return path;
+}
+
+static char *
+file_from_basename (const char *basename)
+{
+ const char * const *system_data_dirs;
+ const char *user_data_dir;
+ char *retval;
+ int i;
+
+ user_data_dir = g_get_user_data_dir ();
+ system_data_dirs = g_get_system_data_dirs ();
+
+ if ((retval = lookup_desktop_file_in_data_dir (basename, user_data_dir))) {
+ return retval;
+ }
+ for (i = 0; system_data_dirs[i]; i++) {
+ if ((retval = lookup_desktop_file_in_data_dir (basename, system_data_dirs[i]))) {
+ return retval;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * mate_desktop_item_new_from_basename:
+ * @basename: The basename of the MateDesktopItem to load.
+ * @flags: Flags to influence the loading process
+ *
+ * This function loads 'basename' from a system data directory and
+ * returns its MateDesktopItem.
+ *
+ * Returns: The newly loaded item.
+ */
+MateDesktopItem *
+mate_desktop_item_new_from_basename (const char *basename,
+ MateDesktopItemLoadFlags flags,
+ GError **error)
+{
+ MateDesktopItem *retval;
+ char *file;
+
+ g_return_val_if_fail (basename != NULL, NULL);
+
+ if (!(file = file_from_basename (basename))) {
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN,
+ _("Cannot find file '%s'"),
+ basename);
+ return NULL;
+ }
+
+ retval = mate_desktop_item_new_from_file (file, flags, error);
+ g_free (file);
+
+ return retval;
+}
+
+/**
+ * mate_desktop_item_save:
+ * @item: A desktop item
+ * @under: A new uri (location) for this #MateDesktopItem
+ * @force: Save even if it wasn't modified
+ * @error: #GError return
+ *
+ * Writes the specified item to disk. If the 'under' is NULL, the original
+ * location is used. It sets the location of this entry to point to the
+ * new location.
+ *
+ * Returns: boolean. %TRUE if the file was saved, %FALSE otherwise
+ */
+gboolean
+mate_desktop_item_save (MateDesktopItem *item,
+ const char *under,
+ gboolean force,
+ GError **error)
+{
+ const char *uri;
+
+ if (under == NULL &&
+ ! force &&
+ ! item->modified)
+ return TRUE;
+
+ if (under == NULL)
+ uri = item->location;
+ else
+ uri = under;
+
+ if (uri == NULL) {
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_NO_FILENAME,
+ _("No filename to save to"));
+ return FALSE;
+ }
+
+ if ( ! ditem_save (item, uri, error))
+ return FALSE;
+
+ item->modified = FALSE;
+ item->mtime = time (NULL);
+
+ return TRUE;
+}
+
+/**
+ * mate_desktop_item_ref:
+ * @item: A desktop item
+ *
+ * Description: Increases the reference count of the specified item.
+ *
+ * Returns: the newly referenced @item
+ */
+MateDesktopItem *
+mate_desktop_item_ref (MateDesktopItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+
+ item->refcount++;
+
+ return item;
+}
+
+static void
+free_section (gpointer data, gpointer user_data)
+{
+ Section *section = data;
+
+ g_free (section->name);
+ section->name = NULL;
+
+ g_list_foreach (section->keys, (GFunc)g_free, NULL);
+ g_list_free (section->keys);
+ section->keys = NULL;
+
+ g_free (section);
+}
+
+/**
+ * mate_desktop_item_unref:
+ * @item: A desktop item
+ *
+ * Decreases the reference count of the specified item, and destroys the item if there are no more references left.
+ */
+void
+mate_desktop_item_unref (MateDesktopItem *item)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ item->refcount--;
+
+ if(item->refcount != 0)
+ return;
+
+ g_list_foreach (item->languages, (GFunc)g_free, NULL);
+ g_list_free (item->languages);
+ item->languages = NULL;
+
+ g_list_foreach (item->keys, (GFunc)g_free, NULL);
+ g_list_free (item->keys);
+ item->keys = NULL;
+
+ g_list_foreach (item->sections, free_section, NULL);
+ g_list_free (item->sections);
+ item->sections = NULL;
+
+ g_hash_table_destroy (item->main_hash);
+ item->main_hash = NULL;
+
+ g_free (item->location);
+ item->location = NULL;
+
+ g_free (item);
+}
+
+static Section *
+find_section (MateDesktopItem *item, const char *section)
+{
+ GList *li;
+ Section *sec;
+
+ if (section == NULL)
+ return NULL;
+ if (strcmp (section, "Desktop Entry") == 0)
+ return NULL;
+
+ for (li = item->sections; li != NULL; li = li->next) {
+ sec = li->data;
+ if (strcmp (sec->name, section) == 0)
+ return sec;
+ }
+
+ sec = g_new0 (Section, 1);
+ sec->name = g_strdup (section);
+ sec->keys = NULL;
+
+ item->sections = g_list_append (item->sections, sec);
+
+ /* Don't mark the item modified, this is just an empty section,
+ * it won't be saved even */
+
+ return sec;
+}
+
+static Section *
+section_from_key (MateDesktopItem *item, const char *key)
+{
+ char *p;
+ char *name;
+ Section *sec;
+
+ if (key == NULL)
+ return NULL;
+
+ p = strchr (key, '/');
+ if (p == NULL)
+ return NULL;
+
+ name = g_strndup (key, p - key);
+
+ sec = find_section (item, name);
+
+ g_free (name);
+
+ return sec;
+}
+
+static const char *
+key_basename (const char *key)
+{
+ char *p = strrchr (key, '/');
+ if (p != NULL)
+ return p+1;
+ else
+ return key;
+}
+
+
+static const char *
+lookup (const MateDesktopItem *item, const char *key)
+{
+ return g_hash_table_lookup (item->main_hash, key);
+}
+
+static const char *
+lookup_locale (const MateDesktopItem *item, const char *key, const char *locale)
+{
+ if (locale == NULL ||
+ strcmp (locale, "C") == 0) {
+ return lookup (item, key);
+ } else {
+ const char *ret;
+ char *full = g_strdup_printf ("%s[%s]", key, locale);
+ ret = lookup (item, full);
+ g_free (full);
+ return ret;
+ }
+}
+
+static const char *
+lookup_best_locale (const MateDesktopItem *item, const char *key)
+{
+ const char * const *langs_pointer;
+ int i;
+
+ langs_pointer = g_get_language_names ();
+ for (i = 0; langs_pointer[i] != NULL; i++) {
+ const char *ret = NULL;
+
+ ret = lookup_locale (item, key, langs_pointer[i]);
+ if (ret != NULL)
+ return ret;
+ }
+
+ return NULL;
+}
+
+static void
+set (MateDesktopItem *item, const char *key, const char *value)
+{
+ Section *sec = section_from_key (item, key);
+
+ if (sec != NULL) {
+ if (value != NULL) {
+ if (g_hash_table_lookup (item->main_hash, key) == NULL)
+ sec->keys = g_list_append
+ (sec->keys,
+ g_strdup (key_basename (key)));
+
+ g_hash_table_replace (item->main_hash,
+ g_strdup (key),
+ g_strdup (value));
+ } else {
+ GList *list = g_list_find_custom
+ (sec->keys, key_basename (key),
+ (GCompareFunc)strcmp);
+ if (list != NULL) {
+ g_free (list->data);
+ sec->keys =
+ g_list_delete_link (sec->keys, list);
+ }
+ g_hash_table_remove (item->main_hash, key);
+ }
+ } else {
+ if (value != NULL) {
+ if (g_hash_table_lookup (item->main_hash, key) == NULL)
+ item->keys = g_list_append (item->keys,
+ g_strdup (key));
+
+ g_hash_table_replace (item->main_hash,
+ g_strdup (key),
+ g_strdup (value));
+ } else {
+ GList *list = g_list_find_custom
+ (item->keys, key, (GCompareFunc)strcmp);
+ if (list != NULL) {
+ g_free (list->data);
+ item->keys =
+ g_list_delete_link (item->keys, list);
+ }
+ g_hash_table_remove (item->main_hash, key);
+ }
+ }
+ item->modified = TRUE;
+}
+
+static void
+set_locale (MateDesktopItem *item, const char *key,
+ const char *locale, const char *value)
+{
+ if (locale == NULL ||
+ strcmp (locale, "C") == 0) {
+ set (item, key, value);
+ } else {
+ char *full = g_strdup_printf ("%s[%s]", key, locale);
+ set (item, full, value);
+ g_free (full);
+
+ /* add the locale to the list of languages if it wasn't there
+ * before */
+ if (g_list_find_custom (item->languages, locale,
+ (GCompareFunc)strcmp) == NULL)
+ item->languages = g_list_prepend (item->languages,
+ g_strdup (locale));
+ }
+}
+
+static char **
+list_to_vector (GSList *list)
+{
+ int len = g_slist_length (list);
+ char **argv;
+ int i;
+ GSList *li;
+
+ argv = g_new0 (char *, len+1);
+
+ for (i = 0, li = list;
+ li != NULL;
+ li = li->next, i++) {
+ argv[i] = g_strdup (li->data);
+ }
+ argv[i] = NULL;
+
+ return argv;
+}
+
+static GSList *
+make_args (GList *files)
+{
+ GSList *list = NULL;
+ GList *li;
+
+ for (li = files; li != NULL; li = li->next) {
+ GFile *gfile;
+ const char *file = li->data;
+ if (file == NULL)
+ continue;
+ gfile = g_file_new_for_uri (file);
+ list = g_slist_prepend (list, gfile);
+ }
+
+ return g_slist_reverse (list);
+}
+
+static void
+free_args (GSList *list)
+{
+ GSList *li;
+
+ for (li = list; li != NULL; li = li->next) {
+ g_object_unref (G_FILE (li->data));
+ li->data = NULL;
+ }
+ g_slist_free (list);
+}
+
+static char *
+escape_single_quotes (const char *s,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ const char *p;
+ GString *gs;
+ const char *pre = "";
+ const char *post = "";
+
+ if ( ! in_single_quotes && ! in_double_quotes) {
+ pre = "'";
+ post = "'";
+ } else if ( ! in_single_quotes && in_double_quotes) {
+ pre = "\"'";
+ post = "'\"";
+ }
+
+ if (strchr (s, '\'') == NULL) {
+ return g_strconcat (pre, s, post, NULL);
+ }
+
+ gs = g_string_new (pre);
+
+ for (p = s; *p != '\0'; p++) {
+ if (*p == '\'')
+ g_string_append (gs, "'\\''");
+ else
+ g_string_append_c (gs, *p);
+ }
+
+ g_string_append (gs, post);
+
+ return g_string_free (gs, FALSE);
+}
+
+typedef enum {
+ URI_TO_STRING,
+ URI_TO_LOCAL_PATH,
+ URI_TO_LOCAL_DIRNAME,
+ URI_TO_LOCAL_BASENAME
+} ConversionType;
+
+static char *
+convert_uri (GFile *file,
+ ConversionType conversion)
+{
+ char *retval = NULL;
+
+ switch (conversion) {
+ case URI_TO_STRING:
+ retval = g_file_get_uri (file);
+ break;
+ case URI_TO_LOCAL_PATH:
+ retval = g_file_get_path (file);
+ break;
+ case URI_TO_LOCAL_DIRNAME:
+ {
+ char *local_path;
+
+ local_path = g_file_get_path (file);
+ retval = g_path_get_dirname (local_path);
+ g_free (local_path);
+ }
+ break;
+ case URI_TO_LOCAL_BASENAME:
+ retval = g_file_get_basename (file);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return retval;
+}
+
+typedef enum {
+ ADDED_NONE = 0,
+ ADDED_SINGLE,
+ ADDED_ALL
+} AddedStatus;
+
+static AddedStatus
+append_all_converted (GString *str,
+ ConversionType conversion,
+ GSList *args,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes,
+ AddedStatus added_status)
+{
+ GSList *l;
+
+ for (l = args; l; l = l->next) {
+ char *converted;
+ char *escaped;
+
+ if (!(converted = convert_uri (l->data, conversion)))
+ continue;
+
+ g_string_append (str, " ");
+
+ escaped = escape_single_quotes (converted,
+ in_single_quotes,
+ in_double_quotes);
+ g_string_append (str, escaped);
+
+ g_free (escaped);
+ g_free (converted);
+ }
+
+ return ADDED_ALL;
+}
+
+static AddedStatus
+append_first_converted (GString *str,
+ ConversionType conversion,
+ GSList **arg_ptr,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes,
+ AddedStatus added_status)
+{
+ GSList *l;
+ char *converted = NULL;
+ char *escaped;
+
+ for (l = *arg_ptr; l; l = l->next) {
+ if ((converted = convert_uri (l->data, conversion)))
+ break;
+
+ *arg_ptr = l->next;
+ }
+
+ if (!converted)
+ return added_status;
+
+ escaped = escape_single_quotes (converted, in_single_quotes, in_double_quotes);
+ g_string_append (str, escaped);
+ g_free (escaped);
+ g_free (converted);
+
+ return added_status != ADDED_ALL ? ADDED_SINGLE : added_status;
+}
+
+static gboolean
+do_percent_subst (const MateDesktopItem *item,
+ const char *arg,
+ GString *str,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes,
+ GSList *args,
+ GSList **arg_ptr,
+ AddedStatus *added_status)
+{
+ char *esc;
+ const char *cs;
+
+ if (arg[0] != '%' || arg[1] == '\0') {
+ return FALSE;
+ }
+
+ switch (arg[1]) {
+ case '%':
+ g_string_append_c (str, '%');
+ break;
+ case 'U':
+ *added_status = append_all_converted (str,
+ URI_TO_STRING,
+ args,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'F':
+ *added_status = append_all_converted (str,
+ URI_TO_LOCAL_PATH,
+ args,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'N':
+ *added_status = append_all_converted (str,
+ URI_TO_LOCAL_BASENAME,
+ args,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'D':
+ *added_status = append_all_converted (str,
+ URI_TO_LOCAL_DIRNAME,
+ args,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'f':
+ *added_status = append_first_converted (str,
+ URI_TO_LOCAL_PATH,
+ arg_ptr,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'u':
+ *added_status = append_first_converted (str,
+ URI_TO_STRING,
+ arg_ptr,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'd':
+ *added_status = append_first_converted (str,
+ URI_TO_LOCAL_DIRNAME,
+ arg_ptr,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'n':
+ *added_status = append_first_converted (str,
+ URI_TO_LOCAL_BASENAME,
+ arg_ptr,
+ in_single_quotes,
+ in_double_quotes,
+ *added_status);
+ break;
+ case 'm':
+ /* Note: v0.9.4 of the spec says this is deprecated
+ * and replace with --miniicon iconname */
+ cs = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_MINI_ICON);
+ if (cs != NULL) {
+ g_string_append (str, "--miniicon=");
+ esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes);
+ g_string_append (str, esc);
+ }
+ break;
+ case 'i':
+ /* Note: v0.9.4 of the spec says replace with --icon iconname */
+ cs = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_ICON);
+ if (cs != NULL) {
+ g_string_append (str, "--icon=");
+ esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes);
+ g_string_append (str, esc);
+ }
+ break;
+ case 'c':
+ cs = mate_desktop_item_get_localestring (item, MATE_DESKTOP_ITEM_NAME);
+ if (cs != NULL) {
+ esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes);
+ g_string_append (str, esc);
+ g_free (esc);
+ }
+ break;
+ case 'k':
+ if (item->location != NULL) {
+ esc = escape_single_quotes (item->location, in_single_quotes, in_double_quotes);
+ g_string_append (str, esc);
+ g_free (esc);
+ }
+ break;
+ case 'v':
+ cs = mate_desktop_item_get_localestring (item, MATE_DESKTOP_ITEM_DEV);
+ if (cs != NULL) {
+ esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes);
+ g_string_append (str, esc);
+ g_free (esc);
+ }
+ break;
+ default:
+ /* Maintain special characters - e.g. "%20" */
+ if (g_ascii_isdigit (arg [1]))
+ g_string_append_c (str, '%');
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static char *
+expand_string (const MateDesktopItem *item,
+ const char *s,
+ GSList *args,
+ GSList **arg_ptr,
+ AddedStatus *added_status)
+{
+ const char *p;
+ gboolean escape = FALSE;
+ gboolean single_quot = FALSE;
+ gboolean double_quot = FALSE;
+ GString *gs = g_string_new (NULL);
+
+ for (p = s; *p != '\0'; p++) {
+ if (escape) {
+ escape = FALSE;
+ g_string_append_c (gs, *p);
+ } else if (*p == '\\') {
+ if ( ! single_quot)
+ escape = TRUE;
+ g_string_append_c (gs, *p);
+ } else if (*p == '\'') {
+ g_string_append_c (gs, *p);
+ if ( ! single_quot && ! double_quot) {
+ single_quot = TRUE;
+ } else if (single_quot) {
+ single_quot = FALSE;
+ }
+ } else if (*p == '"') {
+ g_string_append_c (gs, *p);
+ if ( ! single_quot && ! double_quot) {
+ double_quot = TRUE;
+ } else if (double_quot) {
+ double_quot = FALSE;
+ }
+ } else if (*p == '%') {
+ if (do_percent_subst (item, p, gs,
+ single_quot, double_quot,
+ args, arg_ptr,
+ added_status)) {
+ p++;
+ }
+ } else {
+ g_string_append_c (gs, *p);
+ }
+ }
+ return g_string_free (gs, FALSE);
+}
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static void
+sn_error_trap_push (SnDisplay *display,
+ Display *xdisplay)
+{
+ gdk_error_trap_push ();
+}
+
+static void
+sn_error_trap_pop (SnDisplay *display,
+ Display *xdisplay)
+{
+ gdk_error_trap_pop ();
+}
+
+static char **
+make_spawn_environment_for_sn_context (SnLauncherContext *sn_context,
+ char **envp)
+{
+ char **retval;
+ char **freeme;
+ int i, j;
+ int desktop_startup_id_len;
+
+ retval = freeme = NULL;
+
+ if (envp == NULL) {
+ envp = freeme = g_listenv ();
+ for (i = 0; envp[i]; i++) {
+ char *name = envp[i];
+
+ envp[i] = g_strjoin ("=", name, g_getenv (name), NULL);
+ g_free (name);
+ }
+ } else {
+ for (i = 0; envp[i]; i++)
+ ;
+ }
+
+ retval = g_new (char *, i + 2);
+
+ desktop_startup_id_len = strlen ("DESKTOP_STARTUP_ID");
+
+ for (i = 0, j = 0; envp[i]; i++) {
+ if (strncmp (envp[i], "DESKTOP_STARTUP_ID", desktop_startup_id_len) != 0) {
+ retval[j] = g_strdup (envp[i]);
+ ++j;
+ }
+ }
+
+ retval[j] = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
+ sn_launcher_context_get_startup_id (sn_context));
+ ++j;
+ retval[j] = NULL;
+
+ g_strfreev (freeme);
+
+ return retval;
+}
+
+/* This should be fairly long, as it's confusing to users if a startup
+ * ends when it shouldn't (it appears that the startup failed, and
+ * they have to relaunch the app). Also the timeout only matters when
+ * there are bugs and apps don't end their own startup sequence.
+ *
+ * This timeout is a "last resort" timeout that ignores whether the
+ * startup sequence has shown activity or not. Marco and the
+ * tasklist have smarter, and correspondingly able-to-be-shorter
+ * timeouts. The reason our timeout is dumb is that we don't monitor
+ * the sequence (don't use an SnMonitorContext)
+ */
+#define STARTUP_TIMEOUT_LENGTH_SEC 30 /* seconds */
+#define STARTUP_TIMEOUT_LENGTH (STARTUP_TIMEOUT_LENGTH_SEC * 1000)
+
+typedef struct
+{
+ GdkScreen *screen;
+ GSList *contexts;
+ guint timeout_id;
+} StartupTimeoutData;
+
+static void
+free_startup_timeout (void *data)
+{
+ StartupTimeoutData *std = data;
+
+ g_slist_foreach (std->contexts,
+ (GFunc) sn_launcher_context_unref,
+ NULL);
+ g_slist_free (std->contexts);
+
+ if (std->timeout_id != 0) {
+ g_source_remove (std->timeout_id);
+ std->timeout_id = 0;
+ }
+
+ g_free (std);
+}
+
+static gboolean
+startup_timeout (void *data)
+{
+ StartupTimeoutData *std = data;
+ GSList *tmp;
+ GTimeVal now;
+ int min_timeout;
+
+ min_timeout = STARTUP_TIMEOUT_LENGTH;
+
+ g_get_current_time (&now);
+
+ tmp = std->contexts;
+ while (tmp != NULL) {
+ SnLauncherContext *sn_context = tmp->data;
+ GSList *next = tmp->next;
+ long tv_sec, tv_usec;
+ double elapsed;
+
+ sn_launcher_context_get_last_active_time (sn_context,
+ &tv_sec, &tv_usec);
+
+ elapsed =
+ ((((double)now.tv_sec - tv_sec) * G_USEC_PER_SEC +
+ (now.tv_usec - tv_usec))) / 1000.0;
+
+ if (elapsed >= STARTUP_TIMEOUT_LENGTH) {
+ std->contexts = g_slist_remove (std->contexts,
+ sn_context);
+ sn_launcher_context_complete (sn_context);
+ sn_launcher_context_unref (sn_context);
+ } else {
+ min_timeout = MIN (min_timeout, (STARTUP_TIMEOUT_LENGTH - elapsed));
+ }
+
+ tmp = next;
+ }
+
+ /* we'll use seconds for the timeout */
+ if (min_timeout < 1000)
+ min_timeout = 1000;
+
+ if (std->contexts == NULL) {
+ std->timeout_id = 0;
+ } else {
+ std->timeout_id = g_timeout_add_seconds (min_timeout / 1000,
+ startup_timeout,
+ std);
+ }
+
+ /* always remove this one, but we may have reinstalled another one. */
+ return FALSE;
+}
+
+static void
+add_startup_timeout (GdkScreen *screen,
+ SnLauncherContext *sn_context)
+{
+ StartupTimeoutData *data;
+
+ data = g_object_get_data (G_OBJECT (screen), "mate-startup-data");
+ if (data == NULL) {
+ data = g_new (StartupTimeoutData, 1);
+ data->screen = screen;
+ data->contexts = NULL;
+ data->timeout_id = 0;
+
+ g_object_set_data_full (G_OBJECT (screen), "mate-startup-data",
+ data, free_startup_timeout);
+ }
+
+ sn_launcher_context_ref (sn_context);
+ data->contexts = g_slist_prepend (data->contexts, sn_context);
+
+ if (data->timeout_id == 0) {
+ data->timeout_id = g_timeout_add_seconds (
+ STARTUP_TIMEOUT_LENGTH_SEC,
+ startup_timeout,
+ data);
+ }
+}
+#endif /* HAVE_STARTUP_NOTIFICATION */
+
+static inline char *
+stringify_uris (GSList *args)
+{
+ GString *str;
+
+ str = g_string_new (NULL);
+
+ append_all_converted (str, URI_TO_STRING, args, FALSE, FALSE, ADDED_NONE);
+
+ return g_string_free (str, FALSE);
+}
+
+static inline char *
+stringify_files (GSList *args)
+{
+ GString *str;
+
+ str = g_string_new (NULL);
+
+ append_all_converted (str, URI_TO_LOCAL_PATH, args, FALSE, FALSE, ADDED_NONE);
+
+ return g_string_free (str, FALSE);
+}
+
+static char **
+make_environment_for_screen (GdkScreen *screen,
+ char **envp)
+{
+ char **retval;
+ char **freeme;
+ char *display_name;
+ int display_index = -1;
+ int i, env_len;
+
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+ retval = freeme = NULL;
+
+ if (envp == NULL) {
+ envp = freeme = g_listenv ();
+ for (i = 0; envp [i]; i++) {
+ char *name = envp[i];
+
+ envp[i] = g_strjoin ("=", name, g_getenv (name), NULL);
+ g_free (name);
+ }
+ }
+
+ for (env_len = 0; envp [env_len]; env_len++)
+ if (strncmp (envp [env_len], "DISPLAY", strlen ("DISPLAY")) == 0)
+ display_index = env_len;
+
+ retval = g_new (char *, env_len + 1);
+ retval [env_len] = NULL;
+
+ display_name = gdk_screen_make_display_name (screen);
+
+ for (i = 0; i < env_len; i++)
+ if (i == display_index)
+ retval [i] = g_strconcat ("DISPLAY=", display_name, NULL);
+ else
+ retval [i] = g_strdup (envp[i]);
+
+ g_assert (i == env_len);
+
+ g_free (display_name);
+ g_strfreev (freeme);
+
+ return retval;
+}
+
+static int
+ditem_execute (const MateDesktopItem *item,
+ const char *exec,
+ GList *file_list,
+ GdkScreen *screen,
+ int workspace,
+ char **envp,
+ gboolean launch_only_one,
+ gboolean use_current_dir,
+ gboolean append_uris,
+ gboolean append_paths,
+ gboolean do_not_reap_child,
+ GError **error)
+{
+ char **free_me = NULL;
+ char **real_argv;
+ int i, ret;
+ char **term_argv = NULL;
+ int term_argc = 0;
+ GSList *vector_list;
+ GSList *args, *arg_ptr;
+ AddedStatus added_status;
+ const char *working_dir = NULL;
+ char **temp_argv = NULL;
+ int temp_argc = 0;
+ char *new_exec, *uris, *temp;
+ char *exec_locale;
+ int launched = 0;
+#ifdef HAVE_STARTUP_NOTIFICATION
+ GdkDisplay *gdkdisplay;
+ SnLauncherContext *sn_context;
+ SnDisplay *sn_display;
+ const char *startup_class;
+#endif
+
+ g_return_val_if_fail (item, -1);
+
+ if (item->type == MATE_DESKTOP_ITEM_TYPE_APPLICATION) {
+ working_dir = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_PATH);
+ if (working_dir &&
+ !g_file_test (working_dir, G_FILE_TEST_IS_DIR))
+ working_dir = NULL;
+ }
+
+ if (working_dir == NULL && !use_current_dir)
+ working_dir = g_get_home_dir ();
+
+ if (mate_desktop_item_get_boolean (item, MATE_DESKTOP_ITEM_TERMINAL)) {
+ const char *options =
+ mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_TERMINAL_OPTIONS);
+
+ if (options != NULL) {
+ g_shell_parse_argv (options,
+ &term_argc,
+ &term_argv,
+ NULL /* error */);
+ /* ignore errors */
+ }
+
+ mate_desktop_prepend_terminal_to_vector (&term_argc, &term_argv);
+ }
+
+ args = make_args (file_list);
+ arg_ptr = make_args (file_list);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (screen)
+ gdkdisplay = gdk_screen_get_display (screen);
+ else
+ gdkdisplay = gdk_display_get_default ();
+
+ sn_display = sn_display_new (GDK_DISPLAY_XDISPLAY (gdkdisplay),
+ sn_error_trap_push,
+ sn_error_trap_pop);
+
+
+ /* Only initiate notification if desktop file supports it.
+ * (we could avoid setting up the SnLauncherContext if we aren't going
+ * to initiate, but why bother)
+ */
+
+ startup_class = mate_desktop_item_get_string (item,
+ "StartupWMClass");
+ if (startup_class ||
+ mate_desktop_item_get_boolean (item, "StartupNotify")) {
+ const char *name;
+ const char *icon;
+
+ sn_context = sn_launcher_context_new (sn_display,
+ screen ? gdk_screen_get_number (screen) :
+ DefaultScreen (GDK_DISPLAY_XDISPLAY (gdkdisplay)));
+
+ name = mate_desktop_item_get_localestring (item,
+ MATE_DESKTOP_ITEM_NAME);
+
+ if (name == NULL)
+ name = mate_desktop_item_get_localestring (item,
+ MATE_DESKTOP_ITEM_GENERIC_NAME);
+
+ if (name != NULL) {
+ char *description;
+
+ sn_launcher_context_set_name (sn_context, name);
+
+ description = g_strdup_printf (_("Starting %s"), name);
+
+ sn_launcher_context_set_description (sn_context, description);
+
+ g_free (description);
+ }
+
+ icon = mate_desktop_item_get_string (item,
+ MATE_DESKTOP_ITEM_ICON);
+
+ if (icon != NULL)
+ sn_launcher_context_set_icon_name (sn_context, icon);
+
+ sn_launcher_context_set_workspace (sn_context, workspace);
+
+ if (startup_class != NULL)
+ sn_launcher_context_set_wmclass (sn_context,
+ startup_class);
+ } else {
+ sn_context = NULL;
+ }
+#endif
+
+ if (screen) {
+ envp = make_environment_for_screen (screen, envp);
+ if (free_me)
+ g_strfreev (free_me);
+ free_me = envp;
+ }
+
+ exec_locale = g_filename_from_utf8 (exec, -1, NULL, NULL, NULL);
+
+ if (exec_locale == NULL) {
+ exec_locale = g_strdup ("");
+ }
+
+ do {
+ added_status = ADDED_NONE;
+ new_exec = expand_string (item,
+ exec_locale,
+ args, &arg_ptr, &added_status);
+
+ if (launched == 0 && added_status == ADDED_NONE && append_uris) {
+ uris = stringify_uris (args);
+ temp = g_strconcat (new_exec, " ", uris, NULL);
+ g_free (uris);
+ g_free (new_exec);
+ new_exec = temp;
+ added_status = ADDED_ALL;
+ }
+
+ /* append_uris and append_paths are mutually exlusive */
+ if (launched == 0 && added_status == ADDED_NONE && append_paths) {
+ uris = stringify_files (args);
+ temp = g_strconcat (new_exec, " ", uris, NULL);
+ g_free (uris);
+ g_free (new_exec);
+ new_exec = temp;
+ added_status = ADDED_ALL;
+ }
+
+ if (launched > 0 && added_status == ADDED_NONE) {
+ g_free (new_exec);
+ break;
+ }
+
+ if ( ! g_shell_parse_argv (new_exec,
+ &temp_argc, &temp_argv, error)) {
+ /* The error now comes from g_shell_parse_argv */
+ g_free (new_exec);
+ ret = -1;
+ break;
+ }
+ g_free (new_exec);
+
+ vector_list = NULL;
+ for(i = 0; i < term_argc; i++)
+ vector_list = g_slist_append (vector_list,
+ g_strdup (term_argv[i]));
+
+ for(i = 0; i < temp_argc; i++)
+ vector_list = g_slist_append (vector_list,
+ g_strdup (temp_argv[i]));
+
+ g_strfreev (temp_argv);
+
+ real_argv = list_to_vector (vector_list);
+ g_slist_foreach (vector_list, (GFunc)g_free, NULL);
+ g_slist_free (vector_list);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (sn_context != NULL &&
+ !sn_launcher_context_get_initiated (sn_context)) {
+ guint32 launch_time;
+
+ /* This means that we always use the first real_argv[0]
+ * we select for the "binary name", but it's probably
+ * OK to do that. Binary name isn't super-important
+ * anyway, and we can't initiate twice, and we
+ * must initiate prior to fork/exec.
+ */
+
+ sn_launcher_context_set_binary_name (sn_context,
+ real_argv[0]);
+
+ if (item->launch_time > 0)
+ launch_time = item->launch_time;
+ else
+ launch_time = gdk_x11_display_get_user_time (gdkdisplay);
+
+ sn_launcher_context_initiate (sn_context,
+ g_get_prgname () ? g_get_prgname () : "unknown",
+ real_argv[0],
+ launch_time);
+
+ /* Don't allow accidental reuse of same timestamp */
+ ((MateDesktopItem *)item)->launch_time = 0;
+
+ envp = make_spawn_environment_for_sn_context (sn_context, envp);
+ if (free_me)
+ g_strfreev (free_me);
+ free_me = envp;
+ }
+#endif
+
+
+ if ( ! g_spawn_async (working_dir,
+ real_argv,
+ envp,
+ (do_not_reap_child ? G_SPAWN_DO_NOT_REAP_CHILD : 0) | G_SPAWN_SEARCH_PATH /* flags */,
+ NULL, /* child_setup_func */
+ NULL, /* child_setup_func_data */
+ &ret /* child_pid */,
+ error)) {
+ /* The error was set for us,
+ * we just can't launch this thingie */
+ ret = -1;
+ g_strfreev (real_argv);
+ break;
+ }
+ launched ++;
+
+ g_strfreev (real_argv);
+
+ if (arg_ptr != NULL)
+ arg_ptr = arg_ptr->next;
+
+ /* rinse, repeat until we run out of arguments (That
+ * is if we were adding singles anyway) */
+ } while (added_status == ADDED_SINGLE &&
+ arg_ptr != NULL &&
+ ! launch_only_one);
+
+ g_free (exec_locale);
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (sn_context != NULL) {
+ if (ret < 0)
+ sn_launcher_context_complete (sn_context); /* end sequence */
+ else
+ add_startup_timeout (screen ? screen :
+ gdk_display_get_default_screen (gdk_display_get_default ()),
+ sn_context);
+ sn_launcher_context_unref (sn_context);
+ }
+
+ sn_display_unref (sn_display);
+#endif /* HAVE_STARTUP_NOTIFICATION */
+
+ free_args (args);
+
+ if (term_argv)
+ g_strfreev (term_argv);
+
+ if (free_me)
+ g_strfreev (free_me);
+
+ return ret;
+}
+
+/* strip any trailing &, return FALSE if bad things happen and
+ we end up with an empty string */
+static gboolean
+strip_the_amp (char *exec)
+{
+ size_t exec_len;
+
+ g_strstrip (exec);
+ if (*exec == '\0')
+ return FALSE;
+
+ exec_len = strlen (exec);
+ /* kill any trailing '&' */
+ if (exec[exec_len-1] == '&') {
+ exec[exec_len-1] = '\0';
+ g_strchomp (exec);
+ }
+
+ /* can't exactly launch an empty thing */
+ if (*exec == '\0')
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static int
+mate_desktop_item_launch_on_screen_with_env (
+ const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ GdkScreen *screen,
+ int workspace,
+ char **envp,
+ GError **error)
+{
+ const char *exec;
+ char *the_exec;
+ int ret;
+
+ exec = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_EXEC);
+ /* This is a URL, so launch it as a url */
+ if (item->type == MATE_DESKTOP_ITEM_TYPE_LINK) {
+ const char *url;
+ gboolean retval;
+
+ url = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_URL);
+ /* Mate panel used to put this in Exec */
+ if (!(url && url[0] != '\0'))
+ url = exec;
+
+ if (!(url && url[0] != '\0')) {
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_NO_URL,
+ _("No URL to launch"));
+ return -1;
+ }
+
+ retval = gtk_show_uri (screen,
+ url,
+ GDK_CURRENT_TIME,
+ error);
+ return retval ? 0 : -1;
+ }
+
+ /* check the type, if there is one set */
+ if (item->type != MATE_DESKTOP_ITEM_TYPE_APPLICATION) {
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_NOT_LAUNCHABLE,
+ _("Not a launchable item"));
+ return -1;
+ }
+
+
+ if (exec == NULL ||
+ exec[0] == '\0') {
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_NO_EXEC_STRING,
+ _("No command (Exec) to launch"));
+ return -1;
+ }
+
+
+ /* make a new copy and get rid of spaces */
+ the_exec = g_alloca (strlen (exec) + 1);
+ strcpy (the_exec, exec);
+
+ if ( ! strip_the_amp (the_exec)) {
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_BAD_EXEC_STRING,
+ _("Bad command (Exec) to launch"));
+ return -1;
+ }
+
+ ret = ditem_execute (item, the_exec, file_list, screen, workspace, envp,
+ (flags & MATE_DESKTOP_ITEM_LAUNCH_ONLY_ONE),
+ (flags & MATE_DESKTOP_ITEM_LAUNCH_USE_CURRENT_DIR),
+ (flags & MATE_DESKTOP_ITEM_LAUNCH_APPEND_URIS),
+ (flags & MATE_DESKTOP_ITEM_LAUNCH_APPEND_PATHS),
+ (flags & MATE_DESKTOP_ITEM_LAUNCH_DO_NOT_REAP_CHILD),
+ error);
+
+ return ret;
+}
+
+/**
+ * mate_desktop_item_launch:
+ * @item: A desktop item
+ * @file_list: Files/URIs to launch this item with, can be %NULL
+ * @flags: FIXME
+ * @error: FIXME
+ *
+ * This function runs the program listed in the specified 'item',
+ * optionally appending additional arguments to its command line. It uses
+ * #g_shell_parse_argv to parse the the exec string into a vector which is
+ * then passed to #g_spawn_async for execution. This can return all
+ * the errors from MateURL, #g_shell_parse_argv and #g_spawn_async,
+ * in addition to it's own. The files are
+ * only added if the entry defines one of the standard % strings in it's
+ * Exec field.
+ *
+ * Returns: The the pid of the process spawned. If more then one
+ * process was spawned the last pid is returned. On error -1
+ * is returned and @error is set.
+ */
+int
+mate_desktop_item_launch (const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ GError **error)
+{
+ return mate_desktop_item_launch_on_screen_with_env (
+ item, file_list, flags, NULL, -1, NULL, error);
+}
+
+/**
+ * mate_desktop_item_launch_with_env:
+ * @item: A desktop item
+ * @file_list: Files/URIs to launch this item with, can be %NULL
+ * @flags: FIXME
+ * @envp: child's environment, or %NULL to inherit parent's
+ * @error: FIXME
+ *
+ * See mate_desktop_item_launch for a full description. This function
+ * additionally passes an environment vector for the child process
+ * which is to be launched.
+ *
+ * Returns: The the pid of the process spawned. If more then one
+ * process was spawned the last pid is returned. On error -1
+ * is returned and @error is set.
+ */
+int
+mate_desktop_item_launch_with_env (const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ char **envp,
+ GError **error)
+{
+ return mate_desktop_item_launch_on_screen_with_env (
+ item, file_list, flags,
+ NULL, -1, envp, error);
+}
+
+/**
+ * mate_desktop_item_launch_on_screen:
+ * @item: A desktop item
+ * @file_list: Files/URIs to launch this item with, can be %NULL
+ * @flags: FIXME
+ * @screen: the %GdkScreen on which the application should be launched
+ * @workspace: the workspace on which the app should be launched (-1 for current)
+ * @error: FIXME
+ *
+ * See mate_desktop_item_launch for a full description. This function
+ * additionally attempts to launch the application on a given screen
+ * and workspace.
+ *
+ * Returns: The the pid of the process spawned. If more then one
+ * process was spawned the last pid is returned. On error -1
+ * is returned and @error is set.
+ */
+int
+mate_desktop_item_launch_on_screen (const MateDesktopItem *item,
+ GList *file_list,
+ MateDesktopItemLaunchFlags flags,
+ GdkScreen *screen,
+ int workspace,
+ GError **error)
+{
+ return mate_desktop_item_launch_on_screen_with_env (
+ item, file_list, flags,
+ screen, workspace, NULL, error);
+}
+
+/**
+ * mate_desktop_item_drop_uri_list:
+ * @item: A desktop item
+ * @uri_list: text as gotten from a text/uri-list
+ * @flags: FIXME
+ * @error: FIXME
+ *
+ * A list of files or urls dropped onto an icon, the proper (Url or File)
+ * exec is run you can pass directly string that you got as the
+ * text/uri-list. This just parses the list and calls
+ *
+ * Returns: The value returned by #mate_execute_async() upon execution of
+ * the specified item or -1 on error. If multiple instances are run, the
+ * return of the last one is returned.
+ */
+int
+mate_desktop_item_drop_uri_list (const MateDesktopItem *item,
+ const char *uri_list,
+ MateDesktopItemLaunchFlags flags,
+ GError **error)
+{
+ return mate_desktop_item_drop_uri_list_with_env (item, uri_list,
+ flags, NULL, error);
+}
+
+/**
+* mate_desktop_item_drop_uri_list_with_env:
+* @item: A desktop item
+* @uri_list: text as gotten from a text/uri-list
+* @flags: FIXME
+* @envp: child's environment
+* @error: FIXME
+*
+* See mate_desktop_item_drop_uri_list for a full description. This function
+* additionally passes an environment vector for the child process
+* which is to be launched.
+*
+* Returns: The value returned by #mate_execute_async() upon execution of
+* the specified item or -1 on error. If multiple instances are run, the
+* return of the last one is returned.
+*/
+int
+mate_desktop_item_drop_uri_list_with_env (const MateDesktopItem *item,
+ const char *uri_list,
+ MateDesktopItemLaunchFlags flags,
+ char **envp,
+ GError **error)
+{
+ int ret;
+ char *uri;
+ char **uris;
+ GList *list = NULL;
+
+ uris = g_uri_list_extract_uris (uri_list);
+
+ for (uri = uris[0]; uri != NULL; uri++) {
+ list = g_list_prepend (list, uri);
+ }
+ list = g_list_reverse (list);
+
+ ret = mate_desktop_item_launch_with_env (
+ item, list, flags, envp, error);
+
+ g_strfreev (uris);
+ g_list_free (list);
+
+ return ret;
+}
+
+static gboolean
+exec_exists (const char *exec)
+{
+ if (g_path_is_absolute (exec)) {
+ if (access (exec, X_OK) == 0)
+ return TRUE;
+ else
+ return FALSE;
+ } else {
+ char *tryme;
+
+ tryme = g_find_program_in_path (exec);
+ if (tryme != NULL) {
+ g_free (tryme);
+ return TRUE;
+ }
+ return FALSE;
+ }
+}
+
+/**
+ * mate_desktop_item_exists:
+ * @item: A desktop item
+ *
+ * Attempt to figure out if the program that can be executed by this item
+ * actually exists. First it tries the TryExec attribute to see if that
+ * contains a program that is in the path. Then if there is no such
+ * attribute, it tries the first word of the Exec attribute.
+ *
+ * Returns: A boolean, %TRUE if it exists, %FALSE otherwise.
+ */
+gboolean
+mate_desktop_item_exists (const MateDesktopItem *item)
+{
+ const char *try_exec;
+ const char *exec;
+
+ g_return_val_if_fail (item != NULL, FALSE);
+
+ try_exec = lookup (item, MATE_DESKTOP_ITEM_TRY_EXEC);
+
+ if (try_exec != NULL &&
+ ! exec_exists (try_exec)) {
+ return FALSE;
+ }
+
+ if (item->type == MATE_DESKTOP_ITEM_TYPE_APPLICATION) {
+ int argc;
+ char **argv;
+ const char *exe;
+
+ exec = lookup (item, MATE_DESKTOP_ITEM_EXEC);
+ if (exec == NULL)
+ return FALSE;
+
+ if ( ! g_shell_parse_argv (exec, &argc, &argv, NULL))
+ return FALSE;
+
+ if (argc < 1) {
+ g_strfreev (argv);
+ return FALSE;
+ }
+
+ exe = argv[0];
+
+ if ( ! exec_exists (exe)) {
+ g_strfreev (argv);
+ return FALSE;
+ }
+ g_strfreev (argv);
+ }
+
+ return TRUE;
+}
+
+/**
+ * mate_desktop_item_get_entry_type:
+ * @item: A desktop item
+ *
+ * Gets the type attribute (the 'Type' field) of the item. This should
+ * usually be 'Application' for an application, but it can be 'Directory'
+ * for a directory description. There are other types available as well.
+ * The type usually indicates how the desktop item should be handeled and
+ * how the 'Exec' field should be handeled.
+ *
+ * Returns: The type of the specified 'item'. The returned
+ * memory remains owned by the MateDesktopItem and should not be freed.
+ */
+MateDesktopItemType
+mate_desktop_item_get_entry_type (const MateDesktopItem *item)
+{
+ g_return_val_if_fail (item != NULL, 0);
+ g_return_val_if_fail (item->refcount > 0, 0);
+
+ return item->type;
+}
+
+void
+mate_desktop_item_set_entry_type (MateDesktopItem *item,
+ MateDesktopItemType type)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ item->type = type;
+
+ switch (type) {
+ case MATE_DESKTOP_ITEM_TYPE_NULL:
+ set (item, MATE_DESKTOP_ITEM_TYPE, NULL);
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_APPLICATION:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "Application");
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_LINK:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "Link");
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_FSDEVICE:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "FSDevice");
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_MIME_TYPE:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "MimeType");
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_DIRECTORY:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "Directory");
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_SERVICE:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "Service");
+ break;
+ case MATE_DESKTOP_ITEM_TYPE_SERVICE_TYPE:
+ set (item, MATE_DESKTOP_ITEM_TYPE, "ServiceType");
+ break;
+ default:
+ break;
+ }
+}
+
+
+
+/**
+ * mate_desktop_item_get_file_status:
+ * @item: A desktop item
+ *
+ * This function checks the modification time of the on-disk file to
+ * see if it is more recent than the in-memory data.
+ *
+ * Returns: An enum value that specifies whether the item has changed since being loaded.
+ */
+MateDesktopItemStatus
+mate_desktop_item_get_file_status (const MateDesktopItem *item)
+{
+ MateDesktopItemStatus retval;
+ GFile *file;
+ GFileInfo *info;
+
+ g_return_val_if_fail (item != NULL, MATE_DESKTOP_ITEM_DISAPPEARED);
+ g_return_val_if_fail (item->refcount > 0, MATE_DESKTOP_ITEM_DISAPPEARED);
+
+ if (item->location == NULL)
+ return MATE_DESKTOP_ITEM_DISAPPEARED;
+
+ file = g_file_new_for_uri (item->location);
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE, NULL, NULL);
+
+ retval = MATE_DESKTOP_ITEM_UNCHANGED;
+
+ if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ retval = MATE_DESKTOP_ITEM_DISAPPEARED;
+ else if (item->mtime < g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ retval = MATE_DESKTOP_ITEM_CHANGED;
+
+ g_object_unref (info);
+ g_object_unref (file);
+
+ return retval;
+}
+
+/**
+ * mate_desktop_item_find_icon:
+ * @icon_theme: a #GtkIconTheme
+ * @icon: icon name, something you'd get out of the Icon key
+ * @desired_size: FIXME
+ * @flags: FIXME
+ *
+ * Description: This function goes and looks for the icon file. If the icon
+ * is not an absolute filename, this will look for it in the standard places.
+ * If it can't find the icon, it will return %NULL
+ *
+ * Returns: A newly allocated string
+ */
+char *
+mate_desktop_item_find_icon (GtkIconTheme *icon_theme,
+ const char *icon,
+ int desired_size,
+ int flags)
+{
+ GtkIconInfo *info;
+ char *full = NULL;
+
+ g_return_val_if_fail (icon_theme == NULL ||
+ GTK_IS_ICON_THEME (icon_theme), NULL);
+
+ if (icon == NULL || strcmp(icon,"") == 0) {
+ return NULL;
+ } else if (g_path_is_absolute (icon)) {
+ if (g_file_test (icon, G_FILE_TEST_EXISTS)) {
+ return g_strdup (icon);
+ } else {
+ return NULL;
+ }
+ } else {
+ char *icon_no_extension;
+ char *p;
+
+ if (icon_theme == NULL)
+ icon_theme = gtk_icon_theme_get_default ();
+
+ icon_no_extension = g_strdup (icon);
+ p = strrchr (icon_no_extension, '.');
+ if (p &&
+ (strcmp (p, ".png") == 0 ||
+ strcmp (p, ".xpm") == 0 ||
+ strcmp (p, ".svg") == 0)) {
+ *p = 0;
+ }
+
+
+ info = gtk_icon_theme_lookup_icon (icon_theme,
+ icon_no_extension,
+ desired_size,
+ 0);
+
+ full = NULL;
+ if (info) {
+ full = g_strdup (gtk_icon_info_get_filename (info));
+ gtk_icon_info_free (info);
+ }
+ g_free (icon_no_extension);
+ }
+
+ return full;
+
+}
+
+/**
+ * mate_desktop_item_get_icon:
+ * @icon_theme: a #GtkIconTheme
+ * @item: A desktop item
+ *
+ * Description: This function goes and looks for the icon file. If the icon
+ * is not set as an absolute filename, this will look for it in the standard places.
+ * If it can't find the icon, it will return %NULL
+ *
+ * Returns: A newly allocated string
+ */
+char *
+mate_desktop_item_get_icon (const MateDesktopItem *item,
+ GtkIconTheme *icon_theme)
+{
+ /* maybe this function should be deprecated in favour of find icon
+ * -George */
+ const char *icon;
+
+ g_return_val_if_fail (item != NULL, NULL);
+ g_return_val_if_fail (item->refcount > 0, NULL);
+
+ icon = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_ICON);
+
+ return mate_desktop_item_find_icon (icon_theme, icon,
+ 48 /* desired_size */,
+ 0 /* flags */);
+}
+
+/**
+ * mate_desktop_item_get_location:
+ * @item: A desktop item
+ *
+ * Returns: The file location associated with 'item'.
+ *
+ */
+const char *
+mate_desktop_item_get_location (const MateDesktopItem *item)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+ g_return_val_if_fail (item->refcount > 0, NULL);
+
+ return item->location;
+}
+
+/**
+ * mate_desktop_item_set_location:
+ * @item: A desktop item
+ * @location: A uri string specifying the file location of this particular item.
+ *
+ * Set's the 'location' uri of this item.
+ */
+void
+mate_desktop_item_set_location (MateDesktopItem *item, const char *location)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ if (item->location != NULL &&
+ location != NULL &&
+ strcmp (item->location, location) == 0)
+ return;
+
+ g_free (item->location);
+ item->location = g_strdup (location);
+
+ /* This is ugly, but useful internally */
+ if (item->mtime != DONT_UPDATE_MTIME) {
+ item->mtime = 0;
+
+ if (item->location) {
+ GFile *file;
+ GFileInfo *info;
+
+ file = g_file_new_for_uri (item->location);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ if (info) {
+ if (g_file_info_has_attribute (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED))
+ item->mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ g_object_unref (info);
+ }
+
+ g_object_unref (file);
+ }
+ }
+
+ /* Make sure that save actually saves */
+ item->modified = TRUE;
+}
+
+/**
+ * mate_desktop_item_set_location_file:
+ * @item: A desktop item
+ * @file: A local filename specifying the file location of this particular item.
+ *
+ * Set's the 'location' uri of this item to the given @file.
+ */
+void
+mate_desktop_item_set_location_file (MateDesktopItem *item, const char *file)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ if (file != NULL) {
+ GFile *gfile;
+
+ gfile = g_file_new_for_path (file);
+ mate_desktop_item_set_location_gfile (item, gfile);
+ g_object_unref (gfile);
+ } else {
+ mate_desktop_item_set_location (item, NULL);
+ }
+}
+
+static void
+mate_desktop_item_set_location_gfile (MateDesktopItem *item, GFile *file)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ if (file != NULL) {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ mate_desktop_item_set_location (item, uri);
+ g_free (uri);
+ } else {
+ mate_desktop_item_set_location (item, NULL);
+ }
+}
+
+/*
+ * Reading/Writing different sections, NULL is the standard section
+ */
+
+gboolean
+mate_desktop_item_attr_exists (const MateDesktopItem *item,
+ const char *attr)
+{
+ g_return_val_if_fail (item != NULL, FALSE);
+ g_return_val_if_fail (item->refcount > 0, FALSE);
+ g_return_val_if_fail (attr != NULL, FALSE);
+
+ return lookup (item, attr) != NULL;
+}
+
+/*
+ * String type
+ */
+const char *
+mate_desktop_item_get_string (const MateDesktopItem *item,
+ const char *attr)
+{
+ g_return_val_if_fail (item != NULL, NULL);
+ g_return_val_if_fail (item->refcount > 0, NULL);
+ g_return_val_if_fail (attr != NULL, NULL);
+
+ return lookup (item, attr);
+}
+
+void
+mate_desktop_item_set_string (MateDesktopItem *item,
+ const char *attr,
+ const char *value)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+ g_return_if_fail (attr != NULL);
+
+ set (item, attr, value);
+
+ if (strcmp (attr, MATE_DESKTOP_ITEM_TYPE) == 0)
+ item->type = type_from_string (value);
+}
+
+/*
+ * LocaleString type
+ */
+const char* mate_desktop_item_get_localestring(const MateDesktopItem* item, const char* attr)
+{
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->refcount > 0, NULL);
+ g_return_val_if_fail(attr != NULL, NULL);
+
+ return lookup_best_locale(item, attr);
+}
+
+const char* mate_desktop_item_get_localestring_lang(const MateDesktopItem* item, const char* attr, const char* language)
+{
+ g_return_val_if_fail(item != NULL, NULL);
+ g_return_val_if_fail(item->refcount > 0, NULL);
+ g_return_val_if_fail(attr != NULL, NULL);
+
+ return lookup_locale(item, attr, language);
+}
+
+/**
+ * mate_desktop_item_get_string_locale:
+ * @item: A desktop item
+ * @attr: An attribute name
+ *
+ * Returns the current locale that is used for the given attribute.
+ * This might not be the same for all attributes. For example, if your
+ * locale is "en_US.ISO8859-1" but attribute FOO only has "en_US" then
+ * that would be returned for attr = "FOO". If attribute BAR has
+ * "en_US.ISO8859-1" then that would be returned for "BAR".
+ *
+ * Returns: a string equal to the current locale or NULL
+ * if the attribute is invalid or there is no matching locale.
+ */
+const char *
+mate_desktop_item_get_attr_locale (const MateDesktopItem *item,
+ const char *attr)
+{
+ const char * const *langs_pointer;
+ int i;
+
+ langs_pointer = g_get_language_names ();
+ for (i = 0; langs_pointer[i] != NULL; i++) {
+ const char *value = NULL;
+
+ value = lookup_locale (item, attr, langs_pointer[i]);
+ if (value)
+ return langs_pointer[i];
+ }
+
+ return NULL;
+}
+
+GList *
+mate_desktop_item_get_languages (const MateDesktopItem *item,
+ const char *attr)
+{
+ GList *li;
+ GList *list = NULL;
+
+ g_return_val_if_fail (item != NULL, NULL);
+ g_return_val_if_fail (item->refcount > 0, NULL);
+
+ for (li = item->languages; li != NULL; li = li->next) {
+ char *language = li->data;
+ if (attr == NULL ||
+ lookup_locale (item, attr, language) != NULL) {
+ list = g_list_prepend (list, language);
+ }
+ }
+
+ return g_list_reverse (list);
+}
+
+static const char *
+get_language (void)
+{
+ const char * const *langs_pointer;
+ int i;
+
+ langs_pointer = g_get_language_names ();
+ for (i = 0; langs_pointer[i] != NULL; i++) {
+ /* find first without encoding */
+ if (strchr (langs_pointer[i], '.') == NULL) {
+ return langs_pointer[i];
+ }
+ }
+ return NULL;
+}
+
+void
+mate_desktop_item_set_localestring (MateDesktopItem *item,
+ const char *attr,
+ const char *value)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+ g_return_if_fail (attr != NULL);
+
+ set_locale (item, attr, get_language (), value);
+}
+
+void
+mate_desktop_item_set_localestring_lang (MateDesktopItem *item,
+ const char *attr,
+ const char *language,
+ const char *value)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+ g_return_if_fail (attr != NULL);
+
+ set_locale (item, attr, language, value);
+}
+
+void
+mate_desktop_item_clear_localestring (MateDesktopItem *item,
+ const char *attr)
+{
+ GList *l;
+
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+ g_return_if_fail (attr != NULL);
+
+ for (l = item->languages; l != NULL; l = l->next)
+ set_locale (item, attr, l->data, NULL);
+
+ set (item, attr, NULL);
+}
+
+/*
+ * Strings, Regexps types
+ */
+
+char **
+mate_desktop_item_get_strings (const MateDesktopItem *item,
+ const char *attr)
+{
+ const char *value;
+
+ g_return_val_if_fail (item != NULL, NULL);
+ g_return_val_if_fail (item->refcount > 0, NULL);
+ g_return_val_if_fail (attr != NULL, NULL);
+
+ value = lookup (item, attr);
+ if (value == NULL)
+ return NULL;
+
+ /* FIXME: there's no way to escape semicolons apparently */
+ return g_strsplit (value, ";", -1);
+}
+
+void
+mate_desktop_item_set_strings (MateDesktopItem *item,
+ const char *attr,
+ char **strings)
+{
+ char *str, *str2;
+
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+ g_return_if_fail (attr != NULL);
+
+ str = g_strjoinv (";", strings);
+ str2 = g_strconcat (str, ";", NULL);
+ /* FIXME: there's no way to escape semicolons apparently */
+ set (item, attr, str2);
+ g_free (str);
+ g_free (str2);
+}
+
+/*
+ * Boolean type
+ */
+gboolean
+mate_desktop_item_get_boolean (const MateDesktopItem *item,
+ const char *attr)
+{
+ const char *value;
+
+ g_return_val_if_fail (item != NULL, FALSE);
+ g_return_val_if_fail (item->refcount > 0, FALSE);
+ g_return_val_if_fail (attr != NULL, FALSE);
+
+ value = lookup (item, attr);
+ if (value == NULL)
+ return FALSE;
+
+ return (value[0] == 'T' ||
+ value[0] == 't' ||
+ value[0] == 'Y' ||
+ value[0] == 'y' ||
+ atoi (value) != 0);
+}
+
+void
+mate_desktop_item_set_boolean (MateDesktopItem *item,
+ const char *attr,
+ gboolean value)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+ g_return_if_fail (attr != NULL);
+
+ set (item, attr, value ? "true" : "false");
+}
+
+void
+mate_desktop_item_set_launch_time (MateDesktopItem *item,
+ guint32 timestamp)
+{
+ g_return_if_fail (item != NULL);
+
+ item->launch_time = timestamp;
+}
+
+/*
+ * Clearing attributes
+ */
+void
+mate_desktop_item_clear_section (MateDesktopItem *item,
+ const char *section)
+{
+ Section *sec;
+ GList *li;
+
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (item->refcount > 0);
+
+ sec = find_section (item, section);
+
+ if (sec == NULL) {
+ for (li = item->keys; li != NULL; li = li->next) {
+ g_hash_table_remove (item->main_hash, li->data);
+ g_free (li->data);
+ li->data = NULL;
+ }
+ g_list_free (item->keys);
+ item->keys = NULL;
+ } else {
+ for (li = sec->keys; li != NULL; li = li->next) {
+ char *key = li->data;
+ char *full = g_strdup_printf ("%s/%s",
+ sec->name, key);
+ g_hash_table_remove (item->main_hash, full);
+ g_free (full);
+ g_free (key);
+ li->data = NULL;
+ }
+ g_list_free (sec->keys);
+ sec->keys = NULL;
+ }
+ item->modified = TRUE;
+}
+
+/************************************************************
+ * Parser: *
+ ************************************************************/
+
+static gboolean G_GNUC_CONST
+standard_is_boolean (const char * key)
+{
+ static GHashTable *bools = NULL;
+
+ if (bools == NULL) {
+ bools = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (bools,
+ MATE_DESKTOP_ITEM_NO_DISPLAY,
+ MATE_DESKTOP_ITEM_NO_DISPLAY);
+ g_hash_table_insert (bools,
+ MATE_DESKTOP_ITEM_HIDDEN,
+ MATE_DESKTOP_ITEM_HIDDEN);
+ g_hash_table_insert (bools,
+ MATE_DESKTOP_ITEM_TERMINAL,
+ MATE_DESKTOP_ITEM_TERMINAL);
+ g_hash_table_insert (bools,
+ MATE_DESKTOP_ITEM_READ_ONLY,
+ MATE_DESKTOP_ITEM_READ_ONLY);
+ }
+
+ return g_hash_table_lookup (bools, key) != NULL;
+}
+
+static gboolean G_GNUC_CONST
+standard_is_strings (const char *key)
+{
+ static GHashTable *strings = NULL;
+
+ if (strings == NULL) {
+ strings = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (strings,
+ MATE_DESKTOP_ITEM_FILE_PATTERN,
+ MATE_DESKTOP_ITEM_FILE_PATTERN);
+ g_hash_table_insert (strings,
+ MATE_DESKTOP_ITEM_ACTIONS,
+ MATE_DESKTOP_ITEM_ACTIONS);
+ g_hash_table_insert (strings,
+ MATE_DESKTOP_ITEM_MIME_TYPE,
+ MATE_DESKTOP_ITEM_MIME_TYPE);
+ g_hash_table_insert (strings,
+ MATE_DESKTOP_ITEM_PATTERNS,
+ MATE_DESKTOP_ITEM_PATTERNS);
+ g_hash_table_insert (strings,
+ MATE_DESKTOP_ITEM_SORT_ORDER,
+ MATE_DESKTOP_ITEM_SORT_ORDER);
+ }
+
+ return g_hash_table_lookup (strings, key) != NULL;
+}
+
+/* If no need to cannonize, returns NULL */
+static char *
+cannonize (const char *key, const char *value)
+{
+ if (standard_is_boolean (key)) {
+ if (value[0] == 'T' ||
+ value[0] == 't' ||
+ value[0] == 'Y' ||
+ value[0] == 'y' ||
+ atoi (value) != 0) {
+ return g_strdup ("true");
+ } else {
+ return g_strdup ("false");
+ }
+ } else if (standard_is_strings (key)) {
+ int len = strlen (value);
+ if (len == 0 || value[len-1] != ';') {
+ return g_strconcat (value, ";", NULL);
+ }
+ }
+ /* XXX: Perhaps we should canonize numeric values as well, but this
+ * has caused some subtle problems before so it needs to be done
+ * carefully if at all */
+ return NULL;
+}
+
+
+static char *
+decode_string_and_dup (const char *s)
+{
+ char *p = g_malloc (strlen (s) + 1);
+ char *q = p;
+
+ do {
+ if (*s == '\\'){
+ switch (*(++s)){
+ case 's':
+ *p++ = ' ';
+ break;
+ case 't':
+ *p++ = '\t';
+ break;
+ case 'n':
+ *p++ = '\n';
+ break;
+ case '\\':
+ *p++ = '\\';
+ break;
+ case 'r':
+ *p++ = '\r';
+ break;
+ default:
+ *p++ = '\\';
+ *p++ = *s;
+ break;
+ }
+ } else {
+ *p++ = *s;
+ }
+ } while (*s++);
+
+ return q;
+}
+
+static char *
+escape_string_and_dup (const char *s)
+{
+ char *return_value, *p;
+ const char *q;
+ int len = 0;
+
+ if (s == NULL)
+ return g_strdup("");
+
+ q = s;
+ while (*q){
+ len++;
+ if (strchr ("\n\r\t\\", *q) != NULL)
+ len++;
+ q++;
+ }
+ return_value = p = (char *) g_malloc (len + 1);
+ do {
+ switch (*s){
+ case '\t':
+ *p++ = '\\';
+ *p++ = 't';
+ break;
+ case '\n':
+ *p++ = '\\';
+ *p++ = 'n';
+ break;
+ case '\r':
+ *p++ = '\\';
+ *p++ = 'r';
+ break;
+ case '\\':
+ *p++ = '\\';
+ *p++ = '\\';
+ break;
+ default:
+ *p++ = *s;
+ }
+ } while (*s++);
+ return return_value;
+}
+
+static gboolean
+check_locale (const char *locale)
+{
+ GIConv cd = g_iconv_open ("UTF-8", locale);
+ if ((GIConv)-1 == cd)
+ return FALSE;
+ g_iconv_close (cd);
+ return TRUE;
+}
+
+static void
+insert_locales (GHashTable *encodings, char *enc, ...)
+{
+ va_list args;
+ char *s;
+
+ va_start (args, enc);
+ for (;;) {
+ s = va_arg (args, char *);
+ if (s == NULL)
+ break;
+ g_hash_table_insert (encodings, s, enc);
+ }
+ va_end (args);
+}
+
+/* make a standard conversion table from the desktop standard spec */
+static GHashTable *
+init_encodings (void)
+{
+ GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* "C" is plain ascii */
+ insert_locales (encodings, "ASCII", "C", NULL);
+
+ insert_locales (encodings, "ARMSCII-8", "by", NULL);
+ insert_locales (encodings, "BIG5", "zh_TW", NULL);
+ insert_locales (encodings, "CP1251", "be", "bg", NULL);
+ if (check_locale ("EUC-CN")) {
+ insert_locales (encodings, "EUC-CN", "zh_CN", NULL);
+ } else {
+ insert_locales (encodings, "GB2312", "zh_CN", NULL);
+ }
+ insert_locales (encodings, "EUC-JP", "ja", NULL);
+ insert_locales (encodings, "EUC-KR", "ko", NULL);
+ /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/
+ insert_locales (encodings, "GEORGIAN-PS", "ka", NULL);
+ insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL);
+ insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL);
+ insert_locales (encodings, "ISO-8859-3", "eo", NULL);
+ insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL);
+ insert_locales (encodings, "ISO-8859-7", "el", NULL);
+ insert_locales (encodings, "ISO-8859-9", "tr", NULL);
+ insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL);
+ insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL);
+ insert_locales (encodings, "ISO-8859-15", "et", NULL);
+ insert_locales (encodings, "KOI8-R", "ru", NULL);
+ insert_locales (encodings, "KOI8-U", "uk", NULL);
+ if (check_locale ("TCVN-5712")) {
+ insert_locales (encodings, "TCVN-5712", "vi", NULL);
+ } else {
+ insert_locales (encodings, "TCVN", "vi", NULL);
+ }
+ insert_locales (encodings, "TIS-620", "th", NULL);
+ /*insert_locales (encodings, "VISCII", NULL);*/
+
+ return encodings;
+}
+
+static const char *
+get_encoding_from_locale (const char *locale)
+{
+ char lang[3];
+ const char *encoding;
+ static GHashTable *encodings = NULL;
+
+ if (locale == NULL)
+ return NULL;
+
+ /* if locale includes encoding, use it */
+ encoding = strchr (locale, '.');
+ if (encoding != NULL) {
+ return encoding+1;
+ }
+
+ if (encodings == NULL)
+ encodings = init_encodings ();
+
+ /* first try the entire locale (at this point ll_CC) */
+ encoding = g_hash_table_lookup (encodings, locale);
+ if (encoding != NULL)
+ return encoding;
+
+ /* Try just the language */
+ strncpy (lang, locale, 2);
+ lang[2] = '\0';
+ return g_hash_table_lookup (encodings, lang);
+}
+
+static Encoding
+get_encoding (ReadBuf *rb)
+{
+ gboolean old_kde = FALSE;
+ char buf [BUFSIZ];
+ gboolean all_valid_utf8 = TRUE;
+
+ while (readbuf_gets (buf, sizeof (buf), rb) != NULL) {
+ if (strncmp (MATE_DESKTOP_ITEM_ENCODING,
+ buf,
+ strlen (MATE_DESKTOP_ITEM_ENCODING)) == 0) {
+ char *p = &buf[strlen (MATE_DESKTOP_ITEM_ENCODING)];
+ if (*p == ' ')
+ p++;
+ if (*p != '=')
+ continue;
+ p++;
+ if (*p == ' ')
+ p++;
+ if (strcmp (p, "UTF-8") == 0) {
+ return ENCODING_UTF8;
+ } else if (strcmp (p, "Legacy-Mixed") == 0) {
+ return ENCODING_LEGACY_MIXED;
+ } else {
+ /* According to the spec we're not supposed
+ * to read a file like this */
+ return ENCODING_UNKNOWN;
+ }
+ } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) {
+ old_kde = TRUE;
+ /* don't break yet, we still want to support
+ * Encoding even here */
+ }
+ if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL))
+ all_valid_utf8 = FALSE;
+ }
+
+ if (old_kde)
+ return ENCODING_LEGACY_MIXED;
+
+ /* try to guess by location */
+ if (rb->uri != NULL && strstr (rb->uri, "mate/apps/") != NULL) {
+ /* old mate */
+ return ENCODING_LEGACY_MIXED;
+ }
+
+ /* A dilemma, new KDE files are in UTF-8 but have no Encoding
+ * info, at this time we really can't tell. The best thing to
+ * do right now is to just assume UTF-8 if the whole file
+ * validates as utf8 I suppose */
+
+ if (all_valid_utf8)
+ return ENCODING_UTF8;
+ else
+ return ENCODING_LEGACY_MIXED;
+}
+
+static char *
+decode_string (const char *value, Encoding encoding, const char *locale)
+{
+ char *retval = NULL;
+
+ /* if legacy mixed, then convert */
+ if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) {
+ const char *char_encoding = get_encoding_from_locale (locale);
+ char *utf8_string;
+ if (char_encoding == NULL)
+ return NULL;
+ if (strcmp (char_encoding, "ASCII") == 0) {
+ return decode_string_and_dup (value);
+ }
+ utf8_string = g_convert (value, -1, "UTF-8", char_encoding,
+ NULL, NULL, NULL);
+ if (utf8_string == NULL)
+ return NULL;
+ retval = decode_string_and_dup (utf8_string);
+ g_free (utf8_string);
+ return retval;
+ /* if utf8, then validate */
+ } else if (locale != NULL && encoding == ENCODING_UTF8) {
+ if ( ! g_utf8_validate (value, -1, NULL))
+ /* invalid utf8, ignore this key */
+ return NULL;
+ return decode_string_and_dup (value);
+ } else {
+ /* Meaning this is not a localized string */
+ return decode_string_and_dup (value);
+ }
+}
+
+static char *
+snarf_locale_from_key (const char *key)
+{
+ const char *brace;
+ char *locale, *p;
+
+ brace = strchr (key, '[');
+ if (brace == NULL)
+ return NULL;
+
+ locale = g_strdup (brace + 1);
+ if (*locale == '\0') {
+ g_free (locale);
+ return NULL;
+ }
+ p = strchr (locale, ']');
+ if (p == NULL) {
+ g_free (locale);
+ return NULL;
+ }
+ *p = '\0';
+ return locale;
+}
+
+static void
+insert_key (MateDesktopItem *item,
+ Section *cur_section,
+ Encoding encoding,
+ const char *key,
+ const char *value,
+ gboolean old_kde,
+ gboolean no_translations)
+{
+ char *k;
+ char *val;
+ /* we always store everything in UTF-8 */
+ if (cur_section == NULL &&
+ strcmp (key, MATE_DESKTOP_ITEM_ENCODING) == 0) {
+ k = g_strdup (key);
+ val = g_strdup ("UTF-8");
+ } else {
+ char *locale = snarf_locale_from_key (key);
+ /* If we're ignoring translations */
+ if (no_translations && locale != NULL) {
+ g_free (locale);
+ return;
+ }
+ val = decode_string (value, encoding, locale);
+
+ /* Ignore this key, it's whacked */
+ if (val == NULL) {
+ g_free (locale);
+ return;
+ }
+
+ g_strchomp (val);
+
+ /* For old KDE entries, we can also split by a comma
+ * on sort order, so convert to semicolons */
+ if (old_kde &&
+ cur_section == NULL &&
+ strcmp (key, MATE_DESKTOP_ITEM_SORT_ORDER) == 0 &&
+ strchr (val, ';') == NULL) {
+ int i;
+ for (i = 0; val[i] != '\0'; i++) {
+ if (val[i] == ',')
+ val[i] = ';';
+ }
+ }
+
+ /* Check some types, not perfect, but catches a lot
+ * of things */
+ if (cur_section == NULL) {
+ char *cannon = cannonize (key, val);
+ if (cannon != NULL) {
+ g_free (val);
+ val = cannon;
+ }
+ }
+
+ k = g_strdup (key);
+
+ /* Take care of the language part */
+ if (locale != NULL &&
+ strcmp (locale, "C") == 0) {
+ char *p;
+ /* Whack C locale */
+ p = strchr (k, '[');
+ *p = '\0';
+ g_free (locale);
+ } else if (locale != NULL) {
+ char *p, *brace;
+
+ /* Whack the encoding part */
+ p = strchr (locale, '.');
+ if (p != NULL)
+ *p = '\0';
+
+ if (g_list_find_custom (item->languages, locale,
+ (GCompareFunc)strcmp) == NULL) {
+ item->languages = g_list_prepend
+ (item->languages, locale);
+ } else {
+ g_free (locale);
+ }
+
+ /* Whack encoding from encoding in the key */
+ brace = strchr (k, '[');
+ p = strchr (brace, '.');
+ if (p != NULL) {
+ *p = ']';
+ *(p+1) = '\0';
+ }
+ }
+ }
+
+
+ if (cur_section == NULL) {
+ /* only add to list if we haven't seen it before */
+ if (g_hash_table_lookup (item->main_hash, k) == NULL) {
+ item->keys = g_list_prepend (item->keys,
+ g_strdup (k));
+ }
+ /* later duplicates override earlier ones */
+ g_hash_table_replace (item->main_hash, k, val);
+ } else {
+ char *full = g_strdup_printf
+ ("%s/%s",
+ cur_section->name, k);
+ /* only add to list if we haven't seen it before */
+ if (g_hash_table_lookup (item->main_hash, full) == NULL) {
+ cur_section->keys =
+ g_list_prepend (cur_section->keys, k);
+ }
+ /* later duplicates override earlier ones */
+ g_hash_table_replace (item->main_hash,
+ full, val);
+ }
+}
+
+static void
+setup_type (MateDesktopItem *item, const char *uri)
+{
+ const char *type = g_hash_table_lookup (item->main_hash,
+ MATE_DESKTOP_ITEM_TYPE);
+ if (type == NULL && uri != NULL) {
+ char *base = g_path_get_basename (uri);
+ if (base != NULL &&
+ strcmp (base, ".directory") == 0) {
+ /* This gotta be a directory */
+ g_hash_table_replace (item->main_hash,
+ g_strdup (MATE_DESKTOP_ITEM_TYPE),
+ g_strdup ("Directory"));
+ item->keys = g_list_prepend
+ (item->keys, g_strdup (MATE_DESKTOP_ITEM_TYPE));
+ item->type = MATE_DESKTOP_ITEM_TYPE_DIRECTORY;
+ } else {
+ item->type = MATE_DESKTOP_ITEM_TYPE_NULL;
+ }
+ g_free (base);
+ } else {
+ item->type = type_from_string (type);
+ }
+}
+
+/* fallback to find something suitable for C locale */
+static char *
+try_english_key (MateDesktopItem *item, const char *key)
+{
+ char *str;
+ char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL };
+ int i;
+
+ str = NULL;
+ for (i = 0; locales[i] != NULL && str == NULL; i++) {
+ str = g_strdup (lookup_locale (item, key, locales[i]));
+ }
+ if (str != NULL) {
+ /* We need a 7-bit ascii string, so whack all
+ * above 127 chars */
+ guchar *p;
+ for (p = (guchar *)str; *p != '\0'; p++) {
+ if (*p > 127)
+ *p = '?';
+ }
+ }
+ return str;
+}
+
+
+static void
+sanitize (MateDesktopItem *item, const char *uri)
+{
+ const char *type;
+
+ type = lookup (item, MATE_DESKTOP_ITEM_TYPE);
+
+ /* understand old mate style url exec thingies */
+ if (type != NULL && strcmp (type, "URL") == 0) {
+ const char *exec = lookup (item, MATE_DESKTOP_ITEM_EXEC);
+ set (item, MATE_DESKTOP_ITEM_TYPE, "Link");
+ if (exec != NULL) {
+ /* Note, this must be in this order */
+ set (item, MATE_DESKTOP_ITEM_URL, exec);
+ set (item, MATE_DESKTOP_ITEM_EXEC, NULL);
+ }
+ }
+
+ /* we make sure we have Name, Encoding and Version */
+ if (lookup (item, MATE_DESKTOP_ITEM_NAME) == NULL) {
+ char *name = try_english_key (item, MATE_DESKTOP_ITEM_NAME);
+ /* If no name, use the basename */
+ if (name == NULL && uri != NULL)
+ name = g_path_get_basename (uri);
+ /* If no uri either, use same default as mate_desktop_item_new */
+ if (name == NULL) {
+ /* Translators: the "name" mentioned here is the name of
+ * an application or a document */
+ name = g_strdup (_("No name"));
+ }
+ g_hash_table_replace (item->main_hash,
+ g_strdup (MATE_DESKTOP_ITEM_NAME),
+ name);
+ item->keys = g_list_prepend
+ (item->keys, g_strdup (MATE_DESKTOP_ITEM_NAME));
+ }
+ if (lookup (item, MATE_DESKTOP_ITEM_ENCODING) == NULL) {
+ /* We store everything in UTF-8 so write that down */
+ g_hash_table_replace (item->main_hash,
+ g_strdup (MATE_DESKTOP_ITEM_ENCODING),
+ g_strdup ("UTF-8"));
+ item->keys = g_list_prepend
+ (item->keys, g_strdup (MATE_DESKTOP_ITEM_ENCODING));
+ }
+ if (lookup (item, MATE_DESKTOP_ITEM_VERSION) == NULL) {
+ /* this is the version that we follow, so write it down */
+ g_hash_table_replace (item->main_hash,
+ g_strdup (MATE_DESKTOP_ITEM_VERSION),
+ g_strdup ("1.0"));
+ item->keys = g_list_prepend
+ (item->keys, g_strdup (MATE_DESKTOP_ITEM_VERSION));
+ }
+}
+
+enum {
+ FirstBrace,
+ OnSecHeader,
+ IgnoreToEOL,
+ IgnoreToEOLFirst,
+ KeyDef,
+ KeyDefOnKey,
+ KeyValue
+};
+
+static MateDesktopItem *
+ditem_load (ReadBuf *rb,
+ gboolean no_translations,
+ GError **error)
+{
+ int state;
+ char CharBuffer [1024];
+ char *next = CharBuffer;
+ int c;
+ Encoding encoding;
+ MateDesktopItem *item;
+ Section *cur_section = NULL;
+ char *key = NULL;
+ gboolean old_kde = FALSE;
+
+ encoding = get_encoding (rb);
+ if (encoding == ENCODING_UNKNOWN) {
+ /* spec says, don't read this file */
+ g_set_error (error,
+ MATE_DESKTOP_ITEM_ERROR,
+ MATE_DESKTOP_ITEM_ERROR_UNKNOWN_ENCODING,
+ _("Unknown encoding of: %s"),
+ rb->uri);
+ readbuf_close (rb);
+ return NULL;
+ }
+
+ /* Rewind since get_encoding goes through the file */
+ if (! readbuf_rewind (rb, error)) {
+ readbuf_close (rb);
+ /* spec says, don't read this file */
+ return NULL;
+ }
+
+ item = mate_desktop_item_new ();
+ item->modified = FALSE;
+
+ /* Note: location and mtime are filled in by the new_from_file
+ * function since it has those values */
+
+#define OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1])
+
+ state = FirstBrace;
+ while ((c = readbuf_getc (rb)) != EOF) {
+ if (c == '\r') /* Ignore Carriage Return */
+ continue;
+
+ switch (state) {
+
+ case OnSecHeader:
+ if (c == ']' || OVERFLOW) {
+ *next = '\0';
+ next = CharBuffer;
+
+ /* keys were inserted in reverse */
+ if (cur_section != NULL &&
+ cur_section->keys != NULL) {
+ cur_section->keys = g_list_reverse
+ (cur_section->keys);
+ }
+ if (strcmp (CharBuffer,
+ "KDE Desktop Entry") == 0) {
+ /* Main section */
+ cur_section = NULL;
+ old_kde = TRUE;
+ } else if (strcmp (CharBuffer,
+ "Desktop Entry") == 0) {
+ /* Main section */
+ cur_section = NULL;
+ } else {
+ cur_section = g_new0 (Section, 1);
+ cur_section->name =
+ g_strdup (CharBuffer);
+ cur_section->keys = NULL;
+ item->sections = g_list_prepend
+ (item->sections, cur_section);
+ }
+ state = IgnoreToEOL;
+ } else if (c == '[') {
+ /* FIXME: probably error out instead of ignoring this */
+ } else {
+ *next++ = c;
+ }
+ break;
+
+ case IgnoreToEOL:
+ case IgnoreToEOLFirst:
+ if (c == '\n'){
+ if (state == IgnoreToEOLFirst)
+ state = FirstBrace;
+ else
+ state = KeyDef;
+ next = CharBuffer;
+ }
+ break;
+
+ case FirstBrace:
+ case KeyDef:
+ case KeyDefOnKey:
+ if (c == '#') {
+ if (state == FirstBrace)
+ state = IgnoreToEOLFirst;
+ else
+ state = IgnoreToEOL;
+ break;
+ }
+
+ if (c == '[' && state != KeyDefOnKey){
+ state = OnSecHeader;
+ next = CharBuffer;
+ g_free (key);
+ key = NULL;
+ break;
+ }
+ /* On first pass, don't allow dangling keys */
+ if (state == FirstBrace)
+ break;
+
+ if ((c == ' ' && state != KeyDefOnKey) || c == '\t')
+ break;
+
+ if (c == '\n' || OVERFLOW) { /* Abort Definition */
+ next = CharBuffer;
+ state = KeyDef;
+ break;
+ }
+
+ if (c == '=' || OVERFLOW){
+ *next = '\0';
+
+ g_free (key);
+ key = g_strdup (CharBuffer);
+ state = KeyValue;
+ next = CharBuffer;
+ } else {
+ *next++ = c;
+ state = KeyDefOnKey;
+ }
+ break;
+
+ case KeyValue:
+ if (OVERFLOW || c == '\n'){
+ *next = '\0';
+
+ insert_key (item, cur_section, encoding,
+ key, CharBuffer, old_kde,
+ no_translations);
+
+ g_free (key);
+ key = NULL;
+
+ state = (c == '\n') ? KeyDef : IgnoreToEOL;
+ next = CharBuffer;
+ } else {
+ *next++ = c;
+ }
+ break;
+
+ } /* switch */
+
+ } /* while ((c = getc_unlocked (f)) != EOF) */
+ if (c == EOF && state == KeyValue) {
+ *next = '\0';
+
+ insert_key (item, cur_section, encoding,
+ key, CharBuffer, old_kde,
+ no_translations);
+
+ g_free (key);
+ key = NULL;
+ }
+
+#undef OVERFLOW
+
+ /* keys were inserted in reverse */
+ if (cur_section != NULL &&
+ cur_section->keys != NULL) {
+ cur_section->keys = g_list_reverse (cur_section->keys);
+ }
+ /* keys were inserted in reverse */
+ item->keys = g_list_reverse (item->keys);
+ /* sections were inserted in reverse */
+ item->sections = g_list_reverse (item->sections);
+
+ /* sanitize some things */
+ sanitize (item, rb->uri);
+
+ /* make sure that we set up the type */
+ setup_type (item, rb->uri);
+
+ readbuf_close (rb);
+
+ return item;
+}
+
+static void stream_printf (GFileOutputStream *stream,
+ const char *format, ...) G_GNUC_PRINTF (2, 3);
+
+static void
+stream_printf (GFileOutputStream *stream, const char *format, ...)
+{
+ va_list args;
+ gchar *s;
+
+ va_start (args, format);
+ s = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ /* FIXME: what about errors */
+ g_output_stream_write (G_OUTPUT_STREAM (stream), s, strlen (s),
+ NULL, NULL);
+ g_free (s);
+}
+
+static void
+dump_section (MateDesktopItem *item, GFileOutputStream *stream, Section *section)
+{
+ GList *li;
+
+ stream_printf (stream, "[%s]\n", section->name);
+ for (li = section->keys; li != NULL; li = li->next) {
+ const char *key = li->data;
+ char *full = g_strdup_printf ("%s/%s", section->name, key);
+ const char *value = g_hash_table_lookup (item->main_hash, full);
+ if (value != NULL) {
+ char *val = escape_string_and_dup (value);
+ stream_printf (stream, "%s=%s\n", key, val);
+ g_free (val);
+ }
+ g_free (full);
+ }
+}
+
+static gboolean
+ditem_save (MateDesktopItem *item, const char *uri, GError **error)
+{
+ GList *li;
+ GFile *file;
+ GFileOutputStream *stream;
+
+ file = g_file_new_for_uri (uri);
+ stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error);
+ if (stream == NULL)
+ return FALSE;
+
+ stream_printf (stream, "[Desktop Entry]\n");
+ for (li = item->keys; li != NULL; li = li->next) {
+ const char *key = li->data;
+ const char *value = g_hash_table_lookup (item->main_hash, key);
+ if (value != NULL) {
+ char *val = escape_string_and_dup (value);
+ stream_printf (stream, "%s=%s\n", key, val);
+ g_free (val);
+ }
+ }
+
+ if (item->sections != NULL)
+ stream_printf (stream, "\n");
+
+ for (li = item->sections; li != NULL; li = li->next) {
+ Section *section = li->data;
+
+ /* Don't write empty sections */
+ if (section->keys == NULL)
+ continue;
+
+ dump_section (item, stream, section);
+
+ if (li->next != NULL)
+ stream_printf (stream, "\n");
+ }
+
+ g_object_unref (stream);
+ g_object_unref (file);
+
+ return TRUE;
+}
+
+static gpointer
+_mate_desktop_item_copy (gpointer boxed)
+{
+ return mate_desktop_item_copy (boxed);
+}
+
+static void
+_mate_desktop_item_free (gpointer boxed)
+{
+ mate_desktop_item_unref (boxed);
+}
+
+GType
+mate_desktop_item_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ type = g_boxed_type_register_static ("MateDesktopItem",
+ _mate_desktop_item_copy,
+ _mate_desktop_item_free);
+ }
+
+ return type;
+}
+
+GQuark
+mate_desktop_item_error_quark (void)
+{
+ static GQuark q = 0;
+ if (q == 0)
+ q = g_quark_from_static_string ("mate-desktop-item-error-quark");
+
+ return q;
+}
diff --git a/libmate-desktop/mate-desktop-thumbnail.c b/libmate-desktop/mate-desktop-thumbnail.c
new file mode 100644
index 0000000..8b45bab
--- /dev/null
+++ b/libmate-desktop/mate-desktop-thumbnail.c
@@ -0,0 +1,1302 @@
+/*
+ * mate-thumbnail.c: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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: Alexander Larsson <[email protected]>
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+#include <stdio.h>
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include "libmateui/mate-desktop-thumbnail.h"
+#include <mateconf/mateconf.h>
+#include <mateconf/mateconf-client.h>
+#include <glib/gstdio.h>
+
+#define SECONDS_BETWEEN_STATS 10
+
+struct _MateDesktopThumbnailFactoryPrivate {
+ MateDesktopThumbnailSize size;
+
+ GMutex* lock;
+
+ GHashTable *scripts_hash;
+ guint thumbnailers_notify;
+ guint reread_scheduled;
+};
+
+static const char* appname = "mate-thumbnail-factory";
+
+static void mate_desktop_thumbnail_factory_init(MateDesktopThumbnailFactory* factory);
+static void mate_desktop_thumbnail_factory_class_init(MateDesktopThumbnailFactoryClass* class);
+
+G_DEFINE_TYPE (MateDesktopThumbnailFactory, mate_desktop_thumbnail_factory, G_TYPE_OBJECT)
+
+#define parent_class mate_desktop_thumbnail_factory_parent_class
+
+#define MATE_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY, MateDesktopThumbnailFactoryPrivate))
+
+typedef struct {
+ gint width;
+ gint height;
+ gint input_width;
+ gint input_height;
+ gboolean preserve_aspect_ratio;
+} SizePrepareContext;
+
+#define LOAD_BUFFER_SIZE 4096
+
+static void
+size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer data)
+{
+ SizePrepareContext *info = data;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ info->input_width = width;
+ info->input_height = height;
+
+ if (width < info->width && height < info->height) return;
+
+ if (info->preserve_aspect_ratio &&
+ (info->width > 0 || info->height > 0)) {
+ if (info->width < 0)
+ {
+ width = width * (double)info->height/(double)height;
+ height = info->height;
+ }
+ else if (info->height < 0)
+ {
+ height = height * (double)info->width/(double)width;
+ width = info->width;
+ }
+ else if ((double)height * (double)info->width >
+ (double)width * (double)info->height) {
+ width = 0.5 + (double)width * (double)info->height / (double)height;
+ height = info->height;
+ } else {
+ height = 0.5 + (double)height * (double)info->width / (double)width;
+ width = info->width;
+ }
+ } else {
+ if (info->width > 0)
+ width = info->width;
+ if (info->height > 0)
+ height = info->height;
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+static GdkPixbuf *
+_gdk_pixbuf_new_from_uri_at_scale (const char *uri,
+ gint width,
+ gint height,
+ gboolean preserve_aspect_ratio)
+{
+ gboolean result;
+ char buffer[LOAD_BUFFER_SIZE];
+ gsize bytes_read;
+ GdkPixbufLoader *loader;
+ GdkPixbuf *pixbuf;
+ GdkPixbufAnimation *animation;
+ GdkPixbufAnimationIter *iter;
+ gboolean has_frame;
+ SizePrepareContext info;
+ GFile *file;
+ GFileInfo *file_info;
+ GInputStream *input_stream;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ input_stream = NULL;
+
+ file = g_file_new_for_uri (uri);
+
+ /* First see if we can get an input stream via preview::icon */
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, /* GCancellable */
+ NULL); /* return location for GError */
+ if (file_info != NULL) {
+ GObject *object;
+
+ object = g_file_info_get_attribute_object (file_info,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON);
+ if (object != NULL && G_IS_LOADABLE_ICON (object)) {
+ input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
+ 0, /* size */
+ NULL, /* return location for type */
+ NULL, /* GCancellable */
+ NULL); /* return location for GError */
+ }
+ g_object_unref (file_info);
+ }
+
+ if (input_stream == NULL) {
+ input_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+ if (input_stream == NULL) {
+ g_object_unref (file);
+ return NULL;
+ }
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ if (1 <= width || 1 <= height) {
+ info.width = width;
+ info.height = height;
+ info.input_width = info.input_height = 0;
+ info.preserve_aspect_ratio = preserve_aspect_ratio;
+ g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
+ }
+
+ has_frame = FALSE;
+
+ result = FALSE;
+ while (!has_frame) {
+
+ bytes_read = g_input_stream_read (input_stream,
+ buffer,
+ sizeof (buffer),
+ NULL,
+ NULL);
+ if (bytes_read == -1) {
+ break;
+ }
+ result = TRUE;
+ if (bytes_read == 0) {
+ break;
+ }
+
+ if (!gdk_pixbuf_loader_write (loader,
+ (unsigned char *)buffer,
+ bytes_read,
+ NULL)) {
+ result = FALSE;
+ break;
+ }
+
+ animation = gdk_pixbuf_loader_get_animation (loader);
+ if (animation) {
+ iter = gdk_pixbuf_animation_get_iter (animation, NULL);
+ if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
+ has_frame = TRUE;
+ }
+ g_object_unref (iter);
+ }
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+
+ if (!result) {
+ g_object_unref (G_OBJECT (loader));
+ g_input_stream_close (input_stream, NULL, NULL);
+ g_object_unref (input_stream);
+ g_object_unref (file);
+ return NULL;
+ }
+
+ g_input_stream_close (input_stream, NULL, NULL);
+ g_object_unref (input_stream);
+ g_object_unref (file);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (pixbuf != NULL) {
+ g_object_ref (G_OBJECT (pixbuf));
+ g_object_set_data (G_OBJECT (pixbuf), "mate-original-width",
+ GINT_TO_POINTER (info.input_width));
+ g_object_set_data (G_OBJECT (pixbuf), "mate-original-height",
+ GINT_TO_POINTER (info.input_height));
+ }
+ g_object_unref (G_OBJECT (loader));
+
+ return pixbuf;
+}
+
+static void
+mate_desktop_thumbnail_factory_finalize (GObject *object)
+{
+ MateDesktopThumbnailFactory *factory;
+ MateDesktopThumbnailFactoryPrivate *priv;
+ MateConfClient *client;
+
+ factory = MATE_DESKTOP_THUMBNAIL_FACTORY (object);
+
+ priv = factory->priv;
+
+ if (priv->reread_scheduled != 0) {
+ g_source_remove (priv->reread_scheduled);
+ priv->reread_scheduled = 0;
+ }
+
+ if (priv->thumbnailers_notify != 0) {
+ client = mateconf_client_get_default ();
+ mateconf_client_notify_remove (client, priv->thumbnailers_notify);
+ priv->thumbnailers_notify = 0;
+ g_object_unref (client);
+ }
+
+ if (priv->scripts_hash)
+ {
+ g_hash_table_destroy (priv->scripts_hash);
+ priv->scripts_hash = NULL;
+ }
+
+ if (priv->lock)
+ {
+ g_mutex_free (priv->lock);
+ priv->lock = NULL;
+ }
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Must be called on main thread */
+static GHashTable *
+read_scripts (void)
+{
+ GHashTable *scripts_hash;
+ MateConfClient *client;
+ GSList *subdirs, *l;
+ char *subdir, *enable, *escape, *commandkey, *command, *mimetype;
+
+ client = mateconf_client_get_default ();
+
+ if (mateconf_client_get_bool (client,
+ "/desktop/mate/thumbnailers/disable_all",
+ NULL))
+ {
+ g_object_unref (G_OBJECT (client));
+ return NULL;
+ }
+
+ scripts_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free, g_free);
+
+
+ subdirs = mateconf_client_all_dirs (client, "/desktop/mate/thumbnailers", NULL);
+
+ for (l = subdirs; l != NULL; l = l->next)
+ {
+ subdir = l->data;
+
+ enable = g_strdup_printf ("%s/enable", subdir);
+ if (mateconf_client_get_bool (client,
+ enable,
+ NULL))
+ {
+ commandkey = g_strdup_printf ("%s/command", subdir);
+ command = mateconf_client_get_string (client, commandkey, NULL);
+ g_free (commandkey);
+
+ if (command != NULL) {
+ mimetype = strrchr (subdir, '/');
+ if (mimetype != NULL)
+ {
+ mimetype++; /* skip past slash */
+
+ /* Convert '@' to slash in mimetype */
+ escape = strchr (mimetype, '@');
+ if (escape != NULL)
+ *escape = '/';
+
+ /* Convert any remaining '@' to '+' in mimetype */
+ while ((escape = strchr (mimetype, '@')) != NULL)
+ *escape = '+';
+
+ g_hash_table_insert (scripts_hash,
+ g_strdup (mimetype), command);
+ }
+ else
+ {
+ g_free (command);
+ }
+ }
+ }
+ g_free (enable);
+
+ g_free (subdir);
+ }
+
+ g_slist_free(subdirs);
+
+ g_object_unref (G_OBJECT (client));
+
+ return scripts_hash;
+}
+
+
+/* Must be called on main thread */
+static void
+mate_desktop_thumbnail_factory_reread_scripts (MateDesktopThumbnailFactory *factory)
+{
+ MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ GHashTable *scripts_hash;
+
+ scripts_hash = read_scripts ();
+
+ g_mutex_lock (priv->lock);
+
+ if (priv->scripts_hash != NULL)
+ g_hash_table_destroy (priv->scripts_hash);
+
+ priv->scripts_hash = scripts_hash;
+
+ g_mutex_unlock (priv->lock);
+}
+
+static gboolean
+reread_idle_callback (gpointer user_data)
+{
+ MateDesktopThumbnailFactory *factory = user_data;
+ MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ mate_desktop_thumbnail_factory_reread_scripts (factory);
+
+ g_mutex_lock (priv->lock);
+ priv->reread_scheduled = 0;
+ g_mutex_unlock (priv->lock);
+
+ return FALSE;
+}
+
+static void
+schedule_reread (MateConfClient* client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ MateDesktopThumbnailFactory *factory = user_data;
+ MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ g_mutex_lock (priv->lock);
+
+ if (priv->reread_scheduled == 0)
+ {
+ priv->reread_scheduled = g_idle_add (reread_idle_callback,
+ factory);
+ }
+
+ g_mutex_unlock (priv->lock);
+}
+
+
+static void
+mate_desktop_thumbnail_factory_init (MateDesktopThumbnailFactory *factory)
+{
+ MateConfClient *client;
+ MateDesktopThumbnailFactoryPrivate *priv;
+
+ factory->priv = MATE_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory);
+
+ priv = factory->priv;
+
+ priv->size = MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL;
+
+ priv->scripts_hash = NULL;
+
+ priv->lock = g_mutex_new ();
+
+ client = mateconf_client_get_default ();
+ mateconf_client_add_dir (client,
+ "/desktop/mate/thumbnailers",
+ MATECONF_CLIENT_PRELOAD_RECURSIVE, NULL);
+
+ mate_desktop_thumbnail_factory_reread_scripts (factory);
+
+ priv->thumbnailers_notify = mateconf_client_notify_add (client, "/desktop/mate/thumbnailers",
+ schedule_reread, factory, NULL,
+ NULL);
+
+ g_object_unref (G_OBJECT (client));
+}
+
+static void
+mate_desktop_thumbnail_factory_class_init (MateDesktopThumbnailFactoryClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = mate_desktop_thumbnail_factory_finalize;
+
+ g_type_class_add_private (class, sizeof (MateDesktopThumbnailFactoryPrivate));
+}
+
+/**
+ * mate_desktop_thumbnail_factory_new:
+ * @size: The thumbnail size to use
+ *
+ * Creates a new #MateDesktopThumbnailFactory.
+ *
+ * This function must be called on the main thread.
+ *
+ * Return value: a new #MateDesktopThumbnailFactory
+ *
+ * Since: 2.2
+ **/
+MateDesktopThumbnailFactory *
+mate_desktop_thumbnail_factory_new (MateDesktopThumbnailSize size)
+{
+ MateDesktopThumbnailFactory *factory;
+
+ factory = g_object_new (MATE_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
+
+ factory->priv->size = size;
+
+ return factory;
+}
+
+/**
+ * mate_desktop_thumbnail_factory_lookup:
+ * @factory: a #MateDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an existing thumbnail for the file specified.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: The absolute path of the thumbnail, or %NULL if none exist.
+ *
+ * Since: 2.2
+ **/
+char *
+mate_desktop_thumbnail_factory_lookup (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ char *path, *file;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+ GdkPixbuf *pixbuf;
+ gboolean res;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ res = FALSE;
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ (priv->size == MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ file,
+ NULL);
+ g_free (file);
+
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ if (pixbuf != NULL)
+ {
+ res = mate_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
+ g_object_unref (pixbuf);
+ }
+
+ g_checksum_free (checksum);
+
+ if (res)
+ return path;
+
+ g_free (path);
+ return FALSE;
+}
+
+/**
+ * mate_desktop_thumbnail_factory_has_valid_failed_thumbnail:
+ * @factory: a #MateDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an failed thumbnail for the file specified. Writing
+ * and looking for failed thumbnails is important to avoid to try to
+ * thumbnail e.g. broken images several times.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: TRUE if there is a failed thumbnail for the file.
+ *
+ * Since: 2.2
+ **/
+gboolean
+mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ char *path, *file;
+ GdkPixbuf *pixbuf;
+ gboolean res;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ res = FALSE;
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails/fail",
+ appname,
+ file,
+ NULL);
+ g_free (file);
+
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ g_free (path);
+
+ if (pixbuf)
+ {
+ res = mate_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
+ g_object_unref (pixbuf);
+ }
+
+ g_checksum_free (checksum);
+
+ return res;
+}
+
+static gboolean mimetype_supported_by_gdk_pixbuf(const char* mime_type)
+{
+ guint i;
+ static GHashTable* formats_hash = NULL;
+ gchar* key;
+ gboolean result;
+
+ if (!formats_hash)
+ {
+ GSList* formats;
+ GSList* list;
+
+ formats_hash = g_hash_table_new_full(g_str_hash, g_content_type_equals, g_free, NULL);
+
+ formats = gdk_pixbuf_get_formats();
+ list = formats;
+
+ while (list)
+ {
+ GdkPixbufFormat* format = list->data;
+ gchar** mime_types = gdk_pixbuf_format_get_mime_types(format);
+
+ for (i = 0; mime_types[i] != NULL; i++)
+ {
+ g_hash_table_insert(formats_hash, (gpointer) g_content_type_from_mime_type(mime_types[i]), GUINT_TO_POINTER(1));
+ }
+
+ g_strfreev(mime_types);
+
+ list = list->next;
+ }
+
+ g_slist_free(formats);
+ }
+
+ key = g_content_type_from_mime_type(mime_type);
+
+ if (g_hash_table_lookup(formats_hash, key))
+ {
+ result = TRUE;
+ }
+ else
+ {
+ result = FALSE;
+ }
+
+ g_free(key);
+
+ return result;
+}
+
+/**
+ * mate_desktop_thumbnail_factory_can_thumbnail:
+ * @factory: a #MateDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ * @mtime: the mtime of the file
+ *
+ * Returns TRUE if this MateIconFactory can (at least try) to thumbnail
+ * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: TRUE if the file can be thumbnailed.
+ *
+ * Since: 2.2
+ **/
+gboolean
+mate_desktop_thumbnail_factory_can_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type,
+ time_t mtime)
+{
+ gboolean have_script;
+
+ /* Don't thumbnail thumbnails */
+ if (uri &&
+ strncmp (uri, "file:/", 6) == 0 &&
+ strstr (uri, "/.thumbnails/") != NULL)
+ return FALSE;
+
+ if (!mime_type)
+ return FALSE;
+
+ g_mutex_lock (factory->priv->lock);
+ have_script = (factory->priv->scripts_hash != NULL &&
+ g_hash_table_lookup (factory->priv->scripts_hash, mime_type));
+ g_mutex_unlock (factory->priv->lock);
+
+ if (have_script || mimetype_supported_by_gdk_pixbuf (mime_type))
+ {
+ return !mate_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
+ uri,
+ mtime);
+ }
+
+ return FALSE;
+}
+
+static char *
+expand_thumbnailing_script (const char *script,
+ const int size,
+ const char *inuri,
+ const char *outfile)
+{
+ GString *str;
+ const char *p, *last;
+ char *localfile, *quoted;
+ gboolean got_in;
+
+ str = g_string_new (NULL);
+
+ got_in = FALSE;
+ last = script;
+ while ((p = strchr (last, '%')) != NULL)
+ {
+ g_string_append_len (str, last, p - last);
+ p++;
+
+ switch (*p) {
+ case 'u':
+ quoted = g_shell_quote (inuri);
+ g_string_append (str, quoted);
+ g_free (quoted);
+ got_in = TRUE;
+ p++;
+ break;
+ case 'i':
+ localfile = g_filename_from_uri (inuri, NULL, NULL);
+ if (localfile)
+ {
+ quoted = g_shell_quote (localfile);
+ g_string_append (str, quoted);
+ got_in = TRUE;
+ g_free (quoted);
+ g_free (localfile);
+ }
+ p++;
+ break;
+ case 'o':
+ quoted = g_shell_quote (outfile);
+ g_string_append (str, quoted);
+ g_free (quoted);
+ p++;
+ break;
+ case 's':
+ g_string_append_printf (str, "%d", size);
+ p++;
+ break;
+ case '%':
+ g_string_append_c (str, '%');
+ p++;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ last = p;
+ }
+ g_string_append (str, last);
+
+ if (got_in)
+ return g_string_free (str, FALSE);
+
+ g_string_free (str, TRUE);
+ return NULL;
+}
+
+/**
+ * mate_desktop_thumbnail_factory_generate_thumbnail:
+ * @factory: a #MateDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ *
+ * Tries to generate a thumbnail for the specified file. If it succeeds
+ * it returns a pixbuf that can be used as a thumbnail.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
+ *
+ * Since: 2.2
+ **/
+GdkPixbuf *
+mate_desktop_thumbnail_factory_generate_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type)
+{
+ GdkPixbuf *pixbuf, *scaled, *tmp_pixbuf;
+ char *script, *expanded_script;
+ int width, height, size;
+ int original_width = 0;
+ int original_height = 0;
+ char dimension[12];
+ double scale;
+ int exit_status;
+ char *tmpname;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (mime_type != NULL, NULL);
+
+ /* Doesn't access any volatile fields in factory, so it's threadsafe */
+
+ size = 128;
+ if (factory->priv->size == MATE_DESKTOP_THUMBNAIL_SIZE_LARGE)
+ size = 256;
+
+ pixbuf = NULL;
+
+ script = NULL;
+ g_mutex_lock (factory->priv->lock);
+ if (factory->priv->scripts_hash != NULL)
+ {
+ script = g_hash_table_lookup (factory->priv->scripts_hash, mime_type);
+ if (script)
+ script = g_strdup (script);
+ }
+ g_mutex_unlock (factory->priv->lock);
+
+ if (script)
+ {
+ int fd;
+
+ fd = g_file_open_tmp (".mate_desktop_thumbnail.XXXXXX", &tmpname, NULL);
+
+ if (fd != -1)
+ {
+ close (fd);
+
+ expanded_script = expand_thumbnailing_script (script, size, uri, tmpname);
+ if (expanded_script != NULL &&
+ g_spawn_command_line_sync (expanded_script,
+ NULL, NULL, &exit_status, NULL) &&
+ exit_status == 0)
+ {
+ pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
+ }
+
+ g_free (expanded_script);
+ g_unlink(tmpname);
+ g_free (tmpname);
+ }
+
+ g_free (script);
+ }
+
+ /* Fall back to gdk-pixbuf */
+ if (pixbuf == NULL)
+ {
+ pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, size, size, TRUE);
+
+ if (pixbuf != NULL)
+ {
+ original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
+ "mate-original-width"));
+ original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
+ "mate-original-height"));
+ }
+ }
+
+ if (pixbuf == NULL)
+ return NULL;
+
+ /* The pixbuf loader may attach an "orientation" option to the pixbuf,
+ if the tiff or exif jpeg file had an orientation tag. Rotate/flip
+ the pixbuf as specified by this tag, if present. */
+ tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+ pixbuf = tmp_pixbuf;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (width > size || height > size)
+ {
+ const gchar *orig_width, *orig_height;
+ scale = (double)size / MAX (width, height);
+
+ scaled = mate_desktop_thumbnail_scale_down_pixbuf (pixbuf,
+ floor (width * scale + 0.5),
+ floor (height * scale + 0.5));
+
+ orig_width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
+ orig_height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
+
+ if (orig_width != NULL) {
+ gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Width", orig_width);
+ }
+ if (orig_height != NULL) {
+ gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Height", orig_height);
+ }
+
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+ }
+
+ if (original_width > 0) {
+ g_snprintf (dimension, sizeof (dimension), "%i", original_width);
+ gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", dimension);
+ }
+ if (original_height > 0) {
+ g_snprintf (dimension, sizeof (dimension), "%i", original_height);
+ gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", dimension);
+ }
+
+ return pixbuf;
+}
+
+static gboolean
+make_thumbnail_dirs (MateDesktopThumbnailFactory *factory)
+{
+ char *thumbnail_dir;
+ char *image_dir;
+ gboolean res;
+
+ res = FALSE;
+
+ thumbnail_dir = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ NULL);
+ if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (thumbnail_dir, 0700);
+ res = TRUE;
+ }
+
+ image_dir = g_build_filename (thumbnail_dir,
+ (factory->priv->size == MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ NULL);
+ if (!g_file_test (image_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (image_dir, 0700);
+ res = TRUE;
+ }
+
+ g_free (thumbnail_dir);
+ g_free (image_dir);
+
+ return res;
+}
+
+static gboolean
+make_thumbnail_fail_dirs (MateDesktopThumbnailFactory *factory)
+{
+ char *thumbnail_dir;
+ char *fail_dir;
+ char *app_dir;
+ gboolean res;
+
+ res = FALSE;
+
+ thumbnail_dir = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ NULL);
+ if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (thumbnail_dir, 0700);
+ res = TRUE;
+ }
+
+ fail_dir = g_build_filename (thumbnail_dir,
+ "fail",
+ NULL);
+ if (!g_file_test (fail_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (fail_dir, 0700);
+ res = TRUE;
+ }
+
+ app_dir = g_build_filename (fail_dir,
+ appname,
+ NULL);
+ if (!g_file_test (app_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (app_dir, 0700);
+ res = TRUE;
+ }
+
+ g_free (thumbnail_dir);
+ g_free (fail_dir);
+ g_free (app_dir);
+
+ return res;
+}
+
+
+/**
+ * mate_desktop_thumbnail_factory_save_thumbnail:
+ * @factory: a #MateDesktopThumbnailFactory
+ * @thumbnail: the thumbnail as a pixbuf
+ * @uri: the uri of a file
+ * @original_mtime: the modification time of the original file
+ *
+ * Saves @thumbnail at the right place. If the save fails a
+ * failed thumbnail is written.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Since: 2.2
+ **/
+void
+mate_desktop_thumbnail_factory_save_thumbnail (MateDesktopThumbnailFactory *factory,
+ GdkPixbuf *thumbnail,
+ const char *uri,
+ time_t original_mtime)
+{
+ MateDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ char *path, *file;
+ char *tmp_path;
+ const char *width, *height;
+ int tmp_fd;
+ char mtime_str[21];
+ gboolean saved_ok;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ (priv->size == MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ file,
+ NULL);
+
+ g_free (file);
+
+ g_checksum_free (checksum);
+
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+
+ tmp_fd = g_mkstemp (tmp_path);
+ if (tmp_fd == -1 &&
+ make_thumbnail_dirs (factory))
+ {
+ g_free (tmp_path);
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+ tmp_fd = g_mkstemp (tmp_path);
+ }
+
+ if (tmp_fd == -1)
+ {
+ mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
+ g_free (tmp_path);
+ g_free (path);
+ return;
+ }
+ close (tmp_fd);
+
+ g_snprintf (mtime_str, 21, "%ld", original_mtime);
+ width = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Width");
+ height = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Height");
+
+ if (width != NULL && height != NULL)
+ saved_ok = gdk_pixbuf_save (thumbnail,
+ tmp_path,
+ "png", NULL,
+ "tEXt::Thumb::Image::Width", width,
+ "tEXt::Thumb::Image::Height", height,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "MATE::ThumbnailFactory",
+ NULL);
+ else
+ saved_ok = gdk_pixbuf_save (thumbnail,
+ tmp_path,
+ "png", NULL,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "MATE::ThumbnailFactory",
+ NULL);
+
+
+ if (saved_ok)
+ {
+ g_chmod (tmp_path, 0600);
+ g_rename(tmp_path, path);
+ }
+ else
+ {
+ mate_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
+ }
+
+ g_free (path);
+ g_free (tmp_path);
+}
+
+/**
+ * mate_desktop_thumbnail_factory_create_failed_thumbnail:
+ * @factory: a #MateDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the modification time of the file
+ *
+ * Creates a failed thumbnail for the file so that we don't try
+ * to re-thumbnail the file later.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Since: 2.2
+ **/
+void
+mate_desktop_thumbnail_factory_create_failed_thumbnail (MateDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ char *path, *file;
+ char *tmp_path;
+ int tmp_fd;
+ char mtime_str[21];
+ gboolean saved_ok;
+ GdkPixbuf *pixbuf;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails/fail",
+ appname,
+ file,
+ NULL);
+ g_free (file);
+
+ g_checksum_free (checksum);
+
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+
+ tmp_fd = g_mkstemp (tmp_path);
+ if (tmp_fd == -1 &&
+ make_thumbnail_fail_dirs (factory))
+ {
+ g_free (tmp_path);
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+ tmp_fd = g_mkstemp (tmp_path);
+ }
+
+ if (tmp_fd == -1)
+ {
+ g_free (tmp_path);
+ g_free (path);
+ return;
+ }
+ close (tmp_fd);
+
+ g_snprintf (mtime_str, 21, "%ld", mtime);
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
+ saved_ok = gdk_pixbuf_save (pixbuf,
+ tmp_path,
+ "png", NULL,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "MATE::ThumbnailFactory",
+ NULL);
+ g_object_unref (pixbuf);
+ if (saved_ok)
+ {
+ g_chmod (tmp_path, 0600);
+ g_rename(tmp_path, path);
+ }
+
+ g_free (path);
+ g_free (tmp_path);
+}
+
+/**
+ * mate_desktop_thumbnail_md5:
+ * @uri: an uri
+ *
+ * Calculates the MD5 checksum of the uri. This can be useful
+ * if you want to manually handle thumbnail files.
+ *
+ * Return value: A string with the MD5 digest of the uri string.
+ *
+ * Since: 2.2
+ *
+ * @Deprecated: 2.22: Use #GChecksum instead
+ **/
+char *
+mate_desktop_thumbnail_md5 (const char *uri)
+{
+ return g_compute_checksum_for_data (G_CHECKSUM_MD5,
+ (const guchar *) uri,
+ strlen (uri));
+}
+
+/**
+ * mate_desktop_thumbnail_path_for_uri:
+ * @uri: an uri
+ * @size: a thumbnail size
+ *
+ * Returns the filename that a thumbnail of size @size for @uri would have.
+ *
+ * Return value: an absolute filename
+ *
+ * Since: 2.2
+ **/
+char *
+mate_desktop_thumbnail_path_for_uri (const char *uri,
+ MateDesktopThumbnailSize size)
+{
+ char *md5;
+ char *file;
+ char *path;
+
+ md5 = mate_desktop_thumbnail_md5 (uri);
+ file = g_strconcat (md5, ".png", NULL);
+ g_free (md5);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ (size == MATE_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ file,
+ NULL);
+
+ g_free (file);
+
+ return path;
+}
+
+/**
+ * mate_desktop_thumbnail_has_uri:
+ * @pixbuf: an loaded thumbnail pixbuf
+ * @uri: a uri
+ *
+ * Returns whether the thumbnail has the correct uri embedded in the
+ * Thumb::URI option in the png.
+ *
+ * Return value: TRUE if the thumbnail is for @uri
+ *
+ * Since: 2.2
+ **/
+gboolean
+mate_desktop_thumbnail_has_uri (GdkPixbuf *pixbuf,
+ const char *uri)
+{
+ const char *thumb_uri;
+
+ thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+ if (!thumb_uri)
+ return FALSE;
+
+ return strcmp (uri, thumb_uri) == 0;
+}
+
+/**
+ * mate_desktop_thumbnail_is_valid:
+ * @pixbuf: an loaded thumbnail #GdkPixbuf
+ * @uri: a uri
+ * @mtime: the mtime
+ *
+ * Returns whether the thumbnail has the correct uri and mtime embedded in the
+ * png options.
+ *
+ * Return value: TRUE if the thumbnail has the right @uri and @mtime
+ *
+ * Since: 2.2
+ **/
+gboolean
+mate_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
+ const char *uri,
+ time_t mtime)
+{
+ const char *thumb_uri, *thumb_mtime_str;
+ time_t thumb_mtime;
+
+ thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+ if (!thumb_uri)
+ return FALSE;
+ if (strcmp (uri, thumb_uri) != 0)
+ return FALSE;
+
+ thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
+ if (!thumb_mtime_str)
+ return FALSE;
+ thumb_mtime = atol (thumb_mtime_str);
+ if (mtime != thumb_mtime)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/libmate-desktop/mate-desktop-utils.c b/libmate-desktop/mate-desktop-utils.c
new file mode 100644
index 0000000..003a37f
--- /dev/null
+++ b/libmate-desktop/mate-desktop-utils.c
@@ -0,0 +1,178 @@
+/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* mate-desktop-utils.c - Utilities for the MATE Desktop
+
+ Copyright (C) 1998 Tom Tromey
+ All rights reserved.
+
+ This file is part of the Mate Library.
+
+ The Mate 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 Mate 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 Mate 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. */
+/*
+ @NOTATION@
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <mateconf/mateconf-client.h>
+#include <glib/gi18n-lib.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include <libmate/mate-desktop-utils.h>
+
+#include "private.h"
+
+/**
+ * mate_desktop_prepend_terminal_to_vector:
+ * @argc: a pointer to the vector size
+ * @argv: a pointer to the vector
+ *
+ * Description: Prepends a terminal (either the one configured as default in
+ * the user's MATE setup, or one of the common xterm emulators) to the passed
+ * in vector, modifying it in the process. The vector should be allocated with
+ * #g_malloc, as this will #g_free the original vector. Also all elements must
+ * have been allocated separately. That is the standard glib/MATE way of
+ * doing vectors however. If the integer that @argc points to is negative, the
+ * size will first be computed. Also note that passing in pointers to a vector
+ * that is empty, will just create a new vector for you.
+ **/
+void
+mate_desktop_prepend_terminal_to_vector (int *argc, char ***argv)
+{
+#ifndef G_OS_WIN32
+ char **real_argv;
+ int real_argc;
+ int i, j;
+ char **term_argv = NULL;
+ int term_argc = 0;
+ MateConfClient *client;
+
+ gchar *terminal = NULL;
+
+ char **the_argv;
+
+ g_return_if_fail (argc != NULL);
+ g_return_if_fail (argv != NULL);
+
+ _mate_desktop_init_i18n ();
+
+ /* sanity */
+ if(*argv == NULL)
+ *argc = 0;
+
+ the_argv = *argv;
+
+ /* compute size if not given */
+ if (*argc < 0) {
+ for (i = 0; the_argv[i] != NULL; i++)
+ ;
+ *argc = i;
+ }
+
+ client = mateconf_client_get_default ();
+ terminal = mateconf_client_get_string (client, "/desktop/mate/applications/terminal/exec", NULL);
+ g_object_unref (client);
+
+ if (terminal) {
+ gchar *command_line;
+ gchar *exec_flag;
+ exec_flag = mateconf_client_get_string (client, "/desktop/mate/applications/terminal/exec_arg", NULL);
+
+ if (exec_flag == NULL)
+ command_line = g_strdup (terminal);
+ else
+ command_line = g_strdup_printf ("%s %s", terminal,
+ exec_flag);
+
+ g_shell_parse_argv (command_line,
+ &term_argc,
+ &term_argv,
+ NULL /* error */);
+
+ g_free (command_line);
+ g_free (exec_flag);
+ g_free (terminal);
+ }
+
+ if (term_argv == NULL) {
+ char *check;
+
+ term_argc = 2;
+ term_argv = g_new0 (char *, 3);
+
+ check = g_find_program_in_path ("mate-terminal");
+ if (check != NULL) {
+ term_argv[0] = check;
+ /* Note that mate-terminal takes -x and
+ * as -e in mate-terminal is broken we use that. */
+ term_argv[1] = g_strdup ("-x");
+ } else {
+ if (check == NULL)
+ check = g_find_program_in_path ("nxterm");
+ if (check == NULL)
+ check = g_find_program_in_path ("color-xterm");
+ if (check == NULL)
+ check = g_find_program_in_path ("rxvt");
+ if (check == NULL)
+ check = g_find_program_in_path ("xterm");
+ if (check == NULL)
+ check = g_find_program_in_path ("dtterm");
+ if (check == NULL) {
+ g_warning (_("Cannot find a terminal, using "
+ "xterm, even if it may not work"));
+ check = g_strdup ("xterm");
+ }
+ term_argv[0] = check;
+ term_argv[1] = g_strdup ("-e");
+ }
+ }
+
+ real_argc = term_argc + *argc;
+ real_argv = g_new (char *, real_argc + 1);
+
+ for (i = 0; i < term_argc; i++)
+ real_argv[i] = term_argv[i];
+
+ for (j = 0; j < *argc; j++, i++)
+ real_argv[i] = (char *)the_argv[j];
+
+ real_argv[i] = NULL;
+
+ g_free (*argv);
+ *argv = real_argv;
+ *argc = real_argc;
+
+ /* we use g_free here as we sucked all the inner strings
+ * out from it into real_argv */
+ g_free (term_argv);
+#else
+ /* FIXME: Implement when needed */
+ g_warning ("mate_prepend_terminal_to_vector: Not implemented");
+#endif
+}
+
+void
+_mate_desktop_init_i18n (void) {
+ static gboolean initialized = FALSE;
+
+ if (!initialized) {
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
+ initialized = TRUE;
+ }
+}
+
diff --git a/libmate-desktop/mate-rr-config.c b/libmate-desktop/mate-rr-config.c
new file mode 100644
index 0000000..8efcf87
--- /dev/null
+++ b/libmate-desktop/mate-rr-config.c
@@ -0,0 +1,1913 @@
+/* mate-rr-config.c
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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 <[email protected]>
+ */
+
+#define MATE_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>
+
+#undef MATE_DISABLE_DEPRECATED
+#include "libmateui/mate-rr-config.h"
+
+#include "edid.h"
+#include "mate-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 (MateRRScreen *screen,
+ MateOutputInfo **outputs,
+ GError **error);
+static void crtc_assignment_free (CrtcAssignment *assign);
+static void output_free (MateOutputInfo *output);
+static MateOutputInfo *output_copy (MateOutputInfo *output);
+
+typedef struct Parser Parser;
+
+/* Parser for monitor configurations */
+struct Parser
+{
+ int config_file_version;
+ MateOutputInfo * output;
+ MateRRConfig * 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_new0 (MateOutputInfo, 1);
+ parser->output->rotation = 0;
+
+ for (i = 0; attr_names[i] != NULL; ++i)
+ {
+ if (strcmp (attr_names[i], "name") == 0)
+ {
+ parser->output->name = g_strdup (attr_values[i]);
+ break;
+ }
+ }
+
+ if (!parser->output->name)
+ {
+ /* This really shouldn't happen, but it's better to make
+ * something up than to crash later.
+ */
+ g_warning ("Malformed monitor configuration file");
+
+ parser->output->name = g_strdup ("default");
+ }
+ parser->output->connected = FALSE;
+ parser->output->on = FALSE;
+ parser->output->primary = FALSE;
+ }
+ else if (strcmp (name, "configuration") == 0)
+ {
+ g_assert (parser->configuration == NULL);
+
+ parser->configuration = g_new0 (MateRRConfig, 1);
+ parser->configuration->clone = FALSE;
+ parser->configuration->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 MATE_RR_ROTATION_0 */
+ if (parser->output->rotation == 0)
+ parser->output->rotation = MATE_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->outputs =
+ (MateOutputInfo **)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->connected = TRUE;
+
+ strncpy (parser->output->vendor, text, 3);
+ parser->output->vendor[3] = 0;
+ }
+ else if (stack_is (parser, "clone", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ parser->configuration->clone = TRUE;
+ }
+ else if (stack_is (parser, "product", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->connected = TRUE;
+
+ parser->output->product = parse_int (text);
+ }
+ else if (stack_is (parser, "serial", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->connected = TRUE;
+
+ parser->output->serial = parse_uint (text);
+ }
+ else if (stack_is (parser, "width", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->width = parse_int (text);
+ }
+ else if (stack_is (parser, "x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->x = parse_int (text);
+ }
+ else if (stack_is (parser, "y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->y = parse_int (text);
+ }
+ else if (stack_is (parser, "height", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->height = parse_int (text);
+ }
+ else if (stack_is (parser, "rate", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ parser->output->on = TRUE;
+
+ parser->output->rate = parse_int (text);
+ }
+ else if (stack_is (parser, "rotation", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "normal") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_0;
+ }
+ else if (strcmp (text, "left") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_90;
+ }
+ else if (strcmp (text, "upside_down") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_180;
+ }
+ else if (strcmp (text, "right") == 0)
+ {
+ parser->output->rotation |= MATE_RR_ROTATION_270;
+ }
+ }
+ else if (stack_is (parser, "reflect_x", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->rotation |= MATE_RR_REFLECT_X;
+ }
+ }
+ else if (stack_is (parser, "reflect_y", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->rotation |= MATE_RR_REFLECT_Y;
+ }
+ }
+ else if (stack_is (parser, "primary", "output", "configuration", TOPLEVEL_ELEMENT, NULL))
+ {
+ if (strcmp (text, "yes") == 0)
+ {
+ parser->output->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)
+ output_free (parser->output);
+
+ if (parser->configuration)
+ mate_rr_config_free (parser->configuration);
+
+ for (i = 0; i < parser->outputs->len; ++i)
+ {
+ MateOutputInfo *output = parser->outputs->pdata[i];
+
+ output_free (output);
+ }
+
+ g_ptr_array_free (parser->outputs, TRUE);
+
+ for (i = 0; i < parser->configurations->len; ++i)
+ {
+ MateRRConfig *config = parser->configurations->pdata[i];
+
+ mate_rr_config_free (config);
+ }
+
+ g_ptr_array_free (parser->configurations, TRUE);
+
+ for (list = parser->stack->head; list; list = list->next)
+ g_free (list->data);
+ g_queue_free (parser->stack);
+
+ g_free (parser);
+}
+
+static MateRRConfig **
+configurations_read_from_file (const gchar *filename, GError **error)
+{
+ Parser *parser = g_new0 (Parser, 1);
+ MateRRConfig **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 = (MateRRConfig **)g_ptr_array_free (parser->configurations, FALSE);
+ parser->configurations = g_ptr_array_new ();
+
+ g_assert (parser->outputs);
+out:
+ parser_free (parser);
+
+ return result;
+}
+
+MateRRConfig *
+mate_rr_config_new_current (MateRRScreen *screen)
+{
+ MateRRConfig *config = g_new0 (MateRRConfig, 1);
+ GPtrArray *a = g_ptr_array_new ();
+ MateRROutput **rr_outputs;
+ int i;
+ int clone_width = -1;
+ int clone_height = -1;
+ int last_x;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+
+ rr_outputs = mate_rr_screen_list_outputs (screen);
+
+ config->clone = FALSE;
+
+ for (i = 0; rr_outputs[i] != NULL; ++i)
+ {
+ MateRROutput *rr_output = rr_outputs[i];
+ MateOutputInfo *output = g_new0 (MateOutputInfo, 1);
+ MateRRMode *mode = NULL;
+ const guint8 *edid_data = mate_rr_output_get_edid_data (rr_output);
+ MateRRCrtc *crtc;
+
+ output->name = g_strdup (mate_rr_output_get_name (rr_output));
+ output->connected = mate_rr_output_is_connected (rr_output);
+
+ if (!output->connected)
+ {
+ output->x = -1;
+ output->y = -1;
+ output->width = -1;
+ output->height = -1;
+ output->rate = -1;
+ output->rotation = MATE_RR_ROTATION_0;
+ }
+ else
+ {
+ MonitorInfo *info = NULL;
+
+ if (edid_data)
+ info = decode_edid (edid_data);
+
+ if (info)
+ {
+ memcpy (output->vendor, info->manufacturer_code,
+ sizeof (output->vendor));
+
+ output->product = info->product_code;
+ output->serial = info->serial_number;
+ output->aspect = info->aspect_ratio;
+ }
+ else
+ {
+ strcpy (output->vendor, "???");
+ output->product = 0;
+ output->serial = 0;
+ }
+
+ if (mate_rr_output_is_laptop (rr_output))
+ output->display_name = g_strdup (_("Laptop"));
+ else
+ output->display_name = make_display_name (info);
+
+ g_free (info);
+
+ crtc = mate_rr_output_get_crtc (rr_output);
+ mode = crtc? mate_rr_crtc_get_current_mode (crtc) : NULL;
+
+ if (crtc && mode)
+ {
+ output->on = TRUE;
+
+ mate_rr_crtc_get_position (crtc, &output->x, &output->y);
+ output->width = mate_rr_mode_get_width (mode);
+ output->height = mate_rr_mode_get_height (mode);
+ output->rate = mate_rr_mode_get_freq (mode);
+ output->rotation = mate_rr_crtc_get_current_rotation (crtc);
+
+ if (output->x == 0 && output->y == 0) {
+ if (clone_width == -1) {
+ clone_width = output->width;
+ clone_height = output->height;
+ } else if (clone_width == output->width &&
+ clone_height == output->height) {
+ config->clone = TRUE;
+ }
+ }
+ }
+ else
+ {
+ output->on = FALSE;
+ config->clone = FALSE;
+ }
+
+ /* Get preferred size for the monitor */
+ mode = mate_rr_output_get_preferred_mode (rr_output);
+
+ if (!mode)
+ {
+ MateRRMode **modes = mate_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->pref_width = mate_rr_mode_get_width (mode);
+ output->pref_height = mate_rr_mode_get_height (mode);
+ }
+ else
+ {
+ /* Pick some random numbers. This should basically never happen */
+ output->pref_width = 1024;
+ output->pref_height = 768;
+ }
+ }
+
+ output->primary = mate_rr_output_get_is_primary (rr_output);
+
+ g_ptr_array_add (a, output);
+ }
+
+ g_ptr_array_add (a, NULL);
+
+ config->outputs = (MateOutputInfo **)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->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->on)
+ {
+ last_x = MAX (last_x, output->x + output->width);
+ }
+ }
+
+ /* Now position all off displays to the right of the
+ * on displays
+ */
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->connected && !output->on)
+ {
+ output->x = last_x;
+ last_x = output->x + output->width;
+ }
+ }
+
+ g_assert (mate_rr_config_match (config, config));
+
+ return config;
+}
+
+static void
+output_free (MateOutputInfo *output)
+{
+ if (output->display_name)
+ g_free (output->display_name);
+
+ if (output->name)
+ g_free (output->name);
+
+ g_free (output);
+}
+
+static MateOutputInfo *
+output_copy (MateOutputInfo *output)
+{
+ MateOutputInfo *copy = g_new0 (MateOutputInfo, 1);
+
+ *copy = *output;
+
+ copy->name = g_strdup (output->name);
+ copy->display_name = g_strdup (output->display_name);
+
+ return copy;
+}
+
+static void
+outputs_free (MateOutputInfo **outputs)
+{
+ int i;
+
+ g_assert (outputs != NULL);
+
+ for (i = 0; outputs[i] != NULL; ++i)
+ output_free (outputs[i]);
+
+ g_free (outputs);
+}
+
+void
+mate_rr_config_free (MateRRConfig *config)
+{
+ g_return_if_fail (config != NULL);
+ outputs_free (config->outputs);
+
+ g_free (config);
+}
+
+static void
+configurations_free (MateRRConfig **configurations)
+{
+ int i;
+
+ g_assert (configurations != NULL);
+
+ for (i = 0; configurations[i] != NULL; ++i)
+ mate_rr_config_free (configurations[i]);
+
+ g_free (configurations);
+}
+
+static gboolean
+parse_file_gmarkup (const gchar *filename,
+ const GMarkupParser *parser,
+ gpointer data,
+ GError **err)
+{
+ GMarkupParseContext *context = NULL;
+ gchar *contents = NULL;
+ gboolean result = TRUE;
+ 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 (MateOutputInfo *output1, MateOutputInfo *output2)
+{
+ g_assert (output1 != NULL);
+ g_assert (output2 != NULL);
+
+ if (strcmp (output1->name, output2->name) != 0)
+ return FALSE;
+
+ if (strcmp (output1->vendor, output2->vendor) != 0)
+ return FALSE;
+
+ if (output1->product != output2->product)
+ return FALSE;
+
+ if (output1->serial != output2->serial)
+ return FALSE;
+
+ if (output1->connected != output2->connected)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+output_equal (MateOutputInfo *output1, MateOutputInfo *output2)
+{
+ g_assert (output1 != NULL);
+ g_assert (output2 != NULL);
+
+ if (!output_match (output1, output2))
+ return FALSE;
+
+ if (output1->on != output2->on)
+ return FALSE;
+
+ if (output1->on)
+ {
+ if (output1->width != output2->width)
+ return FALSE;
+
+ if (output1->height != output2->height)
+ return FALSE;
+
+ if (output1->rate != output2->rate)
+ return FALSE;
+
+ if (output1->x != output2->x)
+ return FALSE;
+
+ if (output1->y != output2->y)
+ return FALSE;
+
+ if (output1->rotation != output2->rotation)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static MateOutputInfo *
+find_output (MateRRConfig *config, const char *name)
+{
+ int i;
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (strcmp (name, output->name) == 0)
+ return output;
+ }
+
+ return NULL;
+}
+
+/* Match means "these configurations apply to the same hardware
+ * setups"
+ */
+gboolean
+mate_rr_config_match (MateRRConfig *c1, MateRRConfig *c2)
+{
+ int i;
+
+ for (i = 0; c1->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output1 = c1->outputs[i];
+ MateOutputInfo *output2;
+
+ output2 = find_output (c2, output1->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
+mate_rr_config_equal (MateRRConfig *c1,
+ MateRRConfig *c2)
+{
+ int i;
+
+ for (i = 0; c1->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *output1 = c1->outputs[i];
+ MateOutputInfo *output2;
+
+ output2 = find_output (c2, output1->name);
+ if (!output2 || !output_equal (output1, output2))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static MateOutputInfo **
+make_outputs (MateRRConfig *config)
+{
+ GPtrArray *outputs;
+ MateOutputInfo *first_on;
+ int i;
+
+ outputs = g_ptr_array_new ();
+
+ first_on = NULL;
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ {
+ MateOutputInfo *old = config->outputs[i];
+ MateOutputInfo *new = output_copy (old);
+
+ if (old->on && !first_on)
+ first_on = old;
+
+ if (config->clone && new->on)
+ {
+ g_assert (first_on);
+
+ new->width = first_on->width;
+ new->height = first_on->height;
+ new->rotation = first_on->rotation;
+ new->x = 0;
+ new->y = 0;
+ }
+
+ g_ptr_array_add (outputs, new);
+ }
+
+ g_ptr_array_add (outputs, NULL);
+
+ return (MateOutputInfo **)g_ptr_array_free (outputs, FALSE);
+}
+
+gboolean
+mate_rr_config_applicable (MateRRConfig *configuration,
+ MateRRScreen *screen,
+ GError **error)
+{
+ MateOutputInfo **outputs;
+ CrtcAssignment *assign;
+ gboolean result;
+
+ g_return_val_if_fail (configuration != NULL, 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;
+ }
+
+ outputs_free (outputs);
+
+ return result;
+}
+
+/* Database management */
+
+static void
+ensure_config_directory (void)
+{
+ g_mkdir_with_parents (g_get_user_config_dir (), 0700);
+}
+
+char *
+mate_rr_config_get_backup_filename (void)
+{
+ ensure_config_directory ();
+ return g_build_filename (g_get_user_config_dir (), CONFIG_BACKUP_BASENAME, NULL);
+}
+
+char *
+mate_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 (MateRRRotation r)
+{
+ if (r & MATE_RR_ROTATION_0)
+ return "normal";
+ if (r & MATE_RR_ROTATION_90)
+ return "left";
+ if (r & MATE_RR_ROTATION_180)
+ return "upside_down";
+ if (r & MATE_RR_ROTATION_270)
+ return "right";
+
+ return "normal";
+}
+
+static const char *
+yes_no (int x)
+{
+ return x? "yes" : "no";
+}
+
+static const char *
+get_reflect_x (MateRRRotation r)
+{
+ return yes_no (r & MATE_RR_REFLECT_X);
+}
+
+static const char *
+get_reflect_y (MateRRRotation r)
+{
+ return yes_no (r & MATE_RR_REFLECT_Y);
+}
+
+static void
+emit_configuration (MateRRConfig *config,
+ GString *string)
+{
+ int j;
+
+ g_string_append_printf (string, " <configuration>\n");
+
+ g_string_append_printf (string, " <clone>%s</clone>\n", yes_no (config->clone));
+
+ for (j = 0; config->outputs[j] != NULL; ++j)
+ {
+ MateOutputInfo *output = config->outputs[j];
+
+ g_string_append_printf (
+ string, " <output name=\"%s\">\n", output->name);
+
+ if (output->connected && *output->vendor != '\0')
+ {
+ g_string_append_printf (
+ string, " <vendor>%s</vendor>\n", output->vendor);
+ g_string_append_printf (
+ string, " <product>0x%04x</product>\n", output->product);
+ g_string_append_printf (
+ string, " <serial>0x%08x</serial>\n", output->serial);
+ }
+
+ /* An unconnected output which is on does not make sense */
+ if (output->connected && output->on)
+ {
+ g_string_append_printf (
+ string, " <width>%d</width>\n", output->width);
+ g_string_append_printf (
+ string, " <height>%d</height>\n", output->height);
+ g_string_append_printf (
+ string, " <rate>%d</rate>\n", output->rate);
+ g_string_append_printf (
+ string, " <x>%d</x>\n", output->x);
+ g_string_append_printf (
+ string, " <y>%d</y>\n", output->y);
+ g_string_append_printf (
+ string, " <rotation>%s</rotation>\n", get_rotation_name (output->rotation));
+ g_string_append_printf (
+ string, " <reflect_x>%s</reflect_x>\n", get_reflect_x (output->rotation));
+ g_string_append_printf (
+ string, " <reflect_y>%s</reflect_y>\n", get_reflect_y (output->rotation));
+ g_string_append_printf (
+ string, " <primary>%s</primary>\n", yes_no (output->primary));
+ }
+
+ g_string_append_printf (string, " </output>\n");
+ }
+
+ g_string_append_printf (string, " </configuration>\n");
+}
+
+void
+mate_rr_config_sanitize (MateRRConfig *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->outputs[i]; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->on)
+ {
+ x_offset = MIN (x_offset, output->x);
+ y_offset = MIN (y_offset, output->y);
+ }
+ }
+
+ for (i = 0; config->outputs[i]; ++i)
+ {
+ MateOutputInfo *output = config->outputs[i];
+
+ if (output->on)
+ {
+ output->x -= x_offset;
+ output->y -= y_offset;
+ }
+ }
+
+ /* Only one primary, please */
+ found = FALSE;
+ for (i = 0; config->outputs[i]; ++i)
+ {
+ if (config->outputs[i]->primary)
+ {
+ if (found)
+ {
+ config->outputs[i]->primary = FALSE;
+ }
+ else
+ {
+ found = TRUE;
+ }
+ }
+ }
+}
+
+
+gboolean
+mate_rr_config_save (MateRRConfig *configuration, GError **error)
+{
+ MateRRConfig **configurations;
+ GString *output;
+ int i;
+ gchar *intended_filename;
+ gchar *backup_filename;
+ gboolean result;
+
+ g_return_val_if_fail (configuration != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ output = g_string_new ("");
+
+ backup_filename = mate_rr_config_get_backup_filename ();
+ intended_filename = mate_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 (!mate_rr_config_match (configurations[i], configuration))
+ emit_configuration (configurations[i], output);
+ }
+
+ configurations_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;
+}
+
+static MateRRConfig *
+mate_rr_config_copy (MateRRConfig *config)
+{
+ MateRRConfig *copy = g_new0 (MateRRConfig, 1);
+ int i;
+ GPtrArray *array = g_ptr_array_new ();
+
+ copy->clone = config->clone;
+
+ for (i = 0; config->outputs[i] != NULL; ++i)
+ g_ptr_array_add (array, output_copy (config->outputs[i]));
+
+ g_ptr_array_add (array, NULL);
+ copy->outputs = (MateOutputInfo **)g_ptr_array_free (array, FALSE);
+
+ return copy;
+}
+
+static MateRRConfig *
+config_new_stored (MateRRScreen *screen, const char *filename, GError **error)
+{
+ MateRRConfig *current;
+ MateRRConfig **configs;
+ MateRRConfig *result;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ current = mate_rr_config_new_current (screen);
+
+ configs = configurations_read_from_file (filename, error);
+
+ result = NULL;
+ if (configs)
+ {
+ int i;
+
+ for (i = 0; configs[i] != NULL; ++i)
+ {
+ if (mate_rr_config_match (configs[i], current))
+ {
+ result = mate_rr_config_copy (configs[i]);
+ break;
+ }
+ }
+
+ if (result == NULL)
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_NO_MATCHING_CONFIG,
+ _("none of the saved display configurations matched the active configuration"));
+
+ configurations_free (configs);
+ }
+
+ mate_rr_config_free (current);
+
+ return result;
+}
+
+MateRRConfig *
+mate_rr_config_new_stored (MateRRScreen *screen, GError **error)
+{
+ char *intended_filename;
+ MateRRConfig *config;
+
+ intended_filename = mate_rr_config_get_intended_filename ();
+
+ config = config_new_stored (screen, intended_filename, error);
+
+ g_free (intended_filename);
+
+ return config;
+}
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+gboolean
+mate_rr_config_apply (MateRRConfig *config,
+ MateRRScreen *screen,
+ GError **error)
+{
+ return mate_rr_config_apply_with_time (config, screen, GDK_CURRENT_TIME, error);
+}
+#endif
+
+gboolean
+mate_rr_config_apply_with_time (MateRRConfig *config,
+ MateRRScreen *screen,
+ guint32 timestamp,
+ GError **error)
+{
+ CrtcAssignment *assignment;
+ MateOutputInfo **outputs;
+ gboolean result = FALSE;
+
+ outputs = make_outputs (config);
+
+ assignment = crtc_assignment_new (screen, outputs, error);
+
+ outputs_free (outputs);
+
+ if (assignment)
+ {
+ if (crtc_assignment_apply (assignment, timestamp, error))
+ result = TRUE;
+
+ crtc_assignment_free (assignment);
+
+ gdk_flush ();
+ }
+
+ return result;
+}
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+/**
+ * mate_rr_config_apply_stored:
+ * @screen: A #MateRRScreen
+ * @error: Location to store error, or %NULL
+ *
+ * See the documentation for mate_rr_config_apply_from_filename(). This
+ * function simply calls that other function with a filename of
+ * mate_rr_config_get_intended_filename().
+
+ * @Deprecated: 2.26: Use mate_rr_config_apply_from_filename() instead and pass it
+ * the filename from mate_rr_config_get_intended_filename().
+ */
+gboolean
+mate_rr_config_apply_stored (MateRRScreen *screen, GError **error)
+{
+ char *filename;
+ gboolean result;
+
+ filename = mate_rr_config_get_intended_filename ();
+ result = mate_rr_config_apply_from_filename_with_time (screen, filename, GDK_CURRENT_TIME, error);
+ g_free (filename);
+
+ return result;
+}
+#endif
+
+#ifndef MATE_DISABLE_DEPRECATED_SOURCE
+/* mate_rr_config_apply_from_filename:
+ * @screen: A #MateRRScreen
+ * @filename: Path of the file to look in for stored RANDR configurations.
+ * @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 mate_rr_config_get_intended_filename() or
+ * mate_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 #MATE_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 #MATE_RR_ERROR. Note that an
+ * error code of #MATE_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.
+ *
+ * @Deprecated: 2.28: use mate_rr_config_apply_from_filename_with_time() instead.
+ */
+gboolean
+mate_rr_config_apply_from_filename (MateRRScreen *screen, const char *filename, GError **error)
+{
+ return mate_rr_config_apply_from_filename_with_time (screen, filename, GDK_CURRENT_TIME, error);
+}
+#endif
+
+/* mate_rr_config_apply_from_filename_with_time:
+ * @screen: A #MateRRScreen
+ * @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 mate_rr_config_get_intended_filename() or
+ * mate_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 #MATE_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 #MATE_RR_ERROR. Note that an
+ * error code of #MATE_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
+mate_rr_config_apply_from_filename_with_time (MateRRScreen *screen, const char *filename, guint32 timestamp, GError **error)
+{
+ MateRRConfig *stored;
+ GError *my_error;
+
+ g_return_val_if_fail (screen != NULL, FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ my_error = NULL;
+ if (!mate_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 = config_new_stored (screen, filename, error);
+
+ if (stored)
+ {
+ gboolean result;
+
+ result = mate_rr_config_apply_with_time (stored, screen, timestamp, error);
+
+ mate_rr_config_free (stored);
+
+ return result;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+/*
+ * CRTC assignment
+ */
+typedef struct CrtcInfo CrtcInfo;
+
+struct CrtcInfo
+{
+ MateRRMode *mode;
+ int x;
+ int y;
+ MateRRRotation rotation;
+ GPtrArray *outputs;
+};
+
+struct CrtcAssignment
+{
+ MateRRScreen *screen;
+ GHashTable *info;
+ MateRROutput *primary;
+};
+
+static gboolean
+can_clone (CrtcInfo *info,
+ MateRROutput *output)
+{
+ int i;
+
+ for (i = 0; i < info->outputs->len; ++i)
+ {
+ MateRROutput *clone = info->outputs->pdata[i];
+
+ if (!mate_rr_output_can_clone (clone, output))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+crtc_assignment_assign (CrtcAssignment *assign,
+ MateRRCrtc *crtc,
+ MateRRMode *mode,
+ int x,
+ int y,
+ MateRRRotation rotation,
+ gboolean primary,
+ MateRROutput *output,
+ GError **error)
+{
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+ guint32 crtc_id;
+ const char *output_name;
+
+ crtc_id = mate_rr_crtc_get_id (crtc);
+ output_name = mate_rr_output_get_name (output);
+
+ if (!mate_rr_crtc_can_drive_output (crtc, output))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("CRTC %d cannot drive output %s"), crtc_id, output_name);
+ return FALSE;
+ }
+
+ if (!mate_rr_output_supports_mode (output, mode))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("output %s does not support mode %dx%d@%dHz"),
+ output_name,
+ mate_rr_mode_get_width (mode),
+ mate_rr_mode_get_height (mode),
+ mate_rr_mode_get_freq (mode));
+ return FALSE;
+ }
+
+ if (!mate_rr_crtc_supports_rotation (crtc, rotation))
+ {
+ g_set_error (error, MATE_RR_ERROR, MATE_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, MATE_RR_ERROR, MATE_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,
+ mate_rr_mode_get_id (info->mode), mate_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, MATE_RR_ERROR, MATE_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,
+ MateRRCrtc *crtc,
+ MateRROutput *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)
+{
+ MateRRCrtc *crtc = key;
+ CrtcInfo *info = value;
+ ConfigureCrtcState *state = data;
+
+ if (state->has_error)
+ return;
+
+ if (!mate_rr_crtc_set_config_with_time (crtc,
+ state->timestamp,
+ info->x, info->y,
+ info->mode,
+ info->rotation,
+ (MateRROutput **)info->outputs->pdata,
+ info->outputs->len,
+ state->error))
+ state->has_error = TRUE;
+}
+
+static gboolean
+mode_is_rotated (CrtcInfo *info)
+{
+ if ((info->rotation & MATE_RR_ROTATION_270) ||
+ (info->rotation & MATE_RR_ROTATION_90))
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+crtc_is_rotated (MateRRCrtc *crtc)
+{
+ MateRRRotation r = mate_rr_crtc_get_current_rotation (crtc);
+
+ if ((r & MATE_RR_ROTATION_270) ||
+ (r & MATE_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 (MateRRScreen *screen,
+ MateOutputInfo **outputs,
+ CrtcAssignment *assignment,
+ GError **error)
+{
+ MateRRCrtc **crtcs = mate_rr_screen_list_crtcs (screen);
+ MateOutputInfo *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->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)
+ {
+ MateRRCrtc *crtc = crtcs[i];
+ int crtc_id = mate_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)
+ {
+ MateRROutput *mate_rr_output = mate_rr_screen_get_output_by_name (screen, output->name);
+ MateRRMode **modes = mate_rr_output_list_modes (mate_rr_output);
+ int j;
+
+ for (j = 0; modes[j] != NULL; ++j)
+ {
+ MateRRMode *mode = modes[j];
+ int mode_width;
+ int mode_height;
+ int mode_freq;
+
+ mode_width = mate_rr_mode_get_width (mode);
+ mode_height = mate_rr_mode_get_height (mode);
+ mode_freq = mate_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->width, output->height, output->rate,
+ pass);
+
+ if (mode_width == output->width &&
+ mode_height == output->height &&
+ (pass == 1 || mode_freq == output->rate))
+ {
+ tried_mode = TRUE;
+
+ my_error = NULL;
+ if (crtc_assignment_assign (
+ assignment, crtc, modes[j],
+ output->x, output->y,
+ output->rotation,
+ output->primary,
+ mate_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, mate_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, MATE_RR_ERROR, MATE_RR_ERROR_CRTC_ASSIGNMENT,
+ _("could not assign CRTCs to outputs:\n%s"),
+ str);
+ else
+ g_set_error (error, MATE_RR_ERROR, MATE_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)
+ {
+ MateRRCrtc *crtc = list->data;
+ CrtcInfo *info = g_hash_table_lookup (assign->info, crtc);
+ int w, h;
+
+ w = mate_rr_mode_get_width (info->mode);
+ h = mate_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 (MateRRScreen *screen, MateOutputInfo **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);
+
+ mate_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, MATE_RR_ERROR, MATE_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)
+{
+ MateRRCrtc **all_crtcs = mate_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);
+
+ mate_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->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)
+ {
+ MateRRCrtc *crtc = all_crtcs[i];
+ MateRRMode *mode = mate_rr_crtc_get_current_mode (crtc);
+ int x, y;
+
+ if (mode)
+ {
+ int w, h;
+ mate_rr_crtc_get_position (crtc, &x, &y);
+
+ w = mate_rr_mode_get_width (mode);
+ h = mate_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 (!mate_rr_crtc_set_config_with_time (crtc, timestamp, 0, 0, NULL, MATE_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;
+
+ mate_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;
+ }
+
+ mate_rr_screen_set_primary_output (assign->screen, assign->primary);
+
+ gdk_x11_display_ungrab (gdk_screen_get_display (assign->screen->gdk_screen));
+
+ return success;
+}
diff --git a/libmate-desktop/mate-rr-labeler.c b/libmate-desktop/mate-rr-labeler.c
new file mode 100644
index 0000000..fe9a50c
--- /dev/null
+++ b/libmate-desktop/mate-rr-labeler.c
@@ -0,0 +1,317 @@
+/* mate-rr-labeler.c - Utility to label monitors to identify them
+ * while they are being configured.
+ *
+ * Copyright 2008, Novell, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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: Federico Mena-Quintero <[email protected]>
+ */
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include "libmateui/mate-rr-labeler.h"
+#include <gtk/gtk.h>
+
+struct _MateRRLabeler {
+ GObject parent;
+
+ MateRRConfig *config;
+
+ int num_outputs;
+
+ GdkColor *palette;
+ GtkWidget **windows;
+};
+
+struct _MateRRLabelerClass {
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (MateRRLabeler, mate_rr_labeler, G_TYPE_OBJECT);
+
+static void mate_rr_labeler_finalize (GObject *object);
+
+static void
+mate_rr_labeler_init (MateRRLabeler *labeler)
+{
+ /* nothing */
+}
+
+static void
+mate_rr_labeler_class_init (MateRRLabelerClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = (GObjectClass *) class;
+
+ object_class->finalize = mate_rr_labeler_finalize;
+}
+
+static void
+mate_rr_labeler_finalize (GObject *object)
+{
+ MateRRLabeler *labeler;
+
+ labeler = MATE_RR_LABELER (object);
+
+ /* We don't destroy the labeler->config (a MateRRConfig*) here; let our
+ * caller do that instead.
+ */
+
+ if (labeler->windows != NULL) {
+ mate_rr_labeler_hide (labeler);
+ g_free (labeler->windows);
+ labeler->windows = NULL;
+ }
+
+ g_free (labeler->palette);
+ labeler->palette = NULL;
+
+ G_OBJECT_CLASS (mate_rr_labeler_parent_class)->finalize (object);
+}
+
+static int
+count_outputs (MateRRConfig *config)
+{
+ int i;
+
+ for (i = 0; config->outputs[i] != NULL; i++)
+ ;
+
+ return i;
+}
+
+static void
+make_palette (MateRRLabeler *labeler)
+{
+ /* The idea is that we go around an hue color wheel. We want to start
+ * at red, go around to green/etc. and stop at blue --- because magenta
+ * is evil. Eeeeek, no magenta, please!
+ *
+ * Purple would be nice, though. Remember that we are watered down
+ * (i.e. low saturation), so that would be like Like berries with cream.
+ * Mmmmm, berries.
+ */
+ double start_hue;
+ double end_hue;
+ int i;
+
+ g_assert (labeler->num_outputs > 0);
+
+ labeler->palette = g_new (GdkColor, labeler->num_outputs);
+
+ start_hue = 0.0; /* red */
+ end_hue = 2.0/3; /* blue */
+
+ for (i = 0; i < labeler->num_outputs; i++) {
+ double h, s, v;
+ double r, g, b;
+
+ h = start_hue + (end_hue - start_hue) / labeler->num_outputs * i;
+ s = 1.0 / 3;
+ v = 1.0;
+
+ gtk_hsv_to_rgb (h, s, v, &r, &g, &b);
+
+ labeler->palette[i].red = (int) (65535 * r + 0.5);
+ labeler->palette[i].green = (int) (65535 * g + 0.5);
+ labeler->palette[i].blue = (int) (65535 * b + 0.5);
+ }
+}
+
+#define LABEL_WINDOW_EDGE_THICKNESS 2
+#define LABEL_WINDOW_PADDING 12
+
+static gboolean
+label_window_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ cairo_t *cr;
+ GdkColor *color;
+ GtkAllocation allocation;
+
+ color = g_object_get_data (G_OBJECT (widget), "color");
+ gtk_widget_get_allocation (widget, &allocation);
+
+ cr = gdk_cairo_create (gtk_widget_get_window (widget));
+
+ /* edge outline */
+
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_rectangle (cr,
+ LABEL_WINDOW_EDGE_THICKNESS / 2.0,
+ LABEL_WINDOW_EDGE_THICKNESS / 2.0,
+ allocation.width - LABEL_WINDOW_EDGE_THICKNESS,
+ allocation.height - LABEL_WINDOW_EDGE_THICKNESS);
+ cairo_set_line_width (cr, LABEL_WINDOW_EDGE_THICKNESS);
+ cairo_stroke (cr);
+
+ /* fill */
+
+ gdk_cairo_set_source_color (cr, color);
+ cairo_rectangle (cr,
+ LABEL_WINDOW_EDGE_THICKNESS,
+ LABEL_WINDOW_EDGE_THICKNESS,
+ allocation.width - LABEL_WINDOW_EDGE_THICKNESS * 2,
+ allocation.height - LABEL_WINDOW_EDGE_THICKNESS * 2);
+ cairo_fill (cr);
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static GtkWidget *
+create_label_window (MateRRLabeler *labeler, MateOutputInfo *output, GdkColor *color)
+{
+ GtkWidget *window;
+ GtkWidget *widget;
+ char *str;
+ const char *display_name;
+ GdkColor black = { 0, 0, 0, 0 };
+
+ window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_widget_set_app_paintable (window, TRUE);
+
+ gtk_container_set_border_width (GTK_CONTAINER (window), LABEL_WINDOW_PADDING + LABEL_WINDOW_EDGE_THICKNESS);
+
+ /* This is semi-dangerous. The color is part of the labeler->palette
+ * array. Note that in mate_rr_labeler_finalize(), we are careful to
+ * free the palette only after we free the windows.
+ */
+ g_object_set_data (G_OBJECT (window), "color", color);
+
+ g_signal_connect (window, "expose-event",
+ G_CALLBACK (label_window_expose_event_cb), labeler);
+
+ if (labeler->config->clone) {
+ /* Keep this string in sync with mate-control-center/capplets/display/xrandr-capplet.c:get_display_name() */
+
+ /* Translators: this is the feature where what you see on your laptop's
+ * screen is the same as your external monitor. Here, "Mirror" is being
+ * used as an adjective, not as a verb. For example, the Spanish
+ * translation could be "Pantallas en Espejo", *not* "Espejar Pantallas".
+ */
+ display_name = _("Mirror Screens");
+ } else
+ display_name = output->display_name;
+
+ str = g_strdup_printf ("<b>%s</b>", display_name);
+ widget = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (widget), str);
+ g_free (str);
+
+ /* Make the label explicitly black. We don't want it to follow the
+ * theme's colors, since the label is always shown against a light
+ * pastel background. See bgo#556050
+ */
+ gtk_widget_modify_fg (widget, gtk_widget_get_state (widget), &black);
+
+ gtk_container_add (GTK_CONTAINER (window), widget);
+
+ /* Should we center this at the top edge of the monitor, instead of using the upper-left corner? */
+ gtk_window_move (GTK_WINDOW (window), output->x, output->y);
+
+ gtk_widget_show_all (window);
+
+ return window;
+}
+
+static void
+create_label_windows (MateRRLabeler *labeler)
+{
+ int i;
+ gboolean created_window_for_clone;
+
+ labeler->windows = g_new (GtkWidget *, labeler->num_outputs);
+
+ created_window_for_clone = FALSE;
+
+ for (i = 0; i < labeler->num_outputs; i++) {
+ if (!created_window_for_clone && labeler->config->outputs[i]->on) {
+ labeler->windows[i] = create_label_window (labeler, labeler->config->outputs[i], labeler->palette + i);
+
+ if (labeler->config->clone)
+ created_window_for_clone = TRUE;
+ } else
+ labeler->windows[i] = NULL;
+ }
+}
+
+static void
+setup_from_config (MateRRLabeler *labeler)
+{
+ labeler->num_outputs = count_outputs (labeler->config);
+
+ make_palette (labeler);
+
+ create_label_windows (labeler);
+}
+
+MateRRLabeler *
+mate_rr_labeler_new (MateRRConfig *config)
+{
+ MateRRLabeler *labeler;
+
+ g_return_val_if_fail (config != NULL, NULL);
+
+ labeler = g_object_new (MATE_TYPE_RR_LABELER, NULL);
+ labeler->config = config;
+
+ setup_from_config (labeler);
+
+ return labeler;
+}
+
+void
+mate_rr_labeler_hide (MateRRLabeler *labeler)
+{
+ int i;
+
+ g_return_if_fail (MATE_IS_RR_LABELER (labeler));
+
+ for (i = 0; i < labeler->num_outputs; i++)
+ if (labeler->windows[i] != NULL) {
+ gtk_widget_destroy (labeler->windows[i]);
+ labeler->windows[i] = NULL;
+ }
+}
+
+void
+mate_rr_labeler_get_color_for_output (MateRRLabeler *labeler, MateOutputInfo *output, GdkColor *color_out)
+{
+ int i;
+
+ g_return_if_fail (MATE_IS_RR_LABELER (labeler));
+ g_return_if_fail (output != NULL);
+ g_return_if_fail (color_out != NULL);
+
+ for (i = 0; i < labeler->num_outputs; i++)
+ if (labeler->config->outputs[i] == output) {
+ *color_out = labeler->palette[i];
+ return;
+ }
+
+ g_warning ("trying to get the color for unknown MateOutputInfo %p; returning magenta!", output);
+
+ color_out->red = 0xffff;
+ color_out->green = 0;
+ color_out->blue = 0xffff;
+}
diff --git a/libmate-desktop/mate-rr-private.h b/libmate-desktop/mate-rr-private.h
new file mode 100644
index 0000000..a9cddd7
--- /dev/null
+++ b/libmate-desktop/mate-rr-private.h
@@ -0,0 +1,53 @@
+#ifndef MATE_RR_PRIVATE_H
+#define MATE_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
+
+ MateRROutput ** outputs;
+ MateRRCrtc ** crtcs;
+ MateRRMode ** modes;
+
+ MateRRScreen * screen;
+
+ MateRRMode ** clone_modes;
+
+#ifdef HAVE_RANDR
+ RROutput primary;
+#endif
+};
+
+struct MateRRScreen
+{
+ 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;
+
+ MateRRScreenChanged callback;
+ gpointer data;
+
+ Atom connector_type_atom;
+};
+
+#endif
diff --git a/libmate-desktop/mate-rr.c b/libmate-desktop/mate-rr.c
new file mode 100644
index 0000000..5c73ce9
--- /dev/null
+++ b/libmate-desktop/mate-rr.c
@@ -0,0 +1,1824 @@
+/* mate-rr.c
+ *
+ * Copyright 2007, 2008, Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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 <[email protected]>
+ */
+
+#define MATE_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 MATE_DISABLE_DEPRECATED
+#include "libmateui/mate-rr.h"
+
+#include "private.h"
+#include "mate-rr-private.h"
+
+#define DISPLAY(o) ((o)->info->screen->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
+
+struct MateRROutput
+{
+ ScreenInfo * info;
+ RROutput id;
+
+ char * name;
+ MateRRCrtc * current_crtc;
+ gboolean connected;
+ gulong width_mm;
+ gulong height_mm;
+ MateRRCrtc ** possible_crtcs;
+ MateRROutput ** clones;
+ MateRRMode ** modes;
+ int n_preferred;
+ guint8 * edid_data;
+ char * connector_type;
+};
+
+struct MateRROutputWrap
+{
+ RROutput id;
+};
+
+struct MateRRCrtc
+{
+ ScreenInfo * info;
+ RRCrtc id;
+
+ MateRRMode * current_mode;
+ MateRROutput ** current_outputs;
+ MateRROutput ** possible_outputs;
+ int x;
+ int y;
+
+ MateRRRotation current_rotation;
+ MateRRRotation rotations;
+ int gamma_size;
+};
+
+struct MateRRMode
+{
+ ScreenInfo * info;
+ RRMode id;
+ char * name;
+ int width;
+ int height;
+ int freq; /* in mHz */
+};
+
+/* MateRRCrtc */
+static MateRRCrtc * crtc_new (ScreenInfo *info,
+ RRCrtc id);
+static void crtc_free (MateRRCrtc *crtc);
+
+#ifdef HAVE_RANDR
+static gboolean crtc_initialize (MateRRCrtc *crtc,
+ XRRScreenResources *res,
+ GError **error);
+#endif
+
+/* MateRROutput */
+static MateRROutput *output_new (ScreenInfo *info,
+ RROutput id);
+
+#ifdef HAVE_RANDR
+static gboolean output_initialize (MateRROutput *output,
+ XRRScreenResources *res,
+ GError **error);
+#endif
+
+static void output_free (MateRROutput *output);
+
+/* MateRRMode */
+static MateRRMode * mode_new (ScreenInfo *info,
+ RRMode id);
+
+#ifdef HAVE_RANDR
+static void mode_initialize (MateRRMode *mode,
+ XRRModeInfo *info);
+#endif
+
+static void mode_free (MateRRMode *mode);
+
+
+/* Errors */
+
+/**
+ * mate_rr_error_quark:
+ *
+ * Returns the #GQuark that will be used for #GError values returned by the
+ * MateRR API.
+ *
+ * Return value: a #GQuark used to identify errors coming from the MateRR API.
+ */
+GQuark
+mate_rr_error_quark (void)
+{
+ return g_quark_from_static_string ("mate-rr-error-quark");
+}
+
+/* Screen */
+static MateRROutput *
+mate_rr_output_by_id (ScreenInfo *info, RROutput id)
+{
+ MateRROutput **output;
+
+ g_assert (info != NULL);
+
+ for (output = info->outputs; *output; ++output)
+ {
+ if ((*output)->id == id)
+ return *output;
+ }
+
+ return NULL;
+}
+
+static MateRRCrtc *
+crtc_by_id (ScreenInfo *info, RRCrtc id)
+{
+ MateRRCrtc **crtc;
+
+ if (!info)
+ return NULL;
+
+ for (crtc = info->crtcs; *crtc; ++crtc)
+ {
+ if ((*crtc)->id == id)
+ return *crtc;
+ }
+
+ return NULL;
+}
+
+static MateRRMode *
+mode_by_id (ScreenInfo *info, RRMode id)
+{
+ MateRRMode **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)
+{
+ MateRROutput **output;
+ MateRRCrtc **crtc;
+ MateRRMode **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 (MateRROutput *output, MateRRMode *mode)
+{
+ int i;
+ MateRRMode **modes = mate_rr_output_list_modes (output);
+ int width = mate_rr_mode_get_width (mode);
+ int height = mate_rr_mode_get_height (mode);
+
+ for (i = 0; modes[i] != NULL; ++i)
+ {
+ MateRRMode *m = modes[i];
+
+ if (mate_rr_mode_get_width (m) == width &&
+ mate_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;
+ MateRROutput *output1, *output2;
+
+ output1 = info->outputs[i];
+
+ if (!output1->connected)
+ continue;
+
+ for (j = 0; output1->modes[j] != NULL; ++j)
+ {
+ MateRRMode *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 = (MateRRMode **)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;
+ MateRRCrtc **crtc;
+ MateRROutput **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)
+ {
+ MateRRCrtc *crtc = crtc_new (info, resources->crtcs[i]);
+
+ g_ptr_array_add (a, crtc);
+ }
+ g_ptr_array_add (a, NULL);
+ info->crtcs = (MateRRCrtc **)g_ptr_array_free (a, FALSE);
+
+ a = g_ptr_array_new ();
+ for (i = 0; i < resources->noutput; ++i)
+ {
+ MateRROutput *output = output_new (info, resources->outputs[i]);
+
+ g_ptr_array_add (a, output);
+ }
+ g_ptr_array_add (a, NULL);
+ info->outputs = (MateRROutput **)g_ptr_array_free (a, FALSE);
+
+ a = g_ptr_array_new ();
+ for (i = 0; i < resources->nmode; ++i)
+ {
+ MateRRMode *mode = mode_new (info, resources->modes[i].id);
+
+ g_ptr_array_add (a, mode);
+ }
+ g_ptr_array_add (a, NULL);
+ info->modes = (MateRRMode **)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)
+ {
+ MateRRMode *mode = mode_by_id (info, resources->modes[i].id);
+
+ mode_initialize (mode, &(resources->modes[i]));
+ }
+
+ gather_clone_modes (info);
+
+ return TRUE;
+}
+#endif /* HAVE_RANDR */
+
+static gboolean
+fill_out_screen_info (Display *xdisplay,
+ Window xroot,
+ ScreenInfo *info,
+ gboolean needs_reprobe,
+ GError **error)
+{
+#ifdef HAVE_RANDR
+ XRRScreenResources *resources;
+
+ g_assert (xdisplay != NULL);
+ g_assert (info != NULL);
+
+ /* 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_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3))
+ /* Runtime check for RandR 1.3 or higher */
+ if (info->screen->rr_major_version == 1 && info->screen->rr_minor_version >= 3)
+ 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, MATE_RR_ERROR, MATE_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, MATE_RR_ERROR, MATE_RR_ERROR_UNKNOWN,
+ _("unhandled X error while getting the range of screen sizes"));
+ return FALSE;
+ }
+
+ if (!success) {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_RANDR_ERROR,
+ _("could not get the range of screen sizes"));
+ return FALSE;
+ }
+ }
+ else
+ {
+ mate_rr_screen_get_ranges (info->screen,
+ &(info->min_width),
+ &(info->max_width),
+ &(info->min_height),
+ &(info->max_height));
+ }
+
+ info->primary = None;
+#if (RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3))
+ /* Runtime check for RandR 1.3 or higher */
+ if (info->screen->rr_major_version == 1 && info->screen->rr_minor_version >= 3) {
+ gdk_error_trap_push ();
+ info->primary = XRRGetOutputPrimary (xdisplay, xroot);
+ gdk_flush ();
+ gdk_error_trap_pop (); /* ignore error */
+ }
+#endif
+
+ return TRUE;
+#else
+ return FALSE;
+#endif /* HAVE_RANDR */
+}
+
+static ScreenInfo *
+screen_info_new (MateRRScreen *screen, gboolean needs_reprobe, GError **error)
+{
+ ScreenInfo *info = g_new0 (ScreenInfo, 1);
+
+ g_assert (screen != NULL);
+
+ info->outputs = NULL;
+ info->crtcs = NULL;
+ info->modes = NULL;
+ info->screen = screen;
+
+ if (fill_out_screen_info (screen->xdisplay, screen->xroot, info, needs_reprobe, error))
+ {
+ return info;
+ }
+ else
+ {
+ screen_info_free (info);
+ return NULL;
+ }
+}
+
+static gboolean
+screen_update (MateRRScreen *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->info->resources->configTimestamp)
+ changed = TRUE;
+#endif
+
+ screen_info_free (screen->info);
+
+ screen->info = info;
+
+ if ((changed || force_callback) && screen->callback)
+ screen->callback (screen, screen->data);
+
+ return changed;
+}
+
+static GdkFilterReturn
+screen_on_event (GdkXEvent *xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+#ifdef HAVE_RANDR
+ MateRRScreen *screen = data;
+ XEvent *e = xevent;
+ int event_num;
+
+ if (!e)
+ return GDK_FILTER_CONTINUE;
+
+ event_num = e->type - screen->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) screen->info->resources->timestamp,
+ (guint32) screen->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 mate_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;
+}
+
+/* Returns NULL if screen could not be created. For instance, if
+ * the driver does not support Xrandr 1.2.
+ */
+MateRRScreen *
+mate_rr_screen_new (GdkScreen *gdk_screen,
+ MateRRScreenChanged callback,
+ gpointer data,
+ GError **error)
+{
+#ifdef HAVE_RANDR
+ Display *dpy = GDK_SCREEN_XDISPLAY (gdk_screen);
+ int event_base;
+ int ignore;
+#endif
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ _mate_desktop_init_i18n ();
+
+#ifdef HAVE_RANDR
+ if (XRRQueryExtension (dpy, &event_base, &ignore))
+ {
+ MateRRScreen *screen = g_new0 (MateRRScreen, 1);
+
+ screen->gdk_screen = gdk_screen;
+ screen->gdk_root = gdk_screen_get_root_window (gdk_screen);
+ screen->xroot = gdk_x11_drawable_get_xid (screen->gdk_root);
+ screen->xdisplay = dpy;
+ screen->xscreen = gdk_x11_screen_get_xscreen (screen->gdk_screen);
+ screen->connector_type_atom = XInternAtom (dpy, "ConnectorType", FALSE);
+
+ screen->callback = callback;
+ screen->data = data;
+
+ screen->randr_event_base = event_base;
+
+ XRRQueryVersion (dpy, &screen->rr_major_version, &screen->rr_minor_version);
+ if (screen->rr_major_version > 1 || (screen->rr_major_version == 1 && screen->rr_minor_version < 2)) {
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_NO_RANDR_EXTENSION,
+ "RANDR extension is too old (must be at least 1.2)");
+ g_free (screen);
+ return NULL;
+ }
+
+ screen->info = screen_info_new (screen, TRUE, error);
+
+ if (!screen->info) {
+ g_free (screen);
+ return NULL;
+ }
+
+ if (screen->callback) {
+ XRRSelectInput (screen->xdisplay,
+ screen->xroot,
+ RRScreenChangeNotifyMask);
+
+ gdk_x11_register_standard_event_type (gdk_screen_get_display (gdk_screen),
+ event_base,
+ RRNotify + 1);
+
+ gdk_window_add_filter (screen->gdk_root, screen_on_event, screen);
+ }
+
+ return screen;
+ }
+ else
+ {
+#endif /* HAVE_RANDR */
+ g_set_error (error, MATE_RR_ERROR, MATE_RR_ERROR_NO_RANDR_EXTENSION,
+ _("RANDR extension is not present"));
+
+ return NULL;
+#ifdef HAVE_RANDR
+ }
+#endif
+}
+
+void
+mate_rr_screen_destroy (MateRRScreen *screen)
+{
+ g_return_if_fail (screen != NULL);
+
+ gdk_window_remove_filter (screen->gdk_root, screen_on_event, screen);
+
+ screen_info_free (screen->info);
+ screen->info = NULL;
+
+ g_free (screen);
+}
+
+void
+mate_rr_screen_set_size (MateRRScreen *screen,
+ int width,
+ int height,
+ int mm_width,
+ int mm_height)
+{
+ g_return_if_fail (screen != NULL);
+
+#ifdef HAVE_RANDR
+ gdk_error_trap_push ();
+ XRRSetScreenSize (screen->xdisplay, screen->xroot,
+ width, height, mm_width, mm_height);
+ gdk_flush ();
+ gdk_error_trap_pop (); /* ignore error */
+#endif
+}
+
+void
+mate_rr_screen_get_ranges (MateRRScreen *screen,
+ int *min_width,
+ int *max_width,
+ int *min_height,
+ int *max_height)
+{
+ g_return_if_fail (screen != NULL);
+
+ if (min_width)
+ *min_width = screen->info->min_width;
+
+ if (max_width)
+ *max_width = screen->info->max_width;
+
+ if (min_height)
+ *min_height = screen->info->min_height;
+
+ if (max_height)
+ *max_height = screen->info->max_height;
+}
+
+/**
+ * mate_rr_screen_get_timestamps
+ * @screen: a #MateRRScreen
+ * @change_timestamp_ret: Location in which to store the timestamp at which the RANDR configuration was last changed
+ * @config_timestamp_ret: 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
+mate_rr_screen_get_timestamps (MateRRScreen *screen,
+ guint32 *change_timestamp_ret,
+ guint32 *config_timestamp_ret)
+{
+ g_return_if_fail (screen != NULL);
+
+#ifdef HAVE_RANDR
+ if (change_timestamp_ret)
+ *change_timestamp_ret = screen->info->resources->timestamp;
+
+ if (config_timestamp_ret)
+ *config_timestamp_ret = screen->info->resources->configTimestamp;
+#endif
+}
+
+static gboolean
+force_timestamp_update (MateRRScreen *screen)
+{
+ MateRRCrtc *crtc;
+ XRRCrtcInfo *current_info;
+ Status status;
+ gboolean timestamp_updated;
+
+ timestamp_updated = FALSE;
+
+ crtc = screen->info->crtcs[0];
+
+ if (crtc == NULL)
+ goto out;
+
+ current_info = XRRGetCrtcInfo (screen->xdisplay,
+ screen->info->resources,
+ crtc->id);
+
+ if (current_info == NULL)
+ goto out;
+
+ gdk_error_trap_push ();
+ status = XRRSetCrtcConfig (screen->xdisplay,
+ screen->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;
+}
+
+/**
+ * mate_rr_screen_refresh
+ * @screen: a #MateRRScreen
+ * @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
+mate_rr_screen_refresh (MateRRScreen *screen,
+ GError **error)
+{
+ gboolean refreshed;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ gdk_x11_display_grab (gdk_screen_get_display (screen->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->gdk_screen));
+
+ return refreshed;
+}
+
+MateRRMode **
+mate_rr_screen_list_modes (MateRRScreen *screen)
+{
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ return screen->info->modes;
+}
+
+MateRRMode **
+mate_rr_screen_list_clone_modes (MateRRScreen *screen)
+{
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ return screen->info->clone_modes;
+}
+
+MateRRCrtc **
+mate_rr_screen_list_crtcs (MateRRScreen *screen)
+{
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ return screen->info->crtcs;
+}
+
+MateRROutput **
+mate_rr_screen_list_outputs (MateRRScreen *screen)
+{
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ return screen->info->outputs;
+}
+
+MateRRCrtc *
+mate_rr_screen_get_crtc_by_id (MateRRScreen *screen,
+ guint32 id)
+{
+ int i;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ for (i = 0; screen->info->crtcs[i] != NULL; ++i)
+ {
+ if (screen->info->crtcs[i]->id == id)
+ return screen->info->crtcs[i];
+ }
+
+ return NULL;
+}
+
+MateRROutput *
+mate_rr_screen_get_output_by_id (MateRRScreen *screen,
+ guint32 id)
+{
+ int i;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ for (i = 0; screen->info->outputs[i] != NULL; ++i)
+ {
+ if (screen->info->outputs[i]->id == id)
+ return screen->info->outputs[i];
+ }
+
+ return NULL;
+}
+
+/* MateRROutput */
+static MateRROutput *
+output_new (ScreenInfo *info, RROutput id)
+{
+ MateRROutput *output = g_new0 (MateRROutput, 1);
+
+ 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 (MateRROutput *output)
+{
+ Atom edid_atom;
+ guint8 *result;
+ int len;
+
+ 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 (MateRROutput *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->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 (MateRROutput *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, MATE_RR_ERROR, MATE_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)
+ {
+ MateRRCrtc *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 = (MateRRCrtc **)g_ptr_array_free (a, FALSE);
+
+ /* Clones */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->nclone; ++i)
+ {
+ MateRROutput *mate_rr_output = mate_rr_output_by_id (output->info, info->clones[i]);
+
+ if (mate_rr_output)
+ g_ptr_array_add (a, mate_rr_output);
+ }
+ g_ptr_array_add (a, NULL);
+ output->clones = (MateRROutput **)g_ptr_array_free (a, FALSE);
+
+ /* Modes */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->nmode; ++i)
+ {
+ MateRRMode *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 = (MateRRMode **)g_ptr_array_free (a, FALSE);
+
+ output->n_preferred = info->npreferred;
+
+ /* Edid data */
+ output->edid_data = read_edid_data (output);
+
+ XRRFreeOutputInfo (info);
+
+ return TRUE;
+}
+#endif /* HAVE_RANDR */
+
+static void
+output_free (MateRROutput *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_free (output);
+}
+
+guint32
+mate_rr_output_get_id (MateRROutput *output)
+{
+ g_assert(output != NULL);
+
+ return output->id;
+}
+
+const guint8 *
+mate_rr_output_get_edid_data (MateRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+
+ return output->edid_data;
+}
+
+MateRROutput *
+mate_rr_screen_get_output_by_name (MateRRScreen *screen,
+ const char *name)
+{
+ int i;
+
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (screen->info != NULL, NULL);
+
+ for (i = 0; screen->info->outputs[i] != NULL; ++i)
+ {
+ MateRROutput *output = screen->info->outputs[i];
+
+ if (strcmp (output->name, name) == 0)
+ return output;
+ }
+
+ return NULL;
+}
+
+MateRRCrtc *
+mate_rr_output_get_crtc (MateRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+
+ return output->current_crtc;
+}
+
+/* Returns NULL if the ConnectorType property is not available */
+const char *
+mate_rr_output_get_connector_type (MateRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+
+ return output->connector_type;
+}
+
+gboolean
+mate_rr_output_is_laptop (MateRROutput *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 = mate_rr_output_get_connector_type (output);
+ if (connector_type && strcmp (connector_type, MATE_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;
+}
+
+MateRRMode *
+mate_rr_output_get_current_mode (MateRROutput *output)
+{
+ MateRRCrtc *crtc;
+
+ g_return_val_if_fail (output != NULL, NULL);
+
+ if ((crtc = mate_rr_output_get_crtc (output)))
+ return mate_rr_crtc_get_current_mode (crtc);
+
+ return NULL;
+}
+
+void
+mate_rr_output_get_position (MateRROutput *output,
+ int *x,
+ int *y)
+{
+ MateRRCrtc *crtc;
+
+ g_return_if_fail (output != NULL);
+
+ if ((crtc = mate_rr_output_get_crtc (output)))
+ mate_rr_crtc_get_position (crtc, x, y);
+}
+
+const char *
+mate_rr_output_get_name (MateRROutput *output)
+{
+ g_assert (output != NULL);
+ return output->name;
+}
+
+int
+mate_rr_output_get_width_mm (MateRROutput *output)
+{
+ g_assert (output != NULL);
+ return output->width_mm;
+}
+
+int
+mate_rr_output_get_height_mm (MateRROutput *output)
+{
+ g_assert (output != NULL);
+ return output->height_mm;
+}
+
+MateRRMode *
+mate_rr_output_get_preferred_mode (MateRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+ if (output->n_preferred)
+ return output->modes[0];
+
+ return NULL;
+}
+
+MateRRMode **
+mate_rr_output_list_modes (MateRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, NULL);
+ return output->modes;
+}
+
+gboolean
+mate_rr_output_is_connected (MateRROutput *output)
+{
+ g_return_val_if_fail (output != NULL, FALSE);
+ return output->connected;
+}
+
+gboolean
+mate_rr_output_supports_mode (MateRROutput *output,
+ MateRRMode *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
+mate_rr_output_can_clone (MateRROutput *output,
+ MateRROutput *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
+mate_rr_output_get_is_primary (MateRROutput *output)
+{
+#ifdef HAVE_RANDR
+ return output->info->primary == output->id;
+#else
+ return FALSE;
+#endif
+}
+
+void
+mate_rr_screen_set_primary_output (MateRRScreen *screen,
+ MateRROutput *output)
+{
+#ifdef HAVE_RANDR
+#if (RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3))
+ RROutput id;
+
+ if (output)
+ id = output->id;
+ else
+ id = None;
+
+ /* Runtime check for RandR 1.3 or higher */
+ if (screen->rr_major_version == 1 && screen->rr_minor_version >= 3)
+ XRRSetOutputPrimary (screen->xdisplay, screen->xroot, id);
+#endif
+#endif /* HAVE_RANDR */
+}
+
+/* MateRRCrtc */
+typedef struct
+{
+ Rotation xrot;
+ MateRRRotation rot;
+} RotationMap;
+
+static const RotationMap rotation_map[] =
+{
+ { RR_Rotate_0, MATE_RR_ROTATION_0 },
+ { RR_Rotate_90, MATE_RR_ROTATION_90 },
+ { RR_Rotate_180, MATE_RR_ROTATION_180 },
+ { RR_Rotate_270, MATE_RR_ROTATION_270 },
+ { RR_Reflect_X, MATE_RR_REFLECT_X },
+ { RR_Reflect_Y, MATE_RR_REFLECT_Y },
+};
+
+static MateRRRotation
+mate_rr_rotation_from_xrotation (Rotation r)
+{
+ int i;
+ MateRRRotation 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 (MateRRRotation 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 MATE_DISABLE_DEPRECATED_SOURCE
+gboolean
+mate_rr_crtc_set_config (MateRRCrtc *crtc,
+ int x,
+ int y,
+ MateRRMode *mode,
+ MateRRRotation rotation,
+ MateRROutput **outputs,
+ int n_outputs,
+ GError **error)
+{
+ return mate_rr_crtc_set_config_with_time (crtc, GDK_CURRENT_TIME, x, y, mode, rotation, outputs, n_outputs, error);
+}
+#endif
+
+gboolean
+mate_rr_crtc_set_config_with_time (MateRRCrtc *crtc,
+ guint32 timestamp,
+ int x,
+ int y,
+ MateRRMode *mode,
+ MateRRRotation rotation,
+ MateRROutput **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, MATE_RR_ERROR, MATE_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, MATE_RR_ERROR, MATE_RR_ERROR_RANDR_ERROR,
+ _("could not set the configuration for CRTC %d"),
+ (int) crtc->id);
+ }
+
+ return result;
+#else
+ return FALSE;
+#endif /* HAVE_RANDR */
+}
+
+MateRRMode *
+mate_rr_crtc_get_current_mode (MateRRCrtc *crtc)
+{
+ g_return_val_if_fail (crtc != NULL, NULL);
+
+ return crtc->current_mode;
+}
+
+guint32
+mate_rr_crtc_get_id (MateRRCrtc *crtc)
+{
+ g_return_val_if_fail (crtc != NULL, 0);
+
+ return crtc->id;
+}
+
+gboolean
+mate_rr_crtc_can_drive_output (MateRRCrtc *crtc,
+ MateRROutput *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
+mate_rr_crtc_get_position (MateRRCrtc *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()? */
+MateRRRotation
+mate_rr_crtc_get_current_rotation (MateRRCrtc *crtc)
+{
+ g_assert(crtc != NULL);
+ return crtc->current_rotation;
+}
+
+MateRRRotation
+mate_rr_crtc_get_rotations (MateRRCrtc *crtc)
+{
+ g_assert(crtc != NULL);
+ return crtc->rotations;
+}
+
+gboolean
+mate_rr_crtc_supports_rotation (MateRRCrtc * crtc,
+ MateRRRotation rotation)
+{
+ g_return_val_if_fail (crtc != NULL, FALSE);
+ return (crtc->rotations & rotation);
+}
+
+static MateRRCrtc *
+crtc_new (ScreenInfo *info, RROutput id)
+{
+ MateRRCrtc *crtc = g_new0 (MateRRCrtc, 1);
+
+ crtc->id = id;
+ crtc->info = info;
+
+ return crtc;
+}
+
+#ifdef HAVE_RANDR
+static gboolean
+crtc_initialize (MateRRCrtc *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, MATE_RR_ERROR, MATE_RR_ERROR_RANDR_ERROR,
+ _("could not get information about CRTC %d"),
+ (int) crtc->id);
+ return FALSE;
+ }
+
+ /* MateRRMode */
+ 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)
+ {
+ MateRROutput *output = mate_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 = (MateRROutput **)g_ptr_array_free (a, FALSE);
+
+ /* Possible outputs */
+ a = g_ptr_array_new ();
+ for (i = 0; i < info->npossible; ++i)
+ {
+ MateRROutput *output = mate_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 = (MateRROutput **)g_ptr_array_free (a, FALSE);
+
+ /* Rotations */
+ crtc->current_rotation = mate_rr_rotation_from_xrotation (info->rotation);
+ crtc->rotations = mate_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 (MateRRCrtc *crtc)
+{
+ g_free (crtc->current_outputs);
+ g_free (crtc->possible_outputs);
+ g_free (crtc);
+}
+
+/* MateRRMode */
+static MateRRMode *
+mode_new (ScreenInfo *info, RRMode id)
+{
+ MateRRMode *mode = g_new0 (MateRRMode, 1);
+
+ mode->id = id;
+ mode->info = info;
+
+ return mode;
+}
+
+guint32
+mate_rr_mode_get_id (MateRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return mode->id;
+}
+
+guint
+mate_rr_mode_get_width (MateRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return mode->width;
+}
+
+int
+mate_rr_mode_get_freq (MateRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return (mode->freq) / 1000;
+}
+
+guint
+mate_rr_mode_get_height (MateRRMode *mode)
+{
+ g_return_val_if_fail (mode != NULL, 0);
+ return mode->height;
+}
+
+#ifdef HAVE_RANDR
+static void
+mode_initialize (MateRRMode *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 void
+mode_free (MateRRMode *mode)
+{
+ g_free (mode->name);
+ g_free (mode);
+}
+
+void
+mate_rr_crtc_set_gamma (MateRRCrtc *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
+mate_rr_crtc_get_gamma (MateRRCrtc *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/libmate-desktop/mate-thumbnail-pixbuf-utils.c b/libmate-desktop/mate-thumbnail-pixbuf-utils.c
new file mode 100644
index 0000000..4c50e3e
--- /dev/null
+++ b/libmate-desktop/mate-thumbnail-pixbuf-utils.c
@@ -0,0 +1,172 @@
+/*
+ * mate-thumbnail-pixbuf-utils.c: Utilities for handling pixbufs when thumbnailing
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Mate Library.
+ *
+ * The Mate 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 Mate 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 Mate 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: Alexander Larsson <[email protected]>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define MATE_DESKTOP_USE_UNSTABLE_API
+#include "libmateui/mate-desktop-thumbnail.h"
+
+#define LOAD_BUFFER_SIZE 65536
+
+/**
+ * mate_thumbnail_scale_down_pixbuf:
+ * @pixbuf: a #GdkPixbuf
+ * @dest_width: the desired new width
+ * @dest_height: the desired new height
+ *
+ * Scales the pixbuf to the desired size. This function
+ * is a lot faster than gdk-pixbuf when scaling down by
+ * large amounts.
+ *
+ * Return value: a scaled pixbuf
+ *
+ * Since: 2.2
+ **/
+GdkPixbuf *
+mate_desktop_thumbnail_scale_down_pixbuf (GdkPixbuf *pixbuf,
+ int dest_width,
+ int dest_height)
+{
+ int source_width, source_height;
+ int s_x1, s_y1, s_x2, s_y2;
+ int s_xfrac, s_yfrac;
+ int dx, dx_frac, dy, dy_frac;
+ div_t ddx, ddy;
+ int x, y;
+ int r, g, b, a;
+ int n_pixels;
+ gboolean has_alpha;
+ guchar *dest, *src, *xsrc, *src_pixels;
+ GdkPixbuf *dest_pixbuf;
+ int pixel_stride;
+ int source_rowstride, dest_rowstride;
+
+ if (dest_width == 0 || dest_height == 0) {
+ return NULL;
+ }
+
+ source_width = gdk_pixbuf_get_width (pixbuf);
+ source_height = gdk_pixbuf_get_height (pixbuf);
+
+ g_assert (source_width >= dest_width);
+ g_assert (source_height >= dest_height);
+
+ ddx = div (source_width, dest_width);
+ dx = ddx.quot;
+ dx_frac = ddx.rem;
+
+ ddy = div (source_height, dest_height);
+ dy = ddy.quot;
+ dy_frac = ddy.rem;
+
+ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+ source_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ src_pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8,
+ dest_width, dest_height);
+ dest = gdk_pixbuf_get_pixels (dest_pixbuf);
+ dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf);
+
+ pixel_stride = (has_alpha)?4:3;
+
+ s_y1 = 0;
+ s_yfrac = -dest_height/2;
+ while (s_y1 < source_height) {
+ s_y2 = s_y1 + dy;
+ s_yfrac += dy_frac;
+ if (s_yfrac > 0) {
+ s_y2++;
+ s_yfrac -= dest_height;
+ }
+
+ s_x1 = 0;
+ s_xfrac = -dest_width/2;
+ while (s_x1 < source_width) {
+ s_x2 = s_x1 + dx;
+ s_xfrac += dx_frac;
+ if (s_xfrac > 0) {
+ s_x2++;
+ s_xfrac -= dest_width;
+ }
+
+ /* Average block of [x1,x2[ x [y1,y2[ and store in dest */
+ r = g = b = a = 0;
+ n_pixels = 0;
+
+ src = src_pixels + s_y1 * source_rowstride + s_x1 * pixel_stride;
+ for (y = s_y1; y < s_y2; y++) {
+ xsrc = src;
+ if (has_alpha) {
+ for (x = 0; x < s_x2-s_x1; x++) {
+ n_pixels++;
+
+ r += xsrc[3] * xsrc[0];
+ g += xsrc[3] * xsrc[1];
+ b += xsrc[3] * xsrc[2];
+ a += xsrc[3];
+ xsrc += 4;
+ }
+ } else {
+ for (x = 0; x < s_x2-s_x1; x++) {
+ n_pixels++;
+ r += *xsrc++;
+ g += *xsrc++;
+ b += *xsrc++;
+ }
+ }
+ src += source_rowstride;
+ }
+
+ if (has_alpha) {
+ if (a != 0) {
+ *dest++ = r / a;
+ *dest++ = g / a;
+ *dest++ = b / a;
+ *dest++ = a / n_pixels;
+ } else {
+ *dest++ = 0;
+ *dest++ = 0;
+ *dest++ = 0;
+ *dest++ = 0;
+ }
+ } else {
+ *dest++ = r / n_pixels;
+ *dest++ = g / n_pixels;
+ *dest++ = b / n_pixels;
+ }
+
+ s_x1 = s_x2;
+ }
+ s_y1 = s_y2;
+ dest += dest_rowstride - dest_width * pixel_stride;
+ }
+
+ return dest_pixbuf;
+}
diff --git a/libmate-desktop/pnp.ids b/libmate-desktop/pnp.ids
new file mode 100644
index 0000000..1e1e570
--- /dev/null
+++ b/libmate-desktop/pnp.ids
@@ -0,0 +1,2029 @@
+AAE Anatek Electronics Inc.
+AAT Ann Arbor Technologies
+ABA ABBAHOME INC.
+ABC AboCom System Inc
+ABD Allen Bradley Company
+ABE Alcatel Bell
+ABO D-Link Systems Inc
+ABT Anchor Bay Technologies, Inc.
+ABV Advanced Research Technology
+ACA Ariel Corporation
+ACB Aculab Ltd
+ACC Accton Technology Corporation
+ACD AWETA BV
+ACE Actek Engineering Pty Ltd
+ACG A&R Cambridge Ltd
+ACH Archtek Telecom Corporation
+ACI Ancor Communications Inc
+ACK Acksys
+ACL Apricot Computers
+ACM Acroloop Motion Control Systems Inc
+ACO Allion Computer Inc.
+ACP Aspen Tech Inc
+ACR Acer Technologies
+ACS Altos Computer Systems
+ACT Applied Creative Technology
+ACU Acculogic
+ACV ActivCard S.A
+ADA Addi-Data GmbH
+ADB Aldebbaron
+ADC Acnhor Datacomm
+ADD Advanced Peripheral Devices Inc
+ADE Arithmos, Inc.
+ADH Aerodata Holdings Ltd
+ADI ADI Systems Inc
+ADK Adtek System Science Company Ltd
+ADL ASTRA Security Products Ltd
+ADM Ad Lib MultiMedia Inc
+ADN Analog & Digital Devices Tel. Inc
+ADP Adaptec Inc
+ADR Nasa Ames Research Center
+ADS Analog Devices Inc
+ADT Adtek
+ADT Aved Display Technologies
+ADV Advanced Micro Devices Inc
+ADX Adax Inc
+AEC Antex Electronics Corporation
+AED Advanced Electronic Designs, Inc.
+AEI Actiontec Electric Inc
+AEJ Alpha Electronics Company
+AEM ASEM S.p.A.
+AEP Aetas Peripheral International
+AET Aethra Telecomunicazioni S.r.l.
+AFA Alfa Inc
+AGC Beijing Aerospace Golden Card Electronic Engineering Co.,Ltd.
+AGI Artish Graphics Inc
+AGL Argolis
+AGM Advan Int'l Corporation
+AGT Agilent Technologies
+AHC Advantech Co., Ltd.
+AIC Arnos Insturments & Computer Systems
+AIE Altmann Industrieelektronik
+AII Amptron International Inc.
+AIL Altos India Ltd
+AIM AIMS Lab Inc
+AIR Advanced Integ. Research Inc
+AIS Alien Internet Services
+AIW Aiwa Company Ltd
+AIX ALTINEX, INC.
+AJA AJA Video Systems, Inc.
+AKB Akebia Ltd
+AKI AKIA Corporation
+AKL AMiT Ltd
+AKM Asahi Kasei Microsystems Company Ltd
+AKP Atom Komplex Prylad
+AKY Askey Computer Corporation
+ALA Alacron Inc
+ALC Altec Corporation
+ALD In4S Inc
+ALG Realtek Semiconductor Corp.
+ALH AL Systems
+ALI Acer Labs
+ALJ Altec Lansing
+ALK Acrolink Inc
+ALL Alliance Semiconductor Corporation
+ALM Acutec Ltd.
+ALN Alana Technologies
+ALO Algolith Inc.
+ALP Alps Electric Company Ltd
+ALR Advanced Logic
+ALS Avance Logic Inc
+ALT Altra
+ALV AlphaView LCD
+ALX ALEXON Co.,Ltd.
+AMA Asia Microelectronic Development Inc
+AMB Ambient Technologies, Inc.
+AMC Attachmate Corporation
+AMD Amdek Corporation
+AMI American Megatrends Inc
+AML Anderson Multimedia Communications (HK) Limited
+AMN Amimon LTD.
+AMP AMP Inc
+AMT AMT International Industry
+AMX AMX LLC
+ANA Anakron
+ANC Ancot
+AND Adtran Inc
+ANI Anigma Inc
+ANK Anko Electronic Company Ltd
+ANL Analogix Semiconductor, Inc
+ANO Anorad Corporation
+ANP Andrew Network Production
+ANR ANR Ltd
+ANS Ansel Communication Company
+ANT Ace CAD Enterprise Company Ltd
+ANX Acer Netxus Inc
+AOA AOpen Inc.
+AOE Advanced Optics Electronics, Inc.
+AOL America OnLine
+AOT Alcatel
+APC American Power Conversion
+APD AppliAdata
+APG Horner Electric Inc
+API A Plus Info Corporation
+APL Aplicom Oy
+APM Applied Memory Tech
+APN Appian Tech Inc
+APP Apple Computer Inc
+APR Aprilia s.p.a.
+APS Autologic Inc
+APT Audio Processing Technology Ltd
+APX AP Designs Ltd
+ARC Alta Research Corporation
+ARE ICET S.p.A.
+ARG Argus Electronics Co., LTD
+ARI Argosy Research Inc
+ARK Ark Logic Inc
+ARL Arlotto Comnet Inc
+ARM Arima
+ARO Poso International B.V.
+ARS Arescom Inc
+ART Corion Industrial Corporation
+ASC Ascom Strategic Technology Unit
+ASD USC Information Sciences Institute
+ASE AseV Display Labs
+ASI Ahead Systems
+ASK Ask A/S
+ASL AccuScene Corporation Ltd
+ASM ASEM S.p.A.
+ASN Asante Tech Inc
+ASP ASP Microelectronics Ltd
+AST AST Research Inc
+ASU Asuscom Network Inc
+ASX AudioScience
+ASY Rockwell Collins / Airshow Systems
+ATA Allied Telesyn International (Asia) Pte Ltd
+ATC Ably-Tech Corporation
+ATD Alpha Telecom Inc
+ATE Innovate Ltd
+ATH Athena Informatica S.R.L.
+ATI Allied Telesis KK
+ATK Allied Telesyn Int'l
+ATL Arcus Technology Ltd
+ATM ATM Ltd
+ATN Athena Smartcard Solutions Ltd.
+ATO ASTRO DESIGN, INC.
+ATP Alpha-Top Corporation
+ATT AT&T
+ATV Office Depot, Inc.
+ATX Athenix Corporation
+AUI Alps Electric Inc
+AUO AU Optronics
+AUR Aureal Semiconductor
+AUT Autotime Corporation
+AVA Avaya Communication
+AVC Auravision Corporation
+AVD Avid Electronics Corporation
+AVE Add Value Enterpises (Asia) Pte Ltd
+AVI Nippon Avionics Co.,Ltd
+AVM AVM GmbH
+AVO Avocent Corporation
+AVT Avtek (Electronics) Pty Ltd
+AVV SBS Technologies (Canada), Inc. (was Avvida Systems, Inc.)
+AWC Access Works Comm Inc
+AWL Aironet Wireless Communications, Inc
+AWS Wave Systems
+AXB Adrienne Electronics Corporation
+AXC AXIOMTEK CO., LTD.
+AXE D-Link Systems Inc (used as 2nd pnpid)
+AXI American Magnetics
+AXL Axel
+AXP American Express
+AXT Axtend Technologies Inc
+AXX Axxon Computer Corporation
+AXY AXYZ Automation Services, Inc
+AYD Aydin Displays
+AYR Airlib, Inc
+AZM AZ Middelheim - Radiotherapy
+AZT Aztech Systems Ltd
+BAC Biometric Access Corporation
+BAN Banyan
+BBB an-najah university
+BBH B&Bh
+BBL Brain Boxes Limited
+BCC Beaver Computer Corporaton
+BCD Dr. Seufert GmbH
+BCM Broadcom
+BCQ Deutsche Telekom Berkom GmbH
+BCS Booria CAD/CAM systems
+BDO Brahler ICS
+BDR Blonder Tongue Labs, Inc.
+BDS Barco Display Systems
+BEC Elektro Beckhoff GmbH
+BEI Beckworth Enterprises Inc
+BEK Beko Elektronik A.S.
+BEL Beltronic Industrieelektronik GmbH
+BEO Baug & Olufsen
+BFE B.F. Engineering Corporation
+BGB Barco Graphics N.V
+BGT Budzetron Inc
+BHZ BitHeadz, Inc.
+BIC Big Island Communications
+BII Boeckeler Instruments Inc
+BIL Billion Electric Company Ltd
+BIO BioLink Technologies International, Inc.
+BIT Bit 3 Computer
+BLI Busicom
+BLN BioLink Technologies
+BLP Bloomberg L.P.
+BMI Benson Medical Instruments Company
+BML BIOMED Lab
+BMS BIOMEDISYS
+BNE Bull AB
+BNK Banksia Tech Pty Ltd
+BNO Bang & Olufsen
+BNS Boulder Nonlinear Systems
+BOB Rainy Orchard
+BOE BOE
+BOS BOS
+BPD Micro Solutions, Inc.
+BPU Best Power
+BRC BARC
+BRG Bridge Information Co., Ltd
+BRI Boca Research Inc
+BRM Braemar Inc
+BRO BROTHER INDUSTRIES,LTD.
+BSE Bose Corporation
+BSL Biomedical Systems Laboratory
+BST BodySound Technologies, Inc.
+BTC Bit 3 Computer
+BTE Brilliant Technology
+BTF Bitfield Oy
+BTI BusTech Inc
+BUF Yasuhiko Shirai Melco Inc
+BUJ ATI Tech Inc
+BUL Bull
+BUR Bernecker & Rainer Ind-Eletronik GmbH
+BUS BusTek
+BUT 21ST CENTURY ENTERTAINMENT
+BWK Bitworks Inc.
+BXE Buxco Electronics
+BYD byd:sign corporation
+CAA Castles Automation Co., Ltd
+CAC CA & F Elettronica
+CAG CalComp
+CAI Canon Inc.
+CAL Acon
+CAM Cambridge Audio
+CAN Canopus Company Ltd
+CAN Carrera Computer Inc
+CAN CORNEA
+CAR Cardinal Company Ltd
+CAS CASIO COMPUTER CO.,LTD
+CAT Consultancy in Advanced Technology
+CBI ComputerBoards Inc
+CBR Cebra Tech A/S
+CBX Cybex Computer Products Corporation
+CCC C-Cube Microsystems
+CCI Cache
+CCJ CONTEC CO.,LTD.
+CCL CCL/ITRI
+CCP Capetronic USA Inc
+CDC Core Dynamics Corporation
+CDD Convergent Data Devices
+CDE Colin.de
+CDG Christie Digital Systems Inc
+CDI Concept Development Inc
+CDK Cray Communications
+CDN Codenoll Technical Corporation
+CDP CalComp
+CDS Computer Diagnostic Systems
+CDT IBM Corporation
+CDV Convergent Design Inc.
+CEA Consumer Electronics Association
+CEC Chicony Electronics Company Ltd
+CED Cambridge Electronic Design Ltd
+CEF Cefar Digital Vision
+CEI Crestron Electronics, Inc.
+CEM MEC Electronics GmbH
+CEN Centurion Technologies P/L
+CEP C-DAC
+CER Ceronix
+CET TEC CORPORATION
+CFG Atlantis
+CGA Chunghwa Picture Tubes, LTD
+CGS Chyron Corp
+CHA Chase Research PLC
+CHC Chic Technology Corp.
+CHD ChangHong Electric Co.,Ltd
+CHE Acer Inc
+CHG Sichuan Changhong Electric CO, LTD.
+CHI Chrontel Inc
+CHL Chloride-R&D
+CHM CHIC TECHNOLOGY CORP.
+CHO Sichuang Changhong Corporation
+CHP CH Products
+CHS Agentur Chairos
+CHT Chunghwa Picture Tubes,LTD.
+CHY Cherry GmbH
+CIC Comm. Intelligence Corporation
+CII Cromack Industries Inc
+CIL Citicom Infotech Private Limited
+CIN Citron GmbH
+CIP Ciprico Inc
+CIR Cirrus Logic Inc
+CIS Cisco Systems Inc
+CIT Citifax Limited
+CKC The Concept Keyboard Company Ltd
+CLA Clarion Company Ltd
+CLD COMMAT L.t.d.
+CLE Classe Audio
+CLG CoreLogic
+CLI Cirrus Logic Inc
+CLM CrystaLake Multimedia
+CLO Clone Computers
+CLT automated computer control systems
+CLV Clevo Company
+CLX CardLogix
+CMC CMC Ltd
+CMD Colorado MicroDisplay, Inc.
+CMG Chenming Mold Ind. Corp.
+CMI C-Media Electronics
+CMM Comtime GmbH
+CMO Chi Mei Optoelectronics corp.
+CMR Cambridge Research Systems Ltd
+CMS CompuMaster Srl
+CMX Comex Electronics AB
+CNB American Power Conversion
+CNC Alvedon Computers Ltd
+CNE Cine-tal
+CNI Connect Int'l A/S
+CNN Canon Inc
+CNT COINT Multimedia Systems
+COB COBY Electronics Co., Ltd
+COD CODAN Pty. Ltd.
+COI Codec Inc.
+COL Rockwell Collins, Inc.
+COM Comtrol Corporation
+CON Contec Company Ltd
+COR Corollary Inc
+COS CoStar Corporation
+COT Core Technology Inc
+COW Polycow Productions
+CPC Ciprico Inc
+CPD CompuAdd
+CPI Computer Peripherals Inc
+CPL Compal Electronics Inc
+CPQ Compaq Computer Company
+CPT cPATH
+CPX Powermatic Data Systems
+CRC CONRAC GmbH
+CRD Cardinal Technical Inc
+CRE Creative Labs Inc
+CRI Crio Inc.
+CRL Creative Logic  
+CRN Cornerstone Imaging
+CRO Extraordinary Technologies PTY Limited
+CRQ Cirque Corporation
+CRS Crescendo Communication Inc
+CRX Cyrix Corporation
+CSB Transtex SA
+CSC Crystal Semiconductor
+CSD Cresta Systems Inc
+CSE Concept Solutions & Engineering
+CSI Cabletron System Inc
+CSO California Institute of Technology
+CSS CSS Laboratories
+CST CSTI Inc
+CTA CoSystems Inc
+CTC CTC Communication Development Company Ltd
+CTE Chunghwa Telecom Co., Ltd.
+CTL Creative Technology Ltd
+CTM Computerm Corporation
+CTN Computone Products
+CTP Computer Technology Corporation
+CTS Comtec Systems Co., Ltd.
+CTX Creatix Polymedia GmbH
+CUB Cubix Corporation
+CUK Calibre UK Ltd
+CVS Clarity Visual Systems
+CWR Connectware Inc
+CXT Conexant Systems
+CYB CyberVision
+CYC Cylink Corporation
+CYD Cyclades Corporation
+CYL Cyberlabs
+CYT Cytechinfo Inc
+CYV Cyviz AS
+CYW Cyberware
+CYX Cyrix Corporation
+DAC Digital Acoustics Corporation
+DAE Digatron Industrie Elektronik GmbH
+DAI DAIS SET Ltd.
+DAK Daktronics
+DAL Digital Audio Labs Inc
+DAS DAVIS AS
+DAT Datel Inc
+DAU Daou Tech Inc
+DAV Davicom Semiconductor Inc
+DAW DA2 Technologies Inc
+DAX Data Apex Ltd
+DBD Diebold Inc.
+DBI DigiBoard Inc
+DBK Databook Inc
+DBL Doble Engineering Company
+DBN DB Networks Inc
+DCA Digital Communications Association
+DCC Dale Computer Corporation
+DCD Datacast LLC
+DCE dSPACE GmbH
+DCI Concepts Inc
+DCL Dynamic Controls Ltd
+DCM DCM Data Products
+DCO Dialogue Technology Corporation
+DCR Decros Ltd
+DCS Diamond Computer Systems Inc
+DCT Dancall Telecom A/S
+DCV Datatronics Technology Inc
+DDA DA2 Technologies Corporation
+DDD Danka Data Devices
+DDI Data Display AG
+DDS Barco, n.v.
+DDT Datadesk Technologies Inc
+DEC Digital Equipment Corporation
+DEI Deico Electronics
+DEL Dell
+DEN Densitron Computers Ltd
+DEX idex displays
+DFI DFI
+DFK SharkTec A/S
+DGA Digiital Arts Inc
+DGC Data General Corporation
+DGI DIGI International
+DGK DugoTech Co., LTD
+DGP Digicorp European sales S.A.
+DGS Diagsoft Inc
+DGT Dearborn Group Technology
+DGT The Dearborn Group
+DHP DH Print
+DHQ Quadram
+DHT Projectavision Inc
+DIA Diadem
+DIG Digicom S.p.A.
+DII Dataq Instruments Inc
+DIM dPict Imaging, Inc.
+DIN Daintelecom Co., Ltd
+DIS Diseda S.A.
+DIT Dragon Information Technology
+DJE Capstone Visual Product Development
+DJP Maygay Machines, Ltd
+DKY Datakey Inc
+DLC Diamond Lane Comm. Corporation
+DLG Digital-Logic GmbH
+DLK D-Link Systems Inc
+DLT Digitelec Informatique Park Cadera
+DMB Digicom Systems Inc
+DMC Dune Microsystems Corporation
+DMM Dimond Multimedia Systems Inc
+DMP D&M Holdings Inc, Professional Business Company
+DMS DOME imaging systems
+DMV NDS Ltd
+DNA DNA Enterprises, Inc.
+DNG Apache Micro Peripherals Inc
+DNI Deterministic Networks Inc.
+DNT Dr. Neuhous Telekommunikation GmbH
+DNV DiCon
+DOL Dolman Technologies Group Inc
+DOM Dome Imaging Systems
+DON DENON, Ltd.
+DOT Dotronic Mikroelektronik GmbH
+DPA DigiTalk Pro AV
+DPC Delta Electronics Inc
+DPI DocuPoint
+DPL Digital Projection Limited
+DPM ADPM Synthesis sas
+DPS Digital Processing Systems
+DPT DPT
+DPX DpiX, Inc.
+DQB Datacube Inc
+DRB Dr. Bott KG
+DRC Data Ray Corp.
+DRD DIGITAL REFLECTION INC.
+DRI Data Race Inc
+DSD DS Multimedia Pte Ltd
+DSI Digitan Systems Inc
+DSM DSM Digital Services GmbH
+DSP Domain Technology Inc
+DTA DELTATEC
+DTC DTC Tech Corporation
+DTI Diversified Technology, Inc.
+DTK Dynax Electronics (HK) Ltd
+DTL e-Net Inc
+DTN Datang Telephone Co
+DTO Deutsche Thomson OHG
+DTX Data Translation
+DUA Dosch & Amand GmbH & Company KG
+DUN NCR Corporation
+DVD Dictaphone Corporation
+DVL Devolo AG
+DVS Digital Video System
+DVT Data Video
+DWE Daewoo Electronics Company Ltd
+DXC Digipronix Control Systems
+DXP Data Expert Corporation
+DXS Signet
+DYC Dycam Inc
+DYM Dymo-CoStar Corporation
+DYN Askey Computer Corporation
+DYX Dynax Electronics (HK) Ltd
+EAS Evans and Sutherland Computer
+EBH Data Price Informatica
+EBT HUALONG TECHNOLOGY CO., LTD
+ECA Electro Cam Corp.
+ECC ESSential Comm. Corporation
+ECI Enciris Technologies
+ECK Eugene Chukhlomin Sole Proprietorship, d.b.a.
+ECL Excel Company Ltd
+ECM E-Cmos Tech Corporation
+ECO Echo Speech Corporation
+ECP Elecom Company Ltd
+ECS Elitegroup Computer Systems Company Ltd
+ECT Enciris Technologies
+EDC e.Digital Corporation
+EDG Electronic-Design GmbH
+EDI Edimax Tech. Company Ltd
+EDM EDMI
+EEE ET&T Technology Company Ltd
+EEH EEH Datalink GmbH
+EEP E.E.P.D. GmbH
+EES EE Solutions, Inc.
+EGD EIZO GmbH Display Technologies
+EGL Eagle Technology
+EGN Egenera, Inc.
+EGO Ergo Electronics
+EHJ Epson Research
+EIC Eicon Technology Corporation
+EKA MagTek Inc.
+EKC Eastman Kodak Company
+EKS EKSEN YAZILIM
+ELA ELAD srl
+ELC Electro Scientific Ind
+ELE Elecom Company Ltd
+ELG Elmeg GmbH Kommunikationstechnik
+ELI Edsun Laboratories
+ELL Electrosonic Ltd
+ELM Elmic Systems Inc
+ELO Elo TouchSystems Inc
+ELO Tyco Electronics
+ELS ELSA GmbH
+ELT Element Labs, Inc.
+ELX Elonex PLC
+EMB Embedded computing inc ltd
+EMC eMicro Corporation
+EME EMiNE TECHNOLOGY COMPANY, LTD.
+EMG EMG Consultants Inc
+EMI Ex Machina Inc
+EMU Emulex Corporation
+ENC Eizo Nanao Corporation
+END ENIDAN Technologies Ltd
+ENE ENE Technology Inc.
+ENI Efficient Networks
+ENS Ensoniq Corporation
+ENT Enterprise Comm. & Computing Inc
+EPC Empac
+EPI Envision Peripherals, Inc
+EPN EPiCON Inc.
+EPS KEPS
+EQP Equipe Electronics Ltd.
+EQX Equinox Systems Inc
+ERG Ergo System
+ERI Ericsson Mobile Communications AB
+ERN Ericsson, Inc.
+ERP Euraplan GmbH
+ERT Escort Insturments Corporation
+ESC Eden Sistemas de Computacao S/A
+ESG ELCON Systemtechnik GmbH
+ESI Extended Systems, Inc.
+ESK ES&S
+ESS ESS Technology Inc
+EST Embedded Solution Technology
+ESY E-Systems Inc
+ETC Everton Technology Company Ltd
+ETI Eclipse Tech Inc
+ETK eTEK Labs Inc.
+ETL Evertz Microsystems Ltd.
+ETS Electronic Trade Solutions Ltd
+ETT E-Tech Inc
+EUT Ericsson Mobile Networks B.V.
+EVI eviateg GmbH
+EVX Everex
+EXA Exabyte
+EXC Excession Audio
+EXI Exide Electronics
+EXN RGB Systems, Inc. dba Extron Electronics
+EXP Data Export Corporation
+EXT Exatech Computadores & Servicos Ltda
+EXX Exxact GmbH
+EXY Exterity Ltd
+EZE EzE Technologies
+EZP Storm Technology
+FAR Farallon Computing
+FBI Interface Corporation
+FCB Furukawa Electric Company Ltd
+FCG First International Computer Ltd
+FCS Focus Enhancements, Inc.
+FDC Future Domain
+FDT Fujitsu Display Technologies Corp.
+FEC FURUNO ELECTRIC CO., LTD.
+FEL Fellowes & Questec
+FER Ferranti Int'L
+FFI Fairfield Industries
+FGD Lisa Draexlmaier GmbH
+FGL Fujitsu General Limited.
+FHL FHLP
+FIC Formosa Industrial Computing Inc
+FIL Forefront Int'l Ltd
+FIN Finecom Co., Ltd.
+FIR Chaplet Systems Inc
+FIS FLY-IT Simulators
+FJC Fujitsu Takamisawa Component Limited
+FJS Fujitsu Spain
+FJT F.J. Tieman BV
+FLI Faroudja Laboratories
+FLY Butterfly Communications
+FMA Fast Multimedia AG
+FMC Ford Microelectronics Inc
+FMI Fellowes, Inc.
+FMI Fujitsu Microelect Inc
+FML Fujitsu Microelect Ltd
+FMZ Formoza-Altair
+FNC Fanuc LTD
+FNI Funai Electric Co., Ltd.
+FOA FOR-A Company Limited
+FOS Foss Tecator
+FPE Fujitsu Peripherals Ltd
+FPS Deltec Corporation
+FPX Cirel Systemes
+FRC Force Computers
+FRD Freedom Scientific BLV
+FRE Forvus Research Inc
+FRI Fibernet Research Inc
+FRS South Mountain Technologies, LTD
+FSC Future Systems Consulting KK
+FSI Fore Systems Inc
+FST Modesto PC Inc
+FTC Futuretouch Corporation
+FTE Frontline Test Equipment Inc.
+FTG FTG Data Systems
+FTI FastPoint Technologies, Inc.
+FTN Fountain Technologies Inc
+FTR Mediasonic
+FUJ Fujitsu Ltd
+FUN sisel muhendislik
+FUS Fujitsu Siemens Computers GmbH
+FVC First Virtual Corporation
+FVX C-C-C Group Plc
+FWR Flat Connections Inc
+FXX Fuji Xerox
+FZC Founder Group Shenzhen Co.
+FZI FZI Forschungszentrum Informatik
+GAG Gage Applied Sciences Inc
+GAL Galil Motion Control
+GAU Gaudi Co., Ltd.
+GCC GCC Technologies Inc
+GCI Gateway Comm. Inc
+GCS Grey Cell Systems Ltd
+GDC General Datacom
+GDI G. Diehl ISDN GmbH
+GDS GDS
+GDT Vortex Computersysteme GmbH
+GEF GE Fanuc Embedded Systems
+GEM Gem Plus
+GEN Genesys ATE Inc
+GEO GEO Sense
+GES GES Singapore Pte Ltd
+GFM GFMesstechnik GmbH
+GFN Gefen Inc.
+GIC General Inst. Corporation
+GIM Guillemont International
+GIS AT&T Global Info Solutions
+GJN Grand Junction Networks
+GLE AD electronics
+GLM Genesys Logic
+GLS Gadget Labs LLC
+GMK GMK Electronic Design GmbH
+GML General Information Systems
+GMM GMM Research Inc
+GMN GEMINI 2000 Ltd
+GMX GMX Inc
+GND Gennum Corporation
+GNN GN Nettest Inc
+GNZ Gunze Ltd
+GRA Graphica Computer
+GRE GOLD RAIN ENTERPRISES CORP.
+GRH Granch Ltd
+GRV Advanced Gravis
+GRY Robert Gray Company
+GSB NIPPONDENCHI CO,.LTD
+GSC General Standards Corporation
+GSM Goldstar Company Ltd
+GST Graphic SystemTechnology
+GSY Grossenbacher Systeme AG
+GTC Graphtec Corporation
+GTI Goldtouch
+GTK G-Tech Corporation
+GTM Garnet System Company Ltd
+GTS Geotest Marvin Test Systems Inc
+GTT General Touch Technology Co., Ltd.
+GUD Guntermann & Drunck GmbH
+GUZ Guzik Technical Enterprises
+GVC GVC Corporation
+GVL Global Village Communication
+GWI GW Instruments
+GWY Gateway 2000
+GZE GUNZE Limited
+HAE Haider electronics
+HAI Haivision Systems Inc.
+HAL Halberthal
+HAN Hanchang System Corporation
+HAY Hayes Microcomputer Products Inc
+HCA DAT
+HCL HCL America Inc
+HCM HCL Peripherals
+HCP Hitachi Computer Products Inc
+HCW Hauppauge Computer Works Inc
+HDC HardCom Elektronik & Datateknik
+HDI HD-INFO d.o.o.
+HDV Holografika kft.
+HEC Hisense Electric Co., Ltd.
+HEC Hitachi Engineering Company Ltd
+HEL Hitachi Micro Systems Europe Ltd
+HER Ascom Business Systems
+HET HETEC Datensysteme GmbH
+HHC HIRAKAWA HEWTECH CORP.
+HIB Hibino Corporation
+HIC Hitachi Information Technology Co., Ltd.
+HIK Hikom Co., Ltd.
+HIL Hilevel Technology
+HIT Hitachi America Ltd
+HJI Harris & Jeffries Inc
+HKA HONKO MFG. CO., LTD.
+HKG Josef Heim KG
+HMC Hualon Microelectric Corporation
+HMK hmk Daten-System-Technik BmbH
+HMX HUMAX Co., Ltd.
+HNS Hughes Network Systems
+HOB HOB Electronic GmbH
+HOE Hosiden Corporation
+HOL Holoeye Photonics AG
+HPC Hewlett Packard Co.
+HPD Hewlett Packard
+HPI Headplay, Inc.
+HPK HAMAMATSU PHOTONICS K.K.
+HPQ HP
+HPR H.P.R. Electronics GmbH
+HRC Hercules
+HRE Qingdao Haier Electronics Co., Ltd.
+HRL Herolab GmbH
+HRS Harris Semiconductor
+HRT HERCULES
+HSC Hagiwara Sys-Com Company Ltd
+HSM AT&T Microelectronics
+HTC Hitachi Ltd
+HTI Hampshire Company, Inc.
+HTK Holtek Microelectronics Inc
+HTX Hitex Systementwicklung GmbH
+HUB GAI-Tronics, A Hubbell Company
+HUM IMP Electronics Ltd.
+HWA Harris Canada Inc
+HWC DBA Hans Wedemeyer
+HWD Highwater Designs Ltd
+HWP Hewlett Packard
+HXM Hexium Ltd.
+HYC Hypercope Gmbh Aachen
+HYO HYC CO., LTD.
+HYP Hyphen Ltd
+HYR Hypertec Pty Ltd
+HYT Heng Yu Technology (HK) Limited
+HYV Hynix Semiconductor
+IAF Institut f r angewandte Funksystemtechnik GmbH
+IAI Integration Associates, Inc.
+IAT IAT Germany GmbH
+IBC Integrated Business Systems
+IBI INBINE.CO.LTD
+IBM IBM Brasil
+IBM IBM France
+IBP IBP Instruments GmbH
+IBR IBR GmbH
+ICA ICA Inc
+ICC BICC Data Networks Ltd
+ICD ICD Inc
+ICE IC Ensemble
+ICI Infotek Communication Inc
+ICM Intracom SA
+ICN Sanyo Icon
+ICO Intel Corp
+ICS Integrated Circuit Systems
+ICX ICCC A/S
+IDC International Datacasting Corporation
+IDE IDE Associates
+IDK IDK Corporation
+IDO IDEO Product Development
+IDS Interdigital Sistemas de Informacao
+IDT International Display Technology
+IDX IDEXX Labs
+IEC Interlace Engineering Corporation
+IEE IEE
+IEI Interlink Electronics
+IFS In Focus Systems Inc
+IFT Informtech
+IFX Infineon Technologies AG
+IGC Intergate Pty Ltd
+IGM IGM Communi
+IIC ISIC Innoscan Industrial Computers A/S
+III Intelligent Instrumentation
+IIN IINFRA Co., Ltd
+IKS Ikos Systems Inc
+ILC Image Logic Corporation
+ILS Innotech Corporation
+IMA Imagraph
+IMC IMC Networks
+IMD ImasDe Canarias S.A.
+IME Imagraph
+IMG IMAGENICS Co., Ltd.
+IMI International Microsystems Inc
+IMM Immersion Corporation
+IMN Impossible Production
+IMP Impression Products Incorporated
+IMT Inmax Technology Corporation
+INC Home Row Inc
+IND ILC
+INE Inventec Electronics (M) Sdn. Bhd.
+INF Inframetrics Inc
+ING Integraph Corporation
+INI Initio Corporation
+INK Indtek Co., Ltd.
+INL InnoLux Display Corporation
+INM InnoMedia Inc
+INN Innovent Systems, Inc.
+INO Innolab Pte Ltd
+INP Interphase Corporation
+INS Ines GmbH
+INT Interphase Corporation
+inu Inovatec S.p.A.
+INV Inviso, Inc.
+INZ Best Buy
+IOA CRE Technology Corporation
+IOD I-O Data Device Inc
+IOM Iomega
+ION Inside Out Networks
+IOS i-O Display System
+IOT I/OTech Inc
+IPC IPC Corporation
+IPD Industrial Products Design, Inc.
+IPI Intelligent Platform Management Interface (IPMI) forum (Intel, HP, NEC, Dell)
+IPM IPM Industria Politecnica Meridionale SpA
+IPN Performance Technologies
+IPR Ithaca Peripherals
+IPS IPS, Inc. (Intellectual Property Solutions, Inc.)
+IPT International Power Technologies
+IPW IPWireless, Inc
+IQT IMAGEQUEST Co., Ltd
+IRD IRdata
+ISA Symbol Technologies
+ISC Id3 Semiconductors
+ISG Insignia Solutions Inc
+ISI Interface Solutions
+ISL Isolation Systems
+ISP IntreSource Systems Pte Ltd
+ISR INSIS Co., LTD.
+ISS ISS Inc
+IST Intersolve Technologies
+ISY International Integrated Systems,Inc.(IISI)
+ITA Itausa Export North America
+ITC Intercom Inc
+ITD Internet Technology Corporation
+ITE Integrated Tech Express Inc
+ITK ITK Telekommunikation AG
+ITL Inter-Tel
+ITM ITM inc.
+ITN The NTI Group
+ITP IT-PRO Consulting und Systemhaus GmbH
+ITR Infotronic America, Inc.
+ITS IDTECH
+ITT I&T Telecom.
+ITX integrated Technology Express Inc
+IUC ICSL
+IVI Intervoice Inc
+IVM Liyama North America
+IWR Icuiti Corporation
+IWX Intelliworxx, Inc.
+IXD Intertex Data AB
+JAC Astec Inc
+JAE Japan Aviation Electronics Industry, Limited
+JAT Jaton Corporation
+JAZ Carrera Computer Inc (used as second pnpid)
+JCE Jace Tech Inc
+JDL Japan Digital Laboratory Co.,Ltd.
+JEN N-Vision
+JET JET POWER TECHNOLOGY CO., LTD.
+JFX Jones Futurex Inc
+JGD University College
+JIC Jaeik Information & Communication Co., Ltd.
+JMT Micro Technical Company Ltd
+JPC JPC Technology Limited
+JPW Wallis Hamilton Industries
+JQE CNet Technical Inc
+JSD JS DigiTech, Inc
+JSI Jupiter Systems, Inc.
+JSK SANKEN ELECTRIC CO., LTD
+JTS JS Motorsports
+JUK Janich & Klass Computertechnik GmbH
+JUP Jupiter Systems
+JVC JVC
+JWD Video International Inc.
+JWL Jewell Instruments, LLC
+JWS JWSpencer & Co.
+JWY Jetway Information Co., Ltd
+KAR Karna
+KBI Kidboard Inc
+KBL Kobil Systems GmbH
+KCL Keycorp Ltd
+KDE KDE
+KDK Kodiak Tech
+KDM Korea Data Systems Co., Ltd.
+KDS KDS USA
+KEC Kyushu Electronics Systems Inc
+KEM Kontron Embedded Modules GmbH
+KES Kesa Corporation
+KEY Key Tech Inc
+KFC SCD Tech
+KFX Kofax Image Products
+KIS KiSS Technology A/S
+KMC Mitsumi Company Ltd
+KML Kensington Microware Ltd
+KNC Konica corporation
+KNX Nutech Marketing PTL
+KOB Kobil Systems GmbH
+KOD Eastman Kodak Company
+KOE KOLTER ELECTRONIC
+KOL Kollmorgen Motion Technologies Group
+KOW KOWA Company,LTD.
+KPC King Phoenix Company
+KRL Krell Industries Inc.
+KRY Kroy LLC
+KSC Kinetic Systems Corporation
+KSL Karn Solutions Ltd.
+KSX King Tester Corporation
+KTC Kingston Tech Corporation
+KTE K-Tech
+KTG Kayser-Threde GmbH
+KTI Konica Technical Inc
+KTK Key Tronic Corporation
+KTN Katron Tech Inc
+KUR Kurta Corporation
+KVA Kvaser AB
+KWD Kenwood Corporation
+KYC Kyocera Corporation
+KYE KYE Syst Corporation
+KYK Samsung Electronics America Inc
+KZI K-Zone International co. Ltd.
+KZN K-Zone International
+LAB ACT Labs Ltd
+LAC LaCie
+LAF Microline
+LAG Laguna Systems
+LAN Sodeman Lancom Inc
+LAS LASAT Comm. A/S
+LAV Lava Computer MFG Inc
+LBO Lubosoft
+LCC LCI
+LCD Toshiba Matsushita Display Technology Co., Ltd
+LCE La Commande Electronique
+LCI Lite-On Communication Inc
+LCM Latitude Comm.
+LCN LEXICON
+LCS Longshine Electronics Company
+LCT Labcal Technologies
+LDT LogiDataTech Electronic GmbH
+LEC Lectron Company Ltd
+LED Long Engineering Design Inc
+LEG Legerity, Inc
+LEN Lenovo Group Limited
+LEO First International Computer Inc
+LEX Lexical Ltd
+LGC Logic Ltd
+LGI Logitech Inc
+LGS LG Semicom Company Ltd
+LGX Lasergraphics, Inc.
+LHA Lars Haagh ApS
+LHE Lung Hwa Electronics Company Ltd
+LIT Lithics Silicon Technology
+LJX Datalogic Corporation
+LKM Likom Technology Sdn. Bhd.
+LMG Lucent Technologies
+LMI Lexmark Int'l Inc
+LMP Leda Media Products
+LMT Laser Master
+LND Land Computer Company Ltd
+LNK Link Tech Inc
+LNR Linear Systems Ltd.
+LNT LANETCO International
+LNV Lenovo
+LOC Locamation B.V.
+LOE Loewe Opta GmbH
+LOG Logicode Technology Inc
+LPE El-PUSK Co., Ltd.
+LPI Design Technology
+LPL LG Philips
+LSC LifeSize Communications
+LSI Loughborough Sound Images
+LSJ LSI Japan Company Ltd
+LSL Logical Solutions
+LSY LSI Systems Inc
+LTC Labtec Inc
+LTI Jongshine Tech Inc
+LTK Lucidity Technology Company Ltd
+LTN Litronic Inc
+LTS LTS Scale LLC
+LTV Leitch Technology International Inc.
+LTW Lightware, Inc
+LUC Lucent Technologies
+LUM Lumagen, Inc.
+LUX Luxxell Research Inc
+LWC Labway Corporation
+LWR Lightware Visual Engineering
+LWW Lanier Worldwide
+LXN Luxeon
+LXS ELEA CardWare
+LZX Lightwell Company Ltd
+MAC MAC System Company Ltd
+MAD Xedia Corporation
+MAE Maestro Pty Ltd
+MAG MAG InnoVision
+MAI Mutoh America Inc
+MAL Meridian Audio Ltd
+MAN LGIC
+MAS Mass Inc.
+MAT Matsushita Electric Ind. Company Ltd
+MAX Rogen Tech Distribution Inc
+MAY Maynard Electronics
+MAZ MAZeT GmbH
+MBC MBC
+MBD Microbus PLC
+MBM Marshall Electronics
+MBV Moreton Bay
+MCA American Nuclear Systems Inc
+MCC Micro Industries
+MCD McDATA Corporation
+MCE Metz-Werke GmbH & Co KG
+MCG Motorola Computer Group
+MCI Micronics Computers
+MCL Motorola Communications Israel
+MCM Metricom Inc
+MCN Micron Electronics Inc
+MCO Motion Computing Inc.
+MCP Magni Systems Inc
+MCQ Mat's Computers
+MCR Marina Communicaitons
+MCS Micro Computer Systems
+MCT Microtec
+MDA Media4 Inc
+MDC Midori Electronics
+MDD MODIS
+MDG Madge Networks
+MDI Micro Design Inc
+MDK Mediatek Corporation
+MDO Panasonic
+MDR Medar Inc
+MDS Micro Display Systems Inc
+MDT Magus Data Tech
+MDV MET Development Inc
+MDX MicroDatec GmbH
+MDY Microdyne Inc
+MEC Mega System Technologies Inc
+MED Messeltronik Dresden GmbH
+MEE Mitsubishi Electric Engineering Co., Ltd.
+MEG Abeam Tech Ltd
+MEI Panasonic Industry Company
+MEL Mitsubishi Electric Corporation
+MEN MEN Mikroelectronik Nueruberg GmbH
+MEQ Matelect Ltd.
+MET Metheus Corporation
+MFG MicroField Graphics Inc
+MFI Micro Firmware
+MFR MediaFire Corp.
+MGA Mega System Technologies, Inc.
+MGE Schneider Electric S.A.
+MGL M-G Technology Ltd
+MGT Megatech R & D Company
+MIC Micom Communications Inc
+MID miro Displays
+MII Mitec Inc
+MIL Marconi Instruments Ltd
+MIP micronpc.com
+MIR Miro Computer Prod.
+MIS Modular Industrial Solutions Inc
+MIT MCM Industrial Technology GmbH
+MJI MARANTZ JAPAN, INC.
+MJS MJS Designs
+MKC Media Tek Inc.
+MKT MICROTEK Inc.
+MKV Trtheim Technology
+MLD Deep Video Imaging Ltd
+MLG Micrologica AG
+MLI McIntosh Laboratory Inc.
+MLM Millennium Engineering Inc
+MLN Mark Levinson
+MLS Milestone EPE
+MLX Mylex Corporation
+MMA Micromedia AG
+MMD Micromed Biotecnologia Ltd
+MMF Minnesota Mining and Manufacturing
+MMI Multimax
+MMM Electronic Measurements
+MMN MiniMan Inc
+MMS MMS Electronics
+MNC Mini Micro Methods Ltd
+MNL Monorail Inc
+MNP Microcom
+MOD Modular Technology
+MOM Momentum Data Systems
+MOS Moses Corporation
+MOT Motorola UDS
+MPC M-Pact Inc
+MPI Mediatrix Peripherals Inc
+MPJ Microlab
+MPL Maple Research Inst. Company Ltd
+MPN Mainpine Limited
+MPS mps Software GmbH
+MPX Micropix Technologies, Ltd.
+MQP MultiQ Products AB
+MRA Miranda Technologies Inc
+MRC Marconi Simulation & Ty-Coch Way Training
+MRD MicroDisplay Corporation
+MRK Maruko & Company Ltd
+MRL Miratel
+MRO Medikro Oy
+MRT Merging Technologies
+MSA Micro Systemation AB
+MSC Mouse Systems Corporation
+MSD Datenerfassungs- und Informationssysteme
+MSF M-Systems Flash Disk Pioneers
+MSG MSI GmbH
+MSH Microsoft
+MSI Microstep
+MSK Megasoft Inc
+MSL MicroSlate Inc.
+MSM Advanced Digital Systems
+MSP Mistral Solutions [P] Ltd.
+MST MS Telematica
+MSU motorola
+MSV Mosgi Corporation
+MSX Micomsoft Co., Ltd.
+MSY MicroTouch Systems Inc
+MTB Media Technologies Ltd.
+MTC Mars-Tech Corporation
+MTD MindTech Display Co. Ltd
+MTE MediaTec GmbH
+MTH Micro-Tech Hearing Instruments
+MTI MaxCom Technical Inc
+MTI Motorola Inc.
+MTK Microtek International Inc.
+MTL Mitel Corporation
+MTN Mtron Storage Technology Co., Ltd.
+MTR Mitron computer Inc
+MTS Multi-Tech Systems
+MTU Mark of the Unicorn Inc
+MTX Matrox
+MUD Multi-Dimension Institute
+MUK mainpine limited
+MVD Microvitec PLC
+MVI Media Vision Inc
+MVM SOBO VISION
+MVS Microvision
+MVX COM 1
+MWI Multiwave Innovation Pte Ltd
+MWR mware
+MWY Microway Inc
+MXD MaxData Computer GmbH & Co.KG
+MXI Macronix Inc
+MXL Hitachi Maxell, Ltd.
+MXP Maxpeed Corporation
+MXT Maxtech Corporation
+MXV MaxVision Corporation
+MYA Monydata
+MYR Myriad Solutions Ltd
+MYX Micronyx Inc
+NAC Ncast Corporation
+NAD NAD Electronics
+NAL Network Alchemy
+NAV Navigation Corporation
+NAX Naxos Tecnologia
+NBL N*Able Technologies Inc
+NBS National Key Lab. on ISN
+NBT NingBo Bestwinning Technology CO., Ltd
+NCA Nixdorf Company
+NCC NCR Corporation
+NCE Norcent Technology, Inc.
+NCI NewCom Inc
+NCL NetComm Ltd
+NCR NCR Electronics
+NCS Northgate Computer Systems
+NCT NEC CustomTechnica, Ltd.
+NDC National DataComm Corporaiton
+NDI National Display Systems
+NDK Naitoh Densei CO., LTD.
+NDL Network Designers
+NDS Nokia Data
+NEC NEC Corporation
+NEO NEO TELECOM CO.,LTD.
+NET Mettler Toledo
+NEU NEUROTEC - EMPRESA DE PESQUISA E DESENVOLVIMENTO EM BIOMEDICINA
+NEX Nexgen Mediatech Inc.,
+NFC BTC Korea Co., Ltd
+NFS Number Five Software
+NGC Network General
+NGS A D S Exports
+NHT Vinci Labs
+NIC National Instruments Corporation
+NIS Nissei Electric Company
+NIT Network Info Technology
+NIX Seanix Technology Inc
+NLC Next Level Communications
+NMP Nokia Mobile Phones
+NMS Natural Micro System
+NMV NEC-Mitsubishi Electric Visual Systems Corporation
+NMX Neomagic
+NNC NNC
+NOK Nokia Display Products
+NOR Norand Corporation
+NOT Not Limited Inc
+NPI Network Peripherals Inc
+NRL U.S. Naval Research Lab
+NRT Beijing Northern Radiantelecom Co.
+NRV Taugagreining hf
+NSC National Semiconductor Corporation
+NSI NISSEI ELECTRIC CO.,LTD
+NSP Nspire System Inc.
+NSS Newport Systems Solutions
+NST Network Security Technology Co
+NTC NeoTech S.R.L
+NTI New Tech Int'l Company
+NTL National Transcomm. Ltd
+NTN Nuvoton Technology Corporation
+NTR N-trig Innovative Technologies, Inc.
+NTS Nits Technology Inc.
+NTT NTT Advanced Technology Corporation
+NTW Networth Inc
+NTX Netaccess Inc
+NUG NU Technology, Inc.
+NUI NU Inc.
+NVC NetVision Corporation
+NVD Nvidia
+NVI NuVision US, Inc.
+NVL Novell Inc
+NVT Navatek Engineering Corporation
+NWC NW Computer Engineering
+NWP NovaWeb Technologies Inc
+NWS Newisys, Inc.
+NXC NextCom K.K.
+NXG Nexgen
+NXP NXP Semiconductors bv.
+NXQ Nexiq Technologies, Inc.
+NXS Technology Nexus Secure Open Systems AB
+NYC nakayo telecommunications,inc.
+OAK Oak Tech Inc
+OAS Oasys Technology Company
+OCD Macraigor Systems Inc
+OCN Olfan
+OCS Open Connect Solutions
+ODM ODME Inc.
+ODR Odrac
+OEC ORION ELECTRIC CO.,LTD
+OIC Option Industrial Computers
+OIM Option International
+OIN Option International
+OKI OKI Electric Industrial Company Ltd
+OLC Olicom A/S
+OLD Olidata S.p.A.
+OLI Olivetti
+OLT Olitec S.A.
+OLV Olitec S.A.
+OLY OLYMPUS CORPORATION
+OMC OBJIX Multimedia Corporation
+OMN Omnitel
+OMR Omron Corporation
+ONE Oneac Corporation
+ONK ONKYO Corporation
+ONS On Systems Inc
+ONW OPEN Networks Ltd
+ONX SOMELEC Z.I. Du Vert Galanta
+OOS OSRAM
+OPC Opcode Inc
+OPI D.N.S. Corporation
+OPT OPTi Inc
+OPV Optivision Inc
+OQI Oksori Company Ltd
+ORG ORGA Kartensysteme GmbH
+ORI OSR Open Systems Resources, Inc.
+ORN ORION ELECTRIC CO., LTD.
+OSA OSAKA Micro Computer, Inc.
+OSP OPTI-UPS Corporation
+OSR Oksori Company Ltd
+OTI Orchid Technology
+OTT OPTO22, Inc.
+OUK OUK Company Ltd
+OWL Mediacom Technologies Pte Ltd
+OXU Oxus Research S.A.
+OYO Shadow Systems
+OZO Tribe Computer Works Inc
+PAC Pacific Avionics Corporation
+PAD Promotion and Display Technology Ltd.
+PAK Many CNC System Co., Ltd.
+PAM Peter Antesberger Messtechnik
+PAN The Panda Project
+PAR Parallan Comp Inc
+PBI Pitney Bowes
+PBL Packard Bell Electronics
+PBN Packard Bell NEC
+PBV Pitney Bowes
+PCA Philips BU Add On Card
+PCB OCTAL S.A.
+PCC PowerCom Technology Company Ltd
+PCG First Industrial Computer Inc
+PCI Pioneer Computer Inc
+PCK PCBANK21
+PCL pentel.co.,ltd
+PCM PCM Systems Corporation
+PCO Performance Concepts Inc.,
+PCP Procomp USA Inc
+PCT PC-Tel Inc
+PCW Pacific CommWare Inc
+PCX PC Xperten
+PDM Psion Dacom Plc.
+PDN AT&T Paradyne
+PDR Pure Data Inc
+PDS PD Systems International Ltd
+PDT PDTS - Prozessdatentechnik und Systeme
+PDV Prodrive B.V.
+PEC POTRANS Electrical Corp.
+PEI PEI Electronics Inc
+PEL Primax Electric Ltd
+PEN Interactive Computer Products Inc
+PEP Peppercon AG
+PER Perceptive Signal Technologies
+PET Practical Electronic Tools
+PFT Telia ProSoft AB
+PGM Paradigm Advanced Research Centre
+PGP propagamma kommunikation
+PGS Princeton Graphic Systems
+PHC Pijnenburg Beheer N.V.
+PHI DO NOT USE - PHI
+PHL Philips Consumer Electronics Company
+PHO Photonics Systems Inc.
+PHS Philips Communication Systems
+PHY Phylon Communications
+PIE Pacific Image Electronics Company Ltd
+PIM Prism, LLC
+PIO Pioneer Electronic Corporation
+PIX Pixie Tech Inc
+PJA Projecta
+PJD Projectiondesign AS
+PJT Pan Jit International Inc.
+PKA Acco UK ltd.
+PLC Pro-Log Corporation
+PLM PROLINK Microsystems Corp.
+PLV PLUS Vision Corp.
+PLX Parallax Graphics
+PLY Polycom Inc.
+PMC PMC Consumer Electronics Ltd
+PMD TDK USA Corporation
+PMM Point Multimedia System
+PMT Promate Electronic Co., Ltd.
+PMX Photomatrix
+PNG Microsoft
+PNG P.I. Engineering Inc
+PNL Panelview, Inc.
+PNP Microsoft
+PNR Planar Systems, Inc.
+PNS PanaScope
+PNX Phoenix Technologies, Ltd.
+POL PolyComp (PTY) Ltd.
+PON Perpetual Technologies, LLC
+POR Portalis LC
+PPC Phoenixtec Power Company Ltd
+PPD MEPhI
+PPI Practical Peripherals
+PPM Clinton Electronics Corp.
+PPP Purup Prepress AS
+PPR PicPro
+PRA PRO/AUTOMATION
+PRC PerComm
+PRD Praim S.R.L.
+PRF Digital Electronics Corporation
+PRG The Phoenix Research Group Inc
+PRI Priva Hortimation BV
+PRM Prometheus
+PRO Proteon
+PRS Leutron Vision
+PRX Proxima Corporation
+PSA Advanced Signal Processing Technologies
+PSC Philips Semiconductors
+PSD Peus-Systems GmbH
+PSE Practical Solutions Pte., Ltd.
+PSI PSI-Perceptive Solutions Inc
+PSL Perle Systems Limited
+PSM Prosum
+PST Global Data SA
+PTC PS Technology Corporation
+PTG Cipher Systems Inc
+PTH Pathlight Technology Inc
+PTI Promise Technology Inc
+PTL Pantel Inc
+PTS Plain Tree Systems Inc
+PVG Proview Global Co., Ltd
+PVN Pixel Vision
+PVP Klos Technologies, Inc.
+PXC Phoenix Contact
+PXE PIXELA CORPORATION
+PXL The Moving Pixel Company
+PXM Proxim Inc
+QCC QuakeCom Company Ltd
+QCH Metronics Inc
+QCI Quanta Computer Inc
+QCK Quick Corporation
+QCL Quadrant Components Inc
+QCP Qualcomm Inc
+QDI Quantum Data Incorporated
+QDM Quadram
+QDS Quanta Display Inc.
+QFF Padix Co., Inc.
+QFI Quickflex, Inc
+QLC Q-Logic
+QQQ Chuomusen Co., Ltd.
+QSI Quantum Solutions, Inc.
+QTD Quantum 3D Inc
+QTH Questech Ltd
+QTI Quicknet Technologies Inc
+QTM Quantum
+QTR Qtronix Corporation
+QUA Quatographic AG
+QUE Questra Consulting
+RAC Racore Computer Products Inc
+RAD Radisys Corporation
+RAI Rockwell Automation/Intecolor
+RAN Rancho Tech Inc
+RAR Raritan, Inc.
+RAS RAScom Inc
+RAT Rent-A-Tech
+RAY Raylar Design, Inc.
+RCE Parc d'Activite des Bellevues
+RCH Reach Technology Inc
+RCI RC International
+RCN Radio Consult SRL
+RDI Rainbow Displays, Inc.
+RDM Tremon Enterprises Company Ltd
+RDS Radius Inc
+REA Real D
+REC ReCom
+RED Research Electronics Development Inc
+REF Reflectivity, Inc.
+REL Reliance Electric Ind Corporation
+REM SCI Systems Inc.
+REN Renesas Technology Corp.
+RES ResMed Pty Ltd
+RGL Robertson Geologging Ltd
+RHM Rohm Company Ltd
+RII Racal Interlan Inc
+RIO Rios Systems Company Ltd
+RIT Ritech Inc
+RIV Rivulet Communications
+RJA Roland Corporation
+RJS Advanced Engineering
+RKC Reakin Technolohy Corporation
+RLD MEPCO
+RLN RadioLAN Inc
+RMC Raritan Computer, Inc
+RMP Research Machines
+RNB Rainbow Technologies
+ROB Robust Electronics GmbH
+ROH Rohm Co., Ltd.
+ROK Rockwell International
+ROP Roper International Ltd
+RPT R.P.T.Intergroups
+RRI Radicom Research Inc
+RSC PhotoTelesis
+RSH ADC-Centre
+RSI Rampage Systems Inc
+RSN Radiospire Networks, Inc.
+RSQ R Squared
+RSS Rockwell Semiconductor Systems
+RSX Rapid Tech Corporation
+RTC Relia Technologies
+RTI Rancho Tech Inc
+RTL Realtek Semiconductor Company Ltd
+RTS Raintree Systems
+RUN RUNCO International
+RUP Ups Manufactoring s.r.l.
+RVC RSI Systems Inc
+RVI Realvision Inc
+RVL Reveal Computer Prod
+RWC Red Wing Corporation
+RXT Tectona SoftSolutions (P) Ltd.,
+SAA Sanritz Automation Co.,Ltd.
+SAE Saab Aerotech
+SAG Sedlbauer
+SAI Sage Inc
+SAK Saitek Ltd
+SAM Samsung Electric Company
+SAN Sanyo Electric Co.,Ltd.
+SAS Stores Automated Systems Inc
+SAT Shuttle Tech
+SBC Shanghai Bell Telephone Equip Mfg Co
+SBD Softbed - Consulting & Development Ltd
+SBI SMART Technologies Inc.
+SBS SBS-or Industrial Computers GmbH
+SBT Senseboard Technologies AB
+SCC SORD Computer Corporation
+SCD Sanyo Electric Company Ltd
+SCE Sun Corporation
+SCH Schlumberger Cards
+SCI System Craft
+SCL Sigmacom Co., Ltd.
+SCM SCM Microsystems Inc
+SCN Scanport, Inc.
+SCO SORCUS Computer GmbH
+SCP Scriptel Corporation
+SCR Systran Corporation
+SCS Nanomach Anstalt
+SCT Smart Card Technology
+SDA SAT (Societe Anonyme)
+SDD Intrada-SDD Ltd
+SDE Sherwood Digital Electronics Corporation
+SDF SODIFF E&T CO., Ltd.
+SDH Communications Specialies, Inc.
+SDI Samtron Displays Inc
+SDK SAIT-Devlonics
+SDR SDR Systems
+SDS SunRiver Data System
+SDT Siemens AG
+SDX SDX Business Systems Ltd
+SEA Seanix Technology Inc.
+SEB system elektronik GmbH
+SEC Seiko Epson Corporation
+SEE SeeColor Corporation
+SEI Seitz & Associates Inc
+SEL Way2Call Communications
+SEM Samsung Electronics Company Ltd
+SEN Sencore
+SEO SEOS Ltd
+SEP SEP Eletronica Ltda.
+SER Sony Ericsson Mobile Communications Inc.
+SET SendTek Corporation
+SFM TORNADO Company
+SFT Mikroforum Ring 3
+SGC Spectragraphics Corporation
+SGD Sigma Designs, Inc.
+SGE Kansai Electric Company Ltd
+SGI Scan Group Ltd
+SGL Super Gate Technology Company Ltd
+SGM SAGEM
+SGO Logos Design A/S
+SGT Stargate Technology
+SGX Silicon Graphics Inc
+SGZ Systec Computer GmbH
+SHC ShibaSoku Co., Ltd.
+SHG Soft & Hardware development Goldammer GmbH
+SHI Jiangsu Shinco Electronic Group Co., Ltd
+SHP Sharp Corporation
+SHR Digital Discovery
+SHT Shin Ho Tech
+SIA SIEMENS AG
+SIB Sanyo Electric Company Ltd
+SIC Sysmate Corporation
+SID Seiko Instruments Information Devices Inc
+SIE Siemens
+SIG Sigma Designs Inc
+SII Silicon Image, Inc.
+SIL Silicon Laboratories, Inc
+SIM S3 Inc
+SIN Singular Technology Co., Ltd.
+SIR Sirius Technologies Pty Ltd
+SIS Silicon Integrated Systems Corporation
+SIT Sitintel
+SIU Seiko Instruments USA Inc
+SIX Zuniq Data Corporation
+SJE Sejin Electron Inc
+SKD Schneider & Koch
+SKT Samsung Electro-Mechanics Company Ltd
+SKY SKYDATA S.P.A.
+SLA Systeme Lauer GmbH&Co KG
+SLB Shlumberger Ltd
+SLC Syslogic Datentechnik AG
+SLH Silicon Library Inc.
+SLI Symbios Logic Inc
+SLK Silitek Corporation
+SLM Solomon Technology Corporation
+SLR Schlumberger Technology Corporate
+SLT Salt Internatioinal Corp.
+SLX Specialix
+SMA SMART Modular Technologies
+SMB Schlumberger
+SMC Standard Microsystems Corporation
+SME Sysmate Company
+SMI SpaceLabs Medical Inc
+SMK SMK CORPORATION
+SML Sumitomo Metal Industries, Ltd.
+SMM Shark Multimedia Inc
+SMO STMicroelectronics
+SMP Simple Computing
+SMR B.& V. s.r.l.
+SMS Silicom Multimedia Systems Inc
+SMT Silcom Manufacturing Tech Inc
+SNC Sentronic International Corp.
+SNI Siemens Microdesign GmbH
+SNK S&K Electronics
+SNO SINOSUN TECHNOLOGY CO., LTD
+SNP Siemens Nixdorf Info Systems
+SNS Cirtech (UK) Ltd
+SNT SuperNet Inc
+SNW Snell & Wilcox
+SNX Sonix Comm. Ltd
+SNY Sony
+SOI Silicon Optix Corporation
+SOL Solitron Technologies Inc
+SON Sony
+SOR Sorcus Computer GmbH
+SOT Sotec Company Ltd
+SOY SOYO Group, Inc
+SPC SpinCore Technologies, Inc
+SPE SPEA Software AG
+SPH G&W Instruments GmbH
+SPI SPACE-I Co., Ltd.
+SPL Smart Silicon Systems Pty Ltd
+SPN Sapience Corporation
+SPR pmns GmbH
+SPS Synopsys Inc
+SPT Sceptre Tech Inc
+SPU SIM2 Multimedia S.P.A.
+SPX Simplex Time Recorder Co.
+SQT Sequent Computer Systems Inc
+SRC Integrated Tech Express Inc
+SRD Setred
+SRF Surf Communication Solutions Ltd
+SRG Intuitive Surgical, Inc.
+SRT SeeReal Technologies GmbH
+SSC Sierra Semiconductor Inc
+SSD FlightSafety International
+SSE Samsung Electronic Co.
+SSI S-S Technology Inc
+SSJ Sankyo Seiki Mfg.co., Ltd
+SSP Spectrum Signal Proecessing Inc
+SSS S3 Inc
+SST SystemSoft Corporation
+STA ST Electronics Systems Assembly Pte Ltd
+STB STB Systems Inc
+STC STAC Electronics
+STD STD Computer Inc
+STE SII Ido-Tsushin Inc
+STF Starflight Electronics
+STG StereoGraphics Corp.
+STH Semtech Corporation
+STI Smart Tech Inc
+STK SANTAK CORP.
+STL SigmaTel Inc
+STM SGS Thomson Microelectronics
+STN Samsung Electronics America
+STO Stollmann E+V GmbH
+STP StreamPlay Ltd
+STR Starlight Networks Inc
+STS SITECSYSTEM CO., LTD.
+STT Star Paging Telecom Tech (Shenzhen) Co. Ltd.
+STW Starwin Inc.
+STY SDS Technologies
+SUB Subspace Comm. Inc
+SUM Summagraphics Corporation
+SUN Sun Electronics Corporation
+SUP Supra Corporation
+SUR Surenam Computer Corporation
+SVA SGEG
+SVC Intellix Corp.
+SVD SVD Computer
+SVI Sun Microsystems
+SVS SVSI
+SVT SEVIT Co., Ltd.
+SWC Software Café
+SWI Sierra Wireless Inc.
+SWL Sharedware Ltd
+SWS Static
+SWT Software Technologies Group,Inc.
+SXB Syntax-Brillian
+SXD Silex technology, Inc.
+SXL SolutionInside
+SYC Sysmic
+SYK Stryker Communications
+SYL Sylvania Computer Products
+SYM Symicron Computer Communications Ltd.
+SYN Synaptics Inc
+SYP SYPRO Co Ltd
+SYS Sysgration Ltd
+SYT Seyeon Tech Company Ltd
+SYV SYVAX Inc
+SYX Prime Systems, Inc.
+TAA Tandberg
+TAB Todos Data System AB
+TAG Teles AG
+TAI Toshiba America Info Systems Inc
+TAM Tamura Seisakusyo Ltd
+TAS Taskit Rechnertechnik GmbH
+TAT Teleliaison Inc
+TAX Taxan (Europe) Ltd
+TBB Triple S Engineering Inc
+TBC Turbo Communication, Inc
+TBS Turtle Beach System
+TCC Tandon Corporation
+TCD Taicom Data Systems Co., Ltd.
+TCE Century Corporation
+TCH Interaction Systems, Inc
+TCI Tulip Computers Int'l B.V.
+TCJ TEAC America Inc
+TCL Technical Concepts Ltd
+TCM 3Com Corporation
+TCN Tecnetics (PTY) Ltd
+TCO Thomas-Conrad Corporation
+TCR Thomson Consumer Electronics
+TCS Tatung Company of America Inc
+TCT Telecom Technology Centre Co. Ltd.
+TCX FREEMARS Heavy Industries
+TDC Teradici
+TDD Tandberg Data Display AS
+TDK TDK USA Corporation
+TDM Tandem Computer Europe Inc
+TDP 3D Perception
+TDS Tri-Data Systems Inc
+TDT TDT
+TDV TDVision Systems, Inc.
+TDY Tandy Electronics
+TEA TEAC System Corporation
+TEC Tecmar Inc
+TEK Tektronix Inc
+TEL Promotion and Display Technology Ltd.
+TER TerraTec Electronic GmbH
+TGI TriGem Computer Inc
+TGM TriGem Computer,Inc.
+TGS Torus Systems Ltd
+TGV Grass Valley Germany GmbH
+THN Thundercom Holdings Sdn. Bhd.
+TIC Trigem KinfoComm
+TIP TIPTEL AG
+TIV OOO Technoinvest
+TIX Tixi.Com GmbH
+TKC Taiko Electric Works.LTD
+TKN Teknor Microsystem Inc
+TKO TouchKo, Inc.
+TKS TimeKeeping Systems, Inc.
+TLA Ferrari Electronic GmbH
+TLD Telindus
+TLI TOSHIBA TELI CORPORATION
+TLK Telelink AG
+TLS Teleste Educational OY
+TLT Dai Telecom S.p.A.
+TLV S3 Inc
+TLX Telxon Corporation
+TMC Techmedia Computer Systems Corporation
+TME AT&T Microelectronics
+TMI Texas Microsystem
+TMM Time Management, Inc.
+TMR Taicom International Inc
+TMS Trident Microsystems Ltd
+TMT T-Metrics Inc.
+TMX Thermotrex Corporation
+TNC TNC Industrial Company Ltd
+TNM TECNIMAGEN SA
+TNY Tennyson Tech Pty Ltd
+TOE TOEI Electronics Co., Ltd.
+TOG The OPEN Group
+TOP Orion Communications Co., Ltd.
+TOS Toshiba Corporation
+TOU Touchstone Technology
+TPC Touch Panel Systems Corporation
+TPE Technology Power Enterprises Inc
+TPJ (none)
+TPK TOPRE CORPORATION
+TPR Topro Technology Inc
+TPS Teleprocessing Systeme GmbH
+TPV Top Victory Electronics ( Fujian ) Company Ltd
+TPZ Ypoaz Systems Inc
+TRA TriTech Microelectronics International
+TRC Trioc AB
+TRD Trident Microsystem Inc
+TRE Tremetrics
+TRI Tricord Systems
+TRL Royal Information
+TRM Tekram Technology Company Ltd
+TRN Datacommunicatie Tron B.V.
+TRS Torus Systems Ltd
+TRU Aashima Technology B.V.
+TRX Trex Enterprises
+TSB Toshiba America Info Systems Inc
+TSC Sanyo Electric Company Ltd
+TSD TechniSat Digital GmbH
+TSE Tottori Sanyo Electric
+TSF Racal-Airtech Software Forge Ltd
+TSG The Software Group Ltd
+TSI TeleVideo Systems
+TSL Tottori SANYO Electric Co., Ltd.
+TSP U.S. Navy
+TST Transtream Inc
+TSY TouchSystems
+TTA Topson Technology Co., Ltd.
+TTB National Semiconductor Japan Ltd
+TTC Telecommunications Techniques Corporation
+TTE TTE, Inc.
+TTI Trenton Terminals Inc
+TTK Totoku Electric Company Ltd
+TTL 2-Tel B.V.
+TTS TechnoTrend Systemtechnik GmbH
+TUT Tut Systems
+TVD Tecnovision
+TVI Truevision
+TVM Taiwan Video & Monitor Corporation
+TVO TV One Ltd
+TVR TV Interactive Corporation
+TVS TVS Electronics Limited
+TWA Tidewater Association
+TWE Kontron Electronik
+TWH Twinhead International Corporation
+TWI Easytel oy
+TWK TOWITOKO electronics GmbH
+TXL Trixel Ltd
+TXN Texas Insturments
+TXT Textron Defense System
+TYN Tyan Computer Corporation
+UAS Ultima Associates Pte Ltd
+UBI Ungermann-Bass Inc
+UBL Ubinetics Ltd.
+UDN Uniden Corporation
+UEC Ultima Electronics Corporation
+UEG Elitegroup Computer Systems Company Ltd
+UEI Universal Electronics Inc
+UET Universal Empowering Technologies
+UFG UNIGRAF-USA
+UFO UFO Systems Inc
+UHB XOCECO
+UIC Uniform Industrial Corporation
+UJR Ueda Japan Radio Co., Ltd.
+ULT Ultra Network Tech
+UMC United Microelectr Corporation
+UMG Umezawa Giken Co.,Ltd
+UMM Universal Multimedia
+UNA Unisys DSD
+UNB Unisys Corporation
+UNC Unisys Corporation
+UND Unisys Corporation
+UNE Unisys Corporation
+UNF Unisys Corporation
+UNI Uniform Industry Corp.
+UNI Unisys Corporation
+UNM Unisys Corporation
+UNO Unisys Corporation
+UNP Unitop
+UNS Unisys Corporation
+UNT Unisys Corporation
+UNY Unicate
+UPP UPPI
+UPS Systems Enhancement
+URD Video Computer S.p.A.
+USA Utimaco Safeware AG
+USD U.S. Digital Corporation
+USI Universal Scientific Industrial Co., Ltd.
+USR U.S. Robotics Inc
+UTD Up to Date Tech
+UWC Uniwill Computer Corp.
+VAL Valence Computing Corporation
+VAR Varian Australia Pty Ltd
+VBT Valley Board Ltda
+VCC Virtual Computer Corporation
+VCI VistaCom Inc
+VCJ Victor Company of Japan, Limited
+VCM Vector Magnetics, LLC
+VCX VCONEX
+VDA Victor Data Systems
+VDM Vadem
+VDO Video & Display Oriented Corporation
+VDS Vidisys GmbH & Company
+VDT Viditec, Inc.
+VEC Vector Informatik GmbH
+VEK Vektrex
+VES Vestel Elektronik Sanayi ve Ticaret A. S.
+VFI VeriFone Inc
+VHI Macrocad Development Inc.
+VIA VIA Tech Inc
+VIB Tatung UK Ltd
+VIC Victron B.V.
+VID Ingram Macrotron Germany
+VIK Viking Connectors
+VIN Vine Micros Ltd
+VIR Visual Interface, Inc
+VIS Visioneer
+VIT Visitech AS
+VLB ValleyBoard Ltda.
+VLT VideoLan Technologies
+VMI Vermont Microsystems
+VML Vine Micros Limited
+VNC Vinca Corporation
+VOB MaxData Computer AG
+VPR Best Buy
+VRC Virtual Resources Corporation
+VSC ViewSonic Corporation
+VSD 3M
+VSI VideoServer
+VSN Ingram Macrotron
+VSP Vision Systems GmbH
+VSR V-Star Electronics Inc.
+VTC VTel Corporation
+VTG Voice Technologies Group Inc
+VTI VLSI Tech Inc
+VTK Viewteck Co., Ltd.
+VTL Vivid Technology Pte Ltd
+VTS VTech Computers Ltd
+VTV VATIV Technologies
+VUT Vutrix (UK) Ltd
+VWB Vweb Corp.
+WAC Wacom Tech
+WAL Wave Access
+WAV Wavephore
+WBN MicroSoftWare
+WBS WB Systemtechnik GmbH
+WCI Wisecom Inc
+WCS Woodwind Communications Systems Inc
+WDC Western Digital
+WDE Westinghouse Digital Electronics
+WEB WebGear Inc
+WEC Winbond Electronics Corporation
+WEY WEY Design AG
+WHI Whistle Communications
+WII Innoware Inc
+WIL WIPRO Information Technology Ltd
+WIN Wintop Technology Inc
+WIP Wipro Infotech
+WKH Uni-Take Int'l Inc.
+WLD Wildfire Communications Inc
+WML Wolfson Microelectronics Ltd
+WMO Westermo Teleindustri AB
+WMT Winmate Communication Inc
+WNI WillNet Inc.
+WNV Winnov L.P.
+WNX Wincor Nixdorf International GmbH
+WPA Matsushita Communication Industrial Co., Ltd.
+WPI Wearnes Peripherals International (Pte) Ltd
+WRC WiNRADiO Communications
+WSC CIS Technology Inc
+WSP Wireless And Smart Products Inc.
+WTC ACC Microelectronics
+WTI WorkStation Tech
+WTK Wearnes Thakral Pte
+WTS Restek Electric Company Ltd
+WVM Wave Systems Corporation
+WWV World Wide Video, Inc.
+WYS Wyse Technology
+WYT Wooyoung Image & Information Co.,Ltd.
+XAC XAC Automation Corp
+XFG Jan Strapko - FOTO
+XFO EXFO Electro Optical Engineering
+XIN Xinex Networks Inc
+XIO Xiotech Corporation
+XIR Xirocm Inc
+XIT Xitel Pty ltd
+XLX Xilinx, Inc.
+XMM C3PO S.L.
+XNT XN Technologies, Inc.
+XQU SHANGHAI SVA-DAV ELECTRONICS CO., LTD
+XRC Xircom Inc
+XRO XORO ELECTRONICS (CHENGDU) LIMITED
+XSN Xscreen AS
+XST XS Technologies Inc
+XSY XSYS
+XTD Icuiti Corporation
+XTL Crystal Computer
+XTN X-10 (USA) Inc
+XYC Xycotec Computer GmbH
+YED Y-E Data Inc
+YHQ Yokogawa Electric Corporation
+YHW Exacom SA
+YMH Yamaha Corporation
+YOW American Biometric Company
+ZAN Zandar Technologies plc
+ZAX Zefiro Acoustics
+ZAZ Zazzle Technologies
+ZBR Zebra Technologies International, LLC
+ZCT ZeitControl cardsystems GmbH
+ZDS Zenith Data Systems
+ZGT Zenith Data Systems
+ZIC ZTEIC DESIGN CO., LTD.
+ZMT Zalman Tech Co., Ltd.
+ZMZ Z Microsystems
+ZNI Zetinet Inc
+ZNX Znyx Adv. Systems
+ZOW Zowie Intertainment, Inc
+ZRN Zoran Corporation
+ZSE Zenith Data Systems
+ZTC ZyDAS Technology Corporation
+ZTI Zoom Telephonics Inc
+ZTM ZT Group Int'l Inc.
+ZYD Zydacron Inc
+ZYP Zypcom Inc
+ZYT Zytex Computers
+ZYX Zyxel
+ZZZ Boca Research Inc
diff --git a/libmate-desktop/private.h b/libmate-desktop/private.h
new file mode 100644
index 0000000..c3e4d45
--- /dev/null
+++ b/libmate-desktop/private.h
@@ -0,0 +1,38 @@
+/* private.h: various private functions
+
+ Copyright 2009, Novell, Inc.
+
+ This file is part of the Mate Library.
+
+ The Mate 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 Mate 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 Mate 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: Vincent Untz <[email protected]>
+*/
+
+#ifndef __MATE_DESKTOP_PRIVATE_H__
+#define __MATE_DESKTOP_PRIVATE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void _mate_desktop_init_i18n (void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libmate-desktop/test-ditem.c b/libmate-desktop/test-ditem.c
new file mode 100644
index 0000000..98ec57e
--- /dev/null
+++ b/libmate-desktop/test-ditem.c
@@ -0,0 +1,160 @@
+/* -*- Mode: C; c-set-style: gnu indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libmate/mate-desktop-item.h>
+
+#include <locale.h>
+#include <stdlib.h>
+
+static void
+test_ditem (const char *file)
+{
+ MateDesktopItem *ditem;
+ MateDesktopItemType type;
+ const gchar *text;
+ char *uri;
+ char path[256];
+
+ ditem = mate_desktop_item_new_from_file (file,
+ MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS,
+ NULL);
+ if (ditem == NULL) {
+ g_print ("File %s is not an existing ditem\n", file);
+ return;
+ }
+
+ text = mate_desktop_item_get_location (ditem);
+ g_print ("LOCATION: |%s|\n", text);
+
+ type = mate_desktop_item_get_entry_type (ditem);
+ g_print ("TYPE: |%u|\n", type);
+
+ text = mate_desktop_item_get_string
+ (ditem, MATE_DESKTOP_ITEM_TYPE);
+ g_print ("TYPE(string): |%s|\n", text);
+
+ text = mate_desktop_item_get_string
+ (ditem, MATE_DESKTOP_ITEM_EXEC);
+ g_print ("EXEC: |%s|\n", text);
+
+ text = mate_desktop_item_get_string
+ (ditem, MATE_DESKTOP_ITEM_ICON);
+ g_print ("ICON: |%s|\n", text);
+
+ text = mate_desktop_item_get_localestring
+ (ditem, MATE_DESKTOP_ITEM_NAME);
+ g_print ("NAME: |%s|\n", text);
+
+ text = mate_desktop_item_get_localestring_lang
+ (ditem, MATE_DESKTOP_ITEM_NAME,
+ "cs_CZ");
+ g_print ("NAME(lang=cs_CZ): |%s|\n", text);
+
+ text = mate_desktop_item_get_localestring_lang
+ (ditem, MATE_DESKTOP_ITEM_NAME,
+ "de");
+ g_print ("NAME(lang=de): |%s|\n", text);
+
+
+ text = mate_desktop_item_get_localestring_lang
+ (ditem, MATE_DESKTOP_ITEM_NAME,
+ NULL);
+ g_print ("NAME(lang=null): |%s|\n", text);
+
+ text = mate_desktop_item_get_localestring
+ (ditem, MATE_DESKTOP_ITEM_COMMENT);
+ g_print ("COMMENT: |%s|\n", text);
+
+ g_print ("Setting Name[de]=Neu gestzt! (I have no idea what that means)\n");
+ mate_desktop_item_set_localestring
+ (ditem,
+ MATE_DESKTOP_ITEM_NAME,
+ "Neu gesetzt!");
+
+ getcwd (path, 255 - strlen ("/foo.desktop"));
+ strcat (path, "/foo.desktop");
+
+ g_print ("Saving to foo.desktop\n");
+ uri = g_filename_to_uri (path, NULL, NULL);
+ g_print ("URI: %s\n", uri);
+ mate_desktop_item_save (ditem, uri, FALSE, NULL);
+ g_free (uri);
+}
+
+static void
+launch_item (const char *file)
+{
+ MateDesktopItem *ditem;
+ GList *file_list = NULL;
+ int ret;
+
+ ditem = mate_desktop_item_new_from_file (file,
+ MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS,
+ NULL);
+ if (ditem == NULL) {
+ g_print ("File %s is not an existing ditem\n", file);
+ return;
+
+ }
+
+#if 0
+ file_list = g_list_append (NULL, "file:///bin/sh");
+ file_list = g_list_append (file_list, "foo");
+ file_list = g_list_append (file_list, "bar");
+ file_list = g_list_append (file_list, "http://slashdot.org");
+#endif
+
+ ret = mate_desktop_item_launch (ditem, file_list, 0, NULL);
+ g_print ("launch returned: %d\n", ret);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ char *file;
+ gboolean launch = FALSE;
+
+ if (argc < 2 || argc > 3) {
+ fprintf (stderr, "Usage: test-ditem path [LAUNCH]\n");
+ exit (1);
+ }
+
+ if (argc == 3 &&
+ strcmp (argv[2], "LAUNCH") == 0)
+ launch = TRUE;
+
+ file = g_strdup (argv[1]);
+
+ gtk_init (&argc, &argv);
+
+ if (launch)
+ launch_item (file);
+ else
+ test_ditem (file);
+
+ /*
+ test_ditem_edit (file);
+ */
+
+ return 0;
+}