changeset 10229:9aa0b6d11bbf

[gaim-migrate @ 11364] This is a heavily warmenhoved patch from Alceste Scalas. Here's what it changes: If the user drags an image file into the buddy list or conversation, it presents three options (when applicable): Set as buddy icon - sets this image to be the buddy icon for this buddy Send image file - Initiates a file transfer to send this image. Insert in message - Inserts in the gtkimhtml for use as an IM image. If the user drags a .desktop web link, it will insert a hyperlink in the conversation. All other types of .desktop files fail with an error dialog. If anyone can think of better ways to handle any of them, let me know. This also happens to implement gaim_request_choice, which had previously been unimplemented. committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Mon, 22 Nov 2004 02:57:34 +0000
parents 37c411c8cde3
children e76416d46af8
files src/desktopitem.c src/desktopitem.h src/gtkblist.c src/gtkconv.c src/gtkrequest.c src/gtkutils.c src/gtkutils.h src/request.c src/request.h
diffstat 9 files changed, 1835 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/desktopitem.c	Mon Nov 22 02:57:34 2004 +0000
@@ -0,0 +1,1270 @@
+/**
+ * @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, '[');
+			*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 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 = 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/desktopitem.h	Mon Nov 22 02:57:34 2004 +0000
@@ -0,0 +1,172 @@
+/**
+ * @file gaim-desktop-item.h 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.
+ */
+
+#ifndef _GAIM_DESKTOP_ITEM_H_
+#define _GAIM_DESKTOP_ITEM_H_
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	GAIM_DESKTOP_ITEM_TYPE_NULL = 0 /* This means its NULL, that is, not
+					  * set */,
+	GAIM_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: */
+	GAIM_DESKTOP_ITEM_TYPE_APPLICATION,
+	GAIM_DESKTOP_ITEM_TYPE_LINK,
+	GAIM_DESKTOP_ITEM_TYPE_FSDEVICE,
+	GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE,
+	GAIM_DESKTOP_ITEM_TYPE_DIRECTORY,
+	GAIM_DESKTOP_ITEM_TYPE_SERVICE,
+	GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE
+} GaimDesktopItemType;
+
+typedef struct _GaimDesktopItem GaimDesktopItem;
+
+#define GAIM_TYPE_DESKTOP_ITEM         (gaim_desktop_item_get_type ())
+GType gaim_desktop_item_get_type       (void);
+
+/* standard */
+#define GAIM_DESKTOP_ITEM_ENCODING	"Encoding" /* string */
+#define GAIM_DESKTOP_ITEM_VERSION	"Version"  /* numeric */
+#define GAIM_DESKTOP_ITEM_NAME		"Name" /* localestring */
+#define GAIM_DESKTOP_ITEM_GENERIC_NAME	"GenericName" /* localestring */
+#define GAIM_DESKTOP_ITEM_TYPE		"Type" /* string */
+#define GAIM_DESKTOP_ITEM_FILE_PATTERN "FilePattern" /* regexp(s) */
+#define GAIM_DESKTOP_ITEM_TRY_EXEC	"TryExec" /* string */
+#define GAIM_DESKTOP_ITEM_NO_DISPLAY	"NoDisplay" /* boolean */
+#define GAIM_DESKTOP_ITEM_COMMENT	"Comment" /* localestring */
+#define GAIM_DESKTOP_ITEM_EXEC		"Exec" /* string */
+#define GAIM_DESKTOP_ITEM_ACTIONS	"Actions" /* strings */
+#define GAIM_DESKTOP_ITEM_ICON		"Icon" /* string */
+#define GAIM_DESKTOP_ITEM_MINI_ICON	"MiniIcon" /* string */
+#define GAIM_DESKTOP_ITEM_HIDDEN	"Hidden" /* boolean */
+#define GAIM_DESKTOP_ITEM_PATH		"Path" /* string */
+#define GAIM_DESKTOP_ITEM_TERMINAL	"Terminal" /* boolean */
+#define GAIM_DESKTOP_ITEM_TERMINAL_OPTIONS "TerminalOptions" /* string */
+#define GAIM_DESKTOP_ITEM_SWALLOW_TITLE "SwallowTitle" /* string */
+#define GAIM_DESKTOP_ITEM_SWALLOW_EXEC	"SwallowExec" /* string */
+#define GAIM_DESKTOP_ITEM_MIME_TYPE	"MimeType" /* regexp(s) */
+#define GAIM_DESKTOP_ITEM_PATTERNS	"Patterns" /* regexp(s) */
+#define GAIM_DESKTOP_ITEM_DEFAULT_APP	"DefaultApp" /* string */
+#define GAIM_DESKTOP_ITEM_DEV		"Dev" /* string */
+#define GAIM_DESKTOP_ITEM_FS_TYPE	"FSType" /* string */
+#define GAIM_DESKTOP_ITEM_MOUNT_POINT	"MountPoint" /* string */
+#define GAIM_DESKTOP_ITEM_READ_ONLY	"ReadOnly" /* boolean */
+#define GAIM_DESKTOP_ITEM_UNMOUNT_ICON "UnmountIcon" /* string */
+#define GAIM_DESKTOP_ITEM_SORT_ORDER	"SortOrder" /* strings */
+#define GAIM_DESKTOP_ITEM_URL		"URL" /* string */
+#define GAIM_DESKTOP_ITEM_DOC_PATH	"X-GNOME-DocPath" /* string */
+
+/**
+ * This function loads 'filename' and turns it into a GnomeDesktopItem.
+ *
+ * @param file The filename or directory path to load the GaimDesktopItem from
+ * @param flags Flags to influence the loading process
+ *
+ * @return The newly loaded item, or NULL on error.
+ */
+GaimDesktopItem *gaim_desktop_item_new_from_file (const char *filename);
+
+/**
+ * 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.
+ *
+ * @param item A desktop item
+ *
+ * @return The type of the specified 'item'. The returned memory
+ * remains owned by the GnomeDesktopItem and should not be freed.
+ */
+GaimDesktopItemType gaim_desktop_item_get_entry_type (const GaimDesktopItem *item);
+
+/**
+ * Gets the value of an attribute of the item, as a string.
+ *
+ * @param item A desktop item
+ * @param attr The attribute to look for
+ *
+ * @return The value of the specified item attribute.
+ */
+const char *gaim_desktop_item_get_string (const GaimDesktopItem *item,
+					  const char *attr);
+
+/**
+ * Creates a copy of a GnomeDesktopItem.  The new copy has a refcount of 1.
+ * Note: Section stack is NOT copied.
+ *
+ * @param item The item to be copied
+ *
+ * @return The new copy 
+ */
+GaimDesktopItem *gaim_desktop_item_copy (const GaimDesktopItem *item);
+
+/**
+ * Decreases the reference count of the specified item, and destroys
+ * the item if there are no more references left.
+ *
+ * @param item A desktop item
+ */
+void gaim_desktop_item_unref (GaimDesktopItem *item);
+
+G_END_DECLS
+
+#endif /* _GAIM_DESKTOP_ITEM_H_ */
--- a/src/gtkblist.c	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/gtkblist.c	Mon Nov 22 02:57:34 2004 +0000
@@ -2023,18 +2023,10 @@
 				
 				if (GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CONTACT(node)) {
 					GaimBuddy *b = GAIM_BLIST_NODE_IS_BUDDY(node) ? (GaimBuddy*)node : gaim_contact_get_priority_buddy((GaimContact*)node);
-					GList *tmp;
-					GList *files = gaim_uri_list_extract_filenames(sd->data);
-					for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) {
-						gchar *filename = tmp->data;
-						/* XXX - Make ft API support creating a transfer with more than one file */
-						if (g_file_test(filename, G_FILE_TEST_EXISTS)
-								&& !g_file_test(filename, G_FILE_TEST_IS_DIR)) {
-							serv_send_file(gaim_account_get_connection(b->account), b->name, filename);
-						}
-						g_free(filename);
-					}
-					g_list_free(files);
+					gaim_dnd_file_manage(sd, b->account, b->name);
+					gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
+				} else {
+					gtk_drag_finish(dc, FALSE, FALSE, t);
 				}
 			}	
 	}
