# HG changeset patch # User Marcus Lundblad # Date 1222191878 0 # Node ID 53b073da65ee3abc25815664975f9742e839d83b # Parent dc01f9b0aaa31f01549d7b4111e81ff911fde377# Parent 14915e96311e86f94f5beca9ba93aa7cbc9192af propagate from branch 'im.pidgin.pidgin' (head d7476bfe7f9003553f7b3ea48491f4cebd192098) to branch 'im.pidgin.cpw.malu.xmpp.ibb_ft' (head 11e25d42c31c1c2c0cd2dff0c862c638940dac19) diff -r dc01f9b0aaa3 -r 53b073da65ee ChangeLog --- a/ChangeLog Mon Sep 22 17:23:59 2008 +0000 +++ b/ChangeLog Tue Sep 23 17:44:38 2008 +0000 @@ -11,10 +11,18 @@ * Fix a case where a conversation window could close unexpectedly. * A mute sounds option has been added to the preferences window to help with discoverability. CTRL+S is no longer bound to mute. + * Added ability to change the color of visited links (using the theme + control plugin, or setting the color in ~/.gtkrc-2.0) Finch: * A new 'Nested Grouping' option in the 'Grouping' plugin. Group hierarchies are defined by the '/' character in the group names + * A bug was fixed where some key-bindings wouldn't work with some TERMs + (e.g. xterm-color, screen-linux etc.) + + XMPP: + * Sending and receiving custom smileys using the specification in + XEP-0231 (bits of binary) and XHTML-IM version 2.5.1 (08/30/2008): libpurple: diff -r dc01f9b0aaa3 -r 53b073da65ee finch/libgnt/gntkeys.c --- a/finch/libgnt/gntkeys.c Mon Sep 22 17:23:59 2008 +0000 +++ b/finch/libgnt/gntkeys.c Tue Sep 23 17:44:38 2008 +0000 @@ -47,12 +47,12 @@ term = ""; /* Just in case */ } - if (strcmp(term, "xterm") == 0 || strcmp(term, "rxvt") == 0) { + if (strstr(term, "xterm") == term || strcmp(term, "rxvt") == 0) { gnt_key_cup = "\033" "[1;5A"; gnt_key_cdown = "\033" "[1;5B"; gnt_key_cright = "\033" "[1;5C"; gnt_key_cleft = "\033" "[1;5D"; - } else if (strcmp(term, "screen") == 0 || strcmp(term, "rxvt-unicode") == 0) { + } else if (strstr(term, "screen") == term || strcmp(term, "rxvt-unicode") == 0) { gnt_key_cup = "\033" "Oa"; gnt_key_cdown = "\033" "Ob"; gnt_key_cright = "\033" "Oc"; @@ -147,11 +147,11 @@ if (*text == 27 && *(text + 1) == '[' && (*(text + 2) >= 'A' && *(text + 2) <= 'D')) { /* Apparently this is necessary for urxvt and screen and xterm */ - if (strcmp(term, "screen") == 0 || strcmp(term, "rxvt-unicode") == 0 || - strcmp(term, "xterm") == 0) + if (strstr(term, "screen") == term || strcmp(term, "rxvt-unicode") == 0 || + strstr(term, "xterm") == term) *(text + 1) = 'O'; } else if (*(unsigned char*)text == 195) { - if (*(text + 2) == 0 && strcmp(term, "xterm") == 0) { + if (*(text + 2) == 0 && strstr(term, "xterm") == term) { *(text) = 27; *(text + 1) -= 64; /* Say wha? */ } diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/dnsquery.c --- a/libpurple/dnsquery.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/dnsquery.c Tue Sep 23 17:44:38 2008 +0000 @@ -85,7 +85,7 @@ char hostname[512]; int port; } dns_params_t; -#endif +#endif /* end PURPLE_DNSQUERY_USE_FORK */ static void purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts) @@ -108,12 +108,14 @@ } } +#ifdef PURPLE_DNSQUERY_USE_FORK /* - * Set the resolver to NULL so that it doesn't get killed so that - * it sits around waiting for additional DNS requests for a few - * seconds longer. + * Add the resolver to the list of available resolvers, and set it + * to NULL so that it doesn't get destroyed along with the query_data */ + free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver); query_data->resolver = NULL; +#endif /* PURPLE_DNSQUERY_USE_FORK */ purple_dnsquery_destroy(query_data); } @@ -133,10 +135,7 @@ PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops(); if (ops && ops->resolve_host) - { - if (ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed)) - return TRUE; - } + return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed); return FALSE; } @@ -419,9 +418,9 @@ /** * @return TRUE if the request was sent succesfully. FALSE - * if the request could not be sent. This isn't - * necessarily an error. If the child has expired, - * for example, we won't be able to send the message. + * if the request could not be sent. This isn't + * necessarily an error. If the child has expired, + * for example, we won't be able to send the message. */ static gboolean send_dns_request_to_child(PurpleDnsQueryData *query_data, @@ -493,13 +492,6 @@ query_data = queued_requests->data; queued_requests = g_slist_delete_link(queued_requests, queued_requests); - if (purple_dnsquery_ui_resolve(query_data)) - { - /* The UI is handling the resolve; we're done */ - handle_next_queued_request(); - return; - } - /* * If we have any children, attempt to have them perform the DNS * query. If we're able to send the query then resolver will be @@ -596,7 +588,7 @@ purple_dnsquery_failed(query_data, message); } else if (rc == 0) { - g_snprintf(message, sizeof(message), _("EOF while reading from resolver process")); + g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request")); purple_dnsquery_failed(query_data, message); } @@ -611,6 +603,12 @@ query_data = data; query_data->timeout = 0; + if (purple_dnsquery_ui_resolve(query_data)) + { + /* The UI is handling the resolve; we're done */ + return FALSE; + } + handle_next_queued_request(); return FALSE; @@ -634,7 +632,7 @@ query_data->data = data; query_data->resolver = NULL; - if (strlen(query_data->hostname) == 0) + if (*query_data->hostname == '\0') { purple_dnsquery_destroy(query_data); g_return_val_if_reached(NULL); @@ -912,10 +910,13 @@ if (query_data->resolver != NULL) /* - * Ideally we would tell our resolver child to stop resolving - * shit and then we would add it back to the free_dns_children - * linked list. However, it's hard to tell children stuff, - * they just don't listen. + * This is only non-NULL when we're cancelling an in-progress + * query. Ideally we would tell our resolver child to stop + * resolving shit and then we would add it back to the + * free_dns_children linked list. However, it's hard to tell + * children stuff, they just don't listen. So we'll just + * kill the process and allow a new child to be started if we + * have more stuff to resolve. */ purple_dnsquery_resolver_destroy(query_data->resolver); #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ @@ -939,7 +940,7 @@ query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); } g_free(query_data->error_message); -#endif +#endif /* end _WIN32 */ if (query_data->timeout > 0) purple_timeout_remove(query_data->timeout); @@ -993,5 +994,5 @@ purple_dnsquery_resolver_destroy(free_dns_children->data); free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data); } -#endif +#endif /* end PURPLE_DNSQUERY_USE_FORK */ } diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Tue Sep 23 17:44:38 2008 +0000 @@ -946,7 +946,7 @@ bonjour_jabber_send_message(BonjourJabber *jdata, const gchar *to, const gchar *body) { xmlnode *message_node, *node, *node2; - gchar *message; + gchar *message, *xhtml; PurpleBuddy *pb; BonjourBuddy *bb; int ret; @@ -958,6 +958,8 @@ return -10000; } + purple_markup_html_to_xhtml(body, &xhtml, &message); + bb = pb->proto_data; message_node = xmlnode_new("message"); @@ -967,7 +969,6 @@ /* Enclose the message from the UI within a "font" node */ node = xmlnode_new_child(message_node, "body"); - message = purple_markup_strip_html(body); xmlnode_insert_data(node, message, strlen(message)); g_free(message); @@ -975,8 +976,9 @@ xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml"); node = xmlnode_new_child(node, "body"); - message = g_strdup_printf("%s", body); + message = g_strdup_printf("%s", xhtml); node2 = xmlnode_from_str(message, strlen(message)); + g_free(xhtml); g_free(message); xmlnode_insert_child(node, node2); diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Tue Sep 23 17:44:38 2008 +0000 @@ -11,6 +11,8 @@ buddy.h \ chat.c \ chat.h \ + data.c \ + data.h \ disco.c \ disco.h \ google.c \ diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/Makefile.mingw --- a/libpurple/protocols/jabber/Makefile.mingw Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/Makefile.mingw Tue Sep 23 17:44:38 2008 +0000 @@ -48,6 +48,7 @@ buddy.c \ caps.c \ chat.c \ + data.c \ disco.c \ google.c \ ibb.c \ diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/buddy.c Tue Sep 23 17:44:38 2008 +0000 @@ -2498,5 +2498,38 @@ js); } +gboolean +jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap) +{ + const GList *iter = NULL; + if (!jbr->caps) { + purple_debug_error("jabber", + "Unable to find caps: nothing known about buddy\n"); + return FALSE; + } + for (iter = jbr->caps->features ; iter ; iter = g_list_next(iter)) { + purple_debug_info("jabber", "Found cap: %s\n", (char *)iter->data); + if (strcmp(iter->data, cap) == 0) { + return TRUE; + } + } + + return FALSE; +} + +gboolean +jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap) +{ + JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL); + + if (!jbr) { + purple_debug_error("jabber", + "Unable to find caps: buddy might be offline\n"); + return FALSE; + } + + return jabber_resource_has_capability(jbr, cap); +} + diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/buddy.h --- a/libpurple/protocols/jabber/buddy.h Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/buddy.h Tue Sep 23 17:44:38 2008 +0000 @@ -117,4 +117,8 @@ void jabber_vcard_fetch_mine(JabberStream *js); +gboolean jabber_resource_has_capability(const JabberBuddyResource *jbr, + const gchar *cap); +gboolean jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap); + #endif /* _PURPLE_JABBER_BUDDY_H_ */ diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/chat.c Tue Sep 23 17:44:38 2008 +0000 @@ -31,6 +31,7 @@ #include "message.h" #include "presence.h" #include "xdata.h" +#include "data.h" GList *jabber_chat_info(PurpleConnection *gc) { @@ -684,6 +685,7 @@ xmlnode_insert_data(status, msg, -1); } jabber_send(chat->js, presence); + xmlnode_free(presence); g_free(room_jid); } diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/data.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/data.c Tue Sep 23 17:44:38 2008 +0000 @@ -0,0 +1,247 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +#include "internal.h" +#include "data.h" +#include "debug.h" +#include "xmlnode.h" +#include "util.h" +#include "iq.h" + +static GHashTable *local_data_by_alt = NULL; +static GHashTable *local_data_by_cid = NULL; +static GHashTable *remote_data_by_cid = NULL; + +JabberData * +jabber_data_create_from_data(gconstpointer rawdata, gsize size, const char *type, + JabberStream *js) +{ + JabberData *data = g_new0(JabberData, 1); + gchar *checksum = purple_util_get_image_checksum(rawdata, size); + gchar cid[256]; + + g_snprintf(cid, sizeof(cid), "sha1+%s@bob.xmpp.org", checksum); + g_free(checksum); + + data->cid = g_strdup(cid); + data->type = g_strdup(type); + data->size = size; + + data->data = g_memdup(rawdata, size); + + return data; +} + +JabberData * +jabber_data_create_from_xml(xmlnode *tag) +{ + JabberData *data = g_new0(JabberData, 1); + gsize size; + gpointer raw_data = NULL; + + if (data == NULL) { + purple_debug_error("jabber", "Could not allocate data object\n"); + g_free(data); + return NULL; + } + + /* check if this is a "data" tag */ + if (strcmp(tag->name, "data") != 0) { + purple_debug_error("jabber", "Invalid data element"); + g_free(data); + return NULL; + } + + data->cid = g_strdup(xmlnode_get_attrib(tag, "cid")); + data->type = g_strdup(xmlnode_get_attrib(tag, "type")); + + raw_data = xmlnode_get_data(tag); + data->data = purple_base64_decode(raw_data, &size); + data->size = size; + + g_free(raw_data); + + return data; +} + + +static void +jabber_data_delete(gpointer cbdata) +{ + JabberData *data = cbdata; + g_free(data->cid); + g_free(data->type); + g_free(data->data); + g_free(data); +} + +const char * +jabber_data_get_cid(const JabberData *data) +{ + return data->cid; +} + + +const char * +jabber_data_get_type(const JabberData *data) +{ + return data->type; +} + +gsize +jabber_data_get_size(const JabberData *data) +{ + return data->size; +} + +gpointer +jabber_data_get_data(const JabberData *data) +{ + return data->data; +} + +xmlnode * +jabber_data_get_xml_definition(const JabberData *data) +{ + xmlnode *tag = xmlnode_new("data"); + char *base64data = purple_base64_encode(data->data, data->size); + + xmlnode_set_namespace(tag, XEP_0231_NAMESPACE); + xmlnode_set_attrib(tag, "cid", data->cid); + xmlnode_set_attrib(tag, "type", data->type); + + xmlnode_insert_data(tag, base64data, -1); + + g_free(base64data); + + return tag; +} + +xmlnode * +jabber_data_get_xhtml_im(const JabberData *data, const gchar *alt) +{ + xmlnode *img = xmlnode_new("img"); + char src[128]; + + xmlnode_set_attrib(img, "alt", alt); + g_snprintf(src, sizeof(src), "cid:%s", data->cid); + xmlnode_set_attrib(img, "src", src); + + return img; +} + +xmlnode * +jabber_data_get_xml_request(const gchar *cid) +{ + xmlnode *tag = xmlnode_new("data"); + + xmlnode_set_namespace(tag, XEP_0231_NAMESPACE); + xmlnode_set_attrib(tag, "cid", cid); + + return tag; +} + +const JabberData * +jabber_data_find_local_by_alt(const gchar *alt) +{ + purple_debug_info("jabber", "looking up local smiley with alt = %s\n", alt); + return g_hash_table_lookup(local_data_by_alt, alt); +} + +const JabberData * +jabber_data_find_local_by_cid(const gchar *cid) +{ + purple_debug_info("jabber", "lookup local smiley with cid = %s\n", cid); + return g_hash_table_lookup(local_data_by_cid, cid); +} + +const JabberData * +jabber_data_find_remote_by_cid(const gchar *cid) +{ + purple_debug_info("jabber", "lookup remote smiley with cid = %s\n", cid); + + return g_hash_table_lookup(remote_data_by_cid, cid); +} + +void +jabber_data_associate_local(JabberData *data, const gchar *alt) +{ + purple_debug_info("jabber", "associating local smiley\n alt = %s, cid = %s\n", + alt, jabber_data_get_cid(data)); + g_hash_table_insert(local_data_by_alt, g_strdup(alt), data); + g_hash_table_insert(local_data_by_cid, g_strdup(jabber_data_get_cid(data)), + data); +} + +void +jabber_data_associate_remote(JabberData *data) +{ + purple_debug_info("jabber", "associating remote smiley, cid = %s\n", + jabber_data_get_cid(data)); + g_hash_table_insert(remote_data_by_cid, g_strdup(jabber_data_get_cid(data)), + data); +} + +void +jabber_data_parse(JabberStream *js, xmlnode *packet) +{ + JabberIq *result = NULL; + const char *who = xmlnode_get_attrib(packet, "from"); + xmlnode *data_node = xmlnode_get_child(packet, "data"); + const JabberData *data = + jabber_data_find_local_by_cid(xmlnode_get_attrib(data_node, "cid")); + + if (!data) { + xmlnode *item_not_found = xmlnode_new("item-not-found"); + + result = jabber_iq_new(js, JABBER_IQ_ERROR); + xmlnode_set_attrib(result->node, "to", who); + xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_insert_child(result->node, item_not_found); + } else { + result = jabber_iq_new(js, JABBER_IQ_RESULT); + xmlnode_set_attrib(result->node, "to", who); + xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id")); + xmlnode_insert_child(result->node, + jabber_data_get_xml_definition(data)); + } + jabber_iq_send(result); +} + +void +jabber_data_init(void) +{ + purple_debug_info("jabber", "creating hash tables for data objects\n"); + local_data_by_alt = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + local_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, jabber_data_delete); + remote_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, jabber_data_delete); +} + +void +jabber_data_uninit(void) +{ + purple_debug_info("jabber", "destroying hash tables for data objects\n"); + g_hash_table_destroy(local_data_by_alt); + g_hash_table_destroy(local_data_by_cid); + g_hash_table_destroy(remote_data_by_cid); +} diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/data.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/data.h Tue Sep 23 17:44:38 2008 +0000 @@ -0,0 +1,73 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA + */ + +#ifndef JABBER_DATA_H +#define JABBER_DATA_H + +#include "xmlnode.h" +#include "jabber.h" + +#define XEP_0231_NAMESPACE "urn:xmpp:bob" + +#include + +typedef struct { + char *cid; + char *type; + gsize size; + gpointer data; +} JabberData; + +/* creates a JabberData instance from raw data */ +JabberData *jabber_data_create_from_data(gconstpointer data, gsize size, + const char *type, JabberStream *js); + +/* create a JabberData instance from an XML "data" element (as defined by + XEP 0231 */ +JabberData *jabber_data_create_from_xml(xmlnode *tag); + +const char *jabber_data_get_cid(const JabberData *data); +const char *jabber_data_get_type(const JabberData *data); + +gsize jabber_data_get_size(const JabberData *data); +gpointer jabber_data_get_data(const JabberData *data); + +/* returns the XML definition for the data element */ +xmlnode *jabber_data_get_xml_definition(const JabberData *data); + +/* returns an XHTML-IM "img" tag given a data instance */ +xmlnode *jabber_data_get_xhtml_im(const JabberData *data, const gchar *alt); + +/* returns a data request element (to be included in an iq stanza) for requesting + data */ +xmlnode *jabber_data_get_xml_request(const gchar *cid); + +/* lookup functions */ +const JabberData *jabber_data_find_local_by_alt(const gchar *alt); +const JabberData *jabber_data_find_local_by_cid(const gchar *cid); +const JabberData *jabber_data_find_remote_by_cid(const gchar *cid); + +/* store data objects */ +void jabber_data_associate_local(JabberData *data, const gchar *alt); +void jabber_data_associate_remote(JabberData *data); + +/* handles iq requests */ +void jabber_data_parse(JabberStream *js, xmlnode *packet); + +void jabber_data_init(void); +void jabber_data_uninit(void); + +#endif /* JABBER_DATA_H */ diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/iq.c --- a/libpurple/protocols/jabber/iq.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/iq.c Tue Sep 23 17:44:38 2008 +0000 @@ -33,6 +33,7 @@ #include "si.h" #include "ping.h" #include "adhoccommands.h" +#include "data.h" #include "ibb.h" #ifdef _WIN32 @@ -356,6 +357,11 @@ return; } + if (xmlnode_get_child_with_namespace(packet, "data", XEP_0231_NAMESPACE)) { + jabber_data_parse(js, packet); + return; + } + if (xmlnode_get_child_with_namespace(packet, "data", XEP_0047_NAMESPACE) || xmlnode_get_child_with_namespace(packet, "close", XEP_0047_NAMESPACE) || xmlnode_get_child_with_namespace(packet, "open", XEP_0047_NAMESPACE)) { diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/jabber.c Tue Sep 23 17:44:38 2008 +0000 @@ -42,6 +42,7 @@ #include "auth.h" #include "buddy.h" #include "chat.h" +#include "data.h" #include "disco.h" #include "google.h" #include "iq.h" @@ -57,6 +58,7 @@ #include "pep.h" #include "adhoccommands.h" + #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) static PurplePlugin *my_protocol = NULL; @@ -640,7 +642,8 @@ JabberStream *js; JabberBuddy *my_jb = NULL; - gc->flags |= PURPLE_CONNECTION_HTML; + gc->flags |= PURPLE_CONNECTION_HTML | + PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY; js = gc->proto_data = g_new0(JabberStream, 1); js->gc = gc; js->fd = -1; @@ -1899,7 +1902,7 @@ JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr; - + if(!(jid = jabber_id_new(who))) return; diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Tue Sep 23 17:44:38 2008 +0000 @@ -43,7 +43,7 @@ #include "pep.h" #include "usertune.h" #include "caps.h" -#include "ibb.h" +#include "data.h" static PurplePluginProtocolInfo prpl_info = { @@ -137,8 +137,7 @@ purple_marshal_VOID__POINTER_POINTER, NULL, 2, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), purple_value_new_outgoing(PURPLE_TYPE_STRING)); - - + return TRUE; } @@ -149,9 +148,8 @@ purple_signal_unregister(plugin, "jabber-sending-xmlnode"); purple_signal_unregister(plugin, "jabber-sending-text"); - - jabber_si_uninit(); - jabber_ibb_uninit(); + + jabber_data_uninit(); return TRUE; } @@ -245,6 +243,13 @@ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + /* this should probably be part of global smiley theme settings later on, + shared with MSN */ + option = purple_account_option_bool_new(_("Show Custom Smileys"), + "custom_smileys", TRUE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + jabber_init_plugin(plugin); purple_prefs_remove("/plugins/prpl/jabber"); @@ -274,14 +279,19 @@ jabber_tune_init(); jabber_caps_init(); + jabber_data_init(); + + jabber_ibb_init(); jabber_si_init(); jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb); jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb); - jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", jabber_buzz_isenabled); - jabber_add_feature("ibb", XEP_0047_NAMESPACE, NULL); - + jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", + jabber_buzz_isenabled); + jabber_add_feature("bob", XEP_0231_NAMESPACE, + jabber_custom_smileys_isenabled); + jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata); } diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/message.c --- a/libpurple/protocols/jabber/message.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/message.c Tue Sep 23 17:44:38 2008 +0000 @@ -24,13 +24,17 @@ #include "notify.h" #include "server.h" #include "util.h" - #include "buddy.h" #include "chat.h" +#include "data.h" #include "google.h" #include "message.h" #include "xmlnode.h" #include "pep.h" +#include "smiley.h" +#include "iq.h" + +#include void jabber_message_free(JabberMessage *jm) { @@ -312,6 +316,225 @@ g_free(str); } +/* used internally by the functions below */ +typedef struct { + gchar *cid; + gchar *alt; +} JabberSmileyRef; + + +static void +jabber_message_get_refs_from_xmlnode_internal(const xmlnode *message, + GHashTable *table) +{ + xmlnode *child; + + for (child = xmlnode_get_child(message, "img") ; child ; + child = xmlnode_get_next_twin(child)) { + const gchar *src = xmlnode_get_attrib(child, "src"); + + if (g_str_has_prefix(src, "cid:")) { + const gchar *cid = src + 4; + + /* if we haven't "fetched" this yet... */ + if (!g_hash_table_lookup(table, cid)) { + /* take a copy of the cid and let the SmileyRef own it... */ + gchar *temp_cid = g_strdup(cid); + JabberSmileyRef *ref = g_new0(JabberSmileyRef, 1); + const gchar *alt = xmlnode_get_attrib(child, "alt"); + ref->cid = temp_cid; + /* if there is no "alt" string, use the cid... + include the entire src, eg. "cid:.." to avoid linkification */ + if (alt && alt[0] != '\0') { + /* workaround for when "alt" is set to the value of the + CID (which Jabbim seems to do), to avoid it showing up + as an mailto: link */ + if (purple_email_is_valid(alt)) { + ref->alt = g_strdup_printf("smiley:%s", alt); + } else { + ref->alt = g_strdup(alt); + } + } else { + ref->alt = g_strdup(src); + } + g_hash_table_insert(table, temp_cid, ref); + } + } + } + + for (child = message->child ; child ; child = child->next) { + jabber_message_get_refs_from_xmlnode_internal(child, table); + } +} + +static gboolean +jabber_message_get_refs_steal(gpointer key, gpointer value, gpointer user_data) +{ + GList **refs = (GList **) user_data; + JabberSmileyRef *ref = (JabberSmileyRef *) value; + + *refs = g_list_append(*refs, ref); + + return TRUE; +} + +static GList * +jabber_message_get_refs_from_xmlnode(const xmlnode *message) +{ + GList *refs = NULL; + GHashTable *unique_refs = g_hash_table_new(g_str_hash, g_str_equal); + + jabber_message_get_refs_from_xmlnode_internal(message, unique_refs); + (void) g_hash_table_foreach_steal(unique_refs, + jabber_message_get_refs_steal, (gpointer) &refs); + g_hash_table_destroy(unique_refs); + return refs; +} + +static gchar * +jabber_message_xml_to_string_strip_img_smileys(xmlnode *xhtml) +{ + gchar *markup = xmlnode_to_str(xhtml, NULL); + int len = strlen(markup); + int pos = 0; + GString *out = g_string_new(NULL); + + while (pos < len) { + /* this is a bit cludgy, maybe there is a better way to do this... + we need to find all tags within the XHTML and replace those + tags with the value of their "alt" attributes */ + if (g_str_has_prefix(&(markup[pos]), "")) { + pos2 += 2; + break; + } else if (g_str_has_prefix(&(markup[pos2]), "")) { + pos2 += 5; + break; + } + } + + /* note, if the above loop didn't find the end of the tag, + it the parsed string will be until the end of the input string, + in which case xmlnode_from_str will bail out and return NULL, + in this case the "if" statement below doesn't trigger and the + text is copied unchanged */ + img = xmlnode_from_str(&(markup[pos]), pos2 - pos); + src = xmlnode_get_attrib(img, "src"); + + if (g_str_has_prefix(src, "cid:")) { + const gchar *alt = xmlnode_get_attrib(img, "alt"); + /* if the "alt" attribute is empty, put the cid as smiley string */ + if (alt && alt[0] != '\0') { + /* if the "alt" is the same as the CID, as Jabbim does, + this prevents linkification... */ + if (purple_email_is_valid(alt)) { + gchar *safe_alt = g_strdup_printf("smiley:%s", alt); + out = g_string_append(out, safe_alt); + g_free(safe_alt); + } else { + out = g_string_append(out, alt); + } + } else { + out = g_string_append(out, src); + } + pos += pos2 - pos; + } else { + out = g_string_append_c(out, markup[pos]); + pos++; + } + + xmlnode_free(img); + + } else { + out = g_string_append_c(out, markup[pos]); + pos++; + } + } + + g_free(markup); + return g_string_free(out, FALSE); +} + +static void +jabber_message_add_remote_smileys(const xmlnode *message) +{ + xmlnode *data_tag; + for (data_tag = xmlnode_get_child_with_namespace(message, "data", XEP_0231_NAMESPACE) ; + data_tag ; + data_tag = xmlnode_get_next_twin(data_tag)) { + const gchar *cid = xmlnode_get_attrib(data_tag, "cid"); + const JabberData *data = jabber_data_find_remote_by_cid(cid); + + if (!data && cid != NULL) { + /* we haven't cached this already, let's add it */ + JabberData *new_data = jabber_data_create_from_xml(data_tag); + jabber_data_associate_remote(new_data); + } + } +} + +/* used in the function below to supply a conversation and shortcut for a + smiley */ +typedef struct { + PurpleConversation *conv; + gchar *alt; +} JabberDataRef; + +static void +jabber_message_get_data_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + JabberDataRef *ref = (JabberDataRef *) data; + PurpleConversation *conv = ref->conv; + const gchar *alt = ref->alt; + xmlnode *data_element = xmlnode_get_child(packet, "data"); + xmlnode *item_not_found = xmlnode_get_child(packet, "item-not-found"); + + /* did we get a data element as result? */ + if (data_element) { + JabberData *data = jabber_data_create_from_xml(data_element); + + if (data) { + jabber_data_associate_remote(data); + purple_conv_custom_smiley_write(conv, alt, + jabber_data_get_data(data), + jabber_data_get_size(data)); + purple_conv_custom_smiley_close(conv, alt); + } + + } else if (item_not_found) { + purple_debug_info("jabber", + "Responder didn't recognize requested data\n"); + } else { + purple_debug_error("jabber", "Unknown response to data request\n"); + } + g_free(ref->alt); + g_free(ref); +} + +static void +jabber_message_send_data_request(JabberStream *js, PurpleConversation *conv, + const gchar *cid, const gchar *who, + const gchar *alt) +{ + JabberIq *request = jabber_iq_new(js, JABBER_IQ_GET); + JabberDataRef *ref = g_new0(JabberDataRef, 1); + xmlnode *data_request = jabber_data_get_xml_request(cid); + + xmlnode_set_attrib(request->node, "to", who); + ref->conv = conv; + ref->alt = g_strdup(alt); + jabber_iq_set_callback(request, jabber_message_get_data_cb, ref); + xmlnode_insert_child(request->node, data_request); + + jabber_iq_send(request); +} + + void jabber_message_parse(JabberStream *js, xmlnode *packet) { JabberMessage *jm; @@ -368,12 +591,96 @@ } else if(!strcmp(child->name, "html") && !strcmp(xmlns,"http://jabber.org/protocol/xhtml-im")) { if(!jm->xhtml && xmlnode_get_child(child, "body")) { char *c; - jm->xhtml = xmlnode_to_str(child, NULL); - /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention - * treated \n as a newline for compatibility with other protocols + + const PurpleConnection *gc = js->gc; + const gchar *who = xmlnode_get_attrib(packet, "from"); + PurpleAccount *account = purple_connection_get_account(gc); + PurpleConversation *conv = NULL; + GList *smiley_refs = NULL; + gchar *reformatted_xhtml; + + if (purple_account_get_bool(account, "custom_smileys", TRUE)) { + /* find a list of smileys ("cid" and "alt" text pairs) + occuring in the message */ + smiley_refs = jabber_message_get_refs_from_xmlnode(child); + purple_debug_info("jabber", "found %d smileys\n", + g_list_length(smiley_refs)); + + if (jm->type == JABBER_MESSAGE_GROUPCHAT) { + JabberID *jid = jabber_id_new(jm->from); + JabberChat *chat = NULL; + + if (jid) { + chat = jabber_chat_find(js, jid->node, jid->domain); + conv = chat->conv; + } + + jabber_id_free(jid); + } else { + conv = + purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, + who, account); + if (!conv) { + /* we need to create the conversation here */ + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, + account, who); + } + } + + /* process any newly provided smileys */ + jabber_message_add_remote_smileys(packet); + } + + /* reformat xhtml so that img tags with a "cid:" src gets + translated to the bare text of the emoticon (the "alt" attrib) */ + /* this is done also when custom smiley retrieval is turned off, + this way the receiver always sees the shortcut instead */ + reformatted_xhtml = + jabber_message_xml_to_string_strip_img_smileys(child); + + jm->xhtml = reformatted_xhtml; + + /* add known custom emoticons to the conversation */ + /* note: if there were no smileys in the incoming message, or + if receiving custom smileys is turned off, smiley_refs will + be NULL */ + for (; smiley_refs ; smiley_refs = g_list_delete_link(smiley_refs, smiley_refs)) { + JabberSmileyRef *ref = (JabberSmileyRef *) smiley_refs->data; + const gchar *cid = ref->cid; + const gchar *alt = ref->alt; + + purple_debug_info("jabber", + "about to add custom smiley %s to the conv\n", alt); + if (purple_conv_custom_smiley_add(conv, alt, "cid", cid, + TRUE)) { + const JabberData *data = + jabber_data_find_remote_by_cid(cid); + /* if data is already known, we add write it immediatly */ + if (data) { + purple_debug_info("jabber", + "data is already known\n"); + purple_conv_custom_smiley_write(conv, alt, + jabber_data_get_data(data), + jabber_data_get_size(data)); + purple_conv_custom_smiley_close(conv, alt); + } else { + /* we need to request the smiley (data) */ + purple_debug_info("jabber", + "data is unknown, need to request it\n"); + jabber_message_send_data_request(js, conv, cid, who, + alt); + } + } + g_free(ref->cid); + g_free(ref->alt); + g_free(ref); + } + + /* Convert all newlines to whitespace. Technically, even regular, non-XML HTML is supposed to ignore newlines, but Pidgin has, as convention + * treated \n as a newline for compatibility with other protocols */ for (c = jm->xhtml; *c != '\0'; c++) { - if (*c == '\n') + if (*c == '\n') *c = ' '; } } @@ -503,6 +810,129 @@ jabber_message_free(jm); } +static const gchar * +jabber_message_get_mimetype_from_ext(const gchar *ext) +{ + if (strcmp(ext, "png") == 0) { + return "image/png"; + } else if (strcmp(ext, "gif") == 0) { + return "image/gif"; + } else if (strcmp(ext, "jpg") == 0) { + return "image/jpeg"; + } else if (strcmp(ext, "tif") == 0) { + return "image/tif"; + } else { + return "image/x-icon"; /* or something... */ + } +} + +static GList * +jabber_message_xhtml_find_smileys(const char *xhtml) +{ + GList *smileys = purple_smileys_get_all(); + GList *found_smileys = NULL; + + for (; smileys ; smileys = g_list_delete_link(smileys, smileys)) { + PurpleSmiley *smiley = (PurpleSmiley *) smileys->data; + const gchar *shortcut = purple_smiley_get_shortcut(smiley); + const gssize len = strlen(shortcut); + + gchar *escaped = g_markup_escape_text(shortcut, len); + const char *pos = strstr(xhtml, escaped); + + if (pos) { + found_smileys = g_list_append(found_smileys, smiley); + } + + g_free(escaped); + } + + return found_smileys; +} + +static gchar * +jabber_message_get_smileyfied_xhtml(const gchar *xhtml, const GList *smileys) +{ + /* create XML element for all smileys (img tags) */ + GString *result = g_string_new(NULL); + int pos = 0; + int length = strlen(xhtml); + + while (pos < length) { + const GList *iterator; + gboolean found_smiley = FALSE; + + for (iterator = smileys ; iterator ; + iterator = g_list_next(iterator)) { + const PurpleSmiley *smiley = (PurpleSmiley *) iterator->data; + const gchar *shortcut = purple_smiley_get_shortcut(smiley); + const gssize len = strlen(shortcut); + gchar *escaped = g_markup_escape_text(shortcut, len); + + if (g_str_has_prefix(&(xhtml[pos]), escaped)) { + /* we found the current smiley at this position */ + const JabberData *data = + jabber_data_find_local_by_alt(shortcut); + xmlnode *img = jabber_data_get_xhtml_im(data, shortcut); + int len; + gchar *img_text = xmlnode_to_str(img, &len); + + found_smiley = TRUE; + result = g_string_append(result, img_text); + g_free(img_text); + pos += strlen(escaped); + g_free(escaped); + xmlnode_free(img); + break; + } else { + /* cleanup from the before the next round... */ + g_free(escaped); + } + } + if (!found_smiley) { + /* there was no smiley here, just copy one byte */ + result = g_string_append_c(result, xhtml[pos]); + pos++; + } + } + + return g_string_free(result, FALSE); +} + +static gboolean +jabber_conv_support_custom_smileys(const PurpleConnection *gc, + const PurpleConversation *conv, + const gchar *who) +{ + JabberStream *js = (JabberStream *) gc->proto_data; + JabberBuddy *jb; + + if (!js) { + purple_debug_error("jabber", + "jabber_conv_support_custom_smileys: could not find stream\n"); + return FALSE; + } + + jb = jabber_buddy_find(js, who, FALSE); + if (!jb) { + purple_debug_error("jabber", + "jabber_conv_support_custom smileys: could not find buddy\n"); + return FALSE; + } + + + + switch (purple_conversation_get_type(conv)) { + /* for the time being, we will not support custom smileys in MUCs */ + case PURPLE_CONV_TYPE_IM: + return jabber_buddy_has_capability(jb, XEP_0231_NAMESPACE); + break; + default: + return FALSE; + break; + } +} + void jabber_message_send(JabberMessage *jm) { xmlnode *message, *child; @@ -588,7 +1018,55 @@ } if(jm->xhtml) { - child = xmlnode_from_str(jm->xhtml, -1); + PurpleAccount *account = purple_connection_get_account(jm->js->gc); + PurpleConversation *conv = + purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, jm->to, + account); + + if (jabber_conv_support_custom_smileys(jm->js->gc, conv, jm->to)) { + GList *found_smileys = jabber_message_xhtml_find_smileys(jm->xhtml); + + if (found_smileys) { + gchar *smileyfied_xhtml = NULL; + const GList *iterator; + + for (iterator = found_smileys; iterator ; + iterator = g_list_next(iterator)) { + const PurpleSmiley *smiley = + (PurpleSmiley *) iterator->data; + const gchar *shortcut = purple_smiley_get_shortcut(smiley); + const JabberData *data = + jabber_data_find_local_by_alt(shortcut); + + /* the object has not been sent before */ + if (!data) { + PurpleStoredImage *image = + purple_smiley_get_stored_image(smiley); + const gchar *ext = purple_imgstore_get_extension(image); + JabberStream *js = jm->js; + + JabberData *new_data = + jabber_data_create_from_data(purple_imgstore_get_data(image), + purple_imgstore_get_size(image), + jabber_message_get_mimetype_from_ext(ext), js); + purple_debug_info("jabber", + "cache local smiley alt = %s, cid = %s\n", + shortcut, jabber_data_get_cid(new_data)); + jabber_data_associate_local(new_data, shortcut); + } + } + + smileyfied_xhtml = + jabber_message_get_smileyfied_xhtml(jm->xhtml, found_smileys); + child = xmlnode_from_str(smileyfied_xhtml, -1); + g_free(smileyfied_xhtml); + g_list_free(found_smileys); + } else { + child = xmlnode_from_str(jm->xhtml, -1); + } + } else { + child = xmlnode_from_str(jm->xhtml, -1); + } if(child) { xmlnode_insert_child(message, child); } else { @@ -762,3 +1240,11 @@ return js->allowBuzz; } +gboolean jabber_custom_smileys_isenabled(JabberStream *js, const gchar *shortname, + const gchar *namespace) +{ + const PurpleConnection *gc = js->gc; + PurpleAccount *account = purple_connection_get_account(gc); + + return purple_account_get_bool(account, "custom_smileys", TRUE); +} diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/jabber/message.h --- a/libpurple/protocols/jabber/message.h Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/jabber/message.h Tue Sep 23 17:44:38 2008 +0000 @@ -80,4 +80,7 @@ gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace); +gboolean jabber_custom_smileys_isenabled(JabberStream *js, const gchar *shortname, + const gchar *namespace); + #endif /* _PURPLE_JABBER_MESSAGE_H_ */ diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/msn/notification.c --- a/libpurple/protocols/msn/notification.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/msn/notification.c Tue Sep 23 17:44:38 2008 +0000 @@ -613,16 +613,13 @@ xmlnode_set_attrib(adl_node, "l", "1"); /*get the userlist*/ - for (l = session->userlist->users; l != NULL; l = l->next) { + for (l = session->userlist->users; l != NULL; l = l->next){ user = l->data; /* skip RL & PL during initial dump */ if (!(user->list_op & MSN_LIST_OP_MASK)) continue; - if (!strcmp(user->passport, "messenger@microsoft.com")) - continue; - msn_add_contact_xml(session, adl_node, user->passport, user->list_op & MSN_LIST_OP_MASK, user->networkid); @@ -774,6 +771,53 @@ } static void +adl_241_error_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, + size_t len) +{ + /* khc: some googling suggests that error 241 means the buddy is somehow + in the local list, but not the server list, and that we should add + those buddies to the addressbook. For now I will just notify the user + about the raw payload, because I am lazy */ + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + xmlnode *adl; + xmlnode *domain; + GString *emails; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + + adl = xmlnode_from_str(payload, len); + emails = g_string_new(NULL); + + domain = xmlnode_get_child(adl, "d"); + while (domain) { + const char *domain_str = xmlnode_get_attrib(domain, "n"); + xmlnode *contact = xmlnode_get_child(domain, "c"); + while (contact) { + g_string_append_printf(emails, "%s@%s\n", + xmlnode_get_attrib(contact, "n"), domain_str); + contact = xmlnode_get_next_twin(contact); + } + domain = xmlnode_get_next_twin(domain); + } + + purple_notify_error(gc, NULL, + _("The following users are missing from your addressbook"), emails->str); + g_string_free(emails, TRUE); + xmlnode_free(adl); +} + +static void +adl_241_error_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + cmdproc->last_cmd->payload_cb = adl_241_error_cmd_post; + cmd->payload_len = atoi(cmd->params[1]); +} + +static void fqy_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) { @@ -1132,6 +1176,7 @@ friendly = purple_url_decode(cmd->params[3]); user = msn_userlist_find_user(session->userlist, passport); + if (user == NULL) return; old_friendly = msn_user_get_friendly_name(user); if (!old_friendly || (old_friendly && (!friendly || strcmp(old_friendly, friendly)))) @@ -1961,6 +2006,8 @@ msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd); + msn_table_add_cmd(cbs_table, NULL, "241", adl_241_error_cmd); + msn_table_add_error(cbs_table, "ADD", add_error); msn_table_add_error(cbs_table, "ADL", adl_error); msn_table_add_error(cbs_table, "REG", reg_error); diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/null/nullprpl.c --- a/libpurple/protocols/null/nullprpl.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/null/nullprpl.c Tue Sep 23 17:44:38 2008 +0000 @@ -46,8 +46,12 @@ #include +/* If you're using this as the basis of a prpl that will be distributed + * separately from libpurple, remove the internal.h include below and replace + * it with code to include your own config.h or similar. If you're going to + * provide for translation, you'll also need to setup the gettext macros. */ #include "internal.h" -#include "config.h" + #include "account.h" #include "accountopt.h" #include "blist.h" diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Tue Sep 23 17:44:38 2008 +0000 @@ -55,6 +55,12 @@ /* #define TRY_WEBMESSENGER_LOGIN 0 */ +/* One hour */ +#define PING_TIMEOUT 3600 + +/* One minute */ +#define KEEPALIVE_TIMEOUT 60 + static void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *, PurpleGroup *); #ifdef TRY_WEBMESSENGER_LOGIN static void yahoo_login_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message); @@ -3001,6 +3007,7 @@ yd->xfer_peer_idstring_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); yd->confs = NULL; yd->conf_id = 2; + yd->last_keepalive = yd->last_ping = time(NULL); yd->current_status = get_yahoo_status_from_purple_status(status); @@ -3059,7 +3066,7 @@ } g_slist_free(yd->cookies); - yd->chat_online = 0; + yd->chat_online = FALSE; if (yd->in_chat) yahoo_c_leave(gc, 1); /* 1 = YAHOO_CHAT_ID */ @@ -3871,21 +3878,36 @@ static void yahoo_keepalive(PurpleConnection *gc) { + struct yahoo_packet *pkt; struct yahoo_data *yd = gc->proto_data; - struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, 0); - yahoo_packet_send_and_free(pkt, yd); - - if (!yd->chat_online) - return; - - if (yd->wm) { - ycht_chat_send_keepalive(yd->ycht); - return; + time_t now = time(NULL); + + /* We're only allowed to send a ping once an hour or the servers will boot us */ + if ((now - yd->last_ping) >= PING_TIMEOUT) { + yd->last_ping = now; + + /* The native client will only send PING or CHATPING */ + if (yd->chat_online) { + if (yd->wm) { + ycht_chat_send_keepalive(yd->ycht); + } else { + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash_str(pkt, 109, purple_connection_get_display_name(gc)); + yahoo_packet_send_and_free(pkt, yd); + } + } else { + pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_send_and_free(pkt, yd); + } } - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, 0); - yahoo_packet_hash_str(pkt, 109, purple_connection_get_display_name(gc)); - yahoo_packet_send_and_free(pkt, yd); + if ((now - yd->last_keepalive) >= KEEPALIVE_TIMEOUT) { + yd->last_keepalive = now; + pkt = yahoo_packet_new(YAHOO_SERVICE_KEEPALIVE, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash_str(pkt, 0, purple_connection_get_display_name(gc)); + yahoo_packet_send_and_free(pkt, yd); + } + } static void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g) diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/yahoo/yahoo.h --- a/libpurple/protocols/yahoo/yahoo.h Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoo.h Tue Sep 23 17:44:38 2008 +0000 @@ -176,6 +176,8 @@ * the server expects us to keep track of the group for which it is sending us contact names. */ char *current_list15_grp; + time_t last_ping; + time_t last_keepalive; }; #define YAHOO_MAX_STATUS_MESSAGE_LENGTH (255) diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/yahoo/yahoo_packet.h --- a/libpurple/protocols/yahoo/yahoo_packet.h Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.h Tue Sep 23 17:44:38 2008 +0000 @@ -76,7 +76,7 @@ YAHOO_SERVICE_IGNORECONTACT, /* > 1, 7, 13 < 1, 66, 13, 0*/ YAHOO_SERVICE_REJECTCONTACT, YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */ - /* YAHOO_SERVICE_??? = 0x8A, */ + YAHOO_SERVICE_KEEPALIVE = 0x8A, YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1*/ YAHOO_SERVICE_CHATGOTO, YAHOO_SERVICE_CHATJOIN, /* > 1 104-room 129-1600326591 62-2 */ diff -r dc01f9b0aaa3 -r 53b073da65ee libpurple/protocols/yahoo/yahoochat.c --- a/libpurple/protocols/yahoo/yahoochat.c Mon Sep 22 17:23:59 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoochat.c Tue Sep 23 17:44:38 2008 +0000 @@ -369,7 +369,7 @@ struct yahoo_data *yd = (struct yahoo_data *) gc->proto_data; if (pkt->status == 1) { - yd->chat_online = 1; + yd->chat_online = TRUE; /* We need to goto a user in chat */ if (yd->pending_chat_goto) { @@ -411,7 +411,7 @@ } if (pkt->status == 1) { - yd->chat_online = 0; + yd->chat_online = FALSE; g_free(yd->pending_chat_room); yd->pending_chat_room = NULL; g_free(yd->pending_chat_id); @@ -881,7 +881,7 @@ yahoo_packet_hash_str(pkt, 1, dn); yahoo_packet_send_and_free(pkt, yd); - yd->chat_online = 0; + yd->chat_online = FALSE; g_free(yd->pending_chat_room); yd->pending_chat_room = NULL; g_free(yd->pending_chat_id); diff -r dc01f9b0aaa3 -r 53b073da65ee pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Mon Sep 22 17:23:59 2008 +0000 +++ b/pidgin/gtkimhtml.c Tue Sep 23 17:44:38 2008 +0000 @@ -446,6 +446,20 @@ parent_style_set(widget, prev_style); } +static void +gtk_imhtml_set_link_color(GtkIMHtml *imhtml, GtkTextTag *tag) +{ + GdkColor *color = NULL; + gboolean visited = !!g_object_get_data(G_OBJECT(tag), "visited"); + gtk_widget_style_get(GTK_WIDGET(imhtml), visited ? "hyperlink-visited-color" : "hyperlink-color", &color, NULL); + if (color) { + g_object_set(G_OBJECT(tag), "foreground-gdk", color, NULL); + gdk_color_free(color); + } else { + g_object_set(G_OBJECT(tag), "foreground", visited ? "#800000" : "blue", NULL); + } +} + static gint gtk_imhtml_tip_paint (GtkIMHtml *imhtml) { @@ -566,7 +580,6 @@ int x, y; char *tip = NULL; GSList *tags = NULL, *templist = NULL; - GdkColor *norm, *pre; GtkTextTag *tag = NULL, *oldprelit_tag; GtkTextChildAnchor* anchor; gboolean hand = TRUE; @@ -588,13 +601,15 @@ templist = templist->next; } - if (tip) { - gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-prelight-color", &pre, NULL); + if (tip && (!tag || !g_object_get_data(G_OBJECT(tag), "visited"))) { GTK_IMHTML(imhtml)->prelit_tag = tag; if (tag != oldprelit_tag) { - if (pre) + GdkColor *pre = NULL; + gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-prelight-color", &pre, NULL); + if (pre) { g_object_set(G_OBJECT(tag), "foreground-gdk", pre, NULL); - else + gdk_color_free(pre); + } else g_object_set(G_OBJECT(tag), "foreground", "#70a0ff", NULL); } } else { @@ -602,11 +617,7 @@ } if ((oldprelit_tag != NULL) && (GTK_IMHTML(imhtml)->prelit_tag != oldprelit_tag)) { - gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &norm, NULL); - if (norm) - g_object_set(G_OBJECT(oldprelit_tag), "foreground-gdk", norm, NULL); - else - g_object_set(G_OBJECT(oldprelit_tag), "foreground", "blue", NULL); + gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), oldprelit_tag); } if (GTK_IMHTML(imhtml)->tip) { @@ -670,12 +681,7 @@ { /* when leaving the widget, clear any current & pending tooltips and restore the cursor */ if (GTK_IMHTML(imhtml)->prelit_tag) { - GdkColor *norm; - gtk_widget_style_get(GTK_WIDGET(imhtml), "hyperlink-color", &norm, NULL); - if (norm) - g_object_set(G_OBJECT(GTK_IMHTML(imhtml)->prelit_tag), "foreground-gdk", norm, NULL); - else - g_object_set(G_OBJECT(GTK_IMHTML(imhtml)->prelit_tag), "foreground", "blue", NULL); + gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), GTK_IMHTML(imhtml)->prelit_tag); GTK_IMHTML(imhtml)->prelit_tag = NULL; } @@ -1482,6 +1488,10 @@ _("Hyperlink color"), _("Color to draw hyperlinks."), GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-visited-color", + _("Hyperlink visited color"), + _("Color to draw hyperlinks after it has been visited (or activated)."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-prelight-color", _("Hyperlink prelight color"), _("Color to draw hyperlinks when mouse is over them."), @@ -1679,20 +1689,24 @@ struct url_data { GObject *object; gchar *url; + GtkTextTag *tag; }; static void url_data_destroy(gpointer mydata) { struct url_data *data = mydata; g_object_unref(data->object); + g_object_unref(data->tag); g_free(data->url); g_free(data); } -static void url_open(GtkWidget *w, struct url_data *data) { +static void url_open(GtkWidget *w, struct url_data *data) +{ if(!data) return; g_signal_emit(data->object, signals[URL_CLICKED], 0, data->url); - + g_object_set_data(G_OBJECT(data->tag), "visited", GINT_TO_POINTER(TRUE)); + gtk_imhtml_set_link_color(GTK_IMHTML(data->object), data->tag); } static void url_copy(GtkWidget *w, gchar *url) { @@ -1706,7 +1720,8 @@ } /* The callback for an event on a link tag. */ -static gboolean tag_event(GtkTextTag *tag, GObject *imhtml, GdkEvent *event, GtkTextIter *arg2, gpointer unused) { +static gboolean tag_event(GtkTextTag *tag, GObject *imhtml, GdkEvent *event, GtkTextIter *arg2, gpointer unused) +{ GdkEventButton *event_button = (GdkEventButton *) event; if (GTK_IMHTML(imhtml)->editable) return FALSE; @@ -1723,12 +1738,15 @@ g_object_ref(G_OBJECT(tag)); g_signal_emit(imhtml, signals[URL_CLICKED], 0, g_object_get_data(G_OBJECT(tag), "link_url")); g_object_unref(G_OBJECT(tag)); + g_object_set_data(G_OBJECT(tag), "visited", GINT_TO_POINTER(TRUE)); + gtk_imhtml_set_link_color(GTK_IMHTML(imhtml), tag); return FALSE; } else if(event_button->button == 3) { GtkWidget *img, *item, *menu; struct url_data *tempdata = g_new(struct url_data, 1); tempdata->object = g_object_ref(imhtml); tempdata->url = g_strdup(g_object_get_data(G_OBJECT(tag), "link_url")); + tempdata->tag = g_object_ref(tag); /* Don't want the tooltip around if user right-clicked on link */ if (GTK_IMHTML(imhtml)->tip_window) { diff -r dc01f9b0aaa3 -r 53b073da65ee pidgin/gtkmenutray.c --- a/pidgin/gtkmenutray.c Mon Sep 22 17:23:59 2008 +0000 +++ b/pidgin/gtkmenutray.c Tue Sep 23 17:44:38 2008 +0000 @@ -84,6 +84,14 @@ } static void +pidgin_menu_tray_map(GtkWidget *widget) +{ + GTK_WIDGET_CLASS(parent_class)->map(widget); + gtk_container_add(GTK_CONTAINER(widget), + PIDGIN_MENU_TRAY(widget)->tray); +} + +static void pidgin_menu_tray_finalize(GObject *obj) { PidginMenuTray *tray = PIDGIN_MENU_TRAY(obj); @@ -109,6 +117,7 @@ pidgin_menu_tray_class_init(PidginMenuTrayClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GtkItemClass *item_class = GTK_ITEM_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); GParamSpec *pspec; parent_class = g_type_class_peek_parent(klass); @@ -119,6 +128,8 @@ item_class->select = pidgin_menu_tray_select; item_class->deselect = pidgin_menu_tray_deselect; + widget_class->map = pidgin_menu_tray_map; + pspec = g_param_spec_object("box", "The box", "The box", GTK_TYPE_BOX, @@ -152,8 +163,6 @@ gtk_widget_set_size_request(widget, -1, height); } - gtk_container_add(GTK_CONTAINER(menu_tray), menu_tray->tray); - gtk_widget_show(menu_tray->tray); } diff -r dc01f9b0aaa3 -r 53b073da65ee pidgin/gtksmiley.c --- a/pidgin/gtksmiley.c Mon Sep 22 17:23:59 2008 +0000 +++ b/pidgin/gtksmiley.c Tue Sep 23 17:44:38 2008 +0000 @@ -273,8 +273,8 @@ gsize size = 0; gchar *filename; - gdk_pixbuf_save_to_bufferv(s->custom_pixbuf, &buffer, &size, - "png", NULL, NULL, NULL); + gdk_pixbuf_save_to_buffer(s->custom_pixbuf, &buffer, &size, + "png", NULL, "compression", "9", NULL, NULL); filename = purple_util_get_image_filename(buffer, size); s->filename = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL); purple_util_write_data_to_file_absolute(s->filename, buffer, size); diff -r dc01f9b0aaa3 -r 53b073da65ee pidgin/plugins/pidginrc.c --- a/pidgin/plugins/pidginrc.c Mon Sep 22 17:23:59 2008 +0000 +++ b/pidgin/plugins/pidginrc.c Tue Sep 23 17:44:38 2008 +0000 @@ -31,6 +31,7 @@ "/plugins/gtk/purplerc/color/GtkWidget::cursor-color", "/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color", "/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-visited-color", "/plugins/gtk/purplerc/color/GtkIMHtml::send-name-color", "/plugins/gtk/purplerc/color/GtkIMHtml::receive-name-color", "/plugins/gtk/purplerc/color/GtkIMHtml::highlight-name-color", @@ -40,6 +41,7 @@ "/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color", "/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color", "/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-visited-color", "/plugins/gtk/purplerc/set/color/GtkIMHtml::send-name-color", "/plugins/gtk/purplerc/set/color/GtkIMHtml::receive-name-color", "/plugins/gtk/purplerc/set/color/GtkIMHtml::highlight-name-color", @@ -49,6 +51,7 @@ N_("Cursor Color"), N_("Secondary Cursor Color"), N_("Hyperlink Color"), + N_("Visited Hyperlink Color"), N_("Sent Message Name Color"), N_("Received Message Name Color"), N_("Highlighted Message Name Color"), diff -r dc01f9b0aaa3 -r 53b073da65ee po/POTFILES.in --- a/po/POTFILES.in Mon Sep 22 17:23:59 2008 +0000 +++ b/po/POTFILES.in Tue Sep 23 17:44:38 2008 +0000 @@ -183,8 +183,6 @@ libpurple/util.c libpurple/win32/libc_interface.c pidgin.desktop.in -pidgin/artwork/hicolor/24x24/emotes/default.theme.in -pidgin/artwork/hicolor/24x24/emotes/none/none.theme.in pidgin/eggtrayicon.c pidgin/gtkaccount.c pidgin/gtkblist.c