changeset 30710:29e714e980b2

merge of 'c32c1fdff08db87b69fc0eb921a5eaafcb5ef67e' and 'fcd99a6bb8c6362d64f295a1b95a5b1130460b85'
author andrew.victor@mxit.com
date Fri, 28 May 2010 21:10:09 +0000
parents d7325448badb (current diff) 51c31805d2a7 (diff)
children ab0c6287e9a5
files
diffstat 27 files changed, 2355 insertions(+), 1420 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Fri May 28 21:08:49 2010 +0000
+++ b/COPYRIGHT	Fri May 28 21:10:09 2010 +0000
@@ -123,6 +123,7 @@
 Josh Davis
 Martijn Dekker
 Florian Delizy
+Jiri Denemark
 Vinicius Depizzol
 Philip Derrin
 Taso N. Devetzis
@@ -481,6 +482,7 @@
 Marcus Sundberg
 Mårten Svantesson (fursten)
 Amir Szekely (kichik)
+Gábor Szuromi (kukkerman)
 Robert T.
 Greg Taeger
 Rob Taft
--- a/ChangeLog	Fri May 28 21:08:49 2010 +0000
+++ b/ChangeLog	Fri May 28 21:10:09 2010 +0000
@@ -17,11 +17,17 @@
 	MSN:
 	* Fix unnecessary bandwidth consumption for buddy icon requests when
 	  buddies have capital letters in their passport addresses.
+	* Support for direct connections, enabling faster file transfers,
+	  smiley and buddy icon loading.  (Gábor Szuromi)
 
 	XMPP:
 	* Allow connecting to servers that advertise EXTERNAL (broken in
 	  2.7.0)
 
+	Windows-Specific Changes:
+	* Fix a regression introduced in 2.7.0 that caused Window Flashing not
+	  to work.
+
 version 2.7.0 (05/12/2010):
 	General:
 	* Changed GTK+ minimum version requirement to 2.10.0.
@@ -141,6 +147,21 @@
 	* New action 'history-search', with default binding ctrl+r, to search
 	  the entered string in the input history.
 
+	Windows-Specific Changes
+	* Updated GTK+ to 2.16.6
+	* Private GTK+ Runtime now used (GTK+ Installer no longer supported)
+	* Minimum required GTK+ version increased to 2.14.7
+	* Windows 95, Windows 98, Windows 98 Second Edition, Windows ME
+	  (Millennium Edition), and Windows NT 4.0 longer supported due to GTK+
+	  requirements changes.
+	* Crash Report files (pidgin.RPT) are now generated in the ~/.purple
+	  directory instead of the installation directory.
+	* NSS SSL Library upgraded to 3.12.5 (thanks to Berke Viktor)
+	* GtkSpell upgraded to 2.0.16, changing the spellchecking backend to
+	  enchant.  This means that myspell and hunspell (OpenOffice)
+	  dictionaries can be used (previous versions' aspell dictionaries
+	  will not work).
+
 version 2.6.6 (02/18/2010):
 	libpurple:
 	* Fix 'make check' on OS X. (David Fang)
--- a/ChangeLog.win32	Fri May 28 21:08:49 2010 +0000
+++ b/ChangeLog.win32	Fri May 28 21:10:09 2010 +0000
@@ -1,12 +1,13 @@
-version 2.7.1 (??/??/????):
-	* Fix a regression introduced in 2.7.0 that caused Window Flashing not
-	  to work.
+Starting with Pidgin version 2.7.1, this ChangeLog file will no longer be
+updated.  It will be kept in the source tree for historical reasons only.
 
 version 2.7.0 (05/12/2010):
 	* Updated GTK+ to 2.16.6
 	* Private GTK+ Runtime now used (GTK+ Installer no longer supported)
 	* Minimum required GTK+ version increased to 2.14.7
-	* Win9x no longer supported.
+	* Windows 95, Windows 98, Windows 98 Second Edition, Windows ME
+	  (Millennium Edition), and Windows NT 4.0 longer supported due to GTK+
+	  requirements changes.
 	* Crash Report files (pidgin.RPT) are now generated in the ~/.purple
 	  directory instead of the installation directory.
 	* NSS SSL Library upgraded to 3.12.5 (thanks to Berke Viktor)
--- a/Makefile.am	Fri May 28 21:08:49 2010 +0000
+++ b/Makefile.am	Fri May 28 21:10:09 2010 +0000
@@ -80,11 +80,14 @@
 apps_in_files = pidgin.desktop.in
 apps_DATA = $(apps_in_files:.desktop.in=.desktop)
 @INTLTOOL_DESKTOP_RULE@
-GTK_DIR=pidgin
 endif #ENABLE_GTK
 
 endif #INSTALL_I18N
 
+if ENABLE_GTK
+GTK_DIR=pidgin
+endif
+
 if ENABLE_GNT
 GNT_DIR=finch
 endif
--- a/libpurple/media/backend-fs2.h	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/media/backend-fs2.h	Fri May 28 21:10:09 2010 +0000
@@ -55,6 +55,7 @@
  */
 GType purple_media_backend_fs2_get_type(void);
 
+#ifdef USE_GSTREAMER
 /*
  * Temporary function in order to be able to test while
  * integrating with PurpleMedia
@@ -71,6 +72,7 @@
 void purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
 		const gchar *sess_id, const gchar *who, double level);
 /* end tmp */
+#endif /* USE_GSTREAMER */
 
 G_END_DECLS
 
--- a/libpurple/protocols/msn/Makefile.am	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Fri May 28 21:10:09 2010 +0000
@@ -14,6 +14,8 @@
 	contact.h\
 	dialog.c \
 	dialog.h \
+	directconn.c \
+	directconn.h \
 	error.c \
 	error.h \
 	group.c \
--- a/libpurple/protocols/msn/Makefile.mingw	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.mingw	Fri May 28 21:10:09 2010 +0000
@@ -41,6 +41,7 @@
 			command.c \
 			contact.c\
 			dialog.c \
+			directconn.c \
 			error.c \
 			group.c \
 			history.c \
--- a/libpurple/protocols/msn/directconn.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/directconn.c	Fri May 28 21:10:09 2010 +0000
@@ -27,479 +27,976 @@
 #include "slp.h"
 #include "slpmsg.h"
 
-/**************************************************************************
- * Directconn Specific
- **************************************************************************/
-
-void
-msn_directconn_send_handshake(MsnDirectConn *directconn)
-{
-	MsnSlpLink *slplink;
-	MsnSlpMessage *slpmsg;
-
-	g_return_if_fail(directconn != NULL);
-
-	slplink = directconn->slplink;
-
-	slpmsg = msn_slpmsg_new(slplink);
-	slpmsg->flags = 0x100;
-
-	if (directconn->nonce != NULL)
-	{
-		guint32 t1;
-		guint16 t2;
-		guint16 t3;
-		guint16 t4;
-		guint64 t5;
-
-		sscanf (directconn->nonce, "%08X-%04hX-%04hX-%04hX-%012" G_GINT64_MODIFIER "X", &t1, &t2, &t3, &t4, &t5);
-
-		t1 = GUINT32_TO_LE(t1);
-		t2 = GUINT16_TO_LE(t2);
-		t3 = GUINT16_TO_LE(t3);
-		t4 = GUINT16_TO_BE(t4);
-		t5 = GUINT64_TO_BE(t5);
-
-		slpmsg->ack_id     = t1;
-		slpmsg->ack_sub_id = t2 | (t3 << 16);
-		slpmsg->ack_size   = t4 | t5;
-	}
-
-	g_free(directconn->nonce);
-
-	msn_slplink_send_slpmsg(slplink, slpmsg);
-
-	directconn->acked =TRUE;
-}
+#pragma pack(push,1)
+typedef struct {
+	guint32 session_id;
+	guint32 seq_id;
+	guint64 offset;
+	guint64 total_size;
+	guint32 length;
+	guint32 flags;
+	guint32 ack_id;
+	guint32 ack_uid;
+	guint64 ack_size;
+/*	guint8  body[1]; */
+} MsnDcContext;
+#pragma pack(pop)
 
-/**************************************************************************
- * Connection Functions
- **************************************************************************/
-
-static int
-create_listener(int port)
-{
-	int fd;
-	int flags;
-	const int on = 1;
-
-#if 0
-	struct addrinfo hints;
-	struct addrinfo *c, *res;
-	char port_str[5];
-
-	snprintf(port_str, sizeof(port_str), "%d", port);
-
-	memset(&hints, 0, sizeof(hints));
-
-	hints.ai_flags = AI_PASSIVE;
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
+#define DC_PACKET_HEADER_SIZE sizeof(MsnDcContext)
+#define DC_MAX_BODY_SIZE      8*1024
+#define DC_MAX_PACKET_SIZE    (DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE)
 
-	if (getaddrinfo(NULL, port_str, &hints, &res) != 0)
-	{
-		purple_debug_error("msn", "Could not get address info: %s.\n",
-						 port_str);
-		return -1;
-	}
-
-	for (c = res; c != NULL; c = c->ai_next)
-	{
-		fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol);
+static void
+msn_dc_calculate_nonce_hash(MsnDirectConnNonceType type,
+                            const guchar nonce[16], gchar nonce_hash[37])
+{
+	guchar digest[20];
 
-		if (fd < 0)
-			continue;
-
-		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
-
-		if (bind(fd, c->ai_addr, c->ai_addrlen) == 0)
-			break;
-
-		close(fd);
-	}
-
-	if (c == NULL)
-	{
-		purple_debug_error("msn", "Could not find socket: %s.\n", port_str);
-		return -1;
+	if (type == DC_NONCE_SHA1) {
+		PurpleCipher *cipher = purple_ciphers_find_cipher("sha1");
+		PurpleCipherContext *context = purple_cipher_context_new(cipher, NULL);
+		purple_cipher_context_append(context, nonce, sizeof(nonce));
+		purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+		purple_cipher_context_destroy(context);
+	} else if (type == DC_NONCE_PLAIN) {
+		memcpy(digest, nonce, 16);
 	}
 
-	freeaddrinfo(res);
-#else
-	struct sockaddr_in sockin;
-
-	fd = socket(AF_INET, SOCK_STREAM, 0);
-
-	if (fd < 0)
-		return -1;
-
-	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0)
-	{
-		close(fd);
-		return -1;
-	}
-
-	memset(&sockin, 0, sizeof(struct sockaddr_in));
-	sockin.sin_family = AF_INET;
-	sockin.sin_port = htons(port);
-
-	if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0)
-	{
-		close(fd);
-		return -1;
-	}
-#endif
-
-	if (listen (fd, 4) != 0)
-	{
-		close (fd);
-		return -1;
-	}
-
-	flags = fcntl(fd, F_GETFL);
-	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-#ifndef _WIN32
-	fcntl(fd, F_SETFD, FD_CLOEXEC);
-#endif
-
-	return fd;
+	g_sprintf(nonce_hash,
+	          "%08X-%04X-%04X-%04X-%08X%04X",
+	          GUINT32_FROM_LE(*((guint32 *)(digest + 0))),
+	          GUINT16_FROM_LE(*((guint16 *)(digest + 4))),
+	          GUINT16_FROM_LE(*((guint16 *)(digest + 6))),
+	          GUINT16_FROM_BE(*((guint16 *)(digest + 8))),
+	          GUINT32_FROM_BE(*((guint32 *)(digest + 10))),
+	          GUINT16_FROM_BE(*((guint16 *)(digest + 14)))
+	);
 }
 
-static gssize
-msn_directconn_write(MsnDirectConn *directconn,
-					 const char *data, size_t len)
+static void
+msn_dc_generate_nonce(MsnDirectConn *dc)
 {
-	char *buffer, *tmp;
-	size_t buf_size;
-	gssize ret;
-	guint32 sent_len;
-
-	g_return_val_if_fail(directconn != NULL, 0);
-
-	buf_size = len + 4;
-	buffer = tmp = g_malloc(buf_size);
-
-	sent_len = GUINT32_TO_LE(len);
+	guint32 *nonce;
+	int i;
 
-	memcpy(tmp, &sent_len, 4);
-	tmp += 4;
-	memcpy(tmp, data, len);
-	tmp += len;
-
-	ret = write(directconn->fd, buffer, buf_size);
-
-#ifdef DEBUG_DC
-	char *str;
-	str = g_strdup_printf("%s/msntest/w%.4d.bin", g_get_home_dir(), directconn->c);
+	nonce = (guint32 *)&dc->nonce;
+	for (i = 0; i < 4; i++)
+		nonce[i] = rand();
 
-	FILE *tf = g_fopen(str, "w");
-	fwrite(buffer, 1, buf_size, tf);
-	fclose(tf);
+	msn_dc_calculate_nonce_hash(dc->nonce_type, dc->nonce, dc->nonce_hash);
 
-	g_free(str);
-#endif
-
-	g_free(buffer);
-
-	directconn->c++;
-
-	return ret;
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "DC %p generated nonce %s\n", dc, dc->nonce_hash);
 }
 
-#if 0
-void
-msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce)
+static MsnDirectConnPacket *
+msn_dc_new_packet(guint32 length)
 {
-	guint32 t1;
-	guint16 t2;
-	guint16 t3;
-	guint16 t4;
-	guint64 t5;
-
-	g_return_if_fail(directconn != NULL);
-	g_return_if_fail(nonce      != NULL);
-
-	sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &t1, &t2, &t3, &t4, &t5);
+	MsnDirectConnPacket	*p;
 
-	t1 = GUINT32_TO_LE(t1);
-	t2 = GUINT16_TO_LE(t2);
-	t3 = GUINT16_TO_LE(t3);
-	t4 = GUINT16_TO_BE(t4);
-	t5 = GUINT64_TO_BE(t5);
-
-	directconn->slpheader = g_new0(MsnSlpHeader, 1);
+	p = g_new0(MsnDirectConnPacket, 1);
+	p->length = length;
+	p->data = g_malloc(length);
 
-	directconn->slpheader->ack_id     = t1;
-	directconn->slpheader->ack_sub_id = t2 | (t3 << 16);
-	directconn->slpheader->ack_size   = t4 | t5;
-}
-#endif
-
-void
-msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg)
-{
-	char *body;
-	size_t body_len;
-
-	body = msn_message_gen_slp_body(msg, &body_len);
-
-	msn_directconn_write(directconn, body, body_len);
+	return p;
 }
 
 static void
-read_cb(gpointer data, gint source, PurpleInputCondition cond)
+msn_dc_destroy_packet(MsnDirectConnPacket *p)
+{
+	g_free(p->data);
+
+	if (p->msg)
+		msn_message_unref(p->msg);
+
+	g_free(p);
+}
+
+MsnDirectConn *
+msn_dc_new(MsnSlpCall *slpcall)
 {
-	MsnDirectConn* directconn;
-	char *body;
-	size_t body_len;
-	gssize len;
+	MsnDirectConn *dc;
+
+	g_return_val_if_fail(slpcall != NULL, NULL);
+
+	dc = g_new0(MsnDirectConn, 1);
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_new %p\n", dc);
+
+	dc->slplink = slpcall->slplink;
+	dc->slpcall = slpcall;
+
+	if (dc->slplink->dc != NULL)
+		purple_debug_warning("msn", "msn_dc_new: slplink already has an allocated DC!\n");
+
+	dc->slplink->dc = dc;
 
-	purple_debug_info("msn", "read_cb: %d, %d\n", source, cond);
+	dc->msg_body = NULL;
+	dc->prev_ack = NULL;
+	dc->listen_data = NULL;
+	dc->connect_data = NULL;
+	dc->listenfd = -1;
+	dc->listenfd_handle = 0;
+	dc->connect_timeout_handle = 0;
+	dc->fd = -1;
+	dc->recv_handle = 0;
+	dc->send_handle = 0;
+	dc->state = DC_STATE_CLOSED;
+	dc->in_buffer = NULL;
+	dc->out_queue = g_queue_new();
+	dc->msg_pos = -1;
+	dc->send_connection_info_msg_cb = NULL;
+	dc->ext_ip = NULL;
+	dc->timeout_handle = 0;
+	dc->progress = FALSE;
+	/*dc->num_calls = 1;*/
 
-	directconn = data;
+	/* TODO: Probably should set this based on buddy caps */
+	dc->nonce_type = DC_NONCE_PLAIN;
+	msn_dc_generate_nonce(dc);
+
+	return dc;
+}
+
+void
+msn_dc_destroy(MsnDirectConn *dc)
+{
+	MsnSlpLink *slplink;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_destroy %p\n", dc);
 
-	/* Let's read the length of the data. */
-#error This code is broken.  See the note below.
-	/*
-	 * TODO: This has problems!  First of all, sizeof(body_len) will be
-	 *       different on 32bit systems and on 64bit systems (4 bytes
-	 *       vs. 8 bytes).
-	 *       Secondly, we're reading from a TCP stream.  There is no
-	 *       guarantee that we have received the number of bytes we're
-	 *       trying to read.  We need to read into a buffer.  If read
-	 *       returns <0 then we need to check errno.  If errno is EAGAIN
-	 *       then don't destroy anything, just exit and wait for more
-	 *       data.  See every other function in libpurple that does this
-	 *       correctly for an example.
-	 */
-	len = read(directconn->fd, &body_len, sizeof(body_len));
+	g_return_if_fail(dc != NULL);
+
+	if (dc->slpcall != NULL)
+		dc->slpcall->wait_for_socket = FALSE;
+
+	slplink = dc->slplink;
+	if (slplink) {
+		slplink->dc = NULL;
+		if (slplink->swboard == NULL)
+			msn_slplink_destroy(slplink);
+	}
+
+	g_free(dc->msg_body);
+
+	if (dc->prev_ack) {
+		msn_slpmsg_destroy(dc->prev_ack);
+	}
+
+	if (dc->listen_data != NULL) {
+		purple_network_listen_cancel(dc->listen_data);
+	}
+
+	if (dc->connect_data != NULL) {
+		purple_proxy_connect_cancel(dc->connect_data);
+	}
+
+	if (dc->listenfd != -1) {
+		purple_network_remove_port_mapping(dc->listenfd);
+		close(dc->listenfd);
+	}
 
-	if (len <= 0)
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
+	if (dc->listenfd_handle != 0) {
+		purple_input_remove(dc->listenfd_handle);
+	}
+
+	if (dc->connect_timeout_handle != 0) {
+		purple_timeout_remove(dc->connect_timeout_handle);
+	}
+
+	if (dc->fd != -1) {
+		close(dc->fd);
+	}
+
+	if (dc->send_handle != 0) {
+		purple_input_remove(dc->send_handle);
+	}
 
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
+	if (dc->recv_handle != 0) {
+		purple_input_remove(dc->recv_handle);
+	}
+
+	g_free(dc->in_buffer);
 
-		close(directconn->fd);
+	if (dc->out_queue != NULL) {
+		while (!g_queue_is_empty(dc->out_queue))
+			msn_dc_destroy_packet( g_queue_pop_head(dc->out_queue) );
 
-		msn_directconn_destroy(directconn);
+		g_queue_free(dc->out_queue);
+	}
 
-		return;
+	g_free(dc->ext_ip);
+
+	if (dc->timeout_handle != 0) {
+		purple_timeout_remove(dc->timeout_handle);
 	}
 
-	body_len = GUINT32_FROM_LE(body_len);
+	g_free(dc);
+}
+
+/*
+void
+msn_dc_ref(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+
+	dc->num_calls++;
+}
+
+void
+msn_dc_unref(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+
+
+	if (dc->num_calls > 0) {
+		dc->num_calls--;
+	}
+}
+*/
+
+void
+msn_dc_send_invite(MsnDirectConn *dc)
+{
+	MsnSlpCall    *slpcall;
+	MsnSlpMessage *msg;
+	gchar *header;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_send_invite %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	slpcall = dc->slpcall;
+	g_return_if_fail(slpcall != NULL);
 
-	purple_debug_info("msn", "body_len=%" G_GSIZE_FORMAT "\n", body_len);
+	header = g_strdup_printf(
+		"INVITE MSNMSGR:%s MSNSLP/1.0",
+		slpcall->slplink->remote_user
+	);
+
+	msg = msn_slpmsg_sip_new(
+		slpcall,
+		0,
+		header,
+		slpcall->branch,
+		"application/x-msnmsgr-transrespbody",
+		dc->msg_body
+	);
+	msg->info = "DC INVITE";
+	msg->text_body = TRUE;
+	g_free(header);
+	g_free(dc->msg_body);
+	dc->msg_body = NULL;
+
+	msn_slplink_queue_slpmsg(slpcall->slplink, msg);
+}
+
+void
+msn_dc_send_ok(MsnDirectConn *dc)
+{
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_send_ok %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	msn_slp_send_ok(dc->slpcall, dc->slpcall->branch,
+		"application/x-msnmsgr-transrespbody", dc->msg_body);
+	g_free(dc->msg_body);
+	dc->msg_body = NULL;
+
+	msn_slplink_send_slpmsg(dc->slpcall->slplink, dc->prev_ack);
+	msn_slpmsg_destroy(dc->prev_ack);
+	dc->prev_ack = NULL;
+	msn_slplink_send_queued_slpmsgs(dc->slpcall->slplink);
+}
+
+void
+msn_dc_fallback_to_p2p(MsnDirectConn *dc)
+{
+	MsnSlpLink *slplink;
+	MsnSlpCall *slpcall;
+	GQueue *queue = NULL;
 
-	if (body_len <= 0)
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
+	purple_debug_info("msn", "msn_dc_try_fallback_to_p2p %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	slpcall = dc->slpcall;
+	slplink = msn_slplink_ref(dc->slplink);
+	if (slpcall && !g_queue_is_empty(dc->out_queue)) {
+		queue = dc->out_queue;
+		dc->out_queue = NULL;
+	}
+
+	msn_dc_destroy(dc);
+
+	if (slpcall) {
+		msn_slpcall_session_init(slpcall);
+		if (queue) {
+			while (!g_queue_is_empty(queue)) {
+				MsnDirectConnPacket *p = g_queue_pop_head(queue);
+				msn_slplink_send_msg(slplink, p->msg);
+				msn_dc_destroy_packet(p);
+			}
+			g_queue_free(queue);
+		}
+	}
+	msn_slplink_unref(slplink);
+}
+
+static void
+msn_dc_parse_binary_header(MsnDirectConn *dc)
+{
+	MsnSlpHeader *h;
+	MsnDcContext *context;
+
+	g_return_if_fail(dc != NULL);
+
+	h = &dc->header;
+	/* Skip packet size */
+	context = (MsnDcContext *)(dc->in_buffer + 4);
 
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
+	h->session_id = GUINT32_FROM_LE(context->session_id);
+	h->id = GUINT32_FROM_LE(context->seq_id);
+	h->offset = GUINT64_FROM_LE(context->offset);
+	h->total_size = GUINT64_FROM_LE(context->total_size);
+	h->length = GUINT32_FROM_LE(context->length);
+	h->flags = GUINT32_FROM_LE(context->flags);
+	h->ack_id = GUINT32_FROM_LE(context->ack_id);
+	h->ack_sub_id = GUINT32_FROM_LE(context->ack_uid);
+	h->ack_size = GUINT64_FROM_LE(context->ack_size);
+}
+
+static const gchar *
+msn_dc_serialize_binary_header(MsnDirectConn *dc) {
+	MsnSlpHeader *h;
+	static MsnDcContext bin_header;
+
+	g_return_val_if_fail(dc != NULL, NULL);
+
+	h = &dc->header;
 
-		close(directconn->fd);
+	bin_header.session_id = GUINT32_TO_LE(h->session_id);
+	bin_header.seq_id = GUINT32_TO_LE(h->id);
+	bin_header.offset = GUINT64_TO_LE(h->offset);
+	bin_header.total_size = GUINT64_TO_LE(h->total_size);
+	bin_header.length = GUINT32_TO_LE(h->length);
+	bin_header.flags = GUINT32_TO_LE(h->flags);
+	bin_header.ack_id = GUINT32_TO_LE(h->ack_id);
+	bin_header.ack_uid = GUINT32_TO_LE(h->ack_sub_id);
+	bin_header.ack_size = GUINT64_TO_LE(h->ack_size);
+
+	return (const gchar *)&bin_header;
+}
 
-		msn_directconn_destroy(directconn);
+static void
+msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond)
+{
+	MsnDirectConn *dc = data;
+	MsnDirectConnPacket *p;
+	int bytes_to_send;
+	int bytes_sent;
 
+	g_return_if_fail(dc != NULL);
+	g_return_if_fail(fd != -1);
+
+	if (g_queue_is_empty(dc->out_queue)) {
+		if (dc->send_handle != 0) {
+			purple_input_remove(dc->send_handle);
+			dc->send_handle = 0;
+		}
 		return;
 	}
 
-	body = g_try_malloc(body_len);
-
-	if (body != NULL)
-	{
-		/* Let's read the data. */
-		len = read(directconn->fd, body, body_len);
+	p = g_queue_peek_head(dc->out_queue);
 
-		purple_debug_info("msn", "len=%" G_GSIZE_FORMAT "\n", len);
-	}
-	else
-	{
-		purple_debug_error("msn", "Failed to allocate memory for read\n");
-		len = 0;
+	if (dc->msg_pos < 0) {
+		/* First we send the length of the packet */
+		guint32 len = GUINT32_TO_LE(p->length);
+		bytes_sent = send(fd, &len, 4, 0);
+		if (bytes_sent < 0) {
+			if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+				return;
+
+			purple_debug_warning("msn", "msn_dc_send_cb: send error\n");
+			msn_dc_destroy(dc);
+			return;
+		}
+		dc->msg_pos = 0;
 	}
 
-	if (len > 0)
-	{
-		MsnMessage *msg;
-
-#ifdef DEBUG_DC
-		str = g_strdup_printf("%s/msntest/r%.4d.bin", g_get_home_dir(), directconn->c);
-
-		FILE *tf = g_fopen(str, "w");
-		fwrite(body, 1, len, tf);
-		fclose(tf);
-
-		g_free(str);
-#endif
-
-		directconn->c++;
+	bytes_to_send = p->length - dc->msg_pos;
+	bytes_sent = send(fd, p->data + dc->msg_pos, bytes_to_send, 0);
+	if (bytes_sent < 0) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return;
 
-		msg = msn_message_new_msnslp();
-		msn_message_parse_slp_body(msg, body, body_len);
-
-		purple_debug_info("msn", "directconn: process_msg\n");
-		msn_slplink_process_msg(directconn->slplink, msg);
-	}
-	else
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
-
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
-
-		close(directconn->fd);
-
-		msn_directconn_destroy(directconn);
+		purple_debug_warning("msn", "msn_dc_send_cb: send error\n");
+		msn_dc_destroy(dc);
+		return;
 	}
 
-	g_free(body);
+	dc->progress = TRUE;
+
+	dc->msg_pos += bytes_sent;
+	if (dc->msg_pos == p->length) {
+		if (p->sent_cb != NULL)
+			p->sent_cb(p);
+
+		g_queue_pop_head(dc->out_queue);
+		msn_dc_destroy_packet(p);
+
+		dc->msg_pos = -1;
+	}
 }
 
 static void