--- a/src/gtkconv.c	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/gtkconv.c	Mon Nov 22 02:57:34 2004 +0000
@@ -4365,23 +4365,8 @@
 		gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
 	}
 	else if (sd->target == gdk_atom_intern("text/uri-list", FALSE)) {
-		GList *tmp;
-		GList *files = gaim_uri_list_extract_filenames(sd->data);
-		for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) {
-			gchar *filename = tmp->data;
-			/* XXX - Make ft API support creating a transfer with more than one file */
-			if (g_file_test(filename, G_FILE_TEST_EXISTS)
-					&& !g_file_test(filename, G_FILE_TEST_IS_DIR)
-					&& gaim_conversation_get_type(conv) == GAIM_CONV_IM) {
-				serv_send_file(gaim_conversation_get_gc(conv),
-						gaim_conversation_get_name(conv), filename);
-			}
-			g_free(filename);
-		}
-		g_list_free(files);
-		/* XXX - Attempt to load this file into gdk_pixbuf, or otherwise determine if it is an image.  If it is, offer
-		 * the choice of a) sending this file b) inserting this file as an IM image or c) setting this file as a custom
-		 * buddy icon for this buddy */
+		if (gaim_conversation_get_type(conv) == GAIM_CONV_IM)
+			gaim_dnd_file_manage(sd, gaim_conversation_get_account(conv), gaim_conversation_get_name(conv));
 		gtk_drag_finish(dc, TRUE, (dc->action == GDK_ACTION_MOVE), t);
 	}
 	else
