diff options
Diffstat (limited to 'libmate-desktop')
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; +} |