-connect_cb(gpointer data, gint source, PurpleInputCondition cond)
+msn_dc_enqueue_packet(MsnDirectConn *dc, MsnDirectConnPacket *p)
 {
-	MsnDirectConn* directconn;
-	int fd;
-
-	purple_debug_misc("msn", "directconn: connect_cb: %d, %d.\n", source, cond);
-
-	directconn = data;
-	directconn->connect_data = NULL;
-
-	if (TRUE)
-	{
-		fd = source;
-	}
-	else
-	{
-		struct sockaddr_in client_addr;
-		socklen_t client;
-		fd = accept (source, (struct sockaddr *)&client_addr, &client);
-	}
-
-	directconn->fd = fd;
+	gboolean was_empty;
 
-	if (fd > 0)
-	{
-		directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, read_cb,
-										  directconn);
-
-		if (TRUE)
-		{
-			/* Send foo. */
-			msn_directconn_write(directconn, "foo", strlen("foo") + 1);
+	was_empty = g_queue_is_empty(dc->out_queue);
+	g_queue_push_tail(dc->out_queue, p);
 
-			/* Send Handshake */
-			msn_directconn_send_handshake(directconn);
-		}
-		else
-		{
-		}
-	}
-	else
-	{
-		/* ERROR */
-		purple_debug_error("msn", "could not add input\n");
-
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
-
-		close(directconn->fd);
+	if (was_empty && dc->send_handle == 0) {
+		dc->send_handle = purple_input_add(dc->fd, PURPLE_INPUT_WRITE, msn_dc_send_cb, dc);
+		msn_dc_send_cb(dc, dc->fd, PURPLE_INPUT_WRITE);
 	}
 }
 
 static void
-directconn_connect_cb(gpointer data, gint source, const gchar *error_message)
+msn_dc_send_foo(MsnDirectConn *dc)
+{
+	MsnDirectConnPacket	*p;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_send_foo %p\n", dc);
+
+	p = msn_dc_new_packet(4);
+
+	memcpy(p->data, "foo\0", 4);
+
+	msn_dc_enqueue_packet(dc, p);
+}
+
+static void
+msn_dc_send_handshake_with_nonce(MsnDirectConn *dc, MsnDirectConnPacket *p)
 {
-	if (error_message)
-		purple_debug_error("msn", "Error making direct connection: %s\n", error_message);
+	const gchar *h;
+
+	h = msn_dc_serialize_binary_header(dc);
+	memcpy(p->data, h, DC_PACKET_HEADER_SIZE);
+
+	memcpy(p->data + offsetof(MsnDcContext, ack_id), dc->nonce, 16);
+
+	msn_dc_enqueue_packet(dc, p);
+}
 
-	connect_cb(data, source, PURPLE_INPUT_READ);
+static void
+msn_dc_send_handshake(MsnDirectConn *dc)
+{
+	MsnDirectConnPacket *p;
+
+	p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE);
+
+	dc->header.session_id = 0;
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.offset = 0;
+	dc->header.total_size = 0;
+	dc->header.length = 0;
+	dc->header.flags = 0x100;
+
+	msn_dc_send_handshake_with_nonce(dc, p);
 }
 
-gboolean
-msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port)
+static void
+msn_dc_send_handshake_reply(MsnDirectConn *dc)
 {
-	MsnSession *session;
+	MsnDirectConnPacket *p;
+
+	p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE);
+
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.length = 0;
 
-	g_return_val_if_fail(directconn != NULL, FALSE);
-	g_return_val_if_fail(host       != NULL, TRUE);
-	g_return_val_if_fail(port        > 0,    FALSE);
+	msn_dc_send_handshake_with_nonce(dc, p);
+}
 
-	session = directconn->slplink->session;
+static gboolean
+msn_dc_verify_handshake(MsnDirectConn *dc, guint32 packet_length)
+{
+	guchar nonce[16];
+	gchar  nonce_hash[37];
+
+	if (packet_length != DC_PACKET_HEADER_SIZE)
+		return FALSE;
+
+	memcpy(nonce, dc->in_buffer + 4 + offsetof(MsnDcContext, ack_id), 16);
 
-#if 0
-	if (session->http_method)
-	{
-		servconn->http_data->gateway_host = g_strdup(host);
-	}
-#endif
+	if (dc->nonce_type == DC_NONCE_PLAIN) {
+		if (memcmp(dc->nonce, nonce, 16) == 0) {
+			purple_debug_info("msn",
+					"Nonce from buddy request and nonce from DC attempt match, "
+					"allowing direct connection\n");
+			return TRUE;
+		} else {
+			purple_debug_warning("msn",
+					"Nonce from buddy request and nonce from DC attempt "
+					"don't match, ignoring direct connection\n");
+			return FALSE;
+		}
+
+	} else if (dc->nonce_type == DC_NONCE_SHA1) {
+		msn_dc_calculate_nonce_hash(dc->nonce_type, nonce, nonce_hash);
 
-	directconn->connect_data = purple_proxy_connect(NULL, session->account,
-			host, port, directconn_connect_cb, directconn);
+		if (g_str_equal(dc->remote_nonce, nonce_hash)) {
+			purple_debug_info("msn",
+					"Received nonce %s from buddy request "
+					"and calculated nonce %s from DC attempt. "
+					"Nonces match, allowing direct connection\n",
+					dc->remote_nonce, nonce_hash);
+			return TRUE;
+		} else {
+			purple_debug_warning("msn",
+					"Received nonce %s from buddy request "
+					"and calculated nonce %s from DC attempt. "
+					"Nonces don't match, ignoring direct connection\n",
+					dc->remote_nonce, nonce_hash);
+			return FALSE;
+		}
+	} else
+		return FALSE;
+}
 
-	return (directconn->connect_data != NULL);
+static void
+msn_dc_send_packet_cb(MsnDirectConnPacket *p)
+{
+	if (p->msg != NULL && p->msg->ack_cb != NULL)
+		p->msg->ack_cb(p->msg, p->msg->ack_data);
 }
 
 void
-msn_directconn_listen(MsnDirectConn *directconn)
+msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg)
 {
-	int port;
-	int fd;
+	MsnDirectConnPacket *p;
+	guint32 length;
+
+	length = msg->body_len + DC_PACKET_HEADER_SIZE;
+	p = msn_dc_new_packet(length);
+
+	memcpy(p->data, &msg->msnslp_header, DC_PACKET_HEADER_SIZE);
+	memcpy(p->data + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len);
+
+	p->sent_cb = msn_dc_send_packet_cb;
+	p->msg = msn_message_ref(msg);
+
+	msn_dc_enqueue_packet(dc, p);
+}
 
-	port = 7000;
+static int
+msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length)
+{
+	g_return_val_if_fail(dc != NULL, DC_PROCESS_ERROR);
+
+	switch (dc->state) {
+	case DC_STATE_CLOSED:
+		break;
+
+	case DC_STATE_FOO:
+		/* FOO message is always 4 bytes long */
+		if (packet_length != 4 || memcmp(dc->in_buffer, "\4\0\0\0foo", 8) != 0)
+			return DC_PROCESS_FALLBACK;
+
+		dc->state = DC_STATE_HANDSHAKE;
+		break;
 
-	for (fd = -1; fd < 0;)
-		fd = create_listener(++port);
+	case DC_STATE_HANDSHAKE:
+		if (!msn_dc_verify_handshake(dc, packet_length))
+			return DC_PROCESS_FALLBACK;
+
+		msn_dc_send_handshake_reply(dc);
+		dc->state = DC_STATE_ESTABLISHED;
 
-	directconn->fd = fd;
+		msn_slpcall_session_init(dc->slpcall);
+		dc->slpcall = NULL;
+		break;
+
+	case DC_STATE_HANDSHAKE_REPLY:
+		if (!msn_dc_verify_handshake(dc, packet_length))
+			return DC_PROCESS_FALLBACK;
+
+		dc->state = DC_STATE_ESTABLISHED;
 
-	directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb,
-		directconn);
+		msn_slpcall_session_init(dc->slpcall);
+		dc->slpcall = NULL;
+		break;
+
+	case DC_STATE_ESTABLISHED:
+		msn_slplink_process_msg(
+			dc->slplink,
+			&dc->header,
+			dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE,
+			dc->header.length
+		);
 
-	directconn->port = port;
-	directconn->c = 0;
+		/*
+		if (dc->num_calls == 0) {
+			msn_dc_destroy(dc);
+
+			return DC_PROCESS_CLOSE;
+		}
+		*/
+		break;
+	}
+
+	return DC_PROCESS_OK;
 }
 
-MsnDirectConn*
-msn_directconn_new(MsnSlpLink *slplink)
+static void
+msn_dc_recv_cb(gpointer data, gint fd, PurpleInputCondition cond)
 {
-	MsnDirectConn *directconn;
+	MsnDirectConn *dc;
+	int free_buf_space;
+	int bytes_received;
+	guint32 packet_length;
+
+	g_return_if_fail(data != NULL);
+	g_return_if_fail(fd != -1);
+
+	dc = data;
+	free_buf_space = dc->in_size - dc->in_pos;
+
+	bytes_received = recv(fd, dc->in_buffer + dc->in_pos, free_buf_space, 0);
+	if (bytes_received < 0) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return;
+
+		purple_debug_warning("msn", "msn_dc_recv_cb: recv error\n");
+
+		if(dc->state != DC_STATE_ESTABLISHED)
+			msn_dc_fallback_to_p2p(dc);
+		else
+			msn_dc_destroy(dc);
+		return;
 
-	directconn = g_new0(MsnDirectConn, 1);
+	} else if (bytes_received == 0) {
+		/* EOF. Remote side closed connection. */
+		purple_debug_info("msn", "msn_dc_recv_cb: recv EOF\n");
+
+		if(dc->state != DC_STATE_ESTABLISHED)
+			msn_dc_fallback_to_p2p(dc);
+		else
+			msn_dc_destroy(dc);
+		return;
+	}
+
+	dc->progress = TRUE;
+
+	dc->in_pos += bytes_received;
+
+	/* Wait for packet length */
+	while (dc->in_pos >= 4) {
+		packet_length = GUINT32_FROM_LE(*((guint32*)dc->in_buffer));
+
+		if (packet_length > DC_MAX_PACKET_SIZE) {
+			/* Oversized packet */
+			purple_debug_warning("msn", "msn_dc_recv_cb: oversized packet received\n");
+			return;
+		}
 
-	directconn->slplink = slplink;
+		/* Wait for the whole packet to arrive */
+		if (dc->in_pos < 4 + packet_length)
+			return;
+
+		if (dc->state != DC_STATE_FOO) {
+			msn_dc_parse_binary_header(dc);
+		}
+
+		switch (msn_dc_process_packet(dc, packet_length)) {
+		case DC_PROCESS_CLOSE:
+			return;
+
+		case DC_PROCESS_FALLBACK:
+			purple_debug_warning("msn", "msn_dc_recv_cb: packet processing error, fall back to p2p\n");
+			msn_dc_fallback_to_p2p(dc);
+			return;
+
+		}
+
+		if (dc->in_pos > packet_length + 4) {
+			g_memmove(dc->in_buffer, dc->in_buffer + 4 + packet_length, dc->in_pos - packet_length - 4);
+		}
+
+		dc->in_pos -= packet_length + 4;
+	}
+}
 
-	if (slplink->directconn != NULL)
-		purple_debug_info("msn", "got_transresp: LEAK\n");
+static gboolean
+msn_dc_timeout(gpointer data)
+{
+	MsnDirectConn *dc = data;
+
+	g_return_val_if_fail(dc != NULL, FALSE);
 
-	slplink->directconn = directconn;
+	if (dc->progress) {
+		dc->progress = FALSE;
+		return TRUE;
+	} else {
+		dc->timeout_handle = 0;
+		msn_dc_destroy(dc);
+		return FALSE;
+	}
+}
 
-	return directconn;
+static void
+msn_dc_init(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+
+	dc->in_size = DC_MAX_PACKET_SIZE + 4;
+	dc->in_pos = 0;
+	dc->in_buffer = g_malloc(dc->in_size);
+
+	dc->recv_handle = purple_input_add(dc->fd, PURPLE_INPUT_READ, msn_dc_recv_cb, dc);
+	dc->send_handle = purple_input_add(dc->fd, PURPLE_INPUT_WRITE, msn_dc_send_cb, dc);
+
+	dc->timeout_handle = purple_timeout_add_seconds(DC_TIMEOUT, msn_dc_timeout, dc);
 }
 
 void