--- a/src/gtkrequest.c	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/gtkrequest.c	Mon Nov 22 02:57:34 2004 +0000
@@ -44,7 +44,7 @@
 	GaimRequestType type;
 
 	void *user_data;
-	GtkWidget *dialog;
+GtkWidget *dialog;
 
 	GtkWidget *ok_button;
 
@@ -134,6 +134,23 @@
 	gaim_request_close(GAIM_REQUEST_INPUT, data);
 }
 
+
+static void
+choice_response_cb(GtkDialog *dialog, gint id, GaimGtkRequestData *data)
+{
+	GtkWidget *radio = g_object_get_data(G_OBJECT(dialog), "radio");
+	GSList *group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
+	if (id < data->cb_count)
+		while (group) {
+			if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) {
+				((GaimRequestChoiceCb)data->cbs[id])(data->user_data, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(group->data), "choice_id")));
+				break;
+			}
+			group = group->next;
+		}
+	gaim_request_close(GAIM_REQUEST_INPUT, data);
+}
+
 static gboolean
 field_string_focus_out_cb(GtkWidget *entry, GdkEventFocus *event,
 						  GaimRequestField *field)
@@ -404,12 +421,100 @@
 
 static void *
 gaim_gtk_request_choice(const char *title, const char *primary,
-						const char *secondary, unsigned int default_value,
-						const char *ok_text, GCallback ok_cb,
-						const char *cancel_text, GCallback cancel_cb,
-						void *user_data, size_t choice_count, va_list args)
+			const char *secondary, unsigned int default_value,
+			const char *ok_text, GCallback ok_cb,
+			const char *cancel_text, GCallback cancel_cb,
+			void *user_data, va_list args)
 {
-	return NULL;
+	GaimGtkRequestData *data;
+	GtkWidget *dialog;
+	GtkWidget *vbox, *vbox2;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *img;
+	GtkWidget *radio = NULL;
+	char *label_text;
+	char *radio_text;
+	
+	data            = g_new0(GaimGtkRequestData, 1);
+	data->type      = GAIM_REQUEST_ACTION;
+	data->user_data = user_data;
+
+	data->cb_count = 2;
+	data->cbs = g_new0(GCallback, 2);
+	data->cbs[0] = cancel_cb;
+	data->cbs[1] = ok_cb;
+
+	/* Create the dialog. */
+	data->dialog = dialog = gtk_dialog_new();
+
+	if (title != NULL)
+		gtk_window_set_title(GTK_WINDOW(dialog), title);
+
+
+	gtk_dialog_add_button(GTK_DIALOG(dialog),
+			      text_to_stock(cancel_text), 0);
+	
+	gtk_dialog_add_button(GTK_DIALOG(dialog),
+			      text_to_stock(ok_text), 1);
+
+	g_signal_connect(G_OBJECT(dialog), "response",
+			 G_CALLBACK(choice_response_cb), data);
+
+	/* Setup the dialog */
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
+	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 12);
+	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 6);
+
+	/* Setup the main horizontal box */
+	hbox = gtk_hbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
+
+	/* Dialog icon. */
+	img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
+				       GTK_ICON_SIZE_DIALOG);
+	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
+
+	/* Vertical box */
+	vbox = gtk_vbox_new(FALSE, 12);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+	
+	/* Descriptive label */
+	label_text = g_strdup_printf((primary ? "<span weight=\"bold\" size=\"larger\">"
+				      "%s</span>%s%s" : "%s%s%s"),
+				     (primary ? primary : ""),
+				     ((primary && secondary) ? "\n\n" : ""),
+				     (secondary ? secondary : ""));
+	
+	label = gtk_label_new(NULL);
+
+	gtk_label_set_markup(GTK_LABEL(label), label_text);
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
+
+	g_free(label_text);
+	
+	vbox2 = gtk_vbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0);
+	while ((radio_text = va_arg(args, char*))) {
+		       int resp = va_arg(args, int);
+		       radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), radio_text);
+		       gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0);
+		       g_object_set_data(G_OBJECT(radio), "choice_id", GINT_TO_POINTER(resp));
+		       if (resp == default_value)
+			       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
+	}
+	 
+	g_object_set_data(G_OBJECT(dialog), "radio", radio);
+
+	/* Show everything. */
+	gtk_widget_show_all(dialog);
+
+	return data;
 }
 
 static void *
