# HG changeset patch # User andrew.victor@mxit.com # Date 1275081009 0 # Node ID 29e714e980b22e59efa3b5f30c1bcac24faeed2a # Parent d7325448badbad0e8dceaf6273581d2db0e784bf# Parent 51c31805d2a765484f8d478b86f7e0ad79271da9 merge of 'c32c1fdff08db87b69fc0eb921a5eaafcb5ef67e' and 'fcd99a6bb8c6362d64f295a1b95a5b1130460b85' diff -r d7325448badb -r 29e714e980b2 COPYRIGHT --- 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 diff -r d7325448badb -r 29e714e980b2 ChangeLog --- 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) diff -r d7325448badb -r 29e714e980b2 ChangeLog.win32 --- 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) diff -r d7325448badb -r 29e714e980b2 Makefile.am --- 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 diff -r d7325448badb -r 29e714e980b2 libpurple/media/backend-fs2.h --- 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 diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/Makefile.am --- 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 \ diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/Makefile.mingw --- 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 \ diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/directconn.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; + } + } } + diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/directconn.h --- 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 */ diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/msg.c --- 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); diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/slp.c --- 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 diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/slp.h --- 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); diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/slpcall.h --- 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); diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/slplink.c --- 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); } } diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/slplink.h --- 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); diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/slpmsg.c --- 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); diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/state.c --- 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); diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/msn/switchboard.c --- 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) diff -r d7325448badb -r 29e714e980b2 libpurple/protocols/qq/qq_base.c --- 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, diff -r d7325448badb -r 29e714e980b2 pidgin/gtkdocklet-gtk.c --- 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 diff -r d7325448badb -r 29e714e980b2 pidgin/pixmaps/Makefile.am --- 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) \ diff -r d7325448badb -r 29e714e980b2 pidgin/pixmaps/tray/hicolor/index.theme --- /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 + diff -r d7325448badb -r 29e714e980b2 pidgin/win32/nsis/generate_gtk_zip.sh --- 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 + diff -r d7325448badb -r 29e714e980b2 pidgin/win32/nsis/nsis_translations.desktop.in --- 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) diff -r d7325448badb -r 29e714e980b2 po/ca.po --- 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ó \n" "Language-Team: Catalan \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" diff -r d7325448badb -r 29e714e980b2 po/de.po --- 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 \n" "Language-Team: Deutsch \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" diff -r d7325448badb -r 29e714e980b2 po/fr.po --- 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 \n" "Language-Team: fr \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.

" 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+.

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.

" +"%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.

" #, c-format msgid "" @@ -12524,8 +12360,11 @@ "Channel: #pidgin on irc.freenode.net
\tXMPP MUC: devel@conference.pidgin." "im

" msgstr "" - -#, fuzzy, c-format +"Liens utiles
\tSite web
\t
Foire Aux Questions
\tIRC Salon IRC : #pidgin " +"sur irc.freenode.net
\tSalon XMPP : devel@conference.pidgin.im

" + +#, c-format msgid "" "Help from other Pidgin users is available by " "e-mailing support@pidgin.im
_Bold" msgstr "_Gras" @@ -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 "" -#~ "FAQ: http://developer.pidgin.im/wiki/FAQ

" -#~ msgstr "" -#~ "FAQ : http://developer.pidgin.im/wiki/FAQ

" - -#~ msgid "" -#~ "IRC Channel: #pidgin on irc.freenode.net

" -#~ msgstr "" -#~ "Salon IRC : #pidgin sur irc.freenode.net

" - -#~ msgid "XMPP MUC: devel@conference.pidgin.im

" -#~ msgstr "" -#~ "Salon XMPP : devel@conference.pidgin.im

" - #~ 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... "