-msn_directconn_destroy(MsnDirectConn *directconn)
+msn_dc_connected_to_peer_cb(gpointer data, gint fd, const gchar *error_msg)
 {
-	if (directconn->connect_data != NULL)
-		purple_proxy_connect_cancel(directconn->connect_data);
+	MsnDirectConn *dc = data;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_connected_to_peer_cb %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	dc->connect_data = NULL;
+	purple_timeout_remove(dc->connect_timeout_handle);
+	dc->connect_timeout_handle = 0;
+
+	dc->fd = fd;
+	if (dc->fd != -1) {
+		msn_dc_init(dc);
+		msn_dc_send_foo(dc);
+		msn_dc_send_handshake(dc);
+		dc->state = DC_STATE_HANDSHAKE_REPLY;
+	}
+}
+
+/*
+ * This callback will be called when we're the server
+ * and nobody has connected us in DC_INCOMING_TIMEOUT seconds
+ */
+static gboolean
+msn_dc_incoming_connection_timeout_cb(gpointer data) {
+	MsnDirectConn *dc = data;
+	MsnSlpCall *slpcall;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_incoming_connection_timeout_cb %p\n", dc);
+
+	g_return_val_if_fail(dc != NULL, FALSE);
+
+	slpcall = dc->slpcall;
+
+	if (dc->listen_data != NULL) {
+		purple_network_listen_cancel(dc->listen_data);
+		dc->listen_data = NULL;
+	}
+
+	if (dc->listenfd_handle != 0) {
+		purple_input_remove(dc->listenfd_handle);
+		dc->listenfd_handle = 0;
+	}
+
+	if (dc->listenfd != -1) {
+		purple_network_remove_port_mapping(dc->listenfd);
+		close(dc->listenfd);
+		dc->listenfd = -1;
+	}
+
+	dc->connect_timeout_handle = 0;
+	msn_dc_fallback_to_p2p(dc);
 
-	if (directconn->inpa != 0)
-		purple_input_remove(directconn->inpa);
+	return FALSE;
+}
+
+/*
+ * This callback will be called when we're unable to connect to
+ * the remote host in DC_OUTGOING_TIMEOUT seconds.
+ */
+gboolean
+msn_dc_outgoing_connection_timeout_cb(gpointer data)
+{
+	MsnDirectConn *dc = data;
+
+	purple_debug_info("msn", "msn_dc_outgoing_connection_timeout_cb %p\n", dc);
+
+	g_return_val_if_fail(dc != NULL, FALSE);
+
+	dc->connect_timeout_handle = 0;
+
+	if (dc->connect_data != NULL) {
+		purple_proxy_connect_cancel(dc->connect_data);
+		dc->connect_data = NULL;
+	}
+
+	if (dc->ext_ip && dc->ext_port) {
+		/* Try external IP/port if available. */
+		dc->connect_data = purple_proxy_connect(
+			NULL,
+			dc->slpcall->slplink->session->account,
+			dc->ext_ip,
+			dc->ext_port,
+			msn_dc_connected_to_peer_cb,
+			dc
+		);
+
+		g_free(dc->ext_ip);
+		dc->ext_ip = NULL;
+
+		if (dc->connect_data) {
+			dc->connect_timeout_handle = purple_timeout_add_seconds(
+				DC_OUTGOING_TIMEOUT,
+				msn_dc_outgoing_connection_timeout_cb,
+				dc
+			);
+		} else {
+			/*
+			 * Connection failed
+			 * Fall back to P2P transfer
+			 */
+			msn_dc_outgoing_connection_timeout_cb(dc);
+		}
+
+	} else {
+		/*
+		 * Both internal and external connection attempts failed.
+		 * Fall back to p2p transfer.
+		 */
+		msn_dc_fallback_to_p2p(dc);
+	}
 
-	if (directconn->fd >= 0)
-		close(directconn->fd);
+	return FALSE;
+}
+
+/*
+ * This callback will be called when we're the server
+ * and somebody has connected to us in DC_INCOMING_TIMEOUT seconds.
+ */
+static void
+msn_dc_incoming_connection_cb(gpointer data, gint listenfd, PurpleInputCondition cond)
+{
+	MsnDirectConn *dc = data;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_incoming_connection_cb %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	if (dc->connect_timeout_handle != 0) {
+		purple_timeout_remove(dc->connect_timeout_handle);
+		dc->connect_timeout_handle = 0;
+	}
+
+	if (dc->listenfd_handle != 0) {
+		purple_input_remove(dc->listenfd_handle);
+		dc->listenfd_handle = 0;
+	}
+
+	dc->fd = accept(listenfd, NULL, 0);
+
+	purple_network_remove_port_mapping(dc->listenfd);
+	close(dc->listenfd);
+	dc->listenfd = -1;
+
+	if (dc->fd != -1) {
+		msn_dc_init(dc);
+		dc->state = DC_STATE_FOO;
+	}
+}
+
+void
+msn_dc_listen_socket_created_cb(int listenfd, gpointer data)
+{
+	MsnDirectConn *dc = data;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_listen_socket_created_cb %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	dc->listen_data = NULL;
+
+	if (listenfd != -1) {
+		const char *ext_ip;
+		const char *int_ip;
+		int port;
 
-	if (directconn->nonce != NULL)
-		g_free(directconn->nonce);
+		ext_ip = purple_network_get_my_ip(listenfd);
+		int_ip = purple_network_get_local_system_ip(listenfd);
+		port = purple_network_get_port_from_fd(listenfd);
+
+		dc->listenfd = listenfd;
+		dc->listenfd_handle = purple_input_add(
+			listenfd,
+			PURPLE_INPUT_READ,
+			msn_dc_incoming_connection_cb,
+			dc
+		);
+		dc->connect_timeout_handle = purple_timeout_add_seconds(
+			DC_INCOMING_TIMEOUT,
+			msn_dc_incoming_connection_timeout_cb,
+			dc
+		);
+
+		if (strcmp(int_ip, ext_ip) != 0) {
+			dc->msg_body = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: true\r\n"
+				"%sNonce: {%s}\r\n"
+				"IPv4External-Addrs: %s\r\n"
+				"IPv4External-Port: %d\r\n"
+				"IPv4Internal-Addrs: %s\r\n"
+				"IPv4Internal-Port: %d\r\n"
+				"\r\n",
 
-	directconn->slplink->directconn = NULL;
+				dc->nonce_type != DC_NONCE_PLAIN ? "Hashed-" : "",
+				dc->nonce_hash,
+				ext_ip,
+				port,
+				int_ip,
+				port
+			);
+
+		} else {
+			dc->msg_body = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: true\r\n"
+				"%sNonce: {%s}\r\n"
+				"IPv4External-Addrs: %s\r\n"
+				"IPv4External-Port: %d\r\n"
+				"\r\n",
 
-	g_free(directconn);
+				dc->nonce_type != DC_NONCE_PLAIN ? "Hashed-" : "",
+				dc->nonce_hash,
+				ext_ip,
+				port
+			);
+		}
+
+		if (dc->slpcall->wait_for_socket) {
+			if (dc->send_connection_info_msg_cb != NULL)
+				dc->send_connection_info_msg_cb(dc);
+
+			dc->slpcall->wait_for_socket = FALSE;
+		}
+	}
 }
+
--- a/libpurple/protocols/msn/directconn.h	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/directconn.h	Fri May 28 21:10:09 2010 +0000
@@ -26,36 +26,174 @@
 
 typedef struct _MsnDirectConn MsnDirectConn;
 
+#include "network.h"
+#include "proxy.h"
+#include "circbuffer.h"
+
 #include "msg.h"
 #include "slp.h"
 #include "slplink.h"
+#include "slpmsg.h"
+
+typedef enum
+{
+	DC_STATE_CLOSED,            /*< No socket opened yet */
+	DC_STATE_FOO,               /*< Waiting for FOO message */
+	DC_STATE_HANDSHAKE,         /*< Waiting for handshake message */
+	DC_STATE_HANDSHAKE_REPLY,   /*< Waiting for handshake reply message */
+	DC_STATE_ESTABLISHED        /*< Handshake complete */
+} MsnDirectConnState;
+
+typedef enum
+{
+	DC_PROCESS_OK = 0,
+	DC_PROCESS_ERROR,
+	DC_PROCESS_FALLBACK,
+	DC_PROCESS_CLOSE
+
+} MsnDirectConnProcessResult;
+
+typedef enum
+{
+	DC_NONCE_UNKNOWN,	/**< Invalid scheme */
+	DC_NONCE_PLAIN,     /**< No hashing */
+	DC_NONCE_SHA1       /**< First 16 bytes of SHA1 of nonce */
+
+} MsnDirectConnNonceType;
+
+typedef struct _MsnDirectConnPacket MsnDirectConnPacket;
+
+struct _MsnDirectConnPacket {
+	guint32     length;
+	guchar      *data;
+
+	void        (*sent_cb)(struct _MsnDirectConnPacket*);
+	MsnMessage  *msg;
+};
 
 struct _MsnDirectConn
 {
-	MsnSlpLink *slplink;
-	MsnSlpCall *initial_call;
+	MsnDirectConnState  state;      /**< Direct connection status */
+	MsnSlpLink          *slplink;   /**< The slplink using this direct connection */
+	MsnSlpCall          *slpcall;   /**< The slpcall which initiated the direct connection */
+	char                *msg_body;  /**< The body of message sent by send_connection_info_msg_cb */
+	MsnSlpMessage       *prev_ack;  /**< The saved SLP ACK message */
 
-	PurpleProxyConnectData *connect_data;
+	MsnDirectConnNonceType nonce_type;         /**< The type of nonce hashing */
+	guchar                 nonce[16];          /**< The nonce used for handshake */
+	gchar                  nonce_hash[37];     /**< The hash of nonce */
+	gchar                  remote_nonce[37];   /**< The remote side's nonce */
 
-	gboolean acked;
+	PurpleNetworkListenData *listen_data;           /**< The pending socket creation request */
+	PurpleProxyConnectData  *connect_data;          /**< The pending connection attempt */
+	int                     listenfd;               /**< The socket we're listening for incoming connections */
+	guint                   listenfd_handle;        /**< The timeout handle for incoming connection */
+	guint                   connect_timeout_handle; /**< The timeout handle for outgoing connection */
 
-	char *nonce;
+	int     fd;             /**< The direct connection socket */
+	guint   recv_handle;    /**< The incoming data callback handle */
+	guint   send_handle;    /**< The outgoing data callback handle */
 
-	int fd;
+	gchar   *in_buffer; /**< The receive buffer */
+	int     in_size;    /**< The receive buffer size */
+	int     in_pos;     /**< The first free position in receive buffer */
+	GQueue  *out_queue; /**< The outgoing packet queue */
+	int     msg_pos;    /**< The position of next byte to be sent in the actual packet */
 
-	int port;
-	int inpa;
+	MsnSlpHeader    header; /**< SLP header for parsing / serializing */
+
+	/** The callback used for sending information to the peer about the opened socket */
+	void (*send_connection_info_msg_cb)(MsnDirectConn *);
 
-	int c;
+	gchar   *ext_ip;    /**< Our external IP address */
+	int     ext_port;   /**< Our external port */
+
+	guint       timeout_handle;
+	gboolean    progress;
+
+	/*int   num_calls;*/  /**< The number of slpcalls using this direct connection */
 };
 
-MsnDirectConn *msn_directconn_new(MsnSlpLink *slplink);
-gboolean msn_directconn_connect(MsnDirectConn *directconn,
-								const char *host, int port);
-void msn_directconn_listen(MsnDirectConn *directconn);
-void msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg);
-void msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce);
-void msn_directconn_destroy(MsnDirectConn *directconn);
-void msn_directconn_send_handshake(MsnDirectConn *directconn);
+/* Outgoing attempt */
+#define DC_OUTGOING_TIMEOUT (5)
+/* Time for internal + external connection attempts */
+#define DC_INCOMING_TIMEOUT (DC_OUTGOING_TIMEOUT * 3)
+/* Timeout for lack of activity */
+#define DC_TIMEOUT          (60)
+
+/*
+ * Queues an MSN message to be sent via direct connection.
+ */
+void
+msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg);
+
+/*
+ * Creates, initializes, and returns a new MsnDirectConn structure.
+ */
+MsnDirectConn *
+msn_dc_new(MsnSlpCall *slplink);
+
+/*
+ * Destroys an MsnDirectConn structure. Frees every buffer allocated earlier
+ * restores saved callbacks, etc.
+ */
+void
+msn_dc_destroy(MsnDirectConn *dc);
+
+/*
+ * Fallback to switchboard connection. Used when neither side is able to
+ * create a listening socket.
+ */
+void
+msn_dc_fallback_to_p2p(MsnDirectConn *dc);
+
+/*
+ * Increases the slpcall counter in DC. The direct connection remains open
+ * until all slpcalls using it are destroyed.
+ */
+void
+msn_dc_ref(MsnDirectConn *dc);
+
+/*
+ * Decrease the slpcall counter in DC. The direct connection remains open
+ * until all slpcalls using it are destroyed.
+ */
+void
+msn_dc_unref(MsnDirectConn *dc);
+
+/*
+ * Sends a direct connect INVITE message on the associated slplink
+ * with the corresponding connection type and information.
+ */
+void
+msn_dc_send_invite(MsnDirectConn *dc);
+
+/*
+ * Sends a direct connect OK message as a response to an INVITE received earliaer
+ * on the corresponding slplink.
+ */
+void
+msn_dc_send_ok(MsnDirectConn *dc);
+
+/*
+ * This callback will be called when we're successfully connected to
+ * the remote host.
+ */
+void
+msn_dc_connected_to_peer_cb(gpointer data, gint fd, const gchar *error_msg);
+
+/*
+ * This callback will be called when we're unable to connect to
+ * the remote host in DC_CONNECT_TIMEOUT seconds.
+ */
+gboolean
+msn_dc_outgoing_connection_timeout_cb(gpointer data);
+
+/*
+ * This callback will be called when the listening socket is successfully
+ * created and its parameters (IP/port) are available.
+ */
+void
+msn_dc_listen_socket_created_cb(int listenfd, gpointer data);
 
 #endif /* MSN_DIRECTCONN_H */
--- a/libpurple/protocols/msn/msg.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/msg.c	Fri May 28 21:10:09 2010 +0000
@@ -1117,7 +1117,8 @@
 msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	GHashTable *body;
-	const gchar *guid;
+	const gchar *command;
+	const gchar *cookie;
 	gboolean accepted = FALSE;
 
 	g_return_if_fail(cmdproc != NULL);
@@ -1130,59 +1131,64 @@
 				"Unable to parse invite msg body.\n");
 		return;
 	}
-
-	guid = g_hash_table_lookup(body, "Application-GUID");
-
-	if (guid == NULL) {
-		const gchar *cmd = g_hash_table_lookup(
-				body, "Invitation-Command");
+	
+	/*
+	 * GUID is NOT always present but Invitation-Command and Invitation-Cookie
+	 * are mandatory.
+	 */
+	command = g_hash_table_lookup(body, "Invitation-Command");
+	cookie = g_hash_table_lookup(body, "Invitation-Cookie");
 
-		if (cmd && !strcmp(cmd, "CANCEL")) {
-			const gchar *code = g_hash_table_lookup(
-					body, "Cancel-Code");
-			purple_debug_info("msn",
-					"MSMSGS invitation cancelled: %s.\n",
-					code ? code : "no reason given");
-		} else
-			purple_debug_warning("msn", "Invite msg missing "
-					"Application-GUID.\n");
+	if (command == NULL || cookie == NULL) {
+		purple_debug_warning("msn",
+			"Invalid invitation message: either Invitation-Command "
+			"or Invitation-Cookie is missing or invalid.\n"
+		);
+		return;
 
-		accepted = TRUE;
-
-	} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
-		purple_debug_info("msn", "Computer call\n");
+	} else if (!strcmp(command, "INVITE")) {
+		const gchar	*guid = g_hash_table_lookup(body, "Application-GUID");
+	
+		if (guid == NULL) {
+			purple_debug_warning("msn",
+			                     "Invite msg missing Application-GUID.\n");
 
-		if (cmdproc->session) {
-			PurpleConversation *conv = NULL;
-			gchar *from = msg->remote_user;
-			gchar *buf = NULL;
+			accepted = TRUE;
+
+		} else if (!strcmp(guid, MSN_FT_GUID)) {
+
+		} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
+			purple_debug_info("msn", "Computer call\n");
 
-			if (from)
-				conv = purple_find_conversation_with_account(
-						PURPLE_CONV_TYPE_IM, from,
-						cmdproc->session->account);
-			if (conv)
-				buf = g_strdup_printf(
-						_("%s sent you a voice chat "
-						"invite, which is not yet "
-						"supported."), from);
-			if (buf) {
-				purple_conversation_write(conv, NULL, buf,
-						PURPLE_MESSAGE_SYSTEM |
-						PURPLE_MESSAGE_NOTIFY,
-						time(NULL));
-				g_free(buf);
+			if (cmdproc->session) {
+				PurpleConversation *conv = NULL;
+				gchar *from = msg->remote_user;
+				gchar *buf = NULL;
+
+				if (from)
+					conv = purple_find_conversation_with_account(
+							PURPLE_CONV_TYPE_IM, from,
+							cmdproc->session->account);
+				if (conv)
+					buf = g_strdup_printf(
+							_("%s sent you a voice chat "
+							"invite, which is not yet "
+							"supported."), from);
+				if (buf) {
+					purple_conversation_write(conv, NULL, buf,
+							PURPLE_MESSAGE_SYSTEM |
+							PURPLE_MESSAGE_NOTIFY,
+							time(NULL));
+					g_free(buf);
+				}
 			}
+		} else {
+			const gchar *application = g_hash_table_lookup(body, "Application-Name");
+			purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
+			                     guid, application ? application : "(null)");
 		}
-	} else {
-		const gchar *application = g_hash_table_lookup(body, "Application-Name");
-		purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
-		                     guid, application ? application : "(null)");
-	}
-
-	if (!accepted) {
-		const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie");
-		if (cookie) {
+		
+		if (!accepted) {
 			MsnSwitchBoard *swboard = cmdproc->data;
 			char *text;
 			MsnMessage *cancel;
@@ -1202,6 +1208,17 @@
 			msn_switchboard_send_msg(swboard, cancel, TRUE);
 			msn_message_destroy(cancel);
 		}
+
+	} else if (!strcmp(command, "CANCEL")) {
+		const gchar *code = g_hash_table_lookup(body, "Cancel-Code");
+		purple_debug_info("msn", "MSMSGS invitation cancelled: %s.\n",
+		                  code ? code : "no reason given");
+
+	} else {
+		/*
+		 * Some other already established invitation session.
+		 * Can be retrieved by Invitation-Cookie.
+		 */
 	}
 
 	g_hash_table_destroy(body);
--- a/libpurple/protocols/msn/slp.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/slp.c	Fri May 28 21:10:09 2010 +0000
@@ -25,23 +25,20 @@
 #include "slp.h"
 #include "slpcall.h"
 #include "slpmsg.h"
+#include "msnutils.h"
 
 #include "object.h"
 #include "user.h"
 #include "switchboard.h"
+#include "directconn.h"
 
 #include "smiley.h"
 
-/* ms to delay between sending buddy icon requests to the server. */
+/* seconds to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20
 
-static void send_ok(MsnSlpCall *slpcall, const char *branch,
-					const char *type, const char *content);
+static void request_user_display(MsnUser *user);
 
-static void send_decline(MsnSlpCall *slpcall, const char *branch,
-						 const char *type, const char *content);
-
-static void request_user_display(MsnUser *user);
 
 /**************************************************************************
  * Util
@@ -91,7 +88,7 @@
 	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 							  slpcall->session_id);
 
-	send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+	msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
 			content);
 
 	g_free(content);
@@ -120,7 +117,7 @@
 			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 									slpcall->session_id);
 
-			send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+			msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
 						content);
 
 			g_free(content);
@@ -199,6 +196,7 @@
 					  gsize size)
 {
 	PurpleXfer *xfer = slpcall->xfer;
+
 	purple_xfer_set_completed(xfer, TRUE);
 	purple_xfer_end(xfer);
 }
@@ -207,36 +205,8 @@
  * SLP Control
  **************************************************************************/
 
-#if 0
-static void
-got_transresp(MsnSlpCall *slpcall, const char *nonce,
-			  const char *ips_str, int port)
-{
-	MsnDirectConn *directconn;
-	char **ip_addrs, **c;
-
-	directconn = msn_directconn_new(slpcall->slplink);
-
-	directconn->initial_call = slpcall;
-
-	/* msn_directconn_parse_nonce(directconn, nonce); */
-	directconn->nonce = g_strdup(nonce);
-
-	ip_addrs = g_strsplit(ips_str, " ", -1);
-
-	for (c = ip_addrs; *c != NULL; c++)
-	{
-		purple_debug_info("msn", "ip_addr = %s\n", *c);
-		if (msn_directconn_connect(directconn, *c, port))
-			break;
-	}
-
-	g_strfreev(ip_addrs);
-}
-#endif
-
-static void
-send_ok(MsnSlpCall *slpcall, const char *branch,
+void
+msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch,
 		const char *type, const char *content)
 {
 	MsnSlpLink *slplink;
@@ -253,12 +223,10 @@
 	slpmsg->text_body = TRUE;
 
 	msn_slplink_queue_slpmsg(slplink, slpmsg);
-
-	msn_slpcall_session_init(slpcall);
 }
 
-static void
-send_decline(MsnSlpCall *slpcall, const char *branch,
+void
+msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch,
 			 const char *type, const char *content)
 {
 	MsnSlpLink *slplink;
@@ -309,6 +277,190 @@
 	return NULL;
 }
 
+static char *
+parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype)
+{
+	char *nonce;
+
+	*ntype = DC_NONCE_UNKNOWN;
+
+	nonce = get_token(content, "Hashed-Nonce: {", "}\r\n");
+	if (nonce) {
+		*ntype = DC_NONCE_SHA1;
+	} else {
+		guint32 n1, n5;
+		guint16 n2, n3, n4, n6;
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		if (nonce
+		 && sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+		           &n1, &n2, &n3, &n4, &n5, &n6) == 6) {
+			*ntype = DC_NONCE_PLAIN;
+			g_free(nonce);
+			nonce = g_malloc(16);
+			*(guint32 *)(nonce +  0) = GUINT32_TO_LE(n1);
+			*(guint16 *)(nonce +  4) = GUINT16_TO_LE(n2);
+			*(guint16 *)(nonce +  6) = GUINT16_TO_LE(n3);
+			*(guint16 *)(nonce +  8) = GUINT16_TO_BE(n4);
+			*(guint32 *)(nonce + 10) = GUINT32_TO_BE(n5);
+			*(guint16 *)(nonce + 14) = GUINT16_TO_BE(n6);
+		} else {
+			/* Invalid nonce, so ignore request */
+			g_free(nonce);
+			nonce = NULL;
+		}
+	}
+
+	return nonce;
+}
+
+static void
+msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content)
+{
+	/* A direct connection negotiation response */
+	char *bridge;
+	char *nonce;
+	char *listening;
+	MsnDirectConn *dc = slpcall->slplink->dc;
+	MsnDirectConnNonceType ntype;
+
+	purple_debug_info("msn", "process_transresp\n");
+
+	/* Direct connections are disabled. */
+	if (!purple_account_get_bool(slpcall->slplink->session->account, "direct_connect", TRUE))
+		return;
+
+	g_return_if_fail(dc != NULL);
+	g_return_if_fail(dc->state == DC_STATE_CLOSED);
+
+	bridge = get_token(content, "Bridge: ", "\r\n");
+	nonce = parse_dc_nonce(content, &ntype);
+	listening = get_token(content, "Listening: ", "\r\n");
+	if (listening && bridge && !strcmp(bridge, "TCPv1")) {
+		/* Ok, the client supports direct TCP connection */
+
+		/* We always need this. */
+		if (ntype == DC_NONCE_SHA1) {
+			strncpy(dc->remote_nonce, nonce, 36);
+			dc->remote_nonce[36] = '\0';
+		}
+
+		if (!strcasecmp(listening, "false")) {
+			if (dc->listen_data != NULL) {
+				/*
+				 * We'll listen for incoming connections but
+				 * the listening socket isn't ready yet so we cannot
+				 * send the INVITE packet now. Put the slpcall into waiting mode
+				 * and let the callback send the invite.
+				 */
+				slpcall->wait_for_socket = TRUE;
+
+			} else if (dc->listenfd != -1) {
+				/* The listening socket is ready. Send the INVITE here. */
+				msn_dc_send_invite(dc);
+
+			} else {
+				/* We weren't able to create a listener either. Use SB. */
+				msn_dc_fallback_to_p2p(dc);
+			}
+
+		} else {
+			/*
+			 * We should connect to the client so parse
+			 * IP/port from response.
+			 */
+			char *ip, *port_str;
+			int port = 0;
+
+			if (ntype == DC_NONCE_PLAIN) {
+				/* Only needed for listening side. */
+				memcpy(dc->nonce, nonce, 16);
+			}
+
+			/* Cancel any listen attempts because we don't need them. */
+			if (dc->listenfd_handle != 0) {
+				purple_input_remove(dc->listenfd_handle);
+				dc->listenfd_handle = 0;
+			}
+			if (dc->connect_timeout_handle != 0) {
+				purple_timeout_remove(dc->connect_timeout_handle);
+				dc->connect_timeout_handle = 0;
+			}
+			if (dc->listenfd != -1) {
+				purple_network_remove_port_mapping(dc->listenfd);
+				close(dc->listenfd);
+				dc->listenfd = -1;
+			}
+			if (dc->listen_data != NULL) {
+				purple_network_listen_cancel(dc->listen_data);
+				dc->listen_data = NULL;
+			}
+
+			/* Save external IP/port for later use. We'll try local connection first. */
+			dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4External-Port: ", "\r\n");
+			if (port_str) {
+				dc->ext_port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4Internal-Port: ", "\r\n");
+			if (port_str) {
+				port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			if (ip && port) {
+				/* Try internal address first */
+				dc->connect_data = purple_proxy_connect(
+					NULL,
+					slpcall->slplink->session->account,
+					ip,
+					port,
+					msn_dc_connected_to_peer_cb,
+					dc
+				);
+
+				if (dc->connect_data) {
+					/* Add connect timeout handle */
+					dc->connect_timeout_handle = purple_timeout_add_seconds(
+						DC_OUTGOING_TIMEOUT,
+						msn_dc_outgoing_connection_timeout_cb,
+						dc
+					);
+				} else {
+					/*
+					 * Connection failed
+					 * Try external IP/port (if specified)
+					 */
+					msn_dc_outgoing_connection_timeout_cb(dc);
+				}
+
+			} else {
+				/*
+				 * Omitted or invalid internal IP address / port
+				 * Try external IP/port (if specified)
+				 */
+				msn_dc_outgoing_connection_timeout_cb(dc);
+			}
+
+			g_free(ip);
+		}
+
+	} else {
+		/*
+		 * Invalid direct connect invitation or
+		 * TCP connection is not supported
+		 */
+	}
+
+	g_free(listening);
+	g_free(nonce);
+	g_free(bridge);
+
+	return;
+}
+
 static void
 got_sessionreq(MsnSlpCall *slpcall, const char *branch,
 			   const char *euf_guid, const char *context)
@@ -331,7 +483,7 @@
 		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 								  slpcall->session_id);
 
-		send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
+		msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
 				content);
 
 		g_free(content);