--- a/src/gtkutils.c	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/gtkutils.c	Mon Nov 22 02:57:34 2004 +0000
@@ -42,11 +42,14 @@
 
 #include <gdk/gdkkeysyms.h>
 
+#include "conversation.h"
 #include "debug.h"
+#include "desktopitem.h"
 #include "imgstore.h"
 #include "notify.h"
 #include "prefs.h"
 #include "prpl.h"
+#include "request.h"
 #include "signals.h"
 #include "util.h"
 
@@ -1351,3 +1354,249 @@
 
 	return FALSE;
 }
+
+enum {
+	DND_FILE_TRANSFER,
+	DND_IM_IMAGE,
+	DND_BUDDY_ICON
+};
+
+typedef struct {
+	char *filename;
+	GaimAccount *account;
+	char *who;
+} _DndData;
+
+void dnd_set_icon_ok_cb(_DndData *data)
+{
+	free(data->filename);
+	free(data->who);
+	free(data);
+}
+
+void dnd_set_icon_cancel_cb(_DndData *data)
+{
+	free(data->filename);
+	free(data->who);
+	free (data);
+}
+
+void dnd_image_ok_callback(_DndData *data, int choice)
+{
+	char *filedata;
+	size_t size;
+	GError *err = NULL;
+	GaimConversation *conv;
+	GaimGtkConversation *gtkconv;
+	GtkTextIter iter;
+	int id;
+	switch (choice) {
+	case DND_BUDDY_ICON:
+		if (!g_file_get_contents(data->filename, &filedata, &size,
+					 &err)) {
+			char *str;
+
+			str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
+			gaim_notify_error(NULL, NULL,
+					  _("Failed to load image"),
+					  str);
+				
+			g_error_free(err);
+			g_free(str);
+
+			return;
+		}
+		
+		gaim_buddy_icons_set_for_user(data->account, data->who, filedata, size);
+		g_free(filedata);
+		break;
+	case DND_FILE_TRANSFER:
+		serv_send_file(gaim_account_get_connection(data->account), data->who, data->filename);
+		break;
+	case DND_IM_IMAGE:
+		conv = gaim_conversation_new(GAIM_CONV_IM, data->account, data->who);
+		gtkconv = GAIM_GTK_CONVERSATION(conv);
+
+		if (!g_file_get_contents(data->filename, &filedata, &size,
+					 &err)) {
+			char *str;
+			
+			str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
+			gaim_notify_error(NULL, NULL,
+					  _("Failed to load image"),
+					  str);
+				
+			g_error_free(err);
+			g_free(str);
+			
+			return;
+		}
+		id = gaim_imgstore_add(filedata, size, data->filename);
+		g_free(filedata);
+		
+		gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
+						 gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
+		gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
+		gaim_imgstore_unref(id);
+		
+		break;
+	}
+	free(data->filename);
+	free(data->who);
+	free(data);
+}
+
+void dnd_image_cancel_callback(_DndData *data, int choice)
+{
+	free(data->filename);
+	free(data->who);
+	free(data);
+}
+
+void
+gaim_dnd_file_manage(GtkSelectionData *sd, GaimAccount *account, const char *who)
+{
+	GList *tmp;
+	GdkPixbuf *pb;
+	GList *files = gaim_uri_list_extract_filenames(sd->data);
+	GaimConnection *gc = gaim_account_get_connection(account);
+	GaimPluginProtocolInfo *prpl_info = NULL;
+	GaimDesktopItem *item;
+	gboolean file_send_ok = FALSE;
+
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(who != NULL);
+	
+	for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) {
+		gchar *filename = tmp->data;
+		gchar *basename = g_path_get_basename(filename);
+			
+		/* Set the default action: don't send anything */
+		file_send_ok = FALSE;
+
+		/* XXX - Make ft API support creating a transfer with more than one file */
+		if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
+			continue;
+		}
+
+		/* XXX - make ft api suupport sending a directory */
+		/* Are we dealing with a directory? */
+		if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
+			char *str;
+
+			str = g_strdup_printf(_("Cannot send folder %s."), basename);
+			gaim_notify_error(NULL, NULL,
+					  str,_("Gaim cannot transfer a folder. You will need to send the files within individually"));
+				
+			g_free(str);
+			
+		        continue;
+		}
+
+		/* Are we dealing with an image? */
+		pb = gdk_pixbuf_new_from_file(filename, NULL);
+		if (pb) {
+			_DndData *data = g_malloc(sizeof(_DndData));
+			gboolean ft = FALSE, im = FALSE;
+			
+			data->who = g_strdup(who);
+			data->filename = g_strdup(filename);
+			data->account = account;
+
+			if (gc)
+				prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+			if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
+				im = TRUE;
+						
+			if (prpl_info && prpl_info->send_file)
+				ft = prpl_info->can_receive_file(gc, who);
+						
+			if (im && ft)
+				gaim_request_choice(NULL, NULL,
+						    _("You have dragged an image"),
+						    _("You can send this image as a file transfer,"
+						      " embed it into this message, or use it as the buddy icon for this user."),
+						    DND_BUDDY_ICON, "OK", (GCallback)dnd_image_ok_callback,
+						    "Cancel", (GCallback)dnd_image_cancel_callback, data, 
+						    _("Set as buddy icon"), DND_BUDDY_ICON,
+						    _("Send image file"), DND_FILE_TRANSFER, 
+						    _("Insert in message"), DND_IM_IMAGE, NULL);	
+			else if (!(im || ft))
+				gaim_request_yes_no(NULL, NULL, _("You have dragged an image"), 
+						       _("Would you like to set it as the buddy icon for this user?"),
+						    0, data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
+			else 
+				gaim_request_choice(NULL, NULL,
+						    _("You have dragged an image"),
+						    ft ? _("You can send this image as a file transfer or"
+							   "embed it into this message, or use it as the buddy icon for this user.") :
+						    _("You can insert this image into this message, or use it as the buddy icon for this user"),
+						    DND_BUDDY_ICON, "OK", (GCallback)dnd_image_ok_callback,
+						    "Cancel", (GCallback)dnd_image_cancel_callback, data, 
+						    _("Set as buddy icon"), DND_BUDDY_ICON,
+						    ft ? _("Send image file") : _("Insert in message"), ft ? DND_FILE_TRANSFER : DND_IM_IMAGE, NULL);	
+			return;
+		}
+
+#ifndef _WIN32
+		/* Are we trying to send a .desktop file? */
+		else if (g_str_has_suffix(basename, ".desktop") && (item = gaim_desktop_item_new_from_file(filename))) {
+			GaimDesktopItemType dtype;
+			char key[64];
+			char *dot;
+			const char *itemname = NULL;
+		
+#if GTK_CHECK_VERSION(2,6,0)
+			char **langs;
+			int i;
+			langs = g_get_language_names();
+			for (i = 0; langs[i]; i++) {
+				g_snprintf(key, sizeof(key), "Name[%s]", langs[i]);
+				itemname = gaim_desktop_item_get_string(item, key);
+				break;
+			}
+#else
+			const char *lang = g_getenv("LANG");
+			dot = strchr(lang, '.');
+			if (dot) 
+				*dot = '\0';
+			g_snprintf(key, sizeof(key), "Name[%s]", lang);
+			itemname = gaim_desktop_item_get_string(item, key);
+#endif
+			if (!itemname)
+				itemname = gaim_desktop_item_get_string(item, "Name");
+
+			dtype = gaim_desktop_item_get_entry_type(item);
+			switch (dtype) {
+				GaimConversation *conv;
+				GaimGtkConversation *gtkconv;
+
+			case GAIM_DESKTOP_ITEM_TYPE_LINK:
+				conv = gaim_conversation_new(GAIM_CONV_IM, account, who);
+				gtkconv =  GAIM_GTK_CONVERSATION(conv);
+				gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry), 
+						       gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer), 
+						       gaim_desktop_item_get_string(item, "URL"), itemname);
+				break;
+			default: 
+				/* I don't know if we really want to do anything here.  Most of the desktop item types are crap like
+				 * "MIME Type" (I have no clue how that would be a desktop item) and "Comment"... nothing we can really
+				 * send.  The only logical one is "Application," but do we really want to send a binary and nothing else?
+				 * Probably not.  I'll just give an error and return. */
+				/* The original patch sent the icon used by the launcher.  That's probably wrong */
+				gaim_notify_error(NULL, NULL, _("Cannot send launcher"), _("You dragged a desktop launcher. "
+											   "Most likely you wanted to send whatever this launcher points to instead of this launcher"
+											   " itself."));
+				break;
+			}
+			gaim_desktop_item_unref(item);
+			return;
+		}
+#endif /* _WIN32 */
+
+		/* Everything is fine, let's send */
+		serv_send_file(gc, who, filename);
+		g_free(filename);
+	}
+	g_list_free(files);
+}
--- a/src/gtkutils.h	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/gtkutils.h	Mon Nov 22 02:57:34 2004 +0000
@@ -357,4 +357,14 @@
  */
 gboolean gaim_running_gnome(void);
 
