diff libpurple/desktopitem.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 32c366eeeb99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/desktopitem.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,1272 @@
+/**
+ * @file gaim-desktop-item.c Functions for managing .desktop files
+ * @ingroup core
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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
+ *
+ */
+
+/*
+ * The following code has been adapted from gnome-desktop-item.[ch],
+ * as found on gnome-desktop-2.8.1.
+ *
+ *   Copyright (C) 2004 by Alceste Scalas <alceste.scalas@gmx.net>.
+ *
+ * Original copyright notice:
+ *
+ * Copyright (C) 1999, 2000 Red Hat Inc.
+ * Copyright (C) 2001 Sid Vicious
+ * All rights reserved.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ * 
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "desktopitem.h"
+#include "internal.h"
+
+struct _GaimDesktopItem {
+	int refcount;
+
+	/* all languages used */
+	GList *languages;
+
+	GaimDesktopItemType 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;
+};
+
+typedef struct {
+	char *name;
+	GList *keys;
+} Section;
+
+typedef enum {
+	ENCODING_UNKNOWN,
+	ENCODING_UTF8,
+	ENCODING_LEGACY_MIXED
+} Encoding;
+
+/**************************************************************************
+ * Private utility functions
+ **************************************************************************/
+static GaimDesktopItemType
+type_from_string (const char *type)
+{
+	if (!type)
+		return GAIM_DESKTOP_ITEM_TYPE_NULL;
+
+	switch (type [0]) {
+	case 'A':
+		if (!strcmp (type, "Application"))
+			return GAIM_DESKTOP_ITEM_TYPE_APPLICATION;
+		break;
+	case 'L':
+		if (!strcmp (type, "Link"))
+			return GAIM_DESKTOP_ITEM_TYPE_LINK;
+		break;
+	case 'F':
+		if (!strcmp (type, "FSDevice"))
+			return GAIM_DESKTOP_ITEM_TYPE_FSDEVICE;
+		break;
+	case 'M':
+		if (!strcmp (type, "MimeType"))
+			return GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE;
+		break;
+	case 'D':
+		if (!strcmp (type, "Directory"))
+			return GAIM_DESKTOP_ITEM_TYPE_DIRECTORY;
+		break;
+	case 'S':
+		if (!strcmp (type, "Service"))
+			return GAIM_DESKTOP_ITEM_TYPE_SERVICE;
+
+		else if (!strcmp (type, "ServiceType"))
+			return GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE;
+		break;
+	default:
+		break;
+	}
+
+	return GAIM_DESKTOP_ITEM_TYPE_OTHER;
+}
+
+static Section *
+find_section (GaimDesktopItem *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 (GaimDesktopItem *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 void
+set (GaimDesktopItem *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
+_gaim_desktop_item_set_string (GaimDesktopItem *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, GAIM_DESKTOP_ITEM_TYPE) == 0)
+		item->type = type_from_string (value);
+}
+
+static GaimDesktopItem *
+_gaim_desktop_item_new (void)
+{
+	GaimDesktopItem *retval;
+
+	retval = g_new0 (GaimDesktopItem, 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 */
+	_gaim_desktop_item_set_string (retval,
+				       GAIM_DESKTOP_ITEM_NAME,
+				       _("No name"));
+	_gaim_desktop_item_set_string (retval,
+				       GAIM_DESKTOP_ITEM_ENCODING,
+				       "UTF-8");
+	_gaim_desktop_item_set_string (retval,
+				       GAIM_DESKTOP_ITEM_VERSION,
+				       "1.0");
+
+	return retval;
+}
+
+static gpointer
+_gaim_desktop_item_copy (gpointer boxed)
+{
+	return gaim_desktop_item_copy (boxed);
+}
+
+static void
+_gaim_desktop_item_free (gpointer boxed)
+{
+	gaim_desktop_item_unref (boxed);
+}
+
+/* Note, does not include the trailing \n */
+static char *
+my_fgets (char *buf, gsize bufsize, FILE *df)
+{
+	int c;
+	gsize pos;
+
+	g_return_val_if_fail (buf != NULL, NULL);
+	g_return_val_if_fail (df != NULL, NULL);
+
+	pos = 0;
+	buf[0] = '\0';
+
+	do {
+		c = getc (df);
+		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 Encoding
+get_encoding (FILE *df)
+{
+	gboolean old_kde = FALSE;
+	char     buf [BUFSIZ];
+	gboolean all_valid_utf8 = TRUE;
+
+	while (my_fgets (buf, sizeof (buf), df) != NULL) {
+		if (strncmp (GAIM_DESKTOP_ITEM_ENCODING,
+			     buf,
+			     strlen (GAIM_DESKTOP_ITEM_ENCODING)) == 0) {
+			char *p = &buf[strlen (GAIM_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;
+
+	/* 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 *
+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 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 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 *
+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);
+	}
+}
+
+/************************************************************
+ * 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,
+				     GAIM_DESKTOP_ITEM_NO_DISPLAY,
+				     GAIM_DESKTOP_ITEM_NO_DISPLAY);
+		g_hash_table_insert (bools,
+				     GAIM_DESKTOP_ITEM_HIDDEN,
+				     GAIM_DESKTOP_ITEM_HIDDEN);
+		g_hash_table_insert (bools,
+				     GAIM_DESKTOP_ITEM_TERMINAL,
+				     GAIM_DESKTOP_ITEM_TERMINAL);
+		g_hash_table_insert (bools,
+				     GAIM_DESKTOP_ITEM_READ_ONLY,
+				     GAIM_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,
+				     GAIM_DESKTOP_ITEM_FILE_PATTERN,
+				     GAIM_DESKTOP_ITEM_FILE_PATTERN);
+		g_hash_table_insert (strings,
+				     GAIM_DESKTOP_ITEM_ACTIONS,
+				     GAIM_DESKTOP_ITEM_ACTIONS);
+		g_hash_table_insert (strings,
+				     GAIM_DESKTOP_ITEM_MIME_TYPE,
+				     GAIM_DESKTOP_ITEM_MIME_TYPE);
+		g_hash_table_insert (strings,
+				     GAIM_DESKTOP_ITEM_PATTERNS,
+				     GAIM_DESKTOP_ITEM_PATTERNS);
+		g_hash_table_insert (strings,
+				     GAIM_DESKTOP_ITEM_SORT_ORDER,
+				     GAIM_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 void
+insert_key (GaimDesktopItem *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, GAIM_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, GAIM_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, '[');
+			if(p) *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, '[');
+			if(brace != NULL) {
+				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 const char *
+lookup (const GaimDesktopItem *item, const char *key)
+{
+	return g_hash_table_lookup (item->main_hash, key);
+}
+
+static void
+setup_type (GaimDesktopItem *item, const char *uri)
+{
+	const char *type = g_hash_table_lookup (item->main_hash,
+						GAIM_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 (GAIM_DESKTOP_ITEM_TYPE), 
+					      g_strdup ("Directory"));
+			item->keys = g_list_prepend
+				(item->keys, g_strdup (GAIM_DESKTOP_ITEM_TYPE));
+			item->type = GAIM_DESKTOP_ITEM_TYPE_DIRECTORY;
+		} else {
+			item->type = GAIM_DESKTOP_ITEM_TYPE_NULL;
+		}
+		g_free (base);
+	} else {
+		item->type = type_from_string (type);
+	}
+}
+
+static const char *
+lookup_locale (const GaimDesktopItem *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;
+	}
+}
+
+/* fallback to find something suitable for C locale */
+static char *
+try_english_key (GaimDesktopItem *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 (GaimDesktopItem *item, const char *uri)
+{
+	const char *type;
+
+	type = lookup (item, GAIM_DESKTOP_ITEM_TYPE);
+
+	/* understand old gnome style url exec thingies */
+	if (type != NULL && strcmp (type, "URL") == 0) {
+		const char *exec = lookup (item, GAIM_DESKTOP_ITEM_EXEC);
+		set (item, GAIM_DESKTOP_ITEM_TYPE, "Link");
+		if (exec != NULL) {
+			/* Note, this must be in this order */
+			set (item, GAIM_DESKTOP_ITEM_URL, exec);
+			set (item, GAIM_DESKTOP_ITEM_EXEC, NULL);
+		}
+	}
+
+	/* we make sure we have Name, Encoding and Version */
+	if (lookup (item, GAIM_DESKTOP_ITEM_NAME) == NULL) {
+		char *name = try_english_key (item, GAIM_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 gnome_desktop_item_new */
+		if (name == NULL)
+			name = g_strdup (_("No name"));
+		g_hash_table_replace (item->main_hash,
+				      g_strdup (GAIM_DESKTOP_ITEM_NAME), 
+				      name);
+		item->keys = g_list_prepend
+			(item->keys, g_strdup (GAIM_DESKTOP_ITEM_NAME));
+	}
+	if (lookup (item, GAIM_DESKTOP_ITEM_ENCODING) == NULL) {
+		/* We store everything in UTF-8 so write that down */
+		g_hash_table_replace (item->main_hash,
+				      g_strdup (GAIM_DESKTOP_ITEM_ENCODING), 
+				      g_strdup ("UTF-8"));
+		item->keys = g_list_prepend
+			(item->keys, g_strdup (GAIM_DESKTOP_ITEM_ENCODING));
+	}
+	if (lookup (item, GAIM_DESKTOP_ITEM_VERSION) == NULL) {
+		/* this is the version that we follow, so write it down */
+		g_hash_table_replace (item->main_hash,
+				      g_strdup (GAIM_DESKTOP_ITEM_VERSION), 
+				      g_strdup ("1.0"));
+		item->keys = g_list_prepend
+			(item->keys, g_strdup (GAIM_DESKTOP_ITEM_VERSION));
+	}
+}
+
+enum {
+	FirstBrace,
+	OnSecHeader,
+	IgnoreToEOL,
+	IgnoreToEOLFirst,
+	KeyDef,
+	KeyDefOnKey,
+	KeyValue
+};
+
+static GaimDesktopItem *
+ditem_load (FILE *df,
+	    gboolean no_translations,
+	    const char *uri)
+{
+	int state;
+	char CharBuffer [1024];
+	char *next = CharBuffer;
+	int c;
+	Encoding encoding;
+	GaimDesktopItem *item;
+	Section *cur_section = NULL;
+	char *key = NULL;
+	gboolean old_kde = FALSE;
+
+	encoding = get_encoding (df);
+	if (encoding == ENCODING_UNKNOWN) {
+		fclose(df);
+		/* spec says, don't read this file */
+		printf ("Unknown encoding of .desktop file");
+
+		return NULL;
+	}
+
+	/* Rewind since get_encoding goes through the file */
+	if (fseek(df, 0L, SEEK_SET)) {
+		fclose(df);
+		/* spec says, don't read this file */
+		printf ("fseek() error on .desktop file");
+		return NULL;
+	}
+
+	item = _gaim_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 GAIM_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1])
+
+	state = FirstBrace;
+	while ((c = getc (df)) != EOF) {
+		if (c == '\r')		/* Ignore Carriage Return */
+			continue;
+		
+		switch (state) {
+
+		case OnSecHeader:
+			if (c == ']' || GAIM_DESKTOP_ITEM_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' || GAIM_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */
+				next = CharBuffer;
+				state = KeyDef;
+				break;
+			}
+	    
+			if (c == '=' || GAIM_DESKTOP_ITEM_OVERFLOW){
+				*next = '\0';
+
+				g_free (key);
+				key = g_strdup (CharBuffer);
+				state = KeyValue;
+				next = CharBuffer;
+			} else {
+				*next++ = c;
+				state = KeyDefOnKey;
+			}
+			break;
+
+		case KeyValue:
+			if (GAIM_DESKTOP_ITEM_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 GAIM_DESKTOP_ITEM_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, uri);
+
+	/* make sure that we set up the type */
+	setup_type (item, uri);
+
+	fclose (df);
+
+	return item;
+}
+
+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));
+}
+
+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);
+}
+
+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;
+}
+
+/**************************************************************************
+ * Public functions
+ **************************************************************************/
+GaimDesktopItem *
+gaim_desktop_item_new_from_file (const char *filename)
+{
+	GaimDesktopItem *retval;
+	FILE *dfile;
+
+	g_return_val_if_fail (filename != NULL, NULL);
+
+	dfile = g_fopen(filename, "r");
+	if (!dfile) {
+		printf ("Can't open %s: %s", filename, strerror(errno));
+		return NULL;
+	}
+	
+	retval = ditem_load(dfile, FALSE, filename);
+
+	return retval;
+}
+
+GaimDesktopItemType
+gaim_desktop_item_get_entry_type (const GaimDesktopItem *item)
+{
+	g_return_val_if_fail (item != NULL, 0);
+	g_return_val_if_fail (item->refcount > 0, 0);
+
+	return item->type;
+}
+
+const char *
+gaim_desktop_item_get_string (const GaimDesktopItem *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);
+}
+
+GaimDesktopItem *
+gaim_desktop_item_copy (const GaimDesktopItem *item)
+{
+	GList *li;
+	GaimDesktopItem *retval;
+
+	g_return_val_if_fail (item != NULL, NULL);
+	g_return_val_if_fail (item->refcount > 0, NULL);
+
+	retval = _gaim_desktop_item_new ();
+
+	retval->type = item->type;
+	retval->modified = item->modified;
+	retval->location = g_strdup (item->location);
+	retval->mtime = item->mtime;
+
+	/* 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;
+}
+
+void
+gaim_desktop_item_unref (GaimDesktopItem *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);
+}
+
+GType
+gaim_desktop_item_get_type (void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		type = g_boxed_type_register_static ("GaimDesktopItem",
+						     _gaim_desktop_item_copy,
+						     _gaim_desktop_item_free);
+	}
+
+	return type;
+}