@@ -486,7 +638,7 @@
 	if (!accepted) {
 		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 		                                slpcall->session_id);
-		send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
+		msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
 		g_free(content);
 	}
 }
@@ -555,92 +707,105 @@
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
 	{
-		/* A direct connection? */
-
-		char *listening, *nonce;
-		char *content;
+		/* A direct connection negotiation request */
+		char *bridges;
+		char *nonce;
+		MsnDirectConnNonceType ntype;
 
-		if (FALSE)
-		{
-#if 0
-			MsnDirectConn *directconn;
-			/* const char *ip_addr; */
-			char *ip_port;
-			int port;
-
-			/* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */
-			ip_port = "5190";
-			listening = "true";
-			nonce = rand_guid();
-
-			directconn = msn_directconn_new(slplink);
-
-			/* msn_directconn_parse_nonce(directconn, nonce); */
-			directconn->nonce = g_strdup(nonce);
-
-			msn_directconn_listen(directconn);
+		purple_debug_info("msn", "got_invite: transreqbody received\n");
 
-			port = directconn->port;
-
-			content = g_strdup_printf(
+		/* Direct connections may be disabled. */
+		if (!purple_account_get_bool(slplink->session->account, "direct_connect", TRUE)) {
+			msn_slp_send_ok(slpcall, branch,
+				"application/x-msnmsgr-transrespbody",
 				"Bridge: TCPv1\r\n"
-				"Listening: %s\r\n"
-				"Nonce: {%s}\r\n"
-				"Ipv4Internal-Addrs: 192.168.0.82\r\n"
-				"Ipv4Internal-Port: %d\r\n"
-				"\r\n",
-				listening,
-				nonce,
-				port);
-#endif
-		}
-		else
-		{
-			listening = "false";
-			nonce = g_strdup("00000000-0000-0000-0000-000000000000");
+				"Listening: false\r\n"
+				"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+				"\r\n");
+			msn_slpcall_session_init(slpcall);
 
-			content = g_strdup_printf(
-				"Bridge: TCPv1\r\n"
-				"Listening: %s\r\n"
-				"Nonce: {%s}\r\n"
-				"\r\n",
-				listening,
-				nonce);
+			return;
 		}
 
-		send_ok(slpcall, branch,
-				"application/x-msnmsgr-transrespbody", content);
+		/* Don't do anything if we already have a direct connection */
+		if (slplink->dc != NULL)
+			return;
+
+		bridges = get_token(content, "Bridges: ", "\r\n");
+		nonce = parse_dc_nonce(content, &ntype);
+		if (bridges && strstr(bridges, "TCPv1") != NULL) {
+			/*
+			 * Ok, the client supports direct TCP connection
+			 * Try to create a listening port
+			 */
+			MsnDirectConn *dc;
+
+			dc = msn_dc_new(slpcall);
+			if (ntype == DC_NONCE_PLAIN) {
+				/* There is only one nonce for plain auth. */
+				dc->nonce_type = ntype;
+				memcpy(dc->nonce, nonce, 16);
+			} else if (ntype == DC_NONCE_SHA1) {
+				/* Each side has a nonce in SHA1 auth. */
+				dc->nonce_type = ntype;
+				strncpy(dc->remote_nonce, nonce, 36);
+				dc->remote_nonce[36] = '\0';
+			}
+
+			dc->listen_data = purple_network_listen_range(
+				0, 0,
+				SOCK_STREAM,
+				msn_dc_listen_socket_created_cb,
+				dc
+			);
+
+			if (dc->listen_data == NULL) {
+				/* Listen socket creation failed */
+
+				purple_debug_info("msn", "got_invite: listening failed\n");
 
-		g_free(content);
+				if (dc->nonce_type != DC_NONCE_PLAIN)
+					msn_slp_send_ok(slpcall, branch,
+						"application/x-msnmsgr-transrespbody",
+						"Bridge: TCPv1\r\n"
+						"Listening: false\r\n"
+						"Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+						"\r\n");
+				else
+					msn_slp_send_ok(slpcall, branch,
+						"application/x-msnmsgr-transrespbody",
+						"Bridge: TCPv1\r\n"
+						"Listening: false\r\n"
+						"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+						"\r\n");
+
+			} else {
+				/*
+				 * Listen socket created successfully.
+				 * Don't send anything here because we don't know the parameters
+				 * of the created socket yet. msn_dc_send_ok will be called from
+				 * the callback function: dc_listen_socket_created_cb
+				 */
+				purple_debug_info("msn", "got_invite: listening socket created\n");
+
+				dc->send_connection_info_msg_cb = msn_dc_send_ok;
+				slpcall->wait_for_socket = TRUE;
+			}
+
+		} else {
+			/*
+			 * Invalid direct connect invitation or
+			 * TCP connection is not supported.
+			 */
+		}
+
 		g_free(nonce);
+		g_free(bridges);
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
 	{
-#if 0
-		char *ip_addrs;
-		char *temp;
-		char *nonce;
-		int port;
-
-		nonce = get_token(content, "Nonce: {", "}\r\n");
-		if (ip_addrs == NULL)
-			return;
-
-		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
-
-		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
-		if (temp != NULL)
-			port = atoi(temp);
-		else
-			port = -1;
-		g_free(temp);
-
-		if (port > 0)
-			got_transresp(slpcall, nonce, ip_addrs, port);
-
-		g_free(nonce);
-		g_free(ip_addrs);
-#endif
+		/* A direct connection negotiation response */
+		msn_slp_process_transresp(slpcall, content);
 	}
 }
 
@@ -653,52 +818,104 @@
 
 	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
 	{
-#if 0
-		if (slpcall->type == MSN_SLPCALL_DC)
-		{
-			/* First let's try a DirectConnection. */
+		char *content;
+		char *header;
+		char *nonce = NULL;
+		MsnSession *session = slpcall->slplink->session;
+		MsnSlpMessage *msg;
+		MsnDirectConn *dc;
+		MsnUser *user;
+
+		if (!purple_account_get_bool(session->account, "direct_connect", TRUE)) {
+			/* Don't attempt a direct connection if disabled. */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+
+		if (slpcall->slplink->dc != NULL) {
+			/* If we already have an established direct connection
+			 * then just start the transfer.
+			 */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
 
-			MsnSlpLink *slplink;
-			MsnSlpMessage *slpmsg;
-			char *header;
-			char *content;
-			char *branch;
+		user = msn_userlist_find_user(session->userlist,
+		                              slpcall->slplink->remote_user);
+		if (!user || !(user->clientid & 0xF0000000))	{
+			/* Just start a normal SB transfer. */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+
+		/* Try direct file transfer by sending a second INVITE */
+		dc = msn_dc_new(slpcall);
+		slpcall->branch = rand_guid();
 
-			slplink = slpcall->slplink;
+		dc->listen_data = purple_network_listen_range(
+			0, 0,
+			SOCK_STREAM,
+			msn_dc_listen_socket_created_cb,
+			dc
+		);
 
-			branch = rand_guid();
+		header = g_strdup_printf(
+			"INVITE MSNMSGR:%s MSNSLP/1.0",
+			slpcall->slplink->remote_user
+		);
+
+		if (dc->nonce_type == DC_NONCE_SHA1)
+			nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash);
+
+		if (dc->listen_data == NULL) {
+			/* Listen socket creation failed */
+			purple_debug_info("msn", "got_ok: listening failed\n");
 
 			content = g_strdup_printf(
-				"Bridges: TRUDPv1 TCPv1\r\n"
+				"Bridges: TCPv1\r\n"
+				"NetID: %u\r\n"
+				"Conn-Type: IP-Restrict-NAT\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+				"%s"
+				"\r\n",
+
+				rand() % G_MAXUINT32,
+				nonce ? nonce : ""
+			);
+
+		} else {
+			/* Listen socket created successfully. */
+			purple_debug_info("msn", "got_ok: listening socket created\n");
+
+			content = g_strdup_printf(
+				"Bridges: TCPv1\r\n"
 				"NetID: 0\r\n"
 				"Conn-Type: Direct-Connect\r\n"
 				"UPnPNat: false\r\n"
 				"ICF: false\r\n"
-			);
-
-			header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0",
-									 slplink->remote_user);
+				"%s"
+				"\r\n",
 
-			slpmsg = msn_slp_sipmsg_new(slpcall, 0, header, branch,
-										"application/x-msnmsgr-transreqbody",
-										content);
-
-			slpmsg->info = "SLP INVITE";
-			slpmsg->text_body = TRUE;
-			msn_slplink_send_slpmsg(slplink, slpmsg);
+				nonce ? nonce : ""
+			);
+		}
 
-			g_free(header);
-			g_free(content);
+		msg = msn_slpmsg_sip_new(
+			slpcall,
+			0,
+			header,
+			slpcall->branch,
+			"application/x-msnmsgr-transreqbody",
+			content
+		);
+		msg->info = "DC INVITE";
+		msg->text_body = TRUE;
+		g_free(nonce);
+		g_free(header);
+		g_free(content);
 
-			g_free(branch);
-		}
-		else
-		{
-			msn_slpcall_session_init(slpcall);
-		}
-#else
-		msn_slpcall_session_init(slpcall);
-#endif
+		msn_slplink_queue_slpmsg(slpcall->slplink, msg);
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
 	{
@@ -707,31 +924,7 @@
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
 	{
-#if 0
-		char *ip_addrs;
-		char *temp;
-		char *nonce;
-		int port;
-
-		nonce = get_token(content, "Nonce: {", "}\r\n");
-		if (ip_addrs == NULL)
-			return;
-
-		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
-
-		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
-		if (temp != NULL)
-			port = atoi(temp);
-		else
-			port = -1;
-		g_free(temp);
-
-		if (port > 0)
-			got_transresp(slpcall, nonce, ip_addrs, port);
-
-		g_free(nonce);
-		g_free(ip_addrs);
-#endif
+		msn_slp_process_transresp(slpcall, content);
 	}
 }
 
@@ -774,18 +967,25 @@
 
 		content = get_token(body, "\r\n\r\n", NULL);
 
-		if (branch && call_id && content_type && content)
+		slpcall = NULL;
+		if (branch && call_id)
 		{
-			slpcall = msn_slpcall_new(slplink);
-			slpcall->id = call_id;
-			got_invite(slpcall, branch, content_type, content);
-		}
-		else
-		{
-			g_free(call_id);
-			slpcall = NULL;
+			slpcall = msn_slplink_find_slp_call(slplink, call_id);
+			if (slpcall)
+			{
+				g_free(slpcall->branch);
+				slpcall->branch = g_strdup(branch);
+				got_invite(slpcall, branch, content_type, content);
+			}
+			else if (content_type && content)
+			{
+				slpcall = msn_slpcall_new(slplink);
+				slpcall->id = g_strdup(call_id);
+				got_invite(slpcall, branch, content_type, content);
+			}
 		}
 
+		g_free(call_id);
 		g_free(branch);
 		g_free(content_type);
 		g_free(content);
@@ -867,6 +1067,8 @@
 {
 	MsnSession *session;
 	MsnSlpLink *slplink;
+	const char *data;
+	gsize len;
 
 	session = cmdproc->servconn->session;
 	slplink = msn_session_get_slplink(session, msg->remote_user);
@@ -889,7 +1091,9 @@
 		}
 	}
 
-	msn_slplink_process_msg(slplink, msg);
+	data = msn_message_get_bin_data(msg, &len);
+
+	msn_slplink_process_msg(slplink, &msg->msnslp_header, data, len);
 }
 
 static void
--- a/libpurple/protocols/msn/slp.h	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/slp.h	Fri May 28 21:10:09 2010 +0000
@@ -51,6 +51,14 @@
 
 MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink,
 							  const char *body);
+void
+msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch,
+		const char *type, const char *content);
+
+void
+msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch,
+			 const char *type, const char *content);
+
 
 void send_bye(MsnSlpCall *slpcall, const char *type);
 
--- a/libpurple/protocols/msn/slpcall.h	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/slpcall.h	Fri May 28 21:10:09 2010 +0000
@@ -64,6 +64,8 @@
 	gboolean started; /**< A flag that states if this slpcall's session has
 						been initiated. */
 
+	gboolean wait_for_socket;
+
 	void (*progress_cb)(MsnSlpCall *slpcall,
 						gsize total_length, gsize len, gsize offset);
 	void (*session_init_cb)(MsnSlpCall *slpcall);
--- a/libpurple/protocols/msn/slplink.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/slplink.c	Fri May 28 21:10:09 2010 +0000
@@ -101,10 +101,11 @@
 
 	session = slplink->session;
 
-#if 0
-	if (slplink->directconn != NULL)
-		msn_directconn_destroy(slplink->directconn);
-#endif
+	if (slplink->dc != NULL) {
+		slplink->dc->slplink = NULL;
+		msn_dc_destroy(slplink->dc);
+		slplink->dc = NULL;
+	}
 
 	while (slplink->slp_calls != NULL)
 		msn_slpcall_destroy(slplink->slp_calls->data);
@@ -185,18 +186,36 @@
 		slplink->swboard->flag |= MSN_SB_FLAG_FT;
 
 	slplink->slp_calls = g_list_append(slplink->slp_calls, slpcall);
+
+	/*
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
+		msn_dc_ref(slplink->dc);
+	*/
 }
 
 void
 msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall)
 {
+	/*
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
+		msn_dc_unref(slplink->dc);
+	*/
+
 	slplink->slp_calls = g_list_remove(slplink->slp_calls, slpcall);
 
 	/* The slplink has no slpcalls in it, release it from MSN_SB_FLAG_FT.
 	 * If nothing else is using it then this might cause swboard to be
 	 * destroyed. */
-	if (slplink->slp_calls == NULL && slplink->swboard != NULL)
+	if (slplink->slp_calls == NULL && slplink->swboard != NULL) {
 		msn_switchboard_release(slplink->swboard, MSN_SB_FLAG_FT);
+		slplink->swboard = NULL;
+	}
+
+	/* The slplink has no slpcalls in it, release it from the DC. */
+	if (slplink->slp_calls == NULL && slplink->dc != NULL) {
+		msn_dc_destroy(slplink->dc);
+		slplink->dc = NULL;
+	}
 }
 
 MsnSlpCall *
@@ -236,16 +255,14 @@
 	return NULL;
 }
 
-static void
+void
 msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg)
 {
-#if 0
-	if (slplink->directconn != NULL)
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
 	{
-		msn_directconn_send_msg(slplink->directconn, msg);
+		msn_dc_enqueue_msg(slplink->dc, msg);
 	}
 	else
-#endif
 	{
 		if (slplink->swboard == NULL)
 		{
@@ -305,7 +322,7 @@
 #endif
 
 	slpmsg->msgs =
-		g_list_append(slpmsg->msgs, msg);
+		g_list_append(slpmsg->msgs, msn_message_ref(msg));
 	msn_slplink_send_msg(slplink, msg);
 
 	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||
@@ -364,6 +381,8 @@
 			}
 		}
 	}
+
+	msn_message_unref(msg);
 }
 
 /* We have received the message nak. */
@@ -377,6 +396,7 @@
 	msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
 
 	slpmsg->msgs = g_list_remove(slpmsg->msgs, msg);
+	msn_message_unref(msg);
 }
 
 static void
@@ -464,21 +484,29 @@
 	}
 }
 
-static void
-msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg)
+static MsnSlpMessage *
+msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
 {
 	MsnSlpMessage *slpmsg;
 
 	slpmsg = msn_slpmsg_new(slplink);
 
-	slpmsg->session_id = msg->msnslp_header.session_id;
-	slpmsg->size       = msg->msnslp_header.total_size;
+	slpmsg->session_id = header->session_id;
+	slpmsg->size       = header->total_size;
 	slpmsg->flags      = 0x02;
-	slpmsg->ack_id     = msg->msnslp_header.id;
-	slpmsg->ack_sub_id = msg->msnslp_header.ack_id;
-	slpmsg->ack_size   = msg->msnslp_header.total_size;
+	slpmsg->ack_id     = header->id;
+	slpmsg->ack_sub_id = header->ack_id;
+	slpmsg->ack_size   = header->total_size;
 	slpmsg->info = "SLP ACK";
 
+	return slpmsg;
+}
+
+static void
+msn_slplink_send_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
+{
+	MsnSlpMessage *slpmsg = msn_slplink_create_ack(slplink, header);
+
 	msn_slplink_send_slpmsg(slplink, slpmsg);
 	msn_slpmsg_destroy(slpmsg);
 }
@@ -490,6 +518,9 @@
 	PurpleXfer *xfer;
 
 	xfer = (PurpleXfer *)slpcall->xfer;
+	if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED)
+		return;
+
 	purple_xfer_ref(xfer);
 	purple_xfer_start(xfer, -1, NULL, 0);
 	if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) {
@@ -524,38 +555,27 @@
 }
 
 void
-msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg)
+msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len)
 {
 	MsnSlpMessage *slpmsg;
-	const char *data;
 	guint64 offset;
-	gsize len;
 	PurpleXfer *xfer = NULL;
 
-	if (purple_debug_is_verbose())
-		msn_slpmsg_show(msg);
-
-#ifdef MSN_DEBUG_SLP_FILES
-	debug_msg_to_file(msg, FALSE);
-#endif
-
-	if (msg->msnslp_header.total_size < msg->msnslp_header.length)
+	if (header->total_size < header->length)
 	{
 		purple_debug_error("msn", "This can't be good\n");
 		g_return_if_reached();
 	}
 
-	data = msn_message_get_bin_data(msg, &len);
-
-	offset = msg->msnslp_header.offset;
+	offset = header->offset;
 
 	if (offset == 0)
 	{
 		slpmsg = msn_slpmsg_new(slplink);
-		slpmsg->id = msg->msnslp_header.id;
-		slpmsg->session_id = msg->msnslp_header.session_id;
-		slpmsg->size = msg->msnslp_header.total_size;
-		slpmsg->flags = msg->msnslp_header.flags;
+		slpmsg->id = header->id;
+		slpmsg->session_id = header->session_id;
+		slpmsg->size = header->total_size;
+		slpmsg->flags = header->flags;
 
 		if (slpmsg->session_id)
 		{
@@ -600,7 +620,7 @@
 	}
 	else
 	{
-		slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id);
+		slpmsg = msn_slplink_message_find(slplink, header->session_id, header->id);
 		if (slpmsg == NULL)
 		{
 			/* Probably the transfer was canceled */
@@ -648,8 +668,7 @@
 		return;
 #endif
 
-	if (msg->msnslp_header.offset + msg->msnslp_header.length
-		>= msg->msnslp_header.total_size)
+	if (header->offset + header->length >= header->total_size)
 	{
 		/* All the pieces of the slpmsg have been received */
 		MsnSlpCall *slpcall;
@@ -661,32 +680,45 @@
 			return;
 		}
 
-		if (!slpcall->wasted) {
-			if (slpmsg->flags == 0x100)
-			{
-				MsnDirectConn *directconn;
+		purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n");
 
-				directconn = slplink->directconn;
+		if (/* !slpcall->wasted && */ slpmsg->flags == 0x100)
+		{
 #if 0
-				if (!directconn->acked)
-					msn_directconn_send_handshake(directconn);
+			MsnDirectConn *directconn;
+
+			directconn = slplink->directconn;
+			if (!directconn->acked)
+				msn_directconn_send_handshake(directconn);
 #endif
-			}
-			else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||  
-			         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||  
-			         slpmsg->flags == 0x1000030)
-			{
-				/* Release all the messages and send the ACK */
+		}
+		else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||
+		         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||
+		         slpmsg->flags == 0x1000030)
+		{
+			/* Release all the messages and send the ACK */
 
-				msn_slplink_send_ack(slplink, msg);
+			if (slpcall->wait_for_socket) {
+				/*
+				 * Save ack for later because we have to send
+				 * a 200 OK message to the previous direct connect
+				 * invitation before ACK but the listening socket isn't
+				 * created yet.
+				 */
+				purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n");
+
+				slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header);
+			} else if (!slpcall->wasted) {
+				purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n");
+
+				msn_slplink_send_ack(slplink, header);
 				msn_slplink_send_queued_slpmsgs(slplink);
 			}
-
 		}
 
 		msn_slpmsg_destroy(slpmsg);
 
-		if (slpcall->wasted)
+		if (!slpcall->wait_for_socket && slpcall->wasted)
 			msn_slpcall_destroy(slpcall);
 	}
 }
--- a/libpurple/protocols/msn/slplink.h	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/slplink.h	Fri May 28 21:10:09 2010 +0000
@@ -42,6 +42,7 @@
 {
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
+	MsnDirectConn *dc;
 
 	int refs;
 
@@ -49,8 +50,6 @@
 
 	int slp_seq_id;
 
-	MsnDirectConn *directconn;
-
 	GList *slp_calls;
 	GList *slp_msgs;
 
@@ -84,9 +83,10 @@
 void msn_slplink_send_slpmsg(MsnSlpLink *slplink,
 							 MsnSlpMessage *slpmsg);
 void msn_slplink_send_queued_slpmsgs(MsnSlpLink *slplink);
-void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg);
+void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len);
 void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer);
 
+void msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg);
 /* Only exported for msn_xfer_write */
 void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
 
--- a/libpurple/protocols/msn/slpmsg.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/slpmsg.c	Fri May 28 21:10:09 2010 +0000
@@ -67,7 +67,7 @@
 	if (slpmsg->img == NULL)
 		g_free(slpmsg->buffer);
 
-	for (cur = slpmsg->msgs; cur != NULL; cur = cur->next)
+	for (cur = slpmsg->msgs; cur != NULL; cur = g_list_delete_link(cur, cur))
 	{
 		/* Something is pointing to this slpmsg, so we should remove that
 		 * pointer to prevent a crash. */
@@ -78,8 +78,8 @@
 		msg->ack_cb = NULL;
 		msg->nak_cb = NULL;
 		msg->ack_data = NULL;
+		msn_message_unref(msg);
 	}
-	g_list_free(slpmsg->msgs);
 
 	slplink->slp_msgs = g_list_remove(slplink->slp_msgs, slpmsg);
 
--- a/libpurple/protocols/msn/state.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/state.c	Fri May 28 21:10:09 2010 +0000
@@ -260,7 +260,7 @@
 
 	if (msnobj == NULL)
 	{
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d", state_text, caps);
+		msn_cmdproc_send(cmdproc, "CHG", "%s %u", state_text, caps);
 	}
 	else
 	{
@@ -268,7 +268,7 @@
 
 		msnobj_str = msn_object_to_string(msnobj);
 
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d %s", state_text,
+		msn_cmdproc_send(cmdproc, "CHG", "%s %u %s", state_text,
 						 caps, purple_url_encode(msnobj_str));
 
 		g_free(msnobj_str);
--- a/libpurple/protocols/msn/switchboard.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Fri May 28 21:10:09 2010 +0000
@@ -87,8 +87,17 @@
 		purple_timeout_remove(swboard->reconn_timeout_h);
 
 	/* If it linked us is because its looking for trouble */
-	while (swboard->slplinks != NULL)
-		msn_slplink_destroy(swboard->slplinks->data);
+	while (swboard->slplinks != NULL) {
+		MsnSlpLink *slplink = swboard->slplinks->data;
+
+		/* Destroy only those slplinks which use the switchboard */
+		if (slplink->dc == NULL)
+			msn_slplink_destroy(slplink);
+		else {
+			swboard->slplinks = g_list_remove(swboard->slplinks, slplink);
+			slplink->swboard = NULL;
+		}
+	}
 
 	/* Destroy the message queue */
 	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)