+/**
+ * Manages drag'n'drop of files.
+ *
+ * @param sd GtkSelectionData for managing drag'n'drop
+ * @param gc Connection to be used (may be NULL if conv is not NULL)
+ * @param who Buddy name (may be NULL if conv is not NULL)
+ */
+void gaim_dnd_file_manage(GtkSelectionData *sd, GaimAccount *account, const char *who);
+
+
 #endif /* _GAIM_GTKUTILS_H_ */
--- a/src/request.c	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/request.c	Mon Nov 22 02:57:34 2004 +0000
@@ -1139,20 +1139,18 @@
 					const char *secondary, unsigned int default_value,
 					const char *ok_text, GCallback ok_cb,
 					const char *cancel_text, GCallback cancel_cb,
-					void *user_data, size_t choice_count, ...)
+					void *user_data, ...)
 {
 	void *ui_handle;
 	va_list args;
 
 	g_return_val_if_fail(ok_text != NULL,  NULL);
 	g_return_val_if_fail(ok_cb   != NULL,  NULL);
-	g_return_val_if_fail(choice_count > 0, NULL);
 
-	va_start(args, choice_count);
+	va_start(args, user_data);
 	ui_handle = gaim_request_choice_varg(handle, title, primary, secondary,
-										 default_value, ok_text, ok_cb,
-										 cancel_text, cancel_cb, user_data,
-										 choice_count, args);
+					     default_value, ok_text, ok_cb,
+					     cancel_text, cancel_cb, user_data, args);
 	va_end(args);
 
 	return ui_handle;
@@ -1160,18 +1158,16 @@
 
 void *
 gaim_request_choice_varg(void *handle, const char *title,
-						 const char *primary, const char *secondary,
-						 unsigned int default_value,
-						 const char *ok_text, GCallback ok_cb,
-						 const char *cancel_text, GCallback cancel_cb,
-						 void *user_data, size_t choice_count,
-						 va_list choices)
+			 const char *primary, const char *secondary,
+			 unsigned int default_value,
+			 const char *ok_text, GCallback ok_cb,
+			 const char *cancel_text, GCallback cancel_cb,
+			 void *user_data, va_list choices)
 {
 	GaimRequestUiOps *ops;
 
 	g_return_val_if_fail(ok_text != NULL,  NULL);
 	g_return_val_if_fail(ok_cb   != NULL,  NULL);
-	g_return_val_if_fail(choice_count > 0, NULL);
 
 	ops = gaim_request_get_ui_ops();
 
@@ -1182,11 +1178,10 @@
 		info->type      = GAIM_REQUEST_CHOICE;
 		info->handle    = handle;
 		info->ui_handle = ops->request_choice(title, primary, secondary,
-											  default_value,
-											  ok_text, ok_cb,
-											  cancel_text, cancel_cb,
-											  user_data, choice_count,
-											  choices);
+						      default_value,
+						      ok_text, ok_cb,
+						      cancel_text, cancel_cb,
+						      user_data, choices);
 
 		handles = g_list_append(handles, info);
 
--- a/src/request.h	Sun Nov 21 20:36:15 2004 +0000
+++ b/src/request.h	Mon Nov 22 02:57:34 2004 +0000
@@ -182,8 +182,7 @@
 							const char *secondary, unsigned int default_value,
 							const char *ok_text, GCallback ok_cb,
 							const char *cancel_text, GCallback cancel_cb,
-							void *user_data, size_t choice_count,
-							va_list choices);
+							void *user_data, va_list choices);
 	void *(*request_action)(const char *title, const char *primary,
 							const char *secondary, unsigned int default_action,
 							void *user_data, size_t action_count,
@@ -201,6 +200,7 @@
 
 typedef void (*GaimRequestInputCb)(void *, const char *);
 typedef void (*GaimRequestActionCb)(void *, int);
+typedef void (*GaimRequestChoiceCb)(void *, int);
 typedef void (*GaimRequestFieldsCb)(void *, GaimRequestFields *fields);
 typedef void (*GaimRequestFileCb)(void *, const char *filename);
 
@@ -1117,7 +1117,7 @@
 						  unsigned int default_value,
 						  const char *ok_text, GCallback ok_cb,
 						  const char *cancel_text, GCallback cancel_cb,
-						  void *user_data, size_t choice_count, ...);
+						  void *user_data, ...);
 
 /**
  * Prompts the user for multiple-choice input.
@@ -1142,8 +1142,7 @@
 							   unsigned int default_value,
 							   const char *ok_text, GCallback ok_cb,
 							   const char *cancel_text, GCallback cancel_cb,
-							   void *user_data, size_t choice_count,
-							   va_list choices);
+							   void *user_data, va_list choices);
 
 /**
  * Prompts the user for an action.