--- a/libpurple/protocols/qq/qq_base.c	Fri May 28 21:08:49 2010 +0000
+++ b/libpurple/protocols/qq/qq_base.c	Fri May 28 21:10:09 2010 +0000
@@ -811,11 +811,11 @@
 static void captcha_input_cancel_cb(qq_captcha_request *captcha_req,
 		PurpleRequestFields *fields)
 {
-	captcha_request_destory(captcha_req);
-
 	purple_connection_error_reason(captcha_req->gc,
 			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
 			_("Failed captcha verification"));
+
+	captcha_request_destory(captcha_req);
 }
 
 static void captcha_input_ok_cb(qq_captcha_request *captcha_req,
--- a/pidgin/gtkdocklet-gtk.c	Fri May 28 21:08:49 2010 +0000
+++ b/pidgin/gtkdocklet-gtk.c	Fri May 28 21:10:09 2010 +0000
@@ -38,7 +38,13 @@
 static void
 docklet_gtk_status_clicked_cb(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data)
 {
-	pidgin_docklet_clicked(button); 
+	purple_debug_info("docklet", "The button is %u\n", button);
+#ifdef GDK_WINDOWING_QUARTZ
+	/* You can only click left mouse button on MacOSX native GTK. Let that be the menu */
+	pidgin_docklet_clicked(3);
+#else
+	pidgin_docklet_clicked(button);
+#endif
 }
 
 static void
--- a/pidgin/pixmaps/Makefile.am	Fri May 28 21:08:49 2010 +0000
+++ b/pidgin/pixmaps/Makefile.am	Fri May 28 21:10:09 2010 +0000
@@ -482,6 +482,8 @@
 		tray/16/message_4bit.ico \
 		tray/16/offline_4bit.ico
 
+TRAY_THEME =	tray/hicolor/index.theme
+
 TRAY_16 = \
 		tray/hicolor/16x16/status/pidgin-tray-away.png \
 		tray/hicolor/16x16/status/pidgin-tray-busy.png \
@@ -584,6 +586,7 @@
 		$(TOOLBAR_22) \
 		$(TOOLBAR_32) \
 		$(TOOLBAR_48) \
+		$(TRAY_THEME) \
 		$(TRAY_16) \
 		$(TRAY_16_ICO) \
 		$(TRAY_22) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pixmaps/tray/hicolor/index.theme	Fri May 28 21:10:09 2010 +0000
@@ -0,0 +1,33 @@
+[Icon Theme]
+Name=Pidgin
+Comment=Icon theme for Pidgin tray icons
+Hidden=True
+Directories=16x16/status,22x22/status,32x32/status,48x48/status,scalable/status
+
+[16x16/status]
+Size=16
+Context=Status
+Type=Threshold
+
+[22x22/status]
+Size=22
+Context=Status
+Type=Threshold
+
+[32x32/status]
+Size=32
+Context=Status
+Type=Threshold
+
+[48x48/status]
+Size=48
+Context=Status
+Type=Threshold
+
+[scalable/status]
+MinSize=1
+Size=128
+MaxSize=256
+Context=Status
+Type=Scalable
+
--- a/pidgin/win32/nsis/generate_gtk_zip.sh	Fri May 28 21:08:49 2010 +0000
+++ b/pidgin/win32/nsis/generate_gtk_zip.sh	Fri May 28 21:10:09 2010 +0000
@@ -45,14 +45,15 @@
 	FILE=$(basename $URL)
 	if [ ! -e $FILE ]; then
 		echo Downloading $NAME
-		wget $URL
+		wget $URL || return 1
 	fi
 	EXTENSION=${FILE##*.}
 	#This is an OpenSuSE build service RPM
 	if [ $EXTENSION == 'rpm' ]; then
+		echo "Generating zip from $FILE"
 		FILE=$(../rpm2zip.sh $FILE)
 	fi
-	unzip -q $FILE -d $INSTALL_DIR
+	unzip -q $FILE -d $INSTALL_DIR || exit 1
 	echo "$NAME" >> $CONTENTS_FILE
 }
 
@@ -78,3 +79,5 @@
 #Generate zip file to be included in installer
 zip -9 -r ../gtk-runtime-$BUNDLE_VERSION.zip Gtk
 
+exit 0
+
--- a/pidgin/win32/nsis/nsis_translations.desktop.in	Fri May 28 21:08:49 2010 +0000
+++ b/pidgin/win32/nsis/nsis_translations.desktop.in	Fri May 28 21:10:09 2010 +0000
@@ -29,6 +29,8 @@
 _PIDGINDESKTOPSHORTCUTDESC=Create a shortcut to Pidgin on the Desktop
 #Installer Subsection Detailed Description
 _PIDGINSTARTMENUSHORTCUTDESC=Create a Start Menu entry for Pidgin
+#Installer Subsection Detailed Description
+_GTKSECTIONDESCRIPTION=A multi-platform GUI toolkit, used by Pidgin
 #Installer Subsection Text
 _DEBUGSYMBOLSSECTIONTITLE=Debug Symbols (for reporting crashes)
 
--- a/po/ca.po	Fri May 28 21:08:49 2010 +0000
+++ b/po/ca.po	Fri May 28 21:10:09 2010 +0000
@@ -33,8 +33,8 @@
 msgstr ""
 "Project-Id-Version: Pidgin\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-05-17 23:18-0400\n"
-"PO-Revision-Date: 2010-05-11 22:10+0200\n"
+"POT-Creation-Date: 2010-05-25 21:38+0200\n"
+"PO-Revision-Date: 2010-05-25 21:51+0200\n"
 "Last-Translator: Josep Puigdemont i Casamajó <josep.puigdemont@gmail.com>\n"
 "Language-Team: Catalan <tradgnome@softcatala.net>\n"
 "MIME-Version: 1.0\n"
@@ -1385,6 +1385,7 @@
 msgid "Saved Statuses"
 msgstr "Estats desats"
 
+#. title
 msgid "Title"
 msgstr "Títol"
 
@@ -1947,7 +1948,6 @@
 msgid "Thread creation failure: %s"
 msgstr "S'ha produït un error en crear un fil: %s"
 
-#. Data is assumed to be the destination bn
 msgid "Unknown reason"
 msgstr "Motiu desconegut"
 
@@ -3223,6 +3223,9 @@
 msgstr "UIN"
 
 #. first name
+#. purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) );
+#. optional information
+#. purple_notify_user_info_add_pair( info, _( "Title" ), profile->title );
 msgid "First Name"
 msgstr "Nom"
 
@@ -3967,6 +3970,7 @@
 msgid "Postal Code"
 msgstr "Codi postal"
 
+#. purple_notify_user_info_add_pair( info, _( "Email" ), profile->email );
 msgid "Country"
 msgstr "País"
 
@@ -3981,8 +3985,6 @@
 msgid "Organization Unit"
 msgstr "Secció de l'organització"
 
-#. title
-#. optional information
 msgid "Job Title"
 msgstr "Títol de la feina"
 
@@ -5736,6 +5738,9 @@
 msgid "Show custom smileys"
 msgstr "Mostra emoticones personalitzades"
 
+msgid "Allow direct connections"
+msgstr "Permet connexions directes"
+
 msgid "nudge: nudge a user to get their attention"
 msgstr "nudge: doneu un cop de colze a un usuari perquè us pari atenció"
 
@@ -5976,6 +5981,9 @@
 "Encara no s'ha pogut recuperar la informació del vostre perfil. Torneu-ho a "
 "intentar més tard."
 
+msgid "Your MXitId"
+msgstr "El vostre MXitID"
+
 #. pin
 msgid "PIN"
 msgstr "PIN"
@@ -6127,7 +6135,11 @@
 msgid "Status Message"
 msgstr "Missatge d'estat"
 
+msgid "Rejection Message"
+msgstr "Missatge de rebuig"
+
 # Segons la viquipèdia
+#. hidden number
 msgid "Hidden Number"
 msgstr "Nombre ocult"
 
@@ -6934,6 +6946,63 @@
 msgid "Invalid chat room name"
 msgstr "El nom de sala de xat no és vàlid"
 
+msgid "Invalid error"
+msgstr "Error invàlid"
+
+msgid "Cannot receive IM due to parental controls"
+msgstr "Els controls parentals no permeten que es pugui rebre MI"
+
+msgid "Cannot send SMS without accepting terms"
+msgstr "No es poden enviar SMS si no s'accepten els termes"
+
+msgid "Cannot send SMS"
+msgstr "No s'ha pogut enviar l'SMS"
+
+#. SMS_WITHOUT_DISCLAIMER is weird
+msgid "Cannot send SMS to this country"
+msgstr "No es poden enviar SMS a aquest país"
+
+#. Undocumented
+msgid "Cannot send SMS to unknown country"
+msgstr "No es poden enviar SMS a un país desconegut"
+
+msgid "Bot accounts cannot initiate IMs"
+msgstr "Els robots no poden iniciar MI"
+
+msgid "Bot account cannot IM this user"
+msgstr "Aquest robot no pot fer MI amb aquest usuari"
+
+msgid "Bot account reached IM limit"
+msgstr "Aquest robot ha superat el límit de MI"
+
+msgid "Bot account reached daily IM limit"
+msgstr "Aquest robot ha superat el límit de MI diari"
+
+msgid "Bot account reached monthly IM limit"
+msgstr "Aquest robot ha superat el límit de MI mensual"
+
+msgid "Unable to receive offline messages"
+msgstr "No s'han pogut rebre els missatges fora de línia"
+
+msgid "Offline message store full"
+msgstr "El dipòsit per a missatge de fora de línia és ple"
+
+#, c-format
+msgid "Unable to send message: %s (%s)"
+msgstr "No s'ha pogut enviar el missatge: %s (%s)"
+
+#, c-format
+msgid "Unable to send message: %s"
+msgstr "No s'ha pogut enviar el missatge: %s"
+
+#, c-format
+msgid "Unable to send message to %s: %s (%s)"
+msgstr "No s'ha pogut enviar el missatge a %s: %s (%s)"
+
+#, c-format
+msgid "Unable to send message to %s: %s"
+msgstr "No s'ha pogut enviar el missatge a %s: %s"
+
 msgid "Thinking"
 msgstr "Pensant"
 
@@ -7087,116 +7156,6 @@
 msgid "File %s is %s, which is larger than the maximum size of %s."
 msgstr "El fitxer %s és %s, que és més gran que la mida màxima de %s."
 
-msgid "Invalid error"
-msgstr "Error invàlid"
-
-msgid "Invalid SNAC"
-msgstr "SNAC invàlid"
-
-msgid "Rate to host"
-msgstr "Velocitat cap a l'ordinador"
-
-msgid "Rate to client"
-msgstr "Velocitat cap al client"
-
-msgid "Service unavailable"
-msgstr "Servei no disponible"
-
-msgid "Service not defined"
-msgstr "Servei no definit"
-
-msgid "Obsolete SNAC"
-msgstr "SNAC obsolet"
-
-msgid "Not supported by host"
-msgstr "El servidor no ho permet"
-
-msgid "Not supported by client"
-msgstr "El client no ho permet"
-
-msgid "Refused by client"
-msgstr "Rebutjat pel client"
-
-msgid "Reply too big"
-msgstr "Resposta massa gran"
-
-msgid "Responses lost"
-msgstr "S'han perdut respostes"
-
-msgid "Request denied"
-msgstr "Petició denegada"
-
-msgid "Busted SNAC payload"
-msgstr "Càrrega SNAC malmesa"
-
-msgid "Insufficient rights"
-msgstr "Drets insuficients"
-
-msgid "In local permit/deny"
-msgstr "En la llista de permès/denegat local"
-
-msgid "Warning level too high (sender)"
-msgstr "Nivell d'avís massa alt (remitent)"
-
-msgid "Warning level too high (receiver)"
-msgstr "Nivell d'avís massa alt (receptor)"
-
-msgid "User temporarily unavailable"
-msgstr "Usuari no disponible temporalment"
-
-msgid "No match"
-msgstr "Cap coincidència"
-
-msgid "List overflow"
-msgstr "Sobreeiximent de la llista"
-
-msgid "Request ambiguous"
-msgstr "Petició ambigua"
-
-msgid "Queue full"
-msgstr "Cua plena"
-
-msgid "Not while on AOL"
-msgstr "No es pot fer mentre estigui a AOL"
-
-msgid "Cannot receive IM due to parental controls"
-msgstr "Els controls parentals no permeten que es pugui rebre MI"
-
-msgid "Cannot send SMS without accepting terms"
-msgstr "No es poden enviar SMS si no s'accepten els termes"
-
-msgid "Cannot send SMS"
-msgstr "No s'ha pogut enviar l'SMS"
-
-#. SMS_WITHOUT_DISCLAIMER is weird
-msgid "Cannot send SMS to this country"
-msgstr "No es poden enviar SMS a aquest país"
-
-#. Undocumented
-msgid "Cannot send SMS to unknown country"
-msgstr "No es poden enviar SMS a un país desconegut"
-
-msgid "Bot accounts cannot initiate IMs"
-msgstr "Els robots no poden iniciar MI"
-
-msgid "Bot account cannot IM this user"
-msgstr "Aquest robot no pot fer MI amb aquest usuari"
-
-msgid "Bot account reached IM limit"
-msgstr "Aquest robot ha superat el límit de MI"
-
-msgid "Bot account reached daily IM limit"
-msgstr "Aquest robot ha superat el límit de MI diari"
-
-msgid "Bot account reached monthly IM limit"
-msgstr "Aquest robot ha superat el límit de MI mensual"
-
-msgid "Unable to receive offline messages"
-msgstr "No s'han pogut rebre els missatges fora de línia"
-
-msgid "Offline message store full"
-msgstr "El dipòsit per a missatge de fora de línia és ple"
-
 msgid ""
 "(There was an error receiving this message.  The buddy you are speaking with "
 "is probably using a different encoding than expected.  If you know what "
@@ -7548,28 +7507,9 @@
 msgstr[1] "Heu perdut %hu missatges de %s per motius desconeguts."
 
 #, c-format
-msgid "Unable to send message: %s (%s)"
-msgstr "No s'ha pogut enviar el missatge: %s (%s)"
-
-#, c-format
-msgid "Unable to send message: %s"
-msgstr "No s'ha pogut enviar el missatge: %s"
-
-#, c-format
-msgid "Unable to send message to %s: %s (%s)"
-msgstr "No s'ha pogut enviar el missatge a %s: %s (%s)"
-
-#, c-format
-msgid "Unable to send message to %s: %s"
-msgstr "No s'ha pogut enviar el missatge a %s: %s"
-
-#, c-format
 msgid "User information not available: %s"
 msgstr "La informació de l'usuari no està disponible: %s"
 
-msgid "Unknown reason."
-msgstr "Motiu desconegut."
-
 msgid "Online Since"
 msgstr "En línia des de"
 
@@ -15302,6 +15242,7 @@
 msgid "This plugin is useful for debugging XMPP servers or clients."
 msgstr "Aquest connector és útil per a depurar servidors o clients XMPP."
 
+#. $(^Name) is the current Version name (e.g. Pidgin 2.7.0).  $_CLICK will become a translated version of "Click Next to continue."
 msgid ""
 "$(^Name) is released under the GNU General Public License (GPL). The license "
 "is provided here for information purposes only. $_CLICK"
@@ -15309,6 +15250,7 @@
 "$(^Name) és distribuït sota llicència GPL. Podeu consultar la llicència, "
 "només per proposits informatius, aquí.  $_CLICK"
 
+#. Installer Subsection Detailed Description
 msgid "A multi-platform GUI toolkit, used by Pidgin"
 msgstr "Una eina IGU multiplataforma, utilitzada per Pidgin"
 
@@ -15319,75 +15261,99 @@
 "Hi ha una instància del Pidgin executant-se. Surt del Pidgin i torna a "
 "intentar-ho."
 
+#. Installer Subsection Detailed Description
 msgid "Core Pidgin files and dlls"
 msgstr "Fitxers i dlls del nucli de Pidgin"
 
+#. Installer Subsection Detailed Description
 msgid "Create a Start Menu entry for Pidgin"
 msgstr "Crear una entrada Pidgin al Menu Inici"
 
+#. Installer Subsection Detailed Description
 msgid "Create a shortcut to Pidgin on the Desktop"
 msgstr "Afegir un enllaç directe al Pidgin a l'Escriptori"
 
+#. Installer Subsection Text
 msgid "Debug Symbols (for reporting crashes)"
-msgstr ""
-
+msgstr "Símbols de depuració (per informar d'errades)"
+
+#. Installer Subsection Text
 msgid "Desktop"
 msgstr "Escriptori"
 
+#. $R2 will display the URL that the GTK+ Runtime failed to download from
 msgid ""
 "Error Downloading the GTK+ Runtime ($R2).$\\rThis is required for Pidgin to "
 "function; if retrying fails, you may need to use the 'Offline Installer' "
 "from http://pidgin.im/download/windows/ ."
 msgstr ""
-
+"S'ha produït un error en baixar l'entorn d'execució GTK+ ($R2).$\\rEl Pidgin "
+"requereix aquest entorn per a poder-se executar. Si ho torneu a provar i no "
+"funciona, haureu de fer servir l'instal·lador «fora de línia» de http://"
+"pidgin.im/download/windows/ ."
+
+#. $R2 will display the URL that the Debug Symbols failed to download from
 msgid ""
 "Error Installing Debug Symbols ($R2).$\\rIf retrying fails, you may need to "
 "use the 'Offline Installer' from http://pidgin.im/download/windows/ ."
 msgstr ""
-
+"S'ha produït un error en instal·lar els símbols de depuració ($R2).$\\rSi "
+"falla en reintentar-ho, haureu de fer servir l'instal·lador «fora de línia» "
+"de http://pidgin.im/download/windows/ ."
+
+#. $R3 will display the URL that the Dictionary failed to download from
 #, no-c-format
 msgid ""
 "Error Installing Spellchecking ($R3).$\\rIf retrying fails, manual "
 "installation instructions are at: http://developer.pidgin.im/wiki/Installing%"
 "20Pidgin#manual_win32_spellcheck_installation"
 msgstr ""
-
-#, fuzzy
+"S'ha produït un error en instal·lar el corrector ortogràfic ($R3).$\\rSi "
+"falla en reintentar-ho, podeu seguir les instruccions d'instal·lació manual "
+"d'aquí: http://developer.pidgin.im/wiki/Installing%"
+"20Pidgin#manual_win32_spellcheck_installation"
+
+#. Installer Subsection Text
 msgid "GTK+ Runtime (required if not present)"
-msgstr "Entorn d'Execució GTK+ (necessari)"
-
-#, fuzzy
+msgstr "Entorn d'Execució GTK+ (necessari si no està instal·lat)"
+
+#. Installer Subsection Text
 msgid "Localizations"
-msgstr "Ubicació"
-
-#. License Page
+msgstr "Localitzacions"
+
+#. "Next >" appears on a button on the License Page of the Installer
 msgid "Next >"
 msgstr "Següent >"
 
-#. Components Page
+#. Installer Subsection Text
 msgid "Pidgin Instant Messaging Client (required)"
 msgstr "Client Pidgin de Missatgeria Instantània (necessari)"
 
-#. GTK+ Section Prompts
 msgid ""
 "Pidgin requires a compatible GTK+ Runtime (which doesn't appear to be "
 "already present).$\\rAre you sure you want to skip installing the GTK+ "
 "Runtime?"
 msgstr ""
-
+"El Pidgin requereix un entorn d'execució GTK+ compatible (que no sembla que "
+"tingueu instal·lat).$\\rEsteu segur que en voleu ometre la instal·lació?"
+
+#. Installer Subsection Text
 msgid "Shortcuts"
 msgstr "Enllaços directes"
 
+#. Installer Subsection Detailed Description
 msgid "Shortcuts for starting Pidgin"
 msgstr "Enllaços directes per iniciar el Pidgin"
 
-#. Spellcheck Section Prompts
+#. Installer Subsection Text
 msgid "Spellchecking Support"
 msgstr "Suport a la Verificació de l'Ortografia "
 
+#. Installer Subsection Text
 msgid "Start Menu"
 msgstr "Menu Inici"
 
+#. Installer Subsection Detailed Description
 msgid ""
 "Support for Spellchecking.  (Internet connection required for installation)"
 msgstr ""
@@ -15397,7 +15363,6 @@
 msgid "The installer is already running."
 msgstr "L'instal.lador encara està executant-se."
 
-#. Uninstall Section Prompts
 msgid ""
 "The uninstaller could not find registry entries for Pidgin.$\\rIt is likely "
 "that another user installed this application."
@@ -15405,23 +15370,96 @@
 "L'instal.lador podria no trobar les entrades del registre de Pidgin.$"
 "\\rProbablement un altre usuari ha instal.lat aquesta aplicació."
 
-#. URL Handler section
+#. Installer Subsection Text
 msgid "URI Handlers"
-msgstr ""
-
-#. Pidgin Section Prompts and Texts
+msgstr "Gestors d'URI"
+
 msgid ""
 "Unable to uninstall the currently installed version of Pidgin. The new "
 "version will be installed without removing the currently installed version."
 msgstr ""
-
-#. Installer Finish Page
+"No s'ha pogut desinstal·lar la versió actual del Pidgin. La nova versió "
+"s'instal·larà sense suprimir-ne la que hi ha actualment instal·lada."
+
+#. Text displayed on Installer Finish Page
 msgid "Visit the Pidgin Web Page"
 msgstr "Visita la pàgina web de Pidgin per Windows"
 
 msgid "You do not have permission to uninstall this application."
 msgstr "No tens permís per desinstal.lar aquesta aplicació."
 
+#~ msgid "Invalid SNAC"
+#~ msgstr "SNAC invàlid"
+
+#~ msgid "Rate to host"
+#~ msgstr "Velocitat cap a l'ordinador"
+
+#~ msgid "Rate to client"
+#~ msgstr "Velocitat cap al client"
+
+#~ msgid "Service unavailable"
+#~ msgstr "Servei no disponible"
+
+#~ msgid "Service not defined"
+#~ msgstr "Servei no definit"
+
+#~ msgid "Obsolete SNAC"
+#~ msgstr "SNAC obsolet"
+
+#~ msgid "Not supported by host"
+#~ msgstr "El servidor no ho permet"
+
+#~ msgid "Not supported by client"
+#~ msgstr "El client no ho permet"
+
+#~ msgid "Refused by client"
+#~ msgstr "Rebutjat pel client"
+
+#~ msgid "Reply too big"
+#~ msgstr "Resposta massa gran"
+
+#~ msgid "Responses lost"
+#~ msgstr "S'han perdut respostes"
+
+#~ msgid "Request denied"
+#~ msgstr "Petició denegada"
+
+#~ msgid "Busted SNAC payload"
+#~ msgstr "Càrrega SNAC malmesa"
+
+#~ msgid "Insufficient rights"
+#~ msgstr "Drets insuficients"
+
+#~ msgid "In local permit/deny"
+#~ msgstr "En la llista de permès/denegat local"
+
+#~ msgid "Warning level too high (sender)"
+#~ msgstr "Nivell d'avís massa alt (remitent)"
+
+#~ msgid "Warning level too high (receiver)"
+#~ msgstr "Nivell d'avís massa alt (receptor)"
+
+#~ msgid "User temporarily unavailable"
+#~ msgstr "Usuari no disponible temporalment"
+
+#~ msgid "No match"
+#~ msgstr "Cap coincidència"
+
+#~ msgid "List overflow"
+#~ msgstr "Sobreeiximent de la llista"
+
+#~ msgid "Request ambiguous"
+#~ msgstr "Petició ambigua"
+
+#~ msgid "Queue full"
+#~ msgstr "Cua plena"
+
+#~ msgid "Not while on AOL"
+#~ msgstr "No es pot fer mentre estigui a AOL"
+
+#~ msgid "Unknown reason."
+#~ msgstr "Motiu desconegut."
+
 #~ msgid "Current Mood"
 #~ msgstr "Estat d'ànim actual"
 
--- a/po/de.po	Fri May 28 21:08:49 2010 +0000
+++ b/po/de.po	Fri May 28 21:10:09 2010 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-05-20 12:11+0200\n"
-"PO-Revision-Date: 2010-05-20 12:08+0200\n"
+"POT-Creation-Date: 2010-05-26 10:42+0200\n"
+"PO-Revision-Date: 2010-05-26 10:42+0200\n"
 "Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -1360,6 +1360,7 @@
 msgid "Saved Statuses"
 msgstr "Gespeicherter Status"
 
+#. title
 msgid "Title"
 msgstr "Titel"
 
@@ -3205,6 +3206,9 @@
 msgstr "UIN"
 
 #. first name
+#. purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) );
+#. optional information
+#. purple_notify_user_info_add_pair( info, _( "Title" ), profile->title );
 msgid "First Name"
 msgstr "Vorname"
 
@@ -3957,6 +3961,7 @@
 msgid "Postal Code"
 msgstr "Postleitzahl"
 
+#. purple_notify_user_info_add_pair( info, _( "Email" ), profile->email );
 msgid "Country"
 msgstr "Land"
 
@@ -3971,8 +3976,6 @@
 msgid "Organization Unit"
 msgstr "Organisationseinheit"
 
-#. title
-#. optional information
 msgid "Job Title"
 msgstr "Beruf"
 
@@ -5736,6 +5739,9 @@
 msgid "Show custom smileys"
 msgstr "Zeige benutzerdefinierte Smileys"
 
+msgid "Allow direct connections"
+msgstr "Erlaube direkte Verbindungen"
+
 msgid "nudge: nudge a user to get their attention"
 msgstr "nudge: Einen Kontakt anstoßen, um seine Aufmerksamkeit zu erhalten"
 
@@ -5982,6 +5988,9 @@
 "Ihre Profil-Informationen wurden noch nicht abgerufen. Bitte versuchen Sie "
 "es später noch einmal."
 
+msgid "Your MXitId"
+msgstr "Ihre MXit-ID"
+
 #. pin
 msgid "PIN"
 msgstr "PIN"
@@ -6141,6 +6150,10 @@
 msgid "Status Message"
 msgstr "Status-Nachricht"
 
+msgid "Rejection Message"
+msgstr "Ablehnungsnachricht"
+
+#. hidden number
 msgid "Hidden Number"
 msgstr "Versteckte Nummer"
 
@@ -15277,6 +15290,10 @@
 "$(^Name) wird unter der GNU General Public License (GPL) veröffentlicht. Die "
 "Lizenz dient hier nur der Information. $_CLICK"
 
+#. Installer Subsection Detailed Description
+msgid "A multi-platform GUI toolkit, used by Pidgin"
+msgstr "Ein Multi-Plattform-GUI-Toolkit, verwendet von Pidgin"
+
 msgid ""
 "An instance of Pidgin is currently running.  Please exit Pidgin and try "
 "again."
@@ -15486,9 +15503,6 @@
 #~ msgid "Unknown reason."
 #~ msgstr "Unbekannter Grund."
 
-#~ msgid "A multi-platform GUI toolkit, used by Pidgin"
-#~ msgstr "Ein Multi-Plattform-GUI-Toolkit, verwendet von Pidgin"
-
 #~ msgid "Current Mood"
 #~ msgstr "Momentane Stimmung"
 
--- a/po/fr.po	Fri May 28 21:08:49 2010 +0000
+++ b/po/fr.po	Fri May 28 21:10:09 2010 +0000
@@ -21,8 +21,8 @@
 msgstr ""
 "Project-Id-Version: Pidgin\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-05-17 23:18-0400\n"
-"PO-Revision-Date: 2010-02-11 19:10+0100\n"
+"POT-Creation-Date: 2010-05-22 17:13+0200\n"
+"PO-Revision-Date: 2010-02-22 17:08+0200\n"
 "Last-Translator: Éric Boumaour <zongo_fr@users.sourceforge.net>\n"
 "Language-Team: fr <fr@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -1366,6 +1366,7 @@
 msgid "Saved Statuses"
 msgstr "États prédéfinis"
 
+#. title
 msgid "Title"
 msgstr "Titre"
 
@@ -1659,13 +1660,11 @@
 msgid "Set User Info"
 msgstr "Modifier les informations"
 
-#, fuzzy
 msgid "This protocol does not support setting a public alias."
-msgstr "Les salons de discussions ne sont pas supportés par ce protocole."
-
-#, fuzzy
+msgstr "Ce protocole ne permet pas de choisir un alias public."
+
 msgid "This protocol does not support fetching the public alias."
-msgstr "Les salons de discussions ne sont pas supportés par ce protocole."
+msgstr "Ce protocole ne permet pas de récupérer un alias public."
 
 msgid "Unknown"
 msgstr "Inconnu"
@@ -1924,7 +1923,6 @@
 msgid "Thread creation failure: %s"
 msgstr "Échec de la création de processus :%s"
 
-#. Data is assumed to be the destination bn
 msgid "Unknown reason"
 msgstr "Raison inconnue"
 
@@ -3216,6 +3214,9 @@
 msgstr "UIN"
 
 #. first name
+#. purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) );
+#. optional information
+#. purple_notify_user_info_add_pair( info, _( "Title" ), profile->title );
 msgid "First Name"
 msgstr "Prénom"
 
@@ -3867,38 +3868,36 @@
 msgid "SASL error: %s"
 msgstr "Erreur SASL : %s"
 
-#, fuzzy
 msgid "Invalid Encoding"
-msgstr "Condition de saisie non valide."
-
-#, fuzzy
+msgstr "Encodage non valide."
+
 msgid "Unsupported Extension"
-msgstr "Version non supportée"
+msgstr "Extension non supportée"
 
 msgid ""
 "Unexpected response from the server.  This may indicate a possible MITM "
 "attack"
 msgstr ""
+"Réponse inattendue du serveur. Ceci pourrait provenir d'une attaque MITM."
 
 msgid ""
 "The server does support channel binding, but did not appear to advertise "
 "it.  This indicates a likely MITM attack"
 msgstr ""
-
-#, fuzzy
+"Ce serveur ne supporte pas le \"channel binding\" alors qu'il dit le "
+"supporter. Ceci pourrait provenir d'une attaque MITM."
+
 msgid "Server does not support channel binding"
-msgstr "Le serveur ne supporte pas le blocage"
-
-#, fuzzy
+msgstr "Le serveur ne supporte pas le \"channel binding\"."
+
 msgid "Unsupported channel binding method"
-msgstr "Codage de caractère non supporté"
+msgstr "Méthode de \"channel binding\" non supportée"
 
 msgid "User not found"
 msgstr "Utilisateur non trouvé"
 
-#, fuzzy
 msgid "Invalid Username Encoding"
-msgstr "Nom d'utilisateur non valide"
+msgstr "Encodage du nom d'utilisateur non valide"
 
 msgid "Resource Constraint"
 msgstr "Limitation sur la ressource"
@@ -3966,6 +3965,7 @@
 msgid "Postal Code"
 msgstr "Code postal"
 
+#. purple_notify_user_info_add_pair( info, _( "Email" ), profile->email );
 msgid "Country"
 msgstr "Pays"
 
@@ -3980,8 +3980,6 @@
 msgid "Organization Unit"
 msgstr "Service"
 
-#. title
-#. optional information
 msgid "Job Title"
 msgstr "Position"
 
@@ -4222,9 +4220,8 @@
 msgid "Invalid XMPP ID"
 msgstr "Identifiant XMPP non valide."
 
-#, fuzzy
 msgid "Invalid XMPP ID. Username portion must be set."
-msgstr "Identifiant XMPP non valide. Le domaine doit être saisi."
+msgstr "Identifiant XMPP non valide. Le nom d'utilisateur doit être saisi."
 
 msgid "Invalid XMPP ID. Domain must be set."
 msgstr "Identifiant XMPP non valide. Le domaine doit être saisi."
@@ -4360,13 +4357,11 @@
 msgid "Allow Buzz"
 msgstr "Autoriser les Buzz"
 
-#, fuzzy
 msgid "Mood Name"
-msgstr "Deuxième prénom"
-
-#, fuzzy
+msgstr "Nom de l'humeur"
+
 msgid "Mood Comment"
-msgstr "Commentaire"
+msgstr "Commentaire de l'humeur"
 
 #. primitive
 #. ID
@@ -4661,9 +4656,8 @@
 msgid "Initiate Media"
 msgstr "Session média"
 
-#, fuzzy
 msgid "Account does not support PEP, can't set mood"
-msgstr "Les salons de discussions ne sont pas supportés par ce protocole."
+msgstr "Ce compte ne supporte pas PEP, impossible de changer l'humeur."
 
 msgid "config:  Configure a chat room."
 msgstr "config : Configurer un salon de discussions"
@@ -4721,9 +4715,8 @@
 msgid "buzz: Buzz a user to get their attention"
 msgstr "buzz : Faire sonner chez un contact pour attirer son attention."
 
-#, fuzzy
 msgid "mood: Set current user mood"
-msgstr "Choisissez un des utilisateurs"
+msgstr "mood : Changer d'humeur pour l'utilisateur"
 
 msgid "Extended Away"
 msgstr "Longue absence"
@@ -4807,13 +4800,13 @@
 "envoyée."
 
 msgid "XMPP stream header missing"
-msgstr ""
+msgstr "En-tête de flux XMPP manquante"
 
 msgid "XMPP Version Mismatch"
-msgstr ""
+msgstr "Version XMPP incorrecte"
 
 msgid "XMPP stream missing ID"
-msgstr ""
+msgstr "ID de flux XMPP manquant"
 
 msgid "XML Parse error"
 msgstr "Erreur de lecture du XML"
@@ -4894,31 +4887,26 @@
 "Veuillez sélectionner parmi les ressources de %s, à laquelle vous voulez "
 "envoyer un fichier"
 
-#, fuzzy
 msgid "Afraid"
-msgstr "Arabe"
-
-#, fuzzy
+msgstr "Apeuré"
+
 msgid "Amazed"
-msgstr "Honteux"
-
-#, fuzzy
+msgstr "Impressionné"
+
 msgid "Amorous"
-msgstr "Glorieux"
+msgstr "Amoureux"
 
 msgid "Angry"
 msgstr "En colère"
 
-#, fuzzy
 msgid "Annoyed"
-msgstr "Banni"
+msgstr "Ennuyé"
 
 msgid "Anxious"
 msgstr "Anxieux"
 
-#, fuzzy
 msgid "Aroused"
-msgstr "Vous envoyez"
+msgstr "Excité"
 
 msgid "Ashamed"
 msgstr "Honteux"
@@ -4926,152 +4914,125 @@
 msgid "Bored"
 msgstr "Ennuyé"
 
-#, fuzzy
 msgid "Brave"
-msgstr "Enregistrer"
-
-#, fuzzy
+msgstr "Courageux"
+
 msgid "Calm"
-msgstr "Domaine"
-
-#, fuzzy
+msgstr "Calme"
+
 msgid "Cautious"
-msgstr "Discussions"
-
-#, fuzzy
+msgstr "Prudent"
+
 msgid "Cold"
-msgstr "Gras"
-
-#, fuzzy
+msgstr "Froid"
+
 msgid "Confident"
-msgstr "Conflit"
-
-#, fuzzy
+msgstr "Confiant"
+
 msgid "Confused"
-msgstr "Continuer"
-
-#, fuzzy
+msgstr "Déconcerté"
+
 msgid "Contemplative"
-msgstr "Contact"
-
-#, fuzzy
+msgstr "Penseur"
+
 msgid "Contented"
-msgstr "Connecté"
-
-#, fuzzy
+msgstr "Content"
+
 msgid "Cranky"
-msgstr "Société"
+msgstr "À cran"
 
 msgid "Crazy"
-msgstr ""
-
-#, fuzzy
+msgstr "Fou"
+
 msgid "Creative"
-msgstr "Créer"
-
-#, fuzzy
+msgstr "Créatif"
+
 msgid "Curious"
-msgstr "Glorieux"
-
-#, fuzzy
+msgstr "Curieux"
+
 msgid "Dejected"
-msgstr "Refusé"
-
-#, fuzzy
+msgstr "Découragé"
+
 msgid "Depressed"
-msgstr "Supprimé"
-
-#, fuzzy
+msgstr "Déprimé"
+
 msgid "Disappointed"
-msgstr "Déconnecté"
+msgstr "Déçu"
 
 msgid "Disgusted"
-msgstr ""
-
-#, fuzzy
+msgstr "Dégouté"
+
 msgid "Dismayed"
-msgstr "Désactivé"
-
-#, fuzzy
+msgstr "Consterné"
+
 msgid "Distracted"
-msgstr "Détaché"
+msgstr "Distrait"
 
 msgid "Embarrassed"
-msgstr ""
-
-#, fuzzy
+msgstr "Embarrassé"
+
 msgid "Envious"
-msgstr "Anxieux"
+msgstr "Jaloux"
 
 msgid "Excited"
 msgstr "Excité"
 
-#, fuzzy
 msgid "Flirtatious"
-msgstr "Glorieux"
-
-#, fuzzy
+msgstr "Flirtant"
+
 msgid "Frustrated"
-msgstr "Prénom :"
+msgstr "Frustré"
 
 msgid "Grateful"
-msgstr ""
-
-#, fuzzy
+msgstr "Reconnaissant"
+
 msgid "Grieving"
-msgstr "Récupération en cours..."
+msgstr "Chagriné"
 
 msgid "Grumpy"
 msgstr "Grognon"
 
-#, fuzzy
 msgid "Guilty"
-msgstr "Localité"
+msgstr "Coupable"
 
 msgid "Happy"
 msgstr "Heureux"
 
 msgid "Hopeful"
-msgstr ""
+msgstr "Plein d'espoir"
 
 msgid "Hot"
 msgstr "Chaud"
 
 msgid "Humbled"
-msgstr ""
+msgstr "Humble"
 
 msgid "Humiliated"
-msgstr ""
-
-#, fuzzy
+msgstr "Humilié"
+
 msgid "Hungry"
-msgstr "En colère"
-
-#, fuzzy
+msgstr "Affamé"
+
 msgid "Hurt"
-msgstr "Humour"
+msgstr "Blessé"
 
 msgid "Impressed"
-msgstr ""
-
-#, fuzzy
+msgstr "Impressionné"
+
 msgid "In awe"
-msgstr "Amoureux"
+msgstr "Admiratif"
 
 msgid "In love"
 msgstr "Amoureux"
 
-#, fuzzy
 msgid "Indignant"
-msgstr "Indonésien"
-
-#, fuzzy
+msgstr "Indigné"
+
 msgid "Interested"
-msgstr "Intérêts"
-
-#, fuzzy
+msgstr "Intéressé"
+
 msgid "Intoxicated"
-msgstr "Invité"
+msgstr "Intoxiqué"
 
 msgid "Invincible"
 msgstr "Invincible"
@@ -5079,83 +5040,68 @@
 msgid "Jealous"
 msgstr "Jaloux"
 
-#, fuzzy
 msgid "Lonely"
-msgstr "Singe"
-
-#, fuzzy
+msgstr "Seul"
+
 msgid "Lost"
-msgstr "Le plus fort"
+msgstr "Perdu"
 
 msgid "Lucky"
-msgstr ""
-
-#, fuzzy
+msgstr "Chanceux"
+
 msgid "Mean"
-msgstr "Allemand"
-
-#, fuzzy
+msgstr "Méchant"
+
 msgid "Moody"
-msgstr "Humeur"
+msgstr "Morose"
 
 msgid "Nervous"
-msgstr ""
-
-#, fuzzy
+msgstr "Nerveux"
+
 msgid "Neutral"
-msgstr "Détail"
-
-#, fuzzy
+msgstr "Neutre"
+
 msgid "Offended"
-msgstr "Déconnecté"
+msgstr "Offensé"
 
 msgid "Outraged"
-msgstr ""
-
-#, fuzzy
+msgstr "Outragé"
+
 msgid "Playful"
-msgstr "Jouer"
-
-#, fuzzy
+msgstr "Joueur"
+
 msgid "Proud"
-msgstr "Fort"
-
-#, fuzzy
+msgstr "Fier"
+
 msgid "Relaxed"
-msgstr "Nom réel"
-
-#, fuzzy
+msgstr "Relaxé"
+
 msgid "Relieved"
-msgstr "Reçu"
-
-#, fuzzy
+msgstr "Soulagé"
+
 msgid "Remorseful"
-msgstr "Supprimer"
-
-#, fuzzy
+msgstr "Pris de remords"
+
 msgid "Restless"
-msgstr "S'enregistrer"
+msgstr "Agité"
 
 msgid "Sad"
 msgstr "Triste"
 
-#, fuzzy
 msgid "Sarcastic"
-msgstr "Marâthî"
+msgstr "Sarcastique"
 
 msgid "Satisfied"
-msgstr ""
-
-#, fuzzy
+msgstr "Satisfait"
+
 msgid "Serious"
-msgstr "Glorieux"
-
-#, fuzzy
+msgstr "Sérieux"
+
 msgid "Shocked"
-msgstr "Bloqué"
+msgstr "Choqué"
 
 msgid "Shy"
-msgstr ""
+msgstr "Timide"
 
 msgid "Sick"
 msgstr "Malade"
@@ -5165,40 +5111,34 @@
 msgstr "Somnolant"
 
 msgid "Spontaneous"
-msgstr ""
-
-#, fuzzy
+msgstr "Spontané"
+
 msgid "Stressed"
-msgstr "Vitesse"
-
-#, fuzzy
+msgstr "Stressé"
+
 msgid "Strong"
-msgstr "Chanson"
+msgstr "Fort"
 
 msgid "Surprised"
-msgstr ""
+msgstr "Surpris"
 
 msgid "Thankful"
-msgstr ""
+msgstr "Remerciant"
 
 msgid "Thirsty"
-msgstr ""
-
-#, fuzzy
+msgstr "Assoiffé"
+
 msgid "Tired"
-msgstr "Fire"
-
-#, fuzzy
+msgstr "Fatigué"
+
 msgid "Undefined"
-msgstr "Souligné"
-
-#, fuzzy
+msgstr "Indéfini"
+
 msgid "Weak"
-msgstr "Frapper"
-
-#, fuzzy
+msgstr "Faible"
+
 msgid "Worried"
-msgstr "Ennuyé"
+msgstr "Inquiet"
 
 msgid "Set User Nickname"
 msgstr "Changer de pseudo"
@@ -5800,6 +5740,9 @@
 msgid "Show custom smileys"
 msgstr "Afficher les frimousses personnalisées"
 
+msgid "Allow direct connections"
+msgstr "Autoriser les connexions directes"
+
 msgid "nudge: nudge a user to get their attention"
 msgstr "nudge : donner un « Nudge » à un contact pour attirer son attention."
 
@@ -6029,6 +5972,9 @@
 "Les informations de votre profil n'ont pu être récupérées. Veuillez "
 "réessayer plus tard."
 
+msgid "Your MXitId"
+msgstr "Votre MXitId"
+
 #. pin
 msgid "PIN"
 msgstr "Code"
@@ -6183,6 +6129,10 @@
 msgid "Status Message"
 msgstr "Messages d'état"
 
+msgid "Rejection Message"
+msgstr "Message de refus"
+
+#. hidden number
 msgid "Hidden Number"
 msgstr "Numéro caché"
 
@@ -6962,9 +6912,9 @@
 msgid "AOL does not allow your screen name to authenticate here"
 msgstr "AOL n'autorise pas votre nom d'utilisateur pour authentification ici."
 
-#, fuzzy, c-format
+#, c-format
 msgid "Error requesting %s"
-msgstr "Erreur à la demande de %s : %s"
+msgstr "Erreur à la demande de %s"
 
 msgid "Could not join chat room"
 msgstr "Impossible de rejoindre le salon de discussions"
@@ -6972,105 +6922,146 @@
 msgid "Invalid chat room name"
 msgstr "Nom de salon non valide"
 
+msgid "Invalid error"
+msgstr "Erreur non valide"
+
+msgid "Cannot receive IM due to parental controls"
+msgstr "Impossible de recevoir le message à cause du contrôle parental"
+
+msgid "Cannot send SMS without accepting terms"
+msgstr "Impossible d'envoyer de SMS sans accepter les termes"
+
+msgid "Cannot send SMS"
+msgstr "Impossible d'envoyer le SMS"
+
+#. SMS_WITHOUT_DISCLAIMER is weird
+msgid "Cannot send SMS to this country"
+msgstr "Impossible d'envoyer le SMS vers ce pays"
+
+#. Undocumented
+msgid "Cannot send SMS to unknown country"
+msgstr "Impossible d'envoyer le SMS vers un pays inconnu"
+
+msgid "Bot accounts cannot initiate IMs"
+msgstr "Les comptes Bot ne peuvent pas commencer les conversations"
+
+msgid "Bot account cannot IM this user"
+msgstr "Le compte Bot ne peut pas envoyer de message à cette personne"
+
+msgid "Bot account reached IM limit"
+msgstr "Le compte Bot a atteint sa limite de messages"
+
+msgid "Bot account reached daily IM limit"
+msgstr "Le compte Bot a atteint sa limite de messages quotidiens"
+
+msgid "Bot account reached monthly IM limit"
+msgstr "Le compte Bot a atteint sa limite de messages mensuels"
+
+msgid "Unable to receive offline messages"
+msgstr "Impossible de recevoir les messages déconnectés"
+
+msgid "Offline message store full"
+msgstr "Stockage des messages déconnectés plein"
+
+#, c-format
+msgid "Unable to send message: %s (%s)"
+msgstr "Impossible d'envoyer le message : %s (%s)"
+
+#, c-format
+msgid "Unable to send message: %s"
+msgstr "Impossible d'envoyer le message : %s"
+
+#, c-format
+msgid "Unable to send message to %s: %s (%s)"
+msgstr "Impossible d'envoyer le message vers %s : %s (%s)"
+
+#, c-format
+msgid "Unable to send message to %s: %s"
+msgstr "Impossible d'envoyer le message vers %s : %s"
+
 msgid "Thinking"
-msgstr ""
-
-#, fuzzy
+msgstr "En pleine réflexion"
+
 msgid "Shopping"
-msgstr "S'arrête d'écrire"
-
-#, fuzzy
+msgstr "En courses"
+
 msgid "Questioning"
-msgstr "Boite de message pour demande"
-
-#, fuzzy
+msgstr "En plein questionnement"
+
 msgid "Eating"
-msgstr "Bipeur"
-
-#, fuzzy
+msgstr "En train de manger"
+
 msgid "Watching a movie"
-msgstr "Joue"
+msgstr "Regarde un film"
 
 msgid "Typing"
 msgstr "En train d'écrire"
 
-#, fuzzy
 msgid "At the office"
-msgstr "Pas au travail"
+msgstr "Au travail"
 
 msgid "Taking a bath"
-msgstr ""
+msgstr "Prend un bain"
 
 msgid "Watching TV"
-msgstr ""
-
-#, fuzzy
+msgstr "Regarde la télé"
+
 msgid "Having fun"
-msgstr "Raccrocher"
-
-#, fuzzy
+msgstr "S'amuse"
+
 msgid "Sleeping"
-msgstr "Somnolant"
+msgstr "Dort"
 
 msgid "Using a PDA"
-msgstr ""
-
-#, fuzzy
+msgstr "Utilise un PDA"
+
 msgid "Meeting friends"
-msgstr "Amis de messagerie"
-
-#, fuzzy
+msgstr "Rencontre des amis"
+
 msgid "On the phone"
 msgstr "Au téléphone"
 
-#, fuzzy
 msgid "Surfing"
-msgstr "Récurrente"
+msgstr "Surf"
 
 #. "I am mobile." / "John is mobile."
 msgid "Mobile"
 msgstr "Téléphone portable"
 
 msgid "Searching the web"
-msgstr ""
+msgstr "Recherche sur internet"
 
 msgid "At a party"
-msgstr ""
+msgstr "À une fête"
 
 msgid "Having Coffee"
-msgstr ""
+msgstr "Prend un café"
 
 #. Playing video games
-#, fuzzy
 msgid "Gaming"
-msgstr "L'utilisateur joue"
+msgstr "Joue"
 
 msgid "Browsing the web"
-msgstr ""
-
-#, fuzzy
+msgstr "Surf le web"
+
 msgid "Smoking"
-msgstr "Chanson"
-
-#, fuzzy
+msgstr "Fume"
+
 msgid "Writing"
-msgstr "Travaille"
+msgstr "Écrit"
 
 #. Drinking [Alcohol]
-#, fuzzy
 msgid "Drinking"
-msgstr "Travaille"
+msgstr "Bois"
 
 msgid "Listening to music"
 msgstr "Écoute de la musique"
 
-#, fuzzy
 msgid "Studying"
-msgstr "Envoi en cours"
-
-#, fuzzy
+msgstr "Étudie"
+
 msgid "In the restroom"
-msgstr "Intérêts"
+msgstr "Aux toilettes"
 
 msgid "Received invalid data on connection with server"
 msgstr "Données non valides reçues à la connexion sur le serveur."
@@ -7140,116 +7131,6 @@
 msgstr ""
 "Le fichier %s fait %s, ce qui est plus gros que la taille maximale de %s."
 
-msgid "Invalid error"
-msgstr "Erreur non valide"
-
-msgid "Invalid SNAC"
-msgstr "SNAC non valide"
-
-msgid "Rate to host"
-msgstr "Fréquence vers l'hôte"
-
-msgid "Rate to client"
-msgstr "Fréquence vers le client"
-
-msgid "Service unavailable"
-msgstr "Service non disponible"
-
-msgid "Service not defined"
-msgstr "Service non défini"
-
-msgid "Obsolete SNAC"
-msgstr "SNAC obsolète"
-
-msgid "Not supported by host"
-msgstr "Non supporté par l'hôte"
-
-msgid "Not supported by client"
-msgstr "Non supporté par le client"
-
-msgid "Refused by client"
-msgstr "Refusé par le client"
-
-msgid "Reply too big"
-msgstr "Réponse trop grosse"
-
-msgid "Responses lost"
-msgstr "Réponses perdues"
-
-msgid "Request denied"
-msgstr "Requête refusée"
-
-msgid "Busted SNAC payload"
-msgstr "Charge SNAC incorrecte"
-
-msgid "Insufficient rights"
-msgstr "Droits insuffisants"
-
-msgid "In local permit/deny"
-msgstr "Dans l'autorisation/interdiction locale"
-
-msgid "Warning level too high (sender)"
-msgstr "Niveau d'avertissement trop élevé (émission)"
-
-msgid "Warning level too high (receiver)"
-msgstr "Niveau d'avertissement trop élevé (réception)"
-
-msgid "User temporarily unavailable"
-msgstr "L'utilisateur est temporairement indisponible."
-
-msgid "No match"
-msgstr "Aucun résultat"
-
-msgid "List overflow"
-msgstr "Dépassement de liste"
-
-msgid "Request ambiguous"
-msgstr "Requête ambiguë"
-
-msgid "Queue full"
-msgstr "File d'attente pleine"
-
-msgid "Not while on AOL"
-msgstr "Impossible sur AOL"
-
-msgid "Cannot receive IM due to parental controls"
-msgstr "Impossible de recevoir le message à cause du contrôle parental"
-
-msgid "Cannot send SMS without accepting terms"
-msgstr "Impossible d'envoyer de SMS sans accepter les termes"
-
-msgid "Cannot send SMS"
-msgstr "Impossible d'envoyer le SMS"
-
-#. SMS_WITHOUT_DISCLAIMER is weird
-msgid "Cannot send SMS to this country"
-msgstr "Impossible d'envoyer le SMS vers ce pays"
-
-#. Undocumented
-msgid "Cannot send SMS to unknown country"
-msgstr "Impossible d'envoyer le SMS vers un pays inconnu"
-
-msgid "Bot accounts cannot initiate IMs"
-msgstr "Les comptes Bot ne peuvent pas commencer les conversations"
-
-msgid "Bot account cannot IM this user"
-msgstr "Le compte Bot ne peut pas envoyer de message à cette personne"
-
-msgid "Bot account reached IM limit"
-msgstr "Le compte Bot a atteint sa limite de messages"
-
-msgid "Bot account reached daily IM limit"
-msgstr "Le compte Bot a atteint sa limite de messages quotidiens"
-
-msgid "Bot account reached monthly IM limit"
-msgstr "Le compte Bot a atteint sa limite de messages mensuels"
-
-msgid "Unable to receive offline messages"
-msgstr "Impossible de recevoir les messages déconnectés"
-
-msgid "Offline message store full"
-msgstr "Stockage des messages déconnectés plein"
-
 msgid ""
 "(There was an error receiving this message.  The buddy you are speaking with "
 "is probably using a different encoding than expected.  If you know what "
@@ -7286,7 +7167,7 @@
 msgstr "Jeux"
 
 msgid "ICQ Xtraz"
-msgstr ""
+msgstr "ICQ Xtraz"
 
 msgid "Add-Ins"
 msgstr "Modules"
@@ -7354,25 +7235,20 @@
 msgid "Invisible"
 msgstr "Invisible"
 
-#, fuzzy
 msgid "Evil"
-msgstr "Courriel"
-
-#, fuzzy
+msgstr "Méchant"
+
 msgid "Depression"
-msgstr "Profession"
-
-#, fuzzy
+msgstr "Dépression"
+
 msgid "At home"
-msgstr "À mon propos"
-
-#, fuzzy
+msgstr "À la maison"
+
 msgid "At work"
-msgstr "Réseau"
-
-#, fuzzy
+msgstr "Au travail"
+
 msgid "At lunch"
-msgstr "Parti manger"
+msgstr "Déjeuner"
 
 msgid "IP Address"
 msgstr "Adresse IP"
@@ -7614,28 +7490,9 @@
 msgstr[1] "Vous avez raté %hu messages de %s pour des raisons inconnues."
 
 #, c-format
-msgid "Unable to send message: %s (%s)"
-msgstr "Impossible d'envoyer le message : %s (%s)"
-
-#, c-format
-msgid "Unable to send message: %s"
-msgstr "Impossible d'envoyer le message : %s"
-
-#, c-format
-msgid "Unable to send message to %s: %s (%s)"
-msgstr "Impossible d'envoyer le message vers %s : %s (%s)"
-
-#, c-format
-msgid "Unable to send message to %s: %s"
-msgstr "Impossible d'envoyer le message vers %s : %s"
-
-#, c-format
 msgid "User information not available: %s"
 msgstr "Les informations ne sont pas disponibles : %s"
 
-msgid "Unknown reason."
-msgstr "Erreur inconnue"
-
 msgid "Online Since"
 msgstr "En ligne depuis"
 
@@ -7904,9 +7761,8 @@
 msgid "iTunes Music Store Link"
 msgstr "Lien de l'iTunes Music Store"
 
-#, fuzzy
 msgid "Lunch"
-msgstr "Finch"
+msgstr "Déjeuner"
 
 #, c-format
 msgid "Buddy Comment for %s"
@@ -7939,9 +7795,8 @@
 msgid "Edit Buddy Comment"
 msgstr "Modifier le commentaire"
 
-#, fuzzy
 msgid "Get X-Status Msg"
-msgstr "Obtenir le message d'état"
+msgstr "Obtenir le message X-Status"
 
 msgid "End Direct IM Session"
 msgstr "Terminer la connexion directe"
@@ -8352,9 +8207,8 @@
 msgstr "Admin"
 
 #. XXX: Should this be "Topic"?
-#, fuzzy
 msgid "Room Title"
-msgstr "Liste des salons de discussions"
+msgstr "Titre du salon"
 
 msgid "Notice"
 msgstr "Envoi d'infos"
@@ -10352,13 +10206,13 @@
 "corriger le problème."
 
 #. indicates a lock due to logging in too frequently
-#, fuzzy
 msgid ""
 "Account locked: You have been logging in too frequently.  Wait a few minutes "
 "before trying to connect again.  Logging into the Yahoo! website may help."
 msgstr ""
-"Compte bloqué : trop de mauvais mots de passe. Se connecter sur le site web "
-"Yahoo! peut corriger le problème."
+"Compte bloqué : vous vous êtes connectés trop rapidement. Veuillez attendre "
+"quelques minutes avant de réessayer de vous connecter. Se connecter sur le "
+"site web Yahoo! peut corriger le problème."
 
 #. username or password missing
 msgid "Username or password missing"
@@ -10441,16 +10295,15 @@
 msgid "Unable to establish a connection with %s: %s"
 msgstr "Impossible de se connecter à %s : %s"
 
-#, fuzzy
 msgid "Unable to connect: The server returned an empty response."
-msgstr ""
-"Impossible de se connecter au serveur MXit. Veuillez vérifier votre "
-"configuration."
+msgstr "Impossible de se connecter : le serveur a renvoyé une réponse vide."
 
 msgid ""
 "Unable to connect: The server's response did not contain the necessary "
 "information"
 msgstr ""
+"Impossible de se connecter : le serveur n'a pas renvoyé les informations "
+"nécessaires."
 
 msgid "Not at Home"
 msgstr "Pas à la maison"
@@ -10909,9 +10762,8 @@
 msgid "Extended away"
 msgstr "Longue absence"
 
-#, fuzzy
 msgid "Feeling"
-msgstr "Réception en cours"
+msgstr "Ressent"
 
 #, c-format
 msgid "%s (%s) changed status from %s to %s"
@@ -11450,13 +11302,11 @@
 msgid "Unknown node type"
 msgstr "Type de noeud inconnu"
 
-#, fuzzy
 msgid "Please select your mood from the list"
 msgstr "Veuillez choisir votre humeur dans la liste."
 
-#, fuzzy
 msgid "Message (optional)"
-msgstr "Alias (facultatif)`"
+msgstr "message (facultatif)`"
 
 msgid "Edit User Mood"
 msgstr "Modifier l'humeur"
@@ -11528,7 +11378,7 @@
 msgstr "/Outils/_Certificats"
 
 msgid "/Tools/Custom Smile_ys"
-msgstr "/Outils/Frimo_usses personnalisée"
+msgstr "/Outils/Frimo_usses personnalisées"
 
 msgid "/Tools/Plu_gins"
 msgstr "/Outils/Plu_gins"
@@ -11539,9 +11389,8 @@
 msgid "/Tools/Pr_ivacy"
 msgstr "/Outils/_Filtres"
 
-#, fuzzy
 msgid "/Tools/Set _Mood"
-msgstr "/Outils/Voir les archives s_ystème"
+msgstr "/Outils/Changer d'_humeur"
 
 msgid "/Tools/_File Transfers"
 msgstr "/Outils/_Transferts de fichier"
@@ -11560,22 +11409,19 @@
 msgstr "/Aid_e"
 
 msgid "/Help/Online _Help"
-msgstr "/Aide/Aid_e en ligne"
-
-#, fuzzy
+msgstr "/Aide/_Aide en ligne"
+
 msgid "/Help/_Build Information"
-msgstr "Informations sur le contact"
+msgstr "/Aide/_Informations sur le programme"
 
 msgid "/Help/_Debug Window"
 msgstr "/Aide/Fenêtre de _debug"
 
-#, fuzzy
 msgid "/Help/De_veloper Information"
-msgstr "Informations du serveur"
-
-#, fuzzy
+msgstr "/Aide/Liste des _développeurs"
+
 msgid "/Help/_Translator Information"
-msgstr "Informations personnelles"
+msgstr "/Aide/Liste des _traducteurs"
 
 msgid "/Help/_About"
 msgstr "/Aide/À _propos de"
@@ -11808,9 +11654,8 @@
 msgid "_Edit Account"
 msgstr "Modifier le c_ompte"
 
-#, fuzzy
 msgid "Set _Mood..."
-msgstr "Changer d'humeur..."
+msgstr "Changer d'_humeur..."
 
 msgid "No actions available"
 msgstr "Aucune action disponible"
@@ -11931,9 +11776,8 @@
 msgid "/Conversation/Se_nd File..."
 msgstr "/Conversation/Envoyer un _fichier..."
 
-#, fuzzy
 msgid "/Conversation/Get _Attention"
-msgstr "/Conversation/Voir les informations"
+msgstr "/Conversation/Attirer l'_attention"
 
 msgid "/Conversation/Add Buddy _Pounce..."
 msgstr "/Conversation/Ajouter une _alerte..."
@@ -12016,9 +11860,8 @@
 msgid "/Conversation/Send File..."
 msgstr "/Conversation/Envoyer un fichier..."
 
-#, fuzzy
 msgid "/Conversation/Get Attention"
-msgstr "/Conversation/Voir les informations"
+msgstr "/Conversation/Attirer l'attention"
 
 msgid "/Conversation/Add Buddy Pounce..."
 msgstr "/Conversation/Ajouter une alerte..."
@@ -12084,13 +11927,11 @@
 msgid "0 people in room"
 msgstr "Personne dans ce salon"
 
-#, fuzzy
 msgid "Close Find bar"
-msgstr "Fermer cet onglet"
-
-#, fuzzy
+msgstr "Fermer la barre de recherche"
+
 msgid "Find:"
-msgstr "Chercher"
+msgstr "Chercher :"
 
 #, c-format
 msgid "%d person in room"
@@ -12255,9 +12096,8 @@
 msgid "Arabic"
 msgstr "Arabe"
 
-#, fuzzy
 msgid "Assamese"
-msgstr "Honteux"
+msgstr "Assamais"
 
 msgid "Belarusian Latin"
 msgstr "Biélorusse latin"
@@ -12268,9 +12108,8 @@
 msgid "Bengali"
 msgstr "Bengalî"
 
-#, fuzzy
 msgid "Bengali-India"
-msgstr "Bengalî"
+msgstr "Bengalî indien"
 
 msgid "Bosnian"
 msgstr "Bosnien"
@@ -12386,9 +12225,8 @@
 msgid "Macedonian"
 msgstr "Macédonien"
 
-#, fuzzy
 msgid "Malayalam"
-msgstr "Malaisien"
+msgstr "Malayâlam"
 
 msgid "Mongolian"
 msgstr "Mongol"
@@ -12498,7 +12336,7 @@
 msgid "Lithuanian"
 msgstr "Lituanien"
 
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "%s is a messaging client based on libpurple which is capable of connecting "
 "to multiple messaging services at once.  %s is written in C using GTK+.  %s "
@@ -12507,15 +12345,13 @@
 "copyrighted by its contributors, a list of whom is also distributed with %"
 "s.  There is no warranty for %s.<BR><BR>"
 msgstr ""
-"%s est un client graphique de messagerie modulaire basé sur libpurple "
-"compatible avec AIM, MSN, Yahoo!, XMPP, ICQ, IRC, SILC, SIP/SIMPLE, Novell "
-"GroupWise, Lotus Sametime, Bonjour, Zephyr, MySpaceIm, Gadu-Gadu et QQ. Il "
-"est écrit avec Gtk+.<BR><BR>Vous pouvez modifier et redistribuer ce "
-"programme sous les conditions énoncées par la licence GNU GPL (version 2 ou "
-"ultérieure). Une copie de la licence GPL est dans le fichier « COPYING » "
-"fourni avec %s. Tous droits réservés par les collaborateurs de %s. Consultez "
-"le fichier « COPYRIGHT » pour avoir la liste complète des collaborateurs. "
-"Aucune garantie n'est fournie pour l'utilisation de ce programme.<BR><BR>"
+"%s est un client de messagerie basé sur libpurple capable de se connecter à "
+"de multiples services de messageries instantanées. %s est écrit en C et "
+"utilise GTK+. %s est distribué, peut être modifié et redistribué sous les "
+"termes de la licence GPL version 2 ou ultérieure. Une copie de la licence "
+"GPL est fournie avec %s. Tous droits réservés par les collaborateurs de %s, "
+"dont une liste est aussi fournie avec %s. Aucune garantie n'est fournie pour "
+"l'utilisation de %s.<BR><BR>"
 
 #, c-format
 msgid ""
@@ -12524,8 +12360,11 @@
 "Channel: #pidgin on irc.freenode.net<BR>\tXMPP MUC: devel@conference.pidgin."
 "im<BR><BR>"
 msgstr ""
-
-#, fuzzy, c-format
+"<FONT SIZE=\"4\"><B>Liens utiles</B></FONT><BR>\t<A HREF=\"%s\">Site web</"
+"A><BR>\t<A HREF=\"%s\">Foire Aux Questions</A><BR>\tIRC Salon IRC : #pidgin "
+"sur irc.freenode.net<BR>\tSalon XMPP : devel@conference.pidgin.im<BR><BR>"
+
+#, c-format
 msgid ""
 "<font size=\"4\"><b>Help from other Pidgin users</b></font> is available by "
 "e-mailing <a href=\"mailto:support@pidgin.im\">support@pidgin.im</a><br/"
@@ -12547,14 +12386,13 @@
 msgid "About %s"
 msgstr "À propos de %s"
 
-#, fuzzy
 msgid "Build Information"
-msgstr "Informations sur le contact"
+msgstr "Informations sur le programme"
 
 #. End of not to be translated section
-#, fuzzy, c-format
+#, c-format
 msgid "%s Build Information"
-msgstr "Informations sur le contact"
+msgstr "Informations sur le programme %s"
 
 msgid "Current Developers"
 msgstr "Codeurs"
@@ -12568,9 +12406,9 @@
 msgid "Retired Crazy Patch Writers"
 msgstr "Patcheurs fous retraités"
 
-#, fuzzy, c-format
+#, c-format
 msgid "%s Developer Information"
-msgstr "Informations du serveur"
+msgstr "Liste des développeurs de %s"
 
 msgid "Current Translators"
 msgstr "Traducteurs"
@@ -12578,9 +12416,9 @@
 msgid "Past Translators"
 msgstr "Anciens traducteurs"
 
-#, fuzzy, c-format
+#, c-format
 msgid "%s Translator Information"
-msgstr "Plus d'informations"
+msgstr "Liste des traducteurs de %s"
 
 msgid "_Name"
 msgstr "_Nom"
@@ -12993,9 +12831,8 @@
 msgid "Insert Smiley"
 msgstr "Insérer une frimousse"
 
-#, fuzzy
 msgid "Send Attention"
-msgstr "Attention !"
+msgstr "Attirer l'attention"
 
 msgid "<b>_Bold</b>"
 msgstr "<b>_Gras</b>"
@@ -13042,9 +12879,8 @@
 msgid "_Smile!"
 msgstr "Sourie_z !"
 
-#, fuzzy
 msgid "_Attention!"
-msgstr "Attention !"
+msgstr "_Attention !"
 
 msgid "Log Deletion Failed"
 msgstr "Échec de suppression de l'archive"
@@ -13980,11 +13816,10 @@
 msgstr "Texte de raccourci"
 
 msgid "Custom Smiley Manager"
-msgstr "Gestionnaire de frimousses personnalisée"
-
-#, fuzzy
+msgstr "Gestionnaire de frimousses personnalisées"
+
 msgid "Attention received"
-msgstr "Activation nécessaire"
+msgstr "Demande d'attention reçue"
 
 msgid "Select Buddy Icon"
 msgstr "Choisir l'icône de contact"
@@ -15135,21 +14970,18 @@
 msgid "Timestamp Format Options"
 msgstr "Options d'affichage de l'horodatage"
 
-#, fuzzy, c-format
+#, c-format
 msgid "_Force timestamp format:"
-msgstr "_Forcer au format 24 heures"
-
-#, fuzzy
+msgstr "_Forcer le formatage des dates :"
+
 msgid "Use system default"
-msgstr "Paramètres par défaut du bureau"
-
-#, fuzzy
+msgstr "Utiliser les paramètres du système"
+
 msgid "12 hour time format"
-msgstr "_Forcer au format 24 heures"
-
-#, fuzzy
+msgstr "Format 12 heures"
+
 msgid "24 hour time format"
-msgstr "_Forcer au format 24 heures"
+msgstr "Format 24 heures"
 
 msgid "Show dates in..."
 msgstr "Afficher les dates dans..."
@@ -15349,10 +15181,10 @@
 msgstr "Envoyer et recevoir des blocs XMPP."
 
 #. *  description
-#, fuzzy
 msgid "This plugin is useful for debugging XMPP servers or clients."
 msgstr "Ce plugin est utile pour débugger les clients ou serveurs XMPP."
 
+#. $(^Name) is the current Version name (e.g. Pidgin 2.7.0).  $_CLICK will become a translated version of "Click Next to continue."
 msgid ""
 "$(^Name) is released under the GNU General Public License (GPL). The license "
 "is provided here for information purposes only. $_CLICK"
@@ -15360,6 +15192,7 @@
 "$(^Name) est disponible sous licence GNU General Public License (GPL). Le "
 "texte de licence suivant est fourni uniquement à titre informatif. $_CLICK"
 
+#. Installer Subsection Detailed Description
 msgid "A multi-platform GUI toolkit, used by Pidgin"
 msgstr ""
 "Un ensemble d'outils pour interfaces graphiques multi-plateforme, utilisé "
@@ -15372,75 +15205,100 @@
 "Une instance de Pidgin est en cours d'exécution. Veuillez quitter Pidgin et "
 "réessayer."
 
+#. Installer Subsection Detailed Description
 msgid "Core Pidgin files and dlls"
 msgstr "Fichiers et DLLs de base de Pidgin"
 
+#. Installer Subsection Detailed Description
 msgid "Create a Start Menu entry for Pidgin"
 msgstr "Créer un raccourci pour Pidgin dans le menu Démarrer"
 
+#. Installer Subsection Detailed Description
 msgid "Create a shortcut to Pidgin on the Desktop"
 msgstr "Créer un raccourci pour Pidgin sur le bureau"
 
+#. Installer Subsection Text
 msgid "Debug Symbols (for reporting crashes)"
-msgstr ""
-
+msgstr "Symboles de debug (pour soumettre des plantages)"
+
+#. Installer Subsection Text
 msgid "Desktop"
 msgstr "Bureau"
 
+#. $R2 will display the URL that the GTK+ Runtime failed to download from
 msgid ""
 "Error Downloading the GTK+ Runtime ($R2).$\\rThis is required for Pidgin to "
 "function; if retrying fails, you may need to use the 'Offline Installer' "
 "from http://pidgin.im/download/windows/ ."
 msgstr ""
-
+"Erreur au téléchargement des bibliothèques GTK+ ($R2).$\\rCeci est "
+"nécessaire au bon fonctionnement de Pidgin. Si une nouvelle tentative "
+"échoue, vous devrez peut-être utiliser l'installateur « Offline » disponible "
+"sur http://pidgin.im/download/windows/ ."
+
+#. $R2 will display the URL that the Debug Symbols failed to download from
 msgid ""
 "Error Installing Debug Symbols ($R2).$\\rIf retrying fails, you may need to "
 "use the 'Offline Installer' from http://pidgin.im/download/windows/ ."
 msgstr ""
-
+"Erreur lors de l'installation des symboles de debug GTK+ ($R2).$\\rSi une "
+"nouvelle tentative échoue, vous devrez peut-être utiliser l'installateur « "
+"Offline » disponible sur http://pidgin.im/download/windows/ ."
+
+#. $R3 will display the URL that the Dictionary failed to download from
 #, no-c-format
 msgid ""
 "Error Installing Spellchecking ($R3).$\\rIf retrying fails, manual "
 "installation instructions are at: http://developer.pidgin.im/wiki/Installing%"
 "20Pidgin#manual_win32_spellcheck_installation"
 msgstr ""
-
-#, fuzzy
+"Erreur lors de l'installation du correcteur orthographique ($R3).$\\rSi une "
+"nouvelle tentative échoue, veuillez suivre les instructions sur http://"
+"developer.pidgin.im/wiki/Installing%"
+"20Pidgin#manual_win32_spellcheck_installation"
+
+#. Installer Subsection Text
 msgid "GTK+ Runtime (required if not present)"
-msgstr "Bibliothèques GTK+ (obligatoire)"
-
-#, fuzzy
+msgstr "Bibliothèques GTK+ (obligatoires si pas déjà installées)"
+
+#. Installer Subsection Text
 msgid "Localizations"
-msgstr "Localisation"
-
-#. License Page
+msgstr "Traductions"
+
+#. "Next >" appears on a button on the License Page of the Installer
 msgid "Next >"
 msgstr "Suivant >"
 
-#. Components Page
+#. Installer Subsection Text
 msgid "Pidgin Instant Messaging Client (required)"
 msgstr "Pidgin client de messagerie instantanée (obligatoire)"
 
-#. GTK+ Section Prompts
 msgid ""
 "Pidgin requires a compatible GTK+ Runtime (which doesn't appear to be "
 "already present).$\\rAre you sure you want to skip installing the GTK+ "
 "Runtime?"
 msgstr ""
-
+"Pidgin a besoin d'une version compatible des bibliothèques GTK+ (qui n'a pas "
+"l'air d'être présente sur votre système).$\\rÊtes-vous sûr de ne pas vouloir "
+"installer ces bibliothèques ?"
+
+#. Installer Subsection Text
 msgid "Shortcuts"
 msgstr "Raccourcis"
 
+#. Installer Subsection Detailed Description
 msgid "Shortcuts for starting Pidgin"
 msgstr "Raccourcis pour lancer Pidgin"
 
-#. Spellcheck Section Prompts
+#. Installer Subsection Text
 msgid "Spellchecking Support"
 msgstr "Correction orthographique"
 
+#. Installer Subsection Text
 msgid "Start Menu"
 msgstr "Menu Démarrer"
 
+#. Installer Subsection Detailed Description
 msgid ""
 "Support for Spellchecking.  (Internet connection required for installation)"
 msgstr ""
@@ -15450,7 +15308,6 @@
 msgid "The installer is already running."
 msgstr "Le programme d'installation est déjà en cours d'exécution."
 
-#. Uninstall Section Prompts
 msgid ""
 "The uninstaller could not find registry entries for Pidgin.$\\rIt is likely "
 "that another user installed this application."
@@ -15459,11 +15316,10 @@
 "la base de registres.$\\rL'application a peut-être été installée par un "
 "utilisateur différent."
 
-#. URL Handler section
+#. Installer Subsection Text
 msgid "URI Handlers"
 msgstr "Gestion des liens (URI)"
 
-#. Pidgin Section Prompts and Texts
 msgid ""
 "Unable to uninstall the currently installed version of Pidgin. The new "
 "version will be installed without removing the currently installed version."
@@ -15471,34 +15327,94 @@
 "Impossible de désinstaller la version de Pidgin en place. La nouvelle "
 "version sera installée sans supprimer la version en place."
 
-#. Installer Finish Page
+#. Text displayed on Installer Finish Page
 msgid "Visit the Pidgin Web Page"
 msgstr "Visitez la page web de Pidgin"
 
 msgid "You do not have permission to uninstall this application."
 msgstr "Vous n'avez pas les permissions pour supprimer cette application."
 
+#~ msgid "Rate to host"
+#~ msgstr "Fréquence vers l'hôte"
+
+#~ msgid "Rate to client"
+#~ msgstr "Fréquence vers le client"
+
+#~ msgid "Service unavailable"
+#~ msgstr "Service non disponible"
+
+#~ msgid "Service not defined"
+#~ msgstr "Service non défini"
+
+#~ msgid "Obsolete SNAC"
+#~ msgstr "SNAC obsolète"
+
+#~ msgid "Not supported by host"
+#~ msgstr "Non supporté par l'hôte"
+
+#~ msgid "Not supported by client"
+#~ msgstr "Non supporté par le client"
+
+#~ msgid "Refused by client"
+#~ msgstr "Refusé par le client"
+
+#~ msgid "Reply too big"
+#~ msgstr "Réponse trop grosse"
+
+#~ msgid "Responses lost"
+#~ msgstr "Réponses perdues"
+
+#~ msgid "Request denied"
+#~ msgstr "Requête refusée"
+
+#~ msgid "Busted SNAC payload"
+#~ msgstr "Charge SNAC incorrecte"
+
+#~ msgid "Insufficient rights"
+#~ msgstr "Droits insuffisants"
+
+#~ msgid "In local permit/deny"
+#~ msgstr "Dans l'autorisation/interdiction locale"
+
+#~ msgid "Warning level too high (sender)"
+#~ msgstr "Niveau d'avertissement trop élevé (émission)"
+
+#~ msgid "Warning level too high (receiver)"
+#~ msgstr "Niveau d'avertissement trop élevé (réception)"
+
+#~ msgid "User temporarily unavailable"
+#~ msgstr "L'utilisateur est temporairement indisponible."
+
+#~ msgid "No match"
+#~ msgstr "Aucun résultat"
+
+#~ msgid "List overflow"
+#~ msgstr "Dépassement de liste"
+
+#~ msgid "Request ambiguous"
+#~ msgstr "Requête ambiguë"
+
+#~ msgid "Queue full"
+#~ msgstr "File d'attente pleine"
+
+#~ msgid "Not while on AOL"
+#~ msgstr "Impossible sur AOL"
+
+#~ msgid "Unknown reason."
+#~ msgstr "Erreur inconnue"
+
+#~ msgid "Orientation"
+#~ msgstr "Disposition"
+
+#~ msgid "The orientation of the tray."
+#~ msgstr "Orientation de l'espace de notification"
+
 #~ msgid "Artist"
 #~ msgstr "Artiste"
 
 #~ msgid "Album"
 #~ msgstr "Album"
 
-#~ msgid "Current Mood"
-#~ msgstr "Humeur actuelle"
-
-#~ msgid "New Mood"
-#~ msgstr "Nouvelle humeur"
-
-#~ msgid "Change your Mood"
-#~ msgstr "Changer d'humeur"
-
-#~ msgid "How do you feel right now?"
-#~ msgstr "Comment vous sentez-vous ?"
-
-#~ msgid "Change Mood..."
-#~ msgstr "Changer d'humeur..."
-
 #~ msgid "Pager server"
 #~ msgstr "Serveur de texto"
 
@@ -15508,12 +15424,6 @@
 #~ msgid "Yahoo Chat port"
 #~ msgstr "Port Yahoo Chat"
 
-#~ msgid "Orientation"
-#~ msgstr "Disposition"
-
-#~ msgid "The orientation of the tray."
-#~ msgstr "Orientation de l'espace de notification"
-
 #~ msgid "Error creating conference."
 #~ msgstr "Erreur à la création de la conférence."
 
@@ -15577,22 +15487,6 @@
 #~ msgid "%s has removed you from his or her buddy list."
 #~ msgstr "L'utilisateur %s vous a supprimé de sa liste de contacts."
 
-#~ msgid ""
-#~ "<FONT SIZE=\"4\">FAQ:</FONT> <A HREF=\"http://developer.pidgin.im/wiki/FAQ"
-#~ "\">http://developer.pidgin.im/wiki/FAQ</A><BR/><BR/>"
-#~ msgstr ""
-#~ "<FONT SIZE=\"4\">FAQ :</FONT> <A HREF=\"http://developer.pidgin.im/wiki/"
-#~ "FAQ\">http://developer.pidgin.im/wiki/FAQ</A><BR/><BR/>"
-
-#~ msgid ""
-#~ "<FONT SIZE=\"4\">IRC Channel:</FONT> #pidgin on irc.freenode.net<BR><BR>"
-#~ msgstr ""
-#~ "<FONT SIZE=\"4\">Salon IRC :</FONT> #pidgin sur irc.freenode.net<BR><BR>"
-
-#~ msgid "<FONT SIZE=\"4\">XMPP MUC:</FONT> devel@conference.pidgin.im<BR><BR>"
-#~ msgstr ""
-#~ "<FONT SIZE=\"4\">Salon XMPP :</FONT> devel@conference.pidgin.im<BR><BR>"
-
 #~ msgid "Debugging Information"
 #~ msgstr "Informations de debug"
 
@@ -15670,6 +15564,9 @@
 #~ msgid "_User:"
 #~ msgstr "_Utilisateur :"
 
+#~ msgid "GTK+ Runtime Version"
+#~ msgstr "Version des bibliothèques GTK+"
+
 #~ msgid "Calling ... "
 #~ msgstr "Appel... "