Mercurial > pidgin
changeset 27053:f5369bdd0285
propagate from branch 'im.pidgin.pidgin' (head 6fb44be5a32516ac1d940330c4f7b8d530208a16)
to branch 'im.pidgin.cpw.malu.client_type' (head d22d5bca23b57e3cc71ec9b4d23bf4ff4cb44fc6)
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Sat, 06 Jun 2009 06:21:39 +0000 |
parents | 5d4e33e3938a (diff) 68d6c6517ef8 (current diff) |
children | dd7e7071d46d |
files | libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/jabber.c |
diffstat | 60 files changed, 3046 insertions(+), 1015 deletions(-) [+] |
line wrap: on
line diff
--- a/COPYRIGHT Sun May 31 18:39:19 2009 +0000 +++ b/COPYRIGHT Sat Jun 06 06:21:39 2009 +0000 @@ -333,6 +333,7 @@ Diego Petten Nathan Peterson Sebastián E. Peyrott +Andrea Piccinelli Celso Pinto Joao Luís Marques Pinto Aleksander Piotrowski @@ -451,6 +452,7 @@ Mark Tiefenbruck Andrew Tinney Jeffery To +Krzysztof Tobola (kreez) Warren Togami Stu Tomlinson Bill Tompkins
--- a/ChangeLog Sun May 31 18:39:19 2009 +0000 +++ b/ChangeLog Sat Jun 06 06:21:39 2009 +0000 @@ -18,10 +18,14 @@ from you on MSN. * DNS servers are re-read when DNS queries fail in case the system has moved to a new network and the old servers are not accessible. + * Gadu-Gadu accounts can specify a server to which to connect. + (Krzysztof "kreez" Tobola) XMPP: - * Voice & Video support with Jingle (XEP-0166, 0167, 0176, & 0177), and - voice support with GTalk and GMail. (Mike "Maiku" Ruprecht) + * Voice & Video support with Jingle (XEP-0166, 0167, 0176, & 0177), voice + support with GTalk and voice and video support with the GMail web + client. (Mike "Maiku" Ruprecht) + * Added a Service Discovery Browser plugin for Pidgin. (Andrei Mozzhuhin) * Support for in-band bytestreams for file transfers (XEP-0047). (Marcus Lundblad) * Support for sending and receiving attentions (equivalent to "buzz" @@ -39,6 +43,8 @@ * /affiliate and /role will now list the room members with the specified affiliation/role if possible. (Andrei Mozzhuhin) * Put section breaks between resources in "Get Info" to improve readability. + * Silently remove invalid XML 1.0 entities (e.g. ASCII control characters) + from sent messages. * XHTML markup is only included in outgoing messages when the message contains formatting. * Show when the user was last logged in when doing "Get Info" on an offline @@ -48,6 +54,9 @@ chat to avoid getting too many fetch requests). * Fix an issue with Jabber (pre-XMPP) servers and the user's preference to require SSL not being respected. + * Fix an issue where Cyrus SASL DIGEST MD5 authentication might fail if + the username, password, or realm (the JID domain) contain non-ASCII + characters. Yahoo: * P2P file transfers. (Sulabh Mahajan) @@ -80,12 +89,24 @@ rejoin. * Always set unseen-count and unseen-state on conversations. (Joshua Stein) + * Fix a bug in 'Conversation Colors' plugin for RTL messages. + * Pressing the Left and Right arrow keys in the buddy list will expand and + collapse buddy groups or contacts. (Peter Ruibal) + * Support saving animated custom smileys as animated images or animated + custom smileys. (Andrea Piccinelli) Finch: * The hardware cursor is updated correctly. This will be useful especially for users of braille terminals, screen readers etc. * Added a TinyURL plugin, which aids copying longer URLs. + Pidgin GTK+ Theme Control Plugin: + * Removed mouse cursor color preferences. + * Added "Typing Notification Color" preference. + * Added "Disable Typing Notification Text" preference. + * Preferences have been reorganized into three tabs for Colors, Fonts, and + Miscellaneous categories. + version 2.5.6 (05/19/2009): libpurple: * Improve sleep behavior by aggregation of longer timeouts on second
--- a/ChangeLog.API Sun May 31 18:39:19 2009 +0000 +++ b/ChangeLog.API Sat Jun 06 06:21:39 2009 +0000 @@ -33,11 +33,13 @@ * purple_global_proxy_set_info * purple_group_destroy * purple_log_get_activity_score + * purple_markup_is_rtl * purple_network_force_online * purple_network_set_stun_server * purple_network_set_turn_server * purple_network_get_stun_ip * purple_network_get_turn_ip + * purple_proxy_connect_udp * purple_prpl_get_media_caps * purple_prpl_got_account_actions * purple_prpl_initiate_media @@ -45,6 +47,8 @@ * purple_request_field_get_ui_data * purple_request_field_set_ui_data * purple_strequal + * purple_utf8_strip_unprintables + * purple_util_fetch_url_request_len_with_account * xmlnode_from_file * xmlnode_get_parent * xmlnode_set_attrib_full @@ -55,6 +59,13 @@ which was completely non-deterministic. If you want to remove the attribute with no namespace, then use NULL with xmlnode_remove_with_namespace. + * Plugins may now emit the jabber-sending-xmlnode signal in order + to send stanzas; this method is preferred to the prpl send_raw + function as other plugins listening to the signal see them. + * The conversation-updated signal with a PURPLE_CONV_UPDATE_TYPING + update type is emitted when receiving an IM. Previously, the + typing state was modified (and the buddy-typing-stopped signal + emitted), but this signal was not emitted. Deprecated: * buddy-added and buddy-removed blist signals @@ -70,6 +81,7 @@ * purple_status_set_attr_string * purple_presence_add_status * purple_presence_add_list + * purple_util_fetch_url_request_len * xmlnode_set_attrib_with_namespace * xmlnode_set_attrib_with_prefix @@ -84,6 +96,7 @@ * pidgin_blist_set_theme * pidgin_blist_get_theme * pidgin_prefs_labeled_password + * pidgin_smiley_editor_set_data * pidgin_sound_is_customized * pidgin_utils_init, pidgin_utils_uninit * pidgin_notify_pounce_add
--- a/configure.ac Sun May 31 18:39:19 2009 +0000 +++ b/configure.ac Sat Jun 06 06:21:39 2009 +0000 @@ -2476,6 +2476,7 @@ pidgin/pixmaps/emotes/small/16/Makefile pidgin/plugins/Makefile pidgin/plugins/cap/Makefile + pidgin/plugins/disco/Makefile pidgin/plugins/gestures/Makefile pidgin/plugins/gevolution/Makefile pidgin/plugins/musicmessaging/Makefile
--- a/libpurple/blist.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/blist.c Sat Jun 06 06:21:39 2009 +0000 @@ -977,18 +977,26 @@ PurpleConversation *conv; PurpleBlistNode *bnode; char *old_alias; + char *new_alias = NULL; g_return_if_fail(contact != NULL); - if (!purple_strings_are_different(contact->alias, alias)) + if ((alias != NULL) && (*alias != '\0')) + new_alias = purple_utf8_strip_unprintables(alias); + + if (!purple_strings_are_different(contact->alias, new_alias)) { + g_free(new_alias); return; + } old_alias = contact->alias; - if ((alias != NULL) && (*alias != '\0')) - contact->alias = g_strdup(alias); - else + if ((new_alias != NULL) && (*new_alias != '\0')) + contact->alias = new_alias; + else { contact->alias = NULL; + g_free(new_alias); /* could be "\0" */ + } purple_blist_schedule_save(); @@ -1014,18 +1022,26 @@ { PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); char *old_alias; + char *new_alias = NULL; g_return_if_fail(chat != NULL); - if (!purple_strings_are_different(chat->alias, alias)) + if ((alias != NULL) && (*alias != '\0')) + new_alias = purple_utf8_strip_unprintables(alias); + + if (!purple_strings_are_different(chat->alias, new_alias)) { + g_free(new_alias); return; + } old_alias = chat->alias; - if ((alias != NULL) && (*alias != '\0')) - chat->alias = g_strdup(alias); - else + if ((new_alias != NULL) && (*new_alias != '\0')) + chat->alias = new_alias; + else { chat->alias = NULL; + g_free(new_alias); /* could be "\0" */ + } purple_blist_schedule_save(); @@ -1042,18 +1058,26 @@ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); PurpleConversation *conv; char *old_alias; + char *new_alias = NULL; g_return_if_fail(buddy != NULL); - if (!purple_strings_are_different(buddy->alias, alias)) + if ((alias != NULL) && (*alias != '\0')) + new_alias = purple_utf8_strip_unprintables(alias); + + if (!purple_strings_are_different(buddy->alias, new_alias)) { + g_free(new_alias); return; + } old_alias = buddy->alias; - if ((alias != NULL) && (*alias != '\0')) + if ((new_alias != NULL) && (*new_alias != '\0')) buddy->alias = g_strdup(alias); - else + else { buddy->alias = NULL; + g_free(new_alias); /* could be "\0" */ + } purple_blist_schedule_save(); @@ -1075,18 +1099,26 @@ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); PurpleConversation *conv; char *old_alias; + char *new_alias = NULL; g_return_if_fail(buddy != NULL); - if (!purple_strings_are_different(buddy->server_alias, alias)) + if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL)) + new_alias = purple_utf8_strip_unprintables(alias); + + if (!purple_strings_are_different(buddy->server_alias, new_alias)) { + g_free(new_alias); return; + } old_alias = buddy->server_alias; - if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL)) - buddy->server_alias = g_strdup(alias); - else + if ((new_alias != NULL) && (*new_alias != '\0')) + buddy->server_alias = new_alias; + else { buddy->server_alias = NULL; + g_free(new_alias); /* could be "\0"; */ + } purple_blist_schedule_save(); @@ -1106,19 +1138,24 @@ /* * TODO: If merging, prompt the user if they want to merge. */ -void purple_blist_rename_group(PurpleGroup *source, const char *new_name) +void purple_blist_rename_group(PurpleGroup *source, const char *name) { PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); PurpleGroup *dest; gchar *old_name; + gchar *new_name; GList *moved_buddies = NULL; GSList *accts; g_return_if_fail(source != NULL); - g_return_if_fail(new_name != NULL); - - if (*new_name == '\0' || purple_strequal(new_name, source->name)) + g_return_if_fail(name != NULL); + + new_name = purple_utf8_strip_unprintables(name); + + if (*new_name == '\0' || purple_strequal(new_name, source->name)) { + g_free(new_name); return; + } dest = purple_find_group(new_name); if (dest != NULL && purple_utf8_strcasecmp(source->name, dest->name) != 0) { @@ -1160,6 +1197,7 @@ old_name = g_strdup(source->name); purple_blist_remove_group(source); source = dest; + g_free(new_name); } else { /* A simple rename */ PurpleBlistNode *cnode, *bnode; @@ -1172,7 +1210,7 @@ } old_name = source->name; - source->name = g_strdup(new_name); + source->name = new_name; } /* Save our changes */ @@ -1184,7 +1222,7 @@ /* Notify all PRPLs */ /* TODO: Is this condition needed? Seems like it would always be TRUE */ - if(old_name && purple_strequal(source->name, old_name)) { + if(old_name && !purple_strequal(source->name, old_name)) { for (accts = purple_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) { PurpleAccount *account = accts->data; PurpleConnection *gc = NULL; @@ -1246,7 +1284,7 @@ chat = g_new0(PurpleChat, 1); chat->account = account; if ((alias != NULL) && (*alias != '\0')) - chat->alias = g_strdup(alias); + chat->alias = purple_utf8_strip_unprintables(alias); chat->components = components; purple_blist_node_initialize_settings((PurpleBlistNode *)chat); ((PurpleBlistNode *)chat)->type = PURPLE_BLIST_CHAT_NODE; @@ -1273,13 +1311,13 @@ PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); PurpleBuddy *buddy; - g_return_val_if_fail(account != NULL, FALSE); - g_return_val_if_fail(name != NULL, FALSE); + g_return_val_if_fail(account != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); buddy = g_new0(PurpleBuddy, 1); buddy->account = account; - buddy->name = g_strdup(name); - buddy->alias = g_strdup(alias); + buddy->name = purple_utf8_strip_unprintables(name); + buddy->alias = purple_utf8_strip_unprintables(alias); buddy->presence = purple_presence_new_for_buddy(buddy); ((PurpleBlistNode *)buddy)->type = PURPLE_BLIST_BUDDY_NODE; @@ -1705,7 +1743,7 @@ return group; group = g_new0(PurpleGroup, 1); - group->name = g_strdup(name); + group->name = purple_utf8_strip_unprintables(name); group->totalsize = 0; group->currentsize = 0; group->online = 0;
--- a/libpurple/conversation.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/conversation.c Sat Jun 06 06:21:39 2009 +0000 @@ -55,7 +55,6 @@ im = PURPLE_CONV_IM(c); purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING); - purple_conv_im_update_typing(im); purple_conv_im_stop_typing_timeout(im); return FALSE; @@ -1050,6 +1049,8 @@ "buddy-typing-stopped", im->conv->account, im->conv->name); break; } + + purple_conv_im_update_typing(im); } }
--- a/libpurple/media.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/media.c Sat Jun 06 06:21:39 2009 +0000 @@ -43,6 +43,7 @@ #ifdef USE_VV #include <gst/farsight/fs-conference-iface.h> +#include <gst/farsight/fs-element-added-notifier.h> /** @copydoc _PurpleMediaSession */ typedef struct _PurpleMediaSession PurpleMediaSession; @@ -2380,6 +2381,18 @@ stream->connected_cb_id = purple_timeout_add(0, (GSourceFunc)purple_media_connected_cb, stream); } + +static void +purple_media_element_added_cb(FsElementAddedNotifier *self, + GstBin *bin, GstElement *element, gpointer user_data) +{ + /* + * Hack to make H264 work with Gmail video. + */ + if (!strncmp(GST_ELEMENT_NAME(element), "x264", 4)) { + g_object_set(GST_OBJECT(element), "cabac", FALSE, NULL); + } +} #endif /* USE_VV */ gboolean @@ -2403,7 +2416,7 @@ if (!session) { GError *err = NULL; - GList *codec_conf = NULL; + GList *codec_conf = NULL, *iter = NULL; gchar *filename = NULL; PurpleMediaSessionType session_type; GstElement *src = NULL; @@ -2420,15 +2433,6 @@ return FALSE; } - /* XXX: SPEEX has a latency of 5 or 6 seconds for me */ -#if 0 - /* SPEEX is added through the configuration */ - codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, - "SPEEX", FS_MEDIA_TYPE_AUDIO, 8000)); - codec_conf = g_list_prepend(codec_conf, fs_codec_new(FS_CODEC_ID_ANY, - "SPEEX", FS_MEDIA_TYPE_AUDIO, 16000)); -#endif - filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL); codec_conf = fs_codec_list_from_keyfile(filename, &err); g_free(filename); @@ -2445,6 +2449,25 @@ g_error_free(err); } + /* + * Add SPEEX if the configuration file doesn't exist or + * there isn't a speex entry. + */ + for (iter = codec_conf; iter; iter = g_list_next(iter)) { + FsCodec *codec = iter->data; + if (!g_ascii_strcasecmp(codec->encoding_name, "speex")) + break; + } + + if (iter == NULL) { + codec_conf = g_list_prepend(codec_conf, + fs_codec_new(FS_CODEC_ID_ANY, + "SPEEX", FS_MEDIA_TYPE_AUDIO, 8000)); + codec_conf = g_list_prepend(codec_conf, + fs_codec_new(FS_CODEC_ID_ANY, + "SPEEX", FS_MEDIA_TYPE_AUDIO, 16000)); + } + fs_session_set_codec_preferences(session->session, codec_conf, NULL); /* @@ -2456,6 +2479,19 @@ g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL); + /* + * Hack to make x264 work with Gmail video. + */ + if (is_nice && !strcmp(sess_id, "google-video")) { + FsElementAddedNotifier *notifier = + fs_element_added_notifier_new(); + g_signal_connect(G_OBJECT(notifier), "element-added", + G_CALLBACK(purple_media_element_added_cb), + stream); + fs_element_added_notifier_add(notifier, + GST_BIN(media->priv->conference)); + } + fs_codec_list_destroy(codec_conf); session->id = g_strdup(sess_id); @@ -2670,7 +2706,8 @@ PurpleMediaStream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); stream = purple_media_get_stream(media, sess_id, participant); - return purple_media_candidate_list_from_fs(stream->local_candidates); + return stream ? purple_media_candidate_list_from_fs( + stream->local_candidates) : NULL; #else return NULL; #endif
--- a/libpurple/plugins/psychic.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/plugins/psychic.c Sat Jun 06 06:21:39 2009 +0000 @@ -74,6 +74,7 @@ time(NULL)); } + /* Necessary because we may be creating a new conversation window. */ purple_conv_im_set_typing_state(PURPLE_CONV_IM(gconv), PURPLE_TYPING); } }
--- a/libpurple/plugins/signals-test.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/plugins/signals-test.c Sat Jun 06 06:21:39 2009 +0000 @@ -661,6 +661,32 @@ return FALSE; } +static gboolean +jabber_watched_iq(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *child) +{ + purple_debug_misc("signals test", "jabber watched IQ (type=%s, id=%s, from=%s)\n" + "child %p name=%s, namespace=%s\n", + type, id, from, child, child->name, + xmlnode_get_namespace(child)); + + if (g_str_equal(type, "get") || g_str_equal(type, "set")) { + /* Send the requisite reply */ + xmlnode *iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "to", from); + xmlnode_set_attrib(iq, "id", id); + xmlnode_set_attrib(iq, "type", "result"); + + purple_signal_emit(purple_connection_get_prpl(pc), + "jabber-sending-xmlnode", pc, &iq); + if (iq != NULL) + xmlnode_free(iq); + } + + /* Cookie monster eats IQ stanzas; the prpl shouldn't keep processing */ + return TRUE; +} + /************************************************************************** * Plugin stuff **************************************************************************/ @@ -830,6 +856,16 @@ PURPLE_CALLBACK(jabber_message_received), NULL); purple_signal_connect(jabber_handle, "jabber-receiving-presence", plugin, PURPLE_CALLBACK(jabber_presence_received), NULL); + + /* IQ namespace signals */ + purple_signal_emit(jabber_handle, "jabber-register-namespace-watcher", + "bogus_node", "super-duper-namespace"); + /* The above is equivalent to doing: + int result = GPOINTER_TO_INT(purple_plugin_ipc_call(jabber_handle, "register_namespace_watcher", &ok, "bogus_node", "super-duper-namespace")); + */ + + purple_signal_connect(jabber_handle, "jabber-watched-iq", plugin, + PURPLE_CALLBACK(jabber_watched_iq), NULL); } return TRUE; @@ -838,8 +874,19 @@ static gboolean plugin_unload(PurplePlugin *plugin) { + void *jabber_handle = purple_plugins_find_with_id("prpl-jabber"); + purple_signals_disconnect_by_handle(plugin); + if (jabber_handle) { + /* Unregister watched namespaces */ + purple_signal_emit(jabber_handle, "jabber-unregister-namespace-watcher", + "bogus_node", "super-duper-namespace"); + /* The above is equivalent to doing: + int result = GPOINTER_TO_INT(purple_plugin_ipc_call(jabber_handle, "unregister_namespace_watcher", &ok, "bogus_node", "super-duper-namespace")); + */ + } + return TRUE; }
--- a/libpurple/protocols/gg/buddylist.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/gg/buddylist.c Sat Jun 06 06:21:39 2009 +0000 @@ -40,57 +40,33 @@ { GGPInfo *info = gc->proto_data; PurpleAccount *account = purple_connection_get_account(gc); - - PurpleBlistNode *gnode, *cnode, *bnode; - PurpleBuddy *buddy; - uin_t *userlist = NULL; - gchar *types = NULL; - int size = 0, ret = 0; + GSList *buddies; + uin_t *userlist; + gchar *types; + int i = 0, ret = 0; + int size; - for (gnode = purple_blist_get_root(); - gnode != NULL; - gnode = purple_blist_node_get_sibling_next(gnode)) - { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - - for (cnode = purple_blist_node_get_first_child(gnode); - cnode != NULL; - cnode = purple_blist_node_get_sibling_next(cnode)) - { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; + buddies = purple_find_buddies(account, NULL); - for (bnode = purple_blist_node_get_first_child(cnode); - bnode != NULL; - bnode = purple_blist_node_get_sibling_next(bnode)) - { - const gchar *name = NULL; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - - buddy = (PurpleBuddy *)bnode; + size = g_slist_length(buddies); + userlist = g_new(uin_t, size); + types = g_new(gchar, size); - if (purple_buddy_get_account(buddy) != account) - continue; - - name = purple_buddy_get_name(buddy); + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies), ++i) + { + PurpleBuddy *buddy = buddies->data; + const gchar *name = purple_buddy_get_name(buddy); - size++; - userlist = (uin_t *) g_renew(uin_t, userlist, size); - types = (gchar *) g_renew(gchar, types, size); - userlist[size - 1] = ggp_str_to_uin(name); - types[size - 1] = GG_USER_NORMAL; - purple_debug_info("gg", "ggp_buddylist_send: adding %d\n", - userlist[size - 1]); - } - } + userlist[i] = ggp_str_to_uin(name); + types[i] = GG_USER_NORMAL; + purple_debug_info("gg", "ggp_buddylist_send: adding %d\n", + userlist[i]); } ret = gg_notify_ex(info->session, userlist, types, size); purple_debug_info("gg", "send: ret=%d; size=%d\n", ret, size); - + if (userlist) { g_free(userlist); g_free(types); @@ -178,105 +154,28 @@ } /* }}} */ -/* void ggp_buddylist_offline(PurpleConnection *gc) {{{ */ -void ggp_buddylist_offline(PurpleConnection *gc) -{ - PurpleAccount *account = purple_connection_get_account(gc); - PurpleBlistNode *gnode, *cnode, *bnode; - PurpleBuddy *buddy; - - for (gnode = purple_blist_get_root(); - gnode != NULL; - gnode = purple_blist_node_get_sibling_next(gnode)) - { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - - for (cnode = purple_blist_node_get_first_child(gnode); - cnode != NULL; - cnode = purple_blist_node_get_sibling_next(cnode)) - { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - - for (bnode = purple_blist_node_get_first_child(cnode); - bnode != NULL; - bnode = purple_blist_node_get_sibling_next(bnode)) - { - const gchar *name = NULL; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - - buddy = (PurpleBuddy *)bnode; - - name = purple_buddy_get_name(buddy); - - if (purple_buddy_get_account(buddy) != account) - continue; - - purple_prpl_got_user_status( - account, name, "offline", NULL); - - purple_debug_info("gg", - "ggp_buddylist_offline: gone: %s\n", - name); - } - } - } -} -/* }}} */ - /* char *ggp_buddylist_dump(PurpleAccount *account) {{{ */ char *ggp_buddylist_dump(PurpleAccount *account) { - PurpleBlistNode *gnode, *cnode, *bnode; - PurpleGroup *group; - PurpleBuddy *buddy; + GSList *buddies; GString *buddylist = g_string_sized_new(1024); char *ptr; - for (gnode = purple_blist_get_root(); - gnode != NULL; - gnode = purple_blist_node_get_sibling_next(gnode)) - { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - - group = (PurpleGroup *)gnode; - - for (cnode = purple_blist_node_get_first_child(gnode); - cnode != NULL; - cnode = purple_blist_node_get_sibling_next(cnode)) - { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + PurpleBuddy *buddy = buddies->data; + PurpleGroup *group = purple_buddy_get_group(buddy); + const char *bname = purple_buddy_get_name(buddy); + const char *gname = purple_group_get_name(group); + const char *alias = purple_buddy_get_alias(buddy); - for (bnode = purple_blist_node_get_first_child(cnode); - bnode != NULL; - bnode = purple_blist_node_get_sibling_next(bnode)) - { - const gchar *name, *alias, *gname; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - - buddy = (PurpleBuddy *)bnode; - if (purple_buddy_get_account(buddy) != account) - continue; + if (alias == NULL) + alias = bname; - name = purple_buddy_get_name(buddy); - alias = purple_buddy_get_alias(buddy); - if(alias == NULL) - alias = name; - gname = purple_group_get_name(group); - - g_string_append_printf(buddylist, - "%s;%s;%s;%s;%s;%s;%s;%s%s\r\n", - alias, alias, alias, alias, - "", gname, name, "", ""); - } - } + g_string_append_printf(buddylist, + "%s;%s;%s;%s;%s;%s;%s;%s%s\r\n", + alias, alias, alias, alias, + "", gname, bname, "", ""); } ptr = charset_convert(buddylist->str, "UTF-8", "CP1250");
--- a/libpurple/protocols/gg/buddylist.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/gg/buddylist.h Sat Jun 06 06:21:39 2009 +0000 @@ -31,7 +31,7 @@ ggp_buddylist_send(PurpleConnection *gc); /** - * Load buddylist from server into the rooster. + * Load buddylist from server into the roster. * * @param gc PurpleConnection * @param buddylist Pointer to the buddylist that will be loaded. @@ -41,14 +41,6 @@ ggp_buddylist_load(PurpleConnection *gc, char *buddylist); /** - * Set offline status for all buddies. - * - * @param gc Connection handler - */ -void -ggp_buddylist_offline(PurpleConnection *gc); - -/** * Get all the buddies in the current account. * * @param account Current account.
--- a/libpurple/protocols/gg/gg.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/gg/gg.c Sat Jun 06 06:21:39 2009 +0000 @@ -1759,6 +1759,7 @@ PurpleStatus *status; struct gg_login_params *glp; GGPInfo *info; + const char *address; if (ggp_setup_proxy(account) == -1) return; @@ -1789,6 +1790,26 @@ glp->status = ggp_to_gg_status(status, &glp->status_descr); glp->tls = 0; + address = purple_account_get_string(account, "gg_server", ""); + if (address && *address) { + struct in_addr *addr = gg_gethostbyname(address); + + purple_debug_info("gg", "Using gg server given by user (%s)\n", address); + + if (addr == NULL) { + purple_debug_error("gg", "gg_gethostbyname returned error (%d): %s\n", + errno, g_strerror(errno)); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, /* should this be a settings error? */ + _("Unable to resolve server")); + return; + } + + glp->server_addr = inet_addr(inet_ntoa(*addr)); + glp->server_port = 8074; + } else + purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n"); + info->session = gg_login(glp); if (info->session == NULL) { purple_connection_error_reason (gc, @@ -1837,8 +1858,6 @@ if (gc->inpa > 0) purple_input_remove(gc->inpa); - ggp_buddylist_offline(gc); - purple_debug_info("gg", "Connection closed.\n"); } @@ -2369,6 +2388,11 @@ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + option = purple_account_option_string_new(_("GG server"), + "gg_server", ""); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + my_protocol = plugin; gg_debug_handler = purple_gg_debug_handler;
--- a/libpurple/protocols/irc/msgs.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/irc/msgs.c Sat Jun 06 06:21:39 2009 +0000 @@ -78,7 +78,7 @@ { PurpleConnection *gc; PurpleStatus *status; - PurpleBlistNode *gnode, *cnode, *bnode; + GSList *buddies; PurpleAccount *account; if ((gc = purple_account_get_connection(irc->account)) == NULL @@ -97,33 +97,13 @@ } /* this used to be in the core, but it's not now */ - for (gnode = purple_blist_get_root(); - gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { - if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for(cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) - { - if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for(bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) - { - PurpleBuddy *b; - if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - b = (PurpleBuddy *)bnode; - if(purple_buddy_get_account(b) == account) { - struct irc_buddy *ib = g_new0(struct irc_buddy, 1); - ib->name = g_strdup(purple_buddy_get_name(b)); - g_hash_table_insert(irc->buddies, ib->name, ib); - } - } - } + PurpleBuddy *b = buddies->data; + struct irc_buddy *ib = g_new0(struct irc_buddy, 1); + ib->name = g_strdup(purple_buddy_get_name(b)); + g_hash_table_insert(irc->buddies, ib->name, ib); } irc_blist_timeout(irc);
--- a/libpurple/protocols/jabber/auth.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/auth.c Sat Jun 06 06:21:39 2009 +0000 @@ -989,7 +989,20 @@ response = xmlnode_new("response"); xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl"); if (clen > 0) { - enc_out = purple_base64_encode((unsigned char*)c_out, clen); + /* Cyrus SASL 2.1.22 appears to contain code to add the charset + * to the response but there is no possibility it will be executed. + * My reading of the digestmd5 plugin indicates the username and + * realm are always encoded in UTF-8 (they seem to be the values + * we pass in), so we need to ensure charset=utf-8 is set. + */ + if (strstr(c_out, ",charset=")) + enc_out = purple_base64_encode((unsigned char*)c_out, clen); + else { + char *tmp = g_strdup_printf("%s,charset=utf-8", c_out); + enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14); + g_free(tmp); + } + xmlnode_insert_data(response, enc_out, -1); g_free(enc_out); }
--- a/libpurple/protocols/jabber/disco.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Sat Jun 06 06:21:39 2009 +0000 @@ -1,5 +1,5 @@ /* - * purple - Jabber Protocol Plugin + * purple - Jabber Service Discovery * * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> * @@ -22,6 +22,7 @@ #include "internal.h" #include "prefs.h" #include "debug.h" +#include "request.h" #include "adhoccommands.h" #include "buddy.h" @@ -40,6 +41,11 @@ JabberDiscoInfoCallback *callback; }; +struct _jabber_disco_items_cb_data { + gpointer data; + JabberDiscoItemsCallback *callback; +}; + #define SUPPORT_FEATURE(x) { \ feature = xmlnode_new_child(query, "feature"); \ xmlnode_set_attrib(feature, "var", x); \ @@ -148,14 +154,26 @@ */ xmlnode *feature = xmlnode_new_child(query, "feature"); xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/voice/v1"); + } else if (g_str_equal(node, CAPS0115_NODE "#" "video-v1")) { + /* + * HUGE HACK! We advertise this ext (see jabber_presence_create_js + * where we add <c/> to the <presence/>) for the Google Talk + * clients that don't actually check disco#info features. + * + * This specific feature is redundant but is what + * node='http://mail.google.com/xmpp/client/caps', ver='1.1' + * advertises as 'video-v1'. + */ + xmlnode *feature = xmlnode_new_child(query, "feature"); + xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/video/v1"); #endif } else { xmlnode *error, *inf; - + /* XXX: gross hack, implement jabber_iq_set_type or something */ xmlnode_set_attrib(iq->node, "type", "error"); iq->type = JABBER_IQ_ERROR; - + error = xmlnode_new_child(query, "error"); xmlnode_set_attrib(error, "code", "404"); xmlnode_set_attrib(error, "type", "cancel"); @@ -164,13 +182,42 @@ } g_free(node_uri); jabber_iq_send(iq); - } else if(type == JABBER_IQ_RESULT) { + } else if (type == JABBER_IQ_SET) { + /* wtf? seriously. wtf‽ */ + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); + xmlnode *error, *bad_request; + + /* Free the <query/> */ + xmlnode_free(xmlnode_get_child(iq->node, "query")); + /* Add an error */ + error = xmlnode_new_child(iq->node, "error"); + xmlnode_set_attrib(error, "type", "modify"); + bad_request = xmlnode_new_child(error, "bad-request"); + xmlnode_set_namespace(bad_request, "urn:ietf:params:xml:ns:xmpp-stanzas"); + + jabber_iq_set_id(iq, id); + xmlnode_set_attrib(iq->node, "to", from); + + jabber_iq_send(iq); + } +} + +static void jabber_disco_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct _jabber_disco_info_cb_data *jdicd = data; + xmlnode *query; + + query = xmlnode_get_child_with_namespace(packet, "query", + "http://jabber.org/protocol/disco#info"); + + if (type == JABBER_IQ_RESULT && query) { xmlnode *child; JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; JabberCapabilities capabilities = JABBER_CAP_NONE; - struct _jabber_disco_info_cb_data *jdicd; if((jid = jabber_id_new(from))) { if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) @@ -181,7 +228,7 @@ if(jbr) capabilities = jbr->capabilities; - for(child = in_query->child; child; child = child->next) { + for(child = query->child; child; child = child->next) { if(child->type != XMLNODE_TYPE_TAG) continue; @@ -233,6 +280,8 @@ capabilities |= JABBER_CAP_IQ_REGISTER; else if(!strcmp(var, "urn:xmpp:ping")) capabilities |= JABBER_CAP_PING; + else if(!strcmp(var, "http://jabber.org/protocol/disco#items")) + capabilities |= JABBER_CAP_ITEMS; else if(!strcmp(var, "http://jabber.org/protocol/commands")) { capabilities |= JABBER_CAP_ADHOC; } @@ -248,19 +297,12 @@ if(jbr) jbr->capabilities = capabilities; - if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) { - jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); - } - } else if(type == JABBER_IQ_ERROR) { + jdicd->callback(js, from, capabilities, jdicd->data); + } else { /* type == JABBER_IQ_ERROR or query == NULL */ JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; JabberCapabilities capabilities = JABBER_CAP_NONE; - struct _jabber_disco_info_cb_data *jdicd; - - if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from))) - return; if((jid = jabber_id_new(from))) { if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) @@ -272,7 +314,6 @@ capabilities = jbr->capabilities; jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); } } @@ -517,10 +558,10 @@ jdicd->data = data; jdicd->callback = callback; - g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd); - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); xmlnode_set_attrib(iq->node, "to", who); + jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd); jabber_iq_send(iq); } +
--- a/libpurple/protocols/jabber/disco.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/disco.h Sat Jun 06 06:21:39 2009 +0000 @@ -1,5 +1,5 @@ /** - * @file disco.h service discovery handlers + * @file disco.h Jabber Service Discovery * * purple * @@ -24,9 +24,18 @@ #include "jabber.h" +typedef struct _JabberDiscoItem { + const char *jid; /* MUST */ + const char *node; /* SHOULD */ + const char *name; /* MAY */ +} JabberDiscoItem; + typedef void (JabberDiscoInfoCallback)(JabberStream *js, const char *who, JabberCapabilities capabilities, gpointer data); +typedef void (JabberDiscoItemsCallback)(JabberStream *js, const char *jid, + const char *node, GSList *items, gpointer data); + void jabber_disco_info_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *in_query); void jabber_disco_items_parse(JabberStream *js, const char *from,
--- a/libpurple/protocols/jabber/google.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Sat Jun 06 06:21:39 2009 +0000 @@ -36,6 +36,9 @@ #ifdef USE_VV +#define NS_GOOGLE_VIDEO "http://www.google.com/session/video" +#define NS_GOOGLE_PHONE "http://www.google.com/session/phone" + typedef struct { char *id; char *initiator; @@ -55,6 +58,7 @@ PurpleMedia *media; JabberStream *js; char *remote_jid; + gboolean video; } GoogleSession; static gboolean @@ -104,9 +108,13 @@ google_session_send_candidates(PurpleMedia *media, gchar *session_id, gchar *participant, GoogleSession *session) { - GList *candidates = purple_media_get_local_candidates(session->media, "google-voice", - session->remote_jid); + GList *candidates = purple_media_get_local_candidates( + session->media, session_id, session->remote_jid); PurpleMediaCandidate *transport; + gboolean video = FALSE; + + if (!strcmp(session_id, "google-video")) + video = TRUE; for (;candidates;candidates = candidates->next) { JabberIq *iq; @@ -114,11 +122,10 @@ PurpleMediaCandidateType type; xmlnode *sess; xmlnode *candidate; + guint component_id; transport = (PurpleMediaCandidate*)(candidates->data); - - if (purple_media_candidate_get_component_id(transport) - != PURPLE_MEDIA_COMPONENT_RTP) - continue; + component_id = purple_media_candidate_get_component_id( + transport); iq = jabber_iq_new(session->js, JABBER_IQ_SET); sess = google_session_create_xmlnode(session, "candidates"); @@ -139,7 +146,11 @@ xmlnode_set_attrib(candidate, "address", ip); xmlnode_set_attrib(candidate, "port", port); - xmlnode_set_attrib(candidate, "name", "rtp"); + xmlnode_set_attrib(candidate, "name", + component_id == PURPLE_MEDIA_COMPONENT_RTP ? + video ? "video_rtp" : "rtp" : + component_id == PURPLE_MEDIA_COMPONENT_RTCP ? + video ? "video_rtcp" : "rtcp" : "none"); xmlnode_set_attrib(candidate, "username", username); /* * As of this writing, Farsight 2 in Google compatibility @@ -205,13 +216,38 @@ google_session_send_candidates(session->media, "google-voice", session->remote_jid, session); + google_session_send_candidates(session->media, + "google-video", session->remote_jid, + session); xmlnode_set_attrib(iq->node, "to", session->remote_jid); xmlnode_set_attrib(iq->node, "from", me); sess = google_session_create_xmlnode(session, "accept"); } xmlnode_insert_child(iq->node, sess); desc = xmlnode_new_child(sess, "description"); - xmlnode_set_namespace(desc, "http://www.google.com/session/phone"); + if (session->video) + xmlnode_set_namespace(desc, NS_GOOGLE_VIDEO); + else + xmlnode_set_namespace(desc, NS_GOOGLE_PHONE); + + codecs = purple_media_get_codecs(media, "google-video"); + + for (iter = codecs; iter; iter = g_list_next(iter)) { + PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data; + gchar *id = g_strdup_printf("%d", + purple_media_codec_get_id(codec)); + gchar *encoding_name = + purple_media_codec_get_encoding_name(codec); + payload = xmlnode_new_child(desc, "payload-type"); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "name", encoding_name); + xmlnode_set_attrib(payload, "width", "320"); + xmlnode_set_attrib(payload, "height", "200"); + xmlnode_set_attrib(payload, "framerate", "30"); + g_free(encoding_name); + g_free(id); + } + purple_media_codec_list_free(codecs); codecs = purple_media_get_codecs(media, "google-voice"); @@ -224,8 +260,17 @@ gchar *clock_rate = g_strdup_printf("%d", purple_media_codec_get_clock_rate(codec)); payload = xmlnode_new_child(desc, "payload-type"); + if (session->video) + xmlnode_set_namespace(payload, NS_GOOGLE_PHONE); xmlnode_set_attrib(payload, "id", id); - xmlnode_set_attrib(payload, "name", encoding_name); + /* + * Hack to make Gmail accept speex as the codec. + * It shouldn't have to be case sensitive. + */ + if (purple_strequal(encoding_name, "SPEEX")) + xmlnode_set_attrib(payload, "name", "speex"); + else + xmlnode_set_attrib(payload, "name", encoding_name); xmlnode_set_attrib(payload, "clockrate", clock_rate); g_free(clock_rate); g_free(encoding_name); @@ -235,10 +280,14 @@ jabber_iq_send(iq); - if (is_initiator) + if (is_initiator) { google_session_send_candidates(session->media, "google-voice", session->remote_jid, session); + google_session_send_candidates(session->media, + "google-video", session->remote_jid, + session); + } g_signal_handlers_disconnect_by_func(G_OBJECT(session->media), G_CALLBACK(google_session_ready), session); @@ -339,6 +388,9 @@ session->js = js; session->remote_jid = jid; + if (type & PURPLE_MEDIA_VIDEO) + session->video = TRUE; + session->media = purple_media_manager_create_media( purple_media_manager_get(), purple_connection_get_account(js->gc), @@ -349,8 +401,12 @@ params = jabber_google_session_get_params(js, &num_params); if (purple_media_add_stream(session->media, "google-voice", - session->remote_jid, PURPLE_MEDIA_AUDIO, - TRUE, "nice", num_params, params) == FALSE) { + session->remote_jid, PURPLE_MEDIA_AUDIO, + TRUE, "nice", num_params, params) == FALSE || + (session->video && purple_media_add_stream( + session->media, "google-video", + session->remote_jid, PURPLE_MEDIA_VIDEO, + TRUE, "nice", num_params, params) == FALSE)) { purple_media_error(session->media, "Error adding stream."); purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); @@ -378,10 +434,10 @@ google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { JabberIq *result; - GList *codecs = NULL; + GList *codecs = NULL, *video_codecs = NULL; xmlnode *desc_element, *codec_element; PurpleMediaCodec *codec; - const char *id, *encoding_name, *clock_rate; + const char *xmlns; GParameter *params; guint num_params; @@ -390,6 +446,19 @@ return; } + desc_element = xmlnode_get_child(sess, "description"); + xmlns = xmlnode_get_namespace(desc_element); + + if (purple_strequal(xmlns, NS_GOOGLE_PHONE)) + session->video = FALSE; + else if (purple_strequal(xmlns, NS_GOOGLE_VIDEO)) + session->video = TRUE; + else { + purple_debug_error("jabber", "Received initiate with " + "invalid namespace %s.\n", xmlns); + return; + } + session->media = purple_media_manager_create_media( purple_media_manager_get(), purple_connection_get_account(js->gc), @@ -401,7 +470,11 @@ if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, PURPLE_MEDIA_AUDIO, FALSE, - "nice", num_params, params) == FALSE) { + "nice", num_params, params) == FALSE || + (session->video && purple_media_add_stream( + session->media, "google-video", + session->remote_jid, PURPLE_MEDIA_VIDEO, + FALSE, "nice", num_params, params) == FALSE)) { purple_media_error(session->media, "Error adding stream."); purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); @@ -412,23 +485,55 @@ g_free(params); - desc_element = xmlnode_get_child(sess, "description"); + for (codec_element = xmlnode_get_child(desc_element, "payload-type"); + codec_element; codec_element = codec_element->next) { + const char *id, *encoding_name, *clock_rate, + *width, *height, *framerate; + gboolean video; + if (codec_element->name && + strcmp(codec_element->name, "payload-type")) + continue; - for (codec_element = xmlnode_get_child(desc_element, "payload-type"); - codec_element; - codec_element = xmlnode_get_next_twin(codec_element)) { + xmlns = xmlnode_get_namespace(codec_element); encoding_name = xmlnode_get_attrib(codec_element, "name"); id = xmlnode_get_attrib(codec_element, "id"); - clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); + + if (!session->video || + (xmlns && !strcmp(xmlns, NS_GOOGLE_PHONE))) { + clock_rate = xmlnode_get_attrib( + codec_element, "clockrate"); + video = FALSE; + } else { + width = xmlnode_get_attrib(codec_element, "width"); + height = xmlnode_get_attrib(codec_element, "height"); + framerate = xmlnode_get_attrib( + codec_element, "framerate"); + clock_rate = "90000"; + video = TRUE; + } if (id) { - codec = purple_media_codec_new(atoi(id), encoding_name, PURPLE_MEDIA_AUDIO, - clock_rate ? atoi(clock_rate) : 0); - codecs = g_list_append(codecs, codec); + codec = purple_media_codec_new(atoi(id), encoding_name, + video ? PURPLE_MEDIA_VIDEO : + PURPLE_MEDIA_AUDIO, + clock_rate ? atoi(clock_rate) : 0); + if (video) + video_codecs = g_list_append( + video_codecs, codec); + else + codecs = g_list_append(codecs, codec); } } - purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs); + if (codecs) + purple_media_set_remote_codecs(session->media, "google-voice", + session->remote_jid, codecs); + if (video_codecs) + purple_media_set_remote_codecs(session->media, "google-video", + session->remote_jid, video_codecs); + + purple_media_codec_list_free(codecs); + purple_media_codec_list_free(video_codecs); g_signal_connect_swapped(G_OBJECT(session->media), "accepted", G_CALLBACK(google_session_ready), session); @@ -442,8 +547,6 @@ g_signal_connect(G_OBJECT(session->media), "stream-info", G_CALLBACK(google_session_stream_info_cb), session); - purple_media_codec_list_free(codecs); - result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, iq_id); xmlnode_set_attrib(result->node, "to", session->remote_jid); @@ -454,19 +557,22 @@ google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { JabberIq *result; - GList *list = NULL; + GList *list = NULL, *video_list = NULL; xmlnode *cand; static int name = 0; char n[4]; - for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { + for (cand = xmlnode_get_child(sess, "candidate"); cand; + cand = xmlnode_get_next_twin(cand)) { PurpleMediaCandidate *info; + const gchar *cname = xmlnode_get_attrib(cand, "name"); const gchar *type = xmlnode_get_attrib(cand, "type"); const gchar *protocol = xmlnode_get_attrib(cand, "protocol"); const gchar *address = xmlnode_get_attrib(cand, "address"); const gchar *port = xmlnode_get_attrib(cand, "port"); + guint component_id; - if (type && address && port) { + if (cname && type && address && port) { PurpleMediaCandidateType candidate_type; g_snprintf(n, sizeof(n), "S%d", name++); @@ -480,7 +586,13 @@ else candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; - info = purple_media_candidate_new(n, PURPLE_MEDIA_COMPONENT_RTP, + if (purple_strequal(cname, "rtcp") || + purple_strequal(cname, "video_rtcp")) + component_id = PURPLE_MEDIA_COMPONENT_RTCP; + else + component_id = PURPLE_MEDIA_COMPONENT_RTP; + + info = purple_media_candidate_new(n, component_id, candidate_type, purple_strequal(protocol, "udp") ? PURPLE_MEDIA_NETWORK_PROTOCOL_UDP : @@ -489,12 +601,23 @@ atoi(port)); g_object_set(info, "username", xmlnode_get_attrib(cand, "username"), "password", xmlnode_get_attrib(cand, "password"), NULL); - list = g_list_append(list, info); + if (!strncmp(cname, "video_", 6)) + video_list = g_list_append(video_list, info); + else + list = g_list_append(list, info); } } - purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list); + if (list) + purple_media_add_remote_candidates( + session->media, "google-voice", + session->remote_jid, list); + if (video_list) + purple_media_add_remote_candidates( + session->media, "google-video", + session->remote_jid, video_list); purple_media_candidate_list_free(list); + purple_media_candidate_list_free(video_list); result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, iq_id); @@ -506,28 +629,57 @@ google_session_handle_accept(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { xmlnode *desc_element = xmlnode_get_child(sess, "description"); - xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type"); - GList *codecs = NULL; + xmlnode *codec_element = xmlnode_get_child( + desc_element, "payload-type"); + GList *codecs = NULL, *video_codecs = NULL; JabberIq *result = NULL; + const gchar *xmlns = xmlnode_get_namespace(desc_element); + gboolean video = (xmlns && !strcmp(xmlns, NS_GOOGLE_VIDEO)); + + for (; codec_element; codec_element = codec_element->next) { + const gchar *xmlns, *encoding_name, *id, + *clock_rate, *width, *height, *framerate; + gboolean video_codec = FALSE; + + if (!purple_strequal(codec_element->name, "payload-type")) + continue; - for (; codec_element; codec_element = - xmlnode_get_next_twin(codec_element)) { - const gchar *encoding_name = - xmlnode_get_attrib(codec_element, "name"); - const gchar *id = xmlnode_get_attrib(codec_element, "id"); - const gchar *clock_rate = - xmlnode_get_attrib(codec_element, "clockrate"); + xmlns = xmlnode_get_namespace(codec_element); + encoding_name = xmlnode_get_attrib(codec_element, "name"); + id = xmlnode_get_attrib(codec_element, "id"); + + if (!video || purple_strequal(xmlns, NS_GOOGLE_PHONE)) + clock_rate = xmlnode_get_attrib( + codec_element, "clockrate"); + else { + clock_rate = "90000"; + width = xmlnode_get_attrib(codec_element, "width"); + height = xmlnode_get_attrib(codec_element, "height"); + framerate = xmlnode_get_attrib( + codec_element, "framerate"); + video_codec = TRUE; + } if (id && encoding_name) { - PurpleMediaCodec *codec = purple_media_codec_new(atoi(id), - encoding_name, PURPLE_MEDIA_AUDIO, + PurpleMediaCodec *codec = purple_media_codec_new( + atoi(id), encoding_name, + video_codec ? PURPLE_MEDIA_VIDEO : + PURPLE_MEDIA_AUDIO, clock_rate ? atoi(clock_rate) : 0); - codecs = g_list_append(codecs, codec); + if (video_codec) + video_codecs = g_list_append( + video_codecs, codec); + else + codecs = g_list_append(codecs, codec); } } - purple_media_set_remote_codecs(session->media, "google-voice", - session->remote_jid, codecs); + if (codecs) + purple_media_set_remote_codecs(session->media, "google-voice", + session->remote_jid, codecs); + if (video_codecs) + purple_media_set_remote_codecs(session->media, "google-video", + session->remote_jid, video_codecs); purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_ACCEPT, NULL, NULL, FALSE);
--- a/libpurple/protocols/jabber/google.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/google.h Sat Jun 06 06:21:39 2009 +0000 @@ -28,6 +28,7 @@ #include "media.h" #define GOOGLE_VOICE_CAP "http://www.google.com/xmpp/protocol/voice/v1" +#define GOOGLE_VIDEO_CAP "http://www.google.com/xmpp/protocol/video/v1" #define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo" void jabber_gmail_init(JabberStream *js);
--- a/libpurple/protocols/jabber/jabber.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sat Jun 06 06:21:39 2009 +0000 @@ -450,22 +450,25 @@ return len; } -void jabber_send(JabberStream *js, xmlnode *packet) +void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet, + gpointer unused) { char *txt; int len; - purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet); - - /* if we get NULL back, we're done processing */ - if(NULL == packet) + if (NULL == packet) return; - txt = xmlnode_to_str(packet, &len); - jabber_send_raw(js, txt, len); + txt = xmlnode_to_str(*packet, &len); + jabber_send_raw(purple_connection_get_protocol_data(pc), txt, len); g_free(txt); } +void jabber_send(JabberStream *js, xmlnode *packet) +{ + purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet); +} + static gboolean jabber_keepalive_timeout(PurpleConnection *gc) { JabberStream *js = gc->proto_data; @@ -767,8 +770,6 @@ js->fd = -1; js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); - js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); js->chats = g_hash_table_new_full(g_str_hash, g_str_equal, @@ -1090,6 +1091,24 @@ jabber_iq_send(iq); } +static const struct { + const char *name; + const char *label; +} registration_fields[] = { + { "email", N_("Email") }, + { "nick", N_("Nickname") }, + { "first", N_("First name") }, + { "last", N_("Last name") }, + { "address", N_("Address") }, + { "city", N_("City") }, + { "state", N_("State") }, + { "zip", N_("Postal code") }, + { "phone", N_("Phone") }, + { "url", N_("URL") }, + { "date", N_("Date") }, + { NULL, NULL } +}; + void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *query) { @@ -1097,18 +1116,15 @@ PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; - xmlnode *x, *y; + xmlnode *x, *y, *node; char *instructions; JabberRegisterCBData *cbdata; gboolean registered = FALSE; + int i; if (type != JABBER_IQ_RESULT) return; - if (!from) - from = js->serverFQDN; - g_return_if_fail(from != NULL); - if(js->registration) { /* get rid of the login thingy */ purple_connection_set_state(js->gc, PURPLE_CONNECTED); @@ -1157,74 +1173,53 @@ group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); - if(js->registration) - field = purple_request_field_string_new("username", _("Username"), js->user->node, FALSE); - else - field = purple_request_field_string_new("username", _("Username"), NULL, FALSE); - - purple_request_field_group_add_field(group, field); - - if(js->registration) - field = purple_request_field_string_new("password", _("Password"), - purple_connection_get_password(js->gc), FALSE); - else - field = purple_request_field_string_new("password", _("Password"), NULL, FALSE); - - purple_request_field_string_set_masked(field, TRUE); - purple_request_field_group_add_field(group, field); - - if(xmlnode_get_child(query, "name")) { + if((node = xmlnode_get_child(query, "username"))) { + char *data = xmlnode_get_data(node); + if(js->registration) + field = purple_request_field_string_new("username", _("Username"), data ? data : js->user->node, FALSE); + else + field = purple_request_field_string_new("username", _("Username"), data, FALSE); + + purple_request_field_group_add_field(group, field); + g_free(data); + } + if((node = xmlnode_get_child(query, "password"))) { + if(js->registration) + field = purple_request_field_string_new("password", _("Password"), + purple_connection_get_password(js->gc), FALSE); + else { + char *data = xmlnode_get_data(node); + field = purple_request_field_string_new("password", _("Password"), data, FALSE); + g_free(data); + } + + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_group_add_field(group, field); + } + + if((node = xmlnode_get_child(query, "name"))) { if(js->registration) field = purple_request_field_string_new("name", _("Name"), purple_account_get_alias(js->gc->account), FALSE); - else - field = purple_request_field_string_new("name", _("Name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "email")) { - field = purple_request_field_string_new("email", _("Email"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "nick")) { - field = purple_request_field_string_new("nick", _("Nickname"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "first")) { - field = purple_request_field_string_new("first", _("First name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "last")) { - field = purple_request_field_string_new("last", _("Last name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "address")) { - field = purple_request_field_string_new("address", _("Address"), NULL, FALSE); + else { + char *data = xmlnode_get_data(node); + field = purple_request_field_string_new("name", _("Name"), data, FALSE); + g_free(data); + } purple_request_field_group_add_field(group, field); } - if(xmlnode_get_child(query, "city")) { - field = purple_request_field_string_new("city", _("City"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "state")) { - field = purple_request_field_string_new("state", _("State"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "zip")) { - field = purple_request_field_string_new("zip", _("Postal code"), NULL, FALSE); - purple_request_field_group_add_field(group, field); + + for (i = 0; registration_fields[i].name != NULL; ++i) { + if ((node = xmlnode_get_child(query, registration_fields[i].name))) { + char *data = xmlnode_get_data(node); + field = purple_request_field_string_new(registration_fields[i].name, + _(registration_fields[i].label), + data, FALSE); + purple_request_field_group_add_field(group, field); + g_free(data); + } } - if(xmlnode_get_child(query, "phone")) { - field = purple_request_field_string_new("phone", _("Phone"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "url")) { - field = purple_request_field_string_new("url", _("URL"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "date")) { - field = purple_request_field_string_new("date", _("Date"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } + if(registered) { field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE); purple_request_field_group_add_field(group, field); @@ -1297,8 +1292,6 @@ js->registration = TRUE; js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); - js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); js->user = jabber_id_new(purple_account_get_username(account)); js->next_id = g_random_int(); js->old_length = 0; @@ -1487,8 +1480,6 @@ if(js->iq_callbacks) g_hash_table_destroy(js->iq_callbacks); - if(js->disco_callbacks) - g_hash_table_destroy(js->disco_callbacks); if(js->buddies) g_hash_table_destroy(js->buddies); if(js->chats) @@ -2972,7 +2963,7 @@ return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION)); } -static gboolean +gboolean jabber_video_enabled(JabberStream *js, const char *namespace) { PurpleMediaManager *manager = purple_media_manager_get(); @@ -3212,8 +3203,12 @@ caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION | PURPLE_MEDIA_CAPS_CHANGE_DIRECTION; } - if (jabber_resource_has_capability(jbr, GOOGLE_VOICE_CAP)) + if (jabber_resource_has_capability(jbr, GOOGLE_VOICE_CAP)) { caps |= PURPLE_MEDIA_CAPS_AUDIO; + if (jabber_resource_has_capability(jbr, + GOOGLE_VIDEO_CAP)) + caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO; + } return caps; } @@ -3493,6 +3488,21 @@ purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_STRING)); + + /* Modifying these? Look at libxmpp.c:load_plugin for the signal versions */ + purple_plugin_ipc_register(plugin, "register_namespace_watcher", + PURPLE_CALLBACK(jabber_iq_signal_register), + purple_marshal_VOID__POINTER_POINTER, + NULL, 2, + purple_value_new(PURPLE_TYPE_STRING), /* node */ + purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ + + purple_plugin_ipc_register(plugin, "unregister_namespace_watcher", + PURPLE_CALLBACK(jabber_iq_signal_unregister), + purple_marshal_VOID__POINTER_POINTER, + NULL, 2, + purple_value_new(PURPLE_TYPE_STRING), /* node */ + purple_value_new(PURPLE_TYPE_STRING)); /* namespace */ } void
--- a/libpurple/protocols/jabber/jabber.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sat Jun 06 06:21:39 2009 +0000 @@ -44,6 +44,8 @@ JABBER_CAP_ADHOC = 1 << 12, JABBER_CAP_BLOCKING = 1 << 13, + JABBER_CAP_ITEMS = 1 << 14, + JABBER_CAP_RETRIEVED = 1 << 31 } JabberCapabilities; @@ -53,12 +55,12 @@ #include <glib.h> #include "circbuffer.h" #include "connection.h" +#include "dnsquery.h" #include "dnssrv.h" #include "media.h" #include "mediamanager.h" #include "roomlist.h" #include "sslconn.h" -#include "dnsquery.h" #include "iq.h" #include "jutil.h" @@ -153,7 +155,6 @@ GList *user_directories; GHashTable *iq_callbacks; - GHashTable *disco_callbacks; int next_id; GList *bs_proxies; @@ -301,6 +302,8 @@ void jabber_process_packet(JabberStream *js, xmlnode **packet); void jabber_send(JabberStream *js, xmlnode *data); void jabber_send_raw(JabberStream *js, const char *data, int len); +void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet, + gpointer unused); void jabber_stream_set_state(JabberStream *js, JabberStreamState state); @@ -366,6 +369,7 @@ GList *jabber_actions(PurplePlugin *plugin, gpointer context); gboolean jabber_audio_enabled(JabberStream *js, const char *unused); +gboolean jabber_video_enabled(JabberStream *js, const char *unused); gboolean jabber_initiate_media(PurpleAccount *account, const char *who, PurpleMediaSessionType type); PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
--- a/libpurple/protocols/jabber/libxmpp.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Sat Jun 06 06:21:39 2009 +0000 @@ -34,6 +34,7 @@ #include "iq.h" #include "jabber.h" #include "chat.h" +#include "disco.h" #include "message.h" #include "roster.h" #include "si.h" @@ -117,6 +118,7 @@ jabber_unregister_account, /* unregister_user */ jabber_send_attention, /* send_attention */ jabber_attention_types, /* attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL, /* get_account_text_table */ jabber_initiate_media, /* initiate_media */ @@ -135,6 +137,14 @@ purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); + /* + * Do not remove this or the plugin will fail. Completely. You have been + * warned! + */ + purple_signal_connect_priority(plugin, "jabber-sending-xmlnode", + plugin, PURPLE_CALLBACK(jabber_send_signal_cb), + NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST); + purple_signal_register(plugin, "jabber-sending-text", purple_marshal_VOID__POINTER_POINTER, NULL, 2, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION), @@ -168,6 +178,7 @@ purple_value_new(PURPLE_TYPE_STRING), /* from */ purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */ + /* Modifying these? Look at jabber_init_plugin for the ipc versions */ purple_signal_register(plugin, "jabber-register-namespace-watcher", purple_marshal_VOID__POINTER_POINTER, NULL, 2,
--- a/libpurple/protocols/jabber/message.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/message.c Sat Jun 06 06:21:39 2009 +0000 @@ -1190,7 +1190,9 @@ jm->typing_style |= JM_TS_JEP_0022; } - purple_markup_html_to_xhtml(msg, &xhtml, &jm->body); + tmp = purple_utf8_strip_unprintables(msg); + purple_markup_html_to_xhtml(tmp, &xhtml, &jm->body); + g_free(tmp); tmp = jabber_message_smileyfy_xhtml(jm, xhtml); if (tmp) { g_free(xhtml); @@ -1231,7 +1233,9 @@ jm->to = g_strdup_printf("%s@%s", chat->room, chat->server); jm->id = jabber_get_next_id(jm->js); + tmp = purple_utf8_strip_unprintables(msg); purple_markup_html_to_xhtml(msg, &xhtml, &jm->body); + g_free(tmp); tmp = jabber_message_smileyfy_xhtml(jm, xhtml); if (tmp) { g_free(xhtml);
--- a/libpurple/protocols/jabber/presence.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Sat Jun 06 06:21:39 2009 +0000 @@ -245,6 +245,7 @@ { xmlnode *show, *status, *presence, *pri, *c; const char *show_string = NULL; + gboolean audio_enabled, video_enabled; presence = xmlnode_new("presence"); @@ -300,9 +301,18 @@ * just assume that if we specify a 'voice-v1' ext (ignoring that * these are to be assigned no semantic value), we support receiving voice * calls. + * + * Ditto for 'video-v1'. */ - if (jabber_audio_enabled(js, NULL /* unused */)) + audio_enabled = jabber_audio_enabled(js, NULL /* unused */); + video_enabled = jabber_video_enabled(js, NULL /* unused */); + + if (audio_enabled && video_enabled) + xmlnode_set_attrib(c, "ext", "voice-v1 video-v1"); + else if (audio_enabled) xmlnode_set_attrib(c, "ext", "voice-v1"); + else if (video_enabled) + xmlnode_set_attrib(c, "ext", "video-v1"); #endif return presence;
--- a/libpurple/protocols/jabber/roster.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/jabber/roster.c Sat Jun 06 06:21:39 2009 +0000 @@ -282,9 +282,23 @@ return; if(grps) { + GString *out = g_string_new(NULL); groups = grps; + + for (l = groups; l; l = l->next) { + out = g_string_append(out, (const char *)l->data); + if (l->next) + out = g_string_append(out, ", "); + } + + purple_debug_info("jabber", "jabber_roster_update(%s): [Source: grps]: groups: %s\n", + name, out->str); + g_string_free(out, TRUE); + } else { GSList *buddies = purple_find_buddies(js->gc->account, name); + GString *out = g_string_new(NULL); + if(!buddies) return; while(buddies) { @@ -293,6 +307,15 @@ groups = g_slist_append(groups, (char *)purple_group_get_name(g)); buddies = g_slist_remove(buddies, b); } + for (l = groups; l; l = l->next) { + out = g_string_append(out, (const char *)l->data); + if (l->next) + out = g_string_append(out, ", "); + } + + purple_debug_info("jabber", "jabber_roster_update(%s): [Source: local blist]: groups: %s\n", + name, out->str); + g_string_free(out, TRUE); } iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); @@ -340,6 +363,9 @@ jb = jabber_buddy_find(js, name, FALSE); + purple_debug_info("jabber", "jabber_roster_add_buddy(): Adding %s\n", + name); + jabber_roster_update(js, who, NULL); my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); @@ -369,6 +395,9 @@ if(b != NULL) { purple_blist_alias_buddy(b, alias); + purple_debug_info("jabber", "jabber_roster_alias_change(): Aliased %s to %s\n", + name, alias ? alias : "(null)"); + jabber_roster_update(gc->proto_data, name, NULL); } } @@ -395,6 +424,10 @@ groups = g_slist_append(groups, (char*)gname); buddies = g_slist_remove(buddies, b); } + + purple_debug_info("jabber", "jabber_roster_group_change(): Moving %s from %s to %s\n", + name, old_group, new_group); + jabber_roster_update(gc->proto_data, name, groups); g_slist_free(groups); } @@ -428,6 +461,9 @@ buddies = g_slist_remove(buddies, tmpbuddy); } + purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s from %s\n", + purple_buddy_get_name(buddy), purple_group_get_name(group)); + jabber_roster_update(gc->proto_data, name, groups); g_slist_free(groups); } else { @@ -439,6 +475,9 @@ xmlnode_set_attrib(item, "jid", name); xmlnode_set_attrib(item, "subscription", "remove"); + purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s\n", + purple_buddy_get_name(buddy)); + jabber_iq_send(iq); } }
--- a/libpurple/protocols/msn/msn.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/msn/msn.c Sat Jun 06 06:21:39 2009 +0000 @@ -1281,7 +1281,7 @@ imdata->gc = gc; imdata->who = who; imdata->msg = body_str; - imdata->flags = flags; + imdata->flags = flags & ~PURPLE_MESSAGE_SEND; imdata->when = time(NULL); purple_timeout_add(0, msn_send_me_im, imdata); }
--- a/libpurple/protocols/msn/session.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/msn/session.c Sat Jun 06 06:21:39 2009 +0000 @@ -261,9 +261,9 @@ static void msn_session_sync_users(MsnSession *session) { - PurpleBlistNode *gnode, *cnode, *bnode; PurpleConnection *gc = purple_account_get_connection(session->account); GList *to_remove = NULL; + GSList *buddies; g_return_if_fail(gc != NULL); @@ -271,60 +271,36 @@ * being logged in. This no longer happens, so we manually iterate * over the whole buddy list to identify sync issues. */ - for (gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - PurpleGroup *group = (PurpleGroup *)gnode; - const char *group_name; - if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - group_name = purple_group_get_name(group); - for(cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for(bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - PurpleBuddy *b; - if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - b = (PurpleBuddy *)bnode; - if(purple_buddy_get_account(b) == purple_connection_get_account(gc)) { - MsnUser *remote_user; - gboolean found = FALSE; - - remote_user = msn_userlist_find_user(session->userlist, purple_buddy_get_name(b)); + for (buddies = purple_find_buddies(session->account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + PurpleBuddy *buddy = buddies->data; + const gchar *buddy_name = purple_buddy_get_name(buddy); + const gchar *group_name = purple_group_get_name(purple_buddy_get_group(buddy)); + MsnUser *remote_user; + gboolean found = FALSE; - if ((remote_user != NULL) && (remote_user->list_op & MSN_LIST_FL_OP)) - { - GList *l; - - for (l = remote_user->group_ids; l != NULL; l = l->next) - { - const char *name = msn_userlist_find_group_name(remote_user->userlist, l->data); - if (name && !g_ascii_strcasecmp(group_name, name)) - { - found = TRUE; - break; - } - } - } + remote_user = msn_userlist_find_user(session->userlist, buddy_name); + if (remote_user && remote_user->list_op & MSN_LIST_FL_OP) { + GList *l; + for (l = remote_user->group_ids; l; l = l->next) { + const char *name = msn_userlist_find_group_name(remote_user->userlist, l->data); + if (name && !g_ascii_strcasecmp(group_name, name)) { + found = TRUE; + break; + } + } - /* We don't care if they're in a different group, as long as they're on the - * list somewhere. If we check for the group, we cause pain, agony and - * suffering for people who decide to re-arrange their buddy list elsewhere. - */ - if (!found) - { - if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) { - /* The user is not on the server list */ - msn_show_sync_issue(session, purple_buddy_get_name(b), group_name); - } else { - /* The user is not in that group on the server list */ - to_remove = g_list_prepend(to_remove, b); - } - } + /* We don't care if they're in a different group, as long as they're on the + * list somewhere. If we check for the group, we cause pain, agony and + * suffering for people who decide to re-arrange their buddy list elsewhere. + */ + if (!found) { + if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) { + /* The user is not on the server list */ + msn_show_sync_issue(session, buddy_name, group_name); + } else { + /* The user is not in that group on the server list */ + to_remove = g_list_prepend(to_remove, buddy); } } }
--- a/libpurple/protocols/msn/userlist.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/msn/userlist.c Sat Jun 06 06:21:39 2009 +0000 @@ -783,7 +783,6 @@ void msn_userlist_load(MsnSession *session) { - PurpleBlistNode *gnode, *cnode, *bnode; PurpleAccount *account = session->account; PurpleConnection *gc = purple_account_get_connection(account); GSList *l; @@ -791,34 +790,14 @@ g_return_if_fail(gc != NULL); - for (gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) - { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for (cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) - { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) - { - PurpleBuddy *b; - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - b = (PurpleBuddy *)bnode; - if (purple_buddy_get_account(b) == account) - { - user = msn_userlist_find_add_user(session->userlist, - purple_buddy_get_name(b), NULL); - purple_buddy_set_protocol_data(b, user); - msn_user_set_op(user, MSN_LIST_FL_OP); - } - } - } + for (l = purple_find_buddies(account, NULL); l != NULL; + l = g_slist_delete_link(l, l)) { + PurpleBuddy *buddy = l->data; + + user = msn_userlist_find_add_user(session->userlist, + purple_buddy_get_name(buddy), NULL); + purple_buddy_set_protocol_data(buddy, user); + msn_user_set_op(user, MSN_LIST_FL_OP); } for (l = session->account->permit; l != NULL; l = l->next) {
--- a/libpurple/protocols/msnp9/session.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/msnp9/session.c Sat Jun 06 06:21:39 2009 +0000 @@ -221,68 +221,51 @@ static void msn_session_sync_users(MsnSession *session) { - PurpleBlistNode *gnode, *cnode, *bnode; PurpleConnection *gc = purple_account_get_connection(session->account); GList *to_remove = NULL; + GSList *buddies; g_return_if_fail(gc != NULL); /* The core used to use msn_add_buddy to add all buddies before * being logged in. This no longer happens, so we manually iterate * over the whole buddy list to identify sync issues. */ + for (buddies = purple_find_buddies(session->account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + PurpleBuddy *buddy = buddies->data; + const char *buddy_name = purple_buddy_get_name(buddy); + const char *group_name = purple_group_get_name(purple_buddy_get_group(buddy)); + MsnUser *remote_user; + gboolean found = FALSE; - for (gnode = purple_blist_get_root(); gnode; gnode = gnode->next) { - PurpleGroup *group = (PurpleGroup *)gnode; - const char *group_name = group->name; - if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for(cnode = gnode->child; cnode; cnode = cnode->next) { - if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for(bnode = cnode->child; bnode; bnode = bnode->next) { - PurpleBuddy *b; - if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - b = (PurpleBuddy *)bnode; - if(purple_buddy_get_account(b) == purple_connection_get_account(gc)) { - MsnUser *remote_user; - gboolean found = FALSE; + remote_user = msn_userlist_find_user(session->userlist, buddy_name); - remote_user = msn_userlist_find_user(session->userlist, purple_buddy_get_name(b)); + if (remote_user && remote_user->list_op & MSN_LIST_FL_OP) { + int group_id; + GList *l; - if ((remote_user != NULL) && (remote_user->list_op & MSN_LIST_FL_OP)) - { - int group_id; - GList *l; + group_id = msn_userlist_find_group_id(remote_user->userlist, + group_name); - group_id = msn_userlist_find_group_id(remote_user->userlist, - group_name); - - for (l = remote_user->group_ids; l != NULL; l = l->next) - { - if (group_id == GPOINTER_TO_INT(l->data)) - { - found = TRUE; - break; - } - } - - } + for (l = remote_user->group_ids; l; l = l->next) { + if (group_id == GPOINTER_TO_INT(l->data)) { + found = TRUE; + break; + } + } - /* We don't care if they're in a different group, as long as they're on the - * list somewhere. If we check for the group, we cause pain, agony and - * suffering for people who decide to re-arrange their buddy list elsewhere. - */ - if (!found) - { - if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) { - /* The user is not on the server list */ - msn_show_sync_issue(session, purple_buddy_get_name(b), group_name); - } else { - /* The user is not in that group on the server list */ - to_remove = g_list_prepend(to_remove, b); - } - } + /* We don't care if they're in a different group, as long as they're on the + * list somewhere. If we check for the group, we cause pain, agony and + * suffering for people who decide to re-arrange their buddy list elsewhere. + */ + if (!found) + { + if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) { + /* The user is not on the server list */ + msn_show_sync_issue(session, buddy_name, group_name); + } else { + /* The user is not in that group on the server list */ + to_remove = g_list_prepend(to_remove, buddy); } } }
--- a/libpurple/protocols/myspace/myspace.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.c Sat Jun 06 06:21:39 2009 +0000 @@ -1118,10 +1118,6 @@ guint buddy_count; body = msim_msg_get_dictionary(reply, "body"); - if (!body) { - /* No friends. Not an error. */ - return; - } buddy_count = 0; @@ -1477,28 +1473,22 @@ * @return TRUE if successful. */ static gboolean -msim_incoming_im(MsimSession *session, MsimMessage *msg) +msim_incoming_im(MsimSession *session, MsimMessage *msg, const gchar *username) { - gchar *username, *msg_msim_markup, *msg_purple_markup; + gchar *msg_msim_markup, *msg_purple_markup; gchar *userid; time_t time_received; PurpleConversation *conv; - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - username = msim_msg_get_string(msg, "_username"); /* I know this isn't really a string... but we need it to be one for * purple_find_conversation_with_account(). */ userid = msim_msg_get_string(msg, "f"); - g_return_val_if_fail(username != NULL, FALSE); purple_debug_info("msim_incoming_im", "UserID is %s", userid); if (msim_is_userid(username)) { purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n", username, purple_account_get_username(session->account)); - g_free(username); return FALSE; } @@ -1523,14 +1513,13 @@ serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received); - g_free(username); g_free(msg_purple_markup); return TRUE; } /** - * Handle an incoming action message. + * Handle an incoming action message or an IM. * * @param session * @param msg @@ -1538,7 +1527,7 @@ * @return TRUE if successful. */ static gboolean -msim_incoming_action(MsimSession *session, MsimMessage *msg) +msim_incoming_action_or_im(MsimSession *session, MsimMessage *msg) { gchar *msg_text, *username; gboolean rc; @@ -1552,7 +1541,8 @@ username = msim_msg_get_string(msg, "_username"); g_return_val_if_fail(username != NULL, FALSE); - purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n", + purple_debug_info("msim", + "msim_incoming_action_or_im: action <%s> from <%s>\n", msg_text, username); if (g_str_equal(msg_text, "%typing%")) { @@ -1566,13 +1556,16 @@ } else if (strstr(msg_text, "!!!GroupCount=")) { /* TODO: support group chats. I think the number in msg_text has * something to do with the 'gid' field. */ - purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); + purple_debug_info("msim", + "msim_incoming_action_or_im: " + "TODO: implement #4691, group chats: %s\n", msg_text); rc = TRUE; } else if (strstr(msg_text, "!!!Offline=")) { /* TODO: support group chats. This one might mean a user * went offline or exited the chat. */ - purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); + purple_debug_info("msim", "msim_incoming_action_or_im: " + "TODO: implement #4691, group chats: %s\n", msg_text); rc = TRUE; } else if (msim_msg_get_integer(msg, "aid") != 0) { @@ -1583,9 +1576,7 @@ rc = TRUE; } else { - msim_unrecognized(session, msg, - "got to msim_incoming_action but unrecognized value for 'msg'"); - rc = FALSE; + rc = msim_incoming_im(session, msg, username); } g_free(msg_text); @@ -1670,10 +1661,9 @@ switch (bm) { case MSIM_BM_STATUS: return msim_incoming_status(session, msg); - case MSIM_BM_INSTANT: - return msim_incoming_im(session, msg); - case MSIM_BM_ACTION: - return msim_incoming_action(session, msg); + case MSIM_BM_INSTANT_ACTION_OR_IM: + case MSIM_BM_DELAYABLE_ACTION_OR_IM: + return msim_incoming_action_or_im(session, msg); case MSIM_BM_MEDIA: return msim_incoming_media(session, msg); case MSIM_BM_UNOFFICIAL_CLIENT: @@ -1681,7 +1671,8 @@ default: /* Not really an IM, but show it for informational * purposes during development. */ - return msim_incoming_im(session, msg); + /* TODO: This is probably wrong */ + return msim_incoming_action_or_im(session, msg); } } @@ -2294,7 +2285,7 @@ message_msim = html_to_msim_markup(session, message); - if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) { + if (msim_send_bm(session, who, message_msim, MSIM_BM_DELAYABLE_ACTION_OR_IM)) { /* Return 1 to have Purple show this IM as being sent, 0 to not. I always * return 1 even if the message could not be sent, since I don't know if * it has failed yet--because the IM is only sent after the userid is @@ -2347,7 +2338,7 @@ } purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str); - msim_send_bm(session, name, typing_str, MSIM_BM_ACTION); + msim_send_bm(session, name, typing_str, MSIM_BM_INSTANT_ACTION_OR_IM); return 0; }
--- a/libpurple/protocols/myspace/myspace.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/myspace/myspace.h Sat Jun 06 06:21:39 2009 +0000 @@ -127,12 +127,12 @@ #define MSIM_FINAL_STRING "\\final\\" /**< Message end marker */ /* Messages */ -#define MSIM_BM_INSTANT 1 -#define MSIM_BM_STATUS 100 -#define MSIM_BM_ACTION 121 -#define MSIM_BM_MEDIA 122 -#define MSIM_BM_PROFILE 124 -#define MSIM_BM_UNOFFICIAL_CLIENT 200 +#define MSIM_BM_DELAYABLE_ACTION_OR_IM 1 +#define MSIM_BM_STATUS 100 +#define MSIM_BM_INSTANT_ACTION_OR_IM 121 +#define MSIM_BM_MEDIA 122 +#define MSIM_BM_PROFILE 124 +#define MSIM_BM_UNOFFICIAL_CLIENT 200 /* Authentication algorithm for login2 */ #define MSIM_AUTH_ALGORITHM 196610
--- a/libpurple/protocols/myspace/zap.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/myspace/zap.c Sat Jun 06 06:21:39 2009 +0000 @@ -109,7 +109,7 @@ /* Construct and send the actual zap command. */ zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code); - if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) { + if (!msim_send_bm(session, username, zap_string, MSIM_BM_INSTANT_ACTION_OR_IM)) { purple_debug_info("msim_send_zap", "msim_send_bm failed: zapping %s with %s\n", username, zap_string);
--- a/libpurple/protocols/oscar/oscar.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.c Sat Jun 06 06:21:39 2009 +0000 @@ -6541,47 +6541,35 @@ { PurpleConnection *gc = (PurpleConnection *) action->context; OscarData *od = purple_connection_get_protocol_data(gc); - gchar *nombre, *text, *tmp; - PurpleBlistNode *gnode, *cnode, *bnode; + gchar *text, *tmp; + GSList *buddies; PurpleAccount *account; int num=0; text = g_strdup(""); account = purple_connection_get_account(gc); - for (gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - PurpleGroup *group = (PurpleGroup *)gnode; - const char *gname; - if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - gname = purple_group_get_name(group); - for (cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - PurpleBuddy *buddy = (PurpleBuddy *)bnode; - const char *bname; - if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - bname = purple_buddy_get_name(buddy); - if (purple_buddy_get_account(buddy) == account && aim_ssi_waitingforauth(od->ssi.local, gname, bname)) { - if (purple_buddy_get_alias_only(buddy)) - nombre = g_strdup_printf(" %s (%s)", bname, purple_buddy_get_alias_only(buddy)); - else - nombre = g_strdup_printf(" %s", bname); - tmp = g_strdup_printf("%s%s<br>", text, nombre); - g_free(text); - text = tmp; - g_free(nombre); - num++; - } - } + buddies = purple_find_buddies(account, NULL); + while (buddies) { + PurpleBuddy *buddy; + const gchar *bname, *gname; + + buddy = buddies->data; + bname = purple_buddy_get_name(buddy); + gname = purple_group_get_name(purple_buddy_get_group(buddy)); + if (aim_ssi_waitingforauth(od->ssi.local, gname, bname)) { + const gchar *alias = purple_buddy_get_alias_only(buddy); + if (alias) + tmp = g_strdup_printf("%s %s (%s)<br>", text, bname, alias); + else + tmp = g_strdup_printf("%s %s<br>", text, bname); + g_free(text); + text = tmp; + + num++; } + + buddies = g_slist_delete_link(buddies, buddies); } if (!num) {
--- a/libpurple/protocols/silc/buddy.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/silc/buddy.c Sat Jun 06 06:21:39 2009 +0000 @@ -1407,35 +1407,16 @@ void silcpurple_send_buddylist(PurpleConnection *gc) { - PurpleBlistNode *gnode, *cnode, *bnode; - PurpleBuddy *buddy; + GSList *buddies; PurpleAccount *account; account = purple_connection_get_account(gc); - for (gnode = purple_blist_get_root(); - gnode != NULL; - gnode = purple_blist_node_get_sibling_next(gnode)) + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for (cnode = purple_blist_node_get_first_child(gnode); - cnode != NULL; - cnode = purple_blist_node_get_sibling_next(cnode)) - { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode != NULL; - bnode = purple_blist_node_get_sibling_next(bnode)) - { - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - buddy = (PurpleBuddy *)bnode; - if (purple_buddy_get_account(buddy) == account) - silcpurple_add_buddy_i(gc, buddy, TRUE); - } - } + PurpleBuddy *buddy = buddies->data; + silcpurple_add_buddy_i(gc, buddy, TRUE); } }
--- a/libpurple/protocols/silc/ops.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/silc/ops.c Sat Jun 06 06:21:39 2009 +0000 @@ -839,7 +839,7 @@ b = NULL; if (public_key) { - PurpleBlistNode *gnode, *cnode, *bnode; + GSList *buddies; const char *f; pk = silc_pkcs_public_key_encode(public_key, &pk_len); @@ -857,29 +857,13 @@ silc_free(pk); /* Find buddy by associated public key */ - for (gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for (cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if( !PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - b = (PurpleBuddy *)bnode; - if (purple_buddy_get_account(b) != account) - continue; - f = purple_blist_node_get_string(bnode, "public-key"); - if (f && !strcmp(f, buf)) - goto cont; - b = NULL; - } - } + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + b = buddies->data; + f = purple_blist_node_get_string(PURPLE_BLIST_NODE(b), "public-key"); + if (purple_strequal(f, buf)) + goto cont; + b = NULL; } } cont:
--- a/libpurple/protocols/simple/simple.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/simple/simple.c Sat Jun 06 06:21:39 2009 +0000 @@ -213,27 +213,18 @@ } static void simple_get_buddies(PurpleConnection *gc) { - PurpleBlistNode *gnode, *cnode, *bnode; + GSList *buddies; PurpleAccount *account; purple_debug_info("simple", "simple_get_buddies\n"); account = purple_connection_get_account(gc); - for(gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue; - for(cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) continue; - for(bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) continue; - if(purple_buddy_get_account((PurpleBuddy*)bnode) == account) - simple_add_buddy(gc, (PurpleBuddy*)bnode, (PurpleGroup *)gnode); - } - } + buddies = purple_find_buddies(account, NULL); + while (buddies) { + PurpleBuddy *buddy = buddies->data; + simple_add_buddy(gc, buddy, purple_buddy_get_group(buddy)); + + buddies = g_slist_delete_link(buddies, buddies); } }
--- a/libpurple/protocols/yahoo/yahoo.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sat Jun 06 06:21:39 2009 +0000 @@ -147,7 +147,6 @@ static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt) { PurpleAccount *account = purple_connection_get_account(gc); - struct yahoo_data *yd = gc->proto_data; GSList *l = pkt->hash; YahooFriend *f = NULL; char *name = NULL; @@ -168,29 +167,7 @@ switch (pair->key) { case 0: /* we won't actually do anything with this */ - break; - case 1: /* we don't get the full buddy list here. */ - if (!yd->logged_in) { - purple_connection_set_display_name(gc, pair->value); - purple_connection_set_state(gc, PURPLE_CONNECTED); - yd->logged_in = TRUE; - if (yd->picture_upload_todo) { - yahoo_buddy_icon_upload(gc, yd->picture_upload_todo); - yd->picture_upload_todo = NULL; - } - yahoo_set_status(account, purple_account_get_active_status(account)); - - /* this requests the list. i have a feeling that this is very evil - * - * scs.yahoo.com sends you the list before this packet without it being - * requested - * - * do_import(gc, NULL); - * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0); - * yahoo_packet_send_and_free(newpkt, yd); - */ - - } + case 1: /* we won't actually do anything with this */ break; case 8: /* how many online buddies we have */ break; @@ -577,6 +554,18 @@ } g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL); + + /* Now that we have processed the buddy list, we can say yahoo has connected */ + purple_connection_set_display_name(gc, purple_normalize(account, purple_account_get_username(account))); + purple_connection_set_state(gc, PURPLE_CONNECTED); + yd->logged_in = TRUE; + if (yd->picture_upload_todo) { + yahoo_buddy_icon_upload(gc, yd->picture_upload_todo); + yd->picture_upload_todo = NULL; + } + yahoo_set_status(account, purple_account_get_active_status(account)); + purple_debug_info("yahoo","Authentication: Connection established\n"); + g_hash_table_destroy(ht); g_free(norm_bud); g_free(temp); @@ -1584,7 +1573,8 @@ to_y64(base64_string, md5_digest, 16); - pkt = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, YAHOO_STATUS_WEBLOGIN, yd->session_id); + purple_debug_info("yahoo", "yahoo status: %d\n", yd->current_status); + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->current_status, yd->session_id); if(yd->jp) { yahoo_packet_hash(pkt, "ssssssss", 1, name,
--- a/libpurple/protocols/zephyr/zephyr.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/protocols/zephyr/zephyr.c Sat Jun 06 06:21:39 2009 +0000 @@ -1254,45 +1254,32 @@ #ifdef WIN32 -static gint check_loc(gpointer_data) +static gint check_loc(gpointer data) { - PurpleBlistNode *gnode, *cnode, *bnode; + GSList *buddies; ZLocations_t locations; + PurpleConnection *gc = data; + zephyr_account *zephyr = gc->proto_data; + PurpleAccount *account = purple_connection_get_account(gc); int numlocs; int one = 1; - for (gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for (cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - PurpleBuddy *b = (PurpleBuddy *) bnode; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - if (purple_buddy_get_account(b)->gc == zgc) { - char *chk; - const char *bname = purple_buddy_get_name(b); - chk = local_zephyr_normalize(bname); - ZLocateUser(chk,&numlocs, ZAUTH); - if (numlocs) { - int i; - for(i=0;i<numlocs;i++) { - ZGetLocations(&locations,&one); - serv_got_update(zgc,bname,1,0,0,0,0); - } - } - } + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + PurpleBuddy *b = buddies->data; + char *chk; + const char *bname = purple_buddy_get_name(b); + chk = local_zephyr_normalize(bname); + ZLocateUser(chk,&numlocs, ZAUTH); + if (numlocs) { + int i; + for(i=0;i<numlocs;i++) { + ZGetLocations(&locations,&one); + serv_got_update(zgc,bname,1,0,0,0,0); } } } + return TRUE; } @@ -1300,7 +1287,7 @@ static gint check_loc(gpointer data) { - PurpleBlistNode *gnode, *cnode, *bnode; + GSList *buddies; ZAsyncLocateData_t ald; PurpleConnection *gc = (PurpleConnection *)data; zephyr_account *zephyr = gc->proto_data; @@ -1312,65 +1299,49 @@ ald.version = NULL; } - for (gnode = purple_blist_get_root(); gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for (cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - PurpleBuddy *b = (PurpleBuddy *) bnode; + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + PurpleBuddy *b = buddies->data; - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - if (purple_buddy_get_account(b) == account) { - const char *chk; - const char *name = purple_buddy_get_name(b); + const char *chk; + const char *name = purple_buddy_get_name(b); - chk = local_zephyr_normalize(zephyr,name); - purple_debug_info("zephyr","chk: %s b->name %s\n",chk,name); - /* XXX add real error reporting */ - /* doesn't matter if this fails or not; we'll just move on to the next one */ - if (use_zeph02(zephyr)) { + chk = local_zephyr_normalize(zephyr,name); + purple_debug_info("zephyr","chk: %s b->name %s\n",chk,name); + /* XXX add real error reporting */ + /* doesn't matter if this fails or not; we'll just move on to the next one */ + if (use_zeph02(zephyr)) { #ifdef WIN32 - int numlocs; - int one=1; - ZLocateUser(chk,&numlocs,ZAUTH); - if (numlocs) { - int i; - for(i=0;i<numlocs;i++) { - ZGetLocations(&locations,&one); - if (nlocs>0) - purple_prpl_got_user_status(account,name,"available",NULL); - else - purple_prpl_got_user_status(account,name,"offline",NULL); - } - } -#else - ZRequestLocations(chk, &ald, UNACKED, ZAUTH); - g_free(ald.user); - g_free(ald.version); -#endif /* WIN32 */ - } else - if (use_tzc(zephyr)) { - gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk); - size_t len = strlen(zlocstr); - size_t result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len); - if (result != len) { - purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno)); - } - g_free(zlocstr); - } + int numlocs; + int one=1; + ZLocateUser(chk,&numlocs,ZAUTH); + if (numlocs) { + int i; + for(i=0;i<numlocs;i++) { + ZGetLocations(&locations,&one); + if (nlocs>0) + purple_prpl_got_user_status(account,name,"available",NULL); + else + purple_prpl_got_user_status(account,name,"offline",NULL); } } - } +#else + ZRequestLocations(chk, &ald, UNACKED, ZAUTH); + g_free(ald.user); + g_free(ald.version); +#endif /* WIN32 */ + } else + if (use_tzc(zephyr)) { + gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk); + size_t len = strlen(zlocstr); + size_t result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len); + if (result != len) { + purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno)); + } + g_free(zlocstr); + } } - + return TRUE; } @@ -1955,8 +1926,7 @@ static void write_anyone(PurpleConnection *gc) { - PurpleBlistNode *gnode, *cnode, *bnode; - PurpleBuddy *b; + GSList *buddies; char *fname; FILE *fd; PurpleAccount *account; @@ -1969,29 +1939,12 @@ } account = purple_connection_get_account(gc); - for (gnode = purple_blist_get_root(); - gnode; - gnode = purple_blist_node_get_sibling_next(gnode)) { - if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) - continue; - for (cnode = purple_blist_node_get_first_child(gnode); - cnode; - cnode = purple_blist_node_get_sibling_next(cnode)) { - if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) - continue; - for (bnode = purple_blist_node_get_first_child(cnode); - bnode; - bnode = purple_blist_node_get_sibling_next(bnode)) { - if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) - continue; - b = (PurpleBuddy *) bnode; - if (purple_buddy_get_account(b) == account) { - gchar *stripped_user = zephyr_strip_local_realm(zephyr, purple_buddy_get_name(b)); - fprintf(fd, "%s\n", stripped_user); - g_free(stripped_user); - } - } - } + for (buddies = purple_find_buddies(account, NULL); buddies; + buddies = g_slist_delete_link(buddies, buddies)) { + PurpleBuddy *b = buddies->data; + gchar *stripped_user = zephyr_strip_local_realm(zephyr, purple_buddy_get_name(b)); + fprintf(fd, "%s\n", stripped_user); + g_free(stripped_user); } fclose(fd);
--- a/libpurple/proxy.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/proxy.c Sat Jun 06 06:21:39 2009 +0000 @@ -47,6 +47,7 @@ gchar *host; int port; int fd; + int socket_type; guint inpa; PurpleProxyInfo *gpi; PurpleDnsQueryData *query_data; @@ -676,6 +677,68 @@ } static void +proxy_connect_udp_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) +{ + int flags; + + purple_debug_info("proxy", "UDP Connecting to %s:%d with no proxy\n", + connect_data->host, connect_data->port); + + connect_data->fd = socket(addr->sa_family, SOCK_DGRAM, 0); + if (connect_data->fd < 0) + { + purple_proxy_connect_data_disconnect_formatted(connect_data, + _("Unable to create socket:\n%s"), g_strerror(errno)); + return; + } + + flags = fcntl(connect_data->fd, F_GETFL); + fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK); +#ifndef _WIN32 + fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC); +#endif + + if (connect(connect_data->fd, addr, addrlen) != 0) + { + if ((errno == EINPROGRESS) || (errno == EINTR)) + { + purple_debug_info("proxy", "UDP Connection in progress\n"); + connect_data->inpa = purple_input_add(connect_data->fd, + PURPLE_INPUT_WRITE, socket_ready_cb, connect_data); + } + else + { + purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); + } + } + else + { + /* + * The connection happened IMMEDIATELY... strange, but whatever. + */ + int error = ETIMEDOUT; + int ret; + + purple_debug_info("proxy", "UDP Connected immediately.\n"); + + ret = purple_input_get_error(connect_data->fd, &error); + if ((ret != 0) || (error != 0)) + { + if (ret != 0) + error = errno; + purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); + return; + } + + /* + * We want to call the "connected" callback eventually, but we + * don't want to call it before we return, just in case. + */ + purple_timeout_add(10, clean_connect, connect_data); + } +} + +static void proxy_connect_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) { int flags; @@ -2042,6 +2105,12 @@ #endif purple_debug_info("proxy", "Attempting connection to %s\n", ipaddr); + if (connect_data->socket_type == SOCK_DGRAM) { + proxy_connect_udp_none(connect_data, addr, addrlen); + g_free(addr); + return; + } + switch (purple_proxy_info_get_type(connect_data->gpi)) { case PURPLE_PROXY_NONE: proxy_connect_none(connect_data, addr, addrlen); @@ -2193,6 +2262,7 @@ connect_data = g_new0(PurpleProxyConnectData, 1); connect_data->fd = -1; + connect_data->socket_type = SOCK_STREAM; connect_data->handle = handle; connect_data->connect_cb = connect_cb; connect_data->data = data; @@ -2243,6 +2313,71 @@ return connect_data; } +PurpleProxyConnectData * +purple_proxy_connect_udp(void *handle, PurpleAccount *account, + const char *host, int port, + PurpleProxyConnectFunction connect_cb, gpointer data) +{ + const char *connecthost = host; + int connectport = port; + PurpleProxyConnectData *connect_data; + + g_return_val_if_fail(host != NULL, NULL); + g_return_val_if_fail(port > 0, NULL); + g_return_val_if_fail(connect_cb != NULL, NULL); + + connect_data = g_new0(PurpleProxyConnectData, 1); + connect_data->fd = -1; + connect_data->socket_type = SOCK_DGRAM; + connect_data->handle = handle; + connect_data->connect_cb = connect_cb; + connect_data->data = data; + connect_data->host = g_strdup(host); + connect_data->port = port; + connect_data->gpi = purple_proxy_get_setup(account); + + if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) && + (purple_proxy_info_get_host(connect_data->gpi) == NULL || + purple_proxy_info_get_port(connect_data->gpi) <= 0)) { + + purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid.")); + purple_proxy_connect_data_destroy(connect_data); + return NULL; + } + + switch (purple_proxy_info_get_type(connect_data->gpi)) + { + case PURPLE_PROXY_NONE: + break; + + case PURPLE_PROXY_HTTP: + case PURPLE_PROXY_SOCKS4: + case PURPLE_PROXY_SOCKS5: + case PURPLE_PROXY_USE_ENVVAR: + purple_debug_info("proxy", "Ignoring Proxy type (%d) for UDP.\n", + purple_proxy_info_get_type(connect_data->gpi)); + break; + + default: + purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n", + purple_proxy_info_get_type(connect_data->gpi)); + purple_proxy_connect_data_destroy(connect_data); + return NULL; + } + + connect_data->query_data = purple_dnsquery_a(connecthost, + connectport, connection_host_resolved, connect_data); + if (connect_data->query_data == NULL) + { + purple_proxy_connect_data_destroy(connect_data); + return NULL; + } + + handles = g_slist_prepend(handles, connect_data); + + return connect_data; +} + /* * Combine some of this code with purple_proxy_connect() */ @@ -2260,6 +2395,7 @@ connect_data = g_new0(PurpleProxyConnectData, 1); connect_data->fd = -1; + connect_data->socket_type = SOCK_STREAM; connect_data->handle = handle; connect_data->connect_cb = connect_cb; connect_data->data = data;
--- a/libpurple/proxy.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/proxy.h Sat Jun 06 06:21:39 2009 +0000 @@ -257,6 +257,35 @@ PurpleProxyConnectFunction connect_cb, gpointer data); /** + * Makes a connection to the specified host and port. Note that this + * function name can be misleading--although it is called "proxy + * connect," it is used for establishing any outgoing UDP connection, + * whether through a proxy or not. + * + * @param handle A handle that should be associated with this + * connection attempt. The handle can be used + * to cancel the connection attempt using the + * purple_proxy_connect_cancel_with_handle() + * function. + * @param account The account making the connection. + * @param host The destination host. + * @param port The destination port. + * @param connect_cb The function to call when the connection is + * established. If the connection failed then + * fd will be -1 and error message will be set + * to something descriptive (hopefully). + * @param data User-defined data. + * + * @return NULL if there was an error, or a reference to an + * opaque data structure that can be used to cancel + * the pending connection, if needed. + */ +PurpleProxyConnectData *purple_proxy_connect_udp(void *handle, + PurpleAccount *account, + const char *host, int port, + PurpleProxyConnectFunction connect_cb, gpointer data); + +/** * Makes a connection through a SOCKS5 proxy. * * @param handle A handle that should be associated with this
--- a/libpurple/server.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/server.c Sat Jun 06 06:21:39 2009 +0000 @@ -728,7 +728,6 @@ im = PURPLE_CONV_IM(conv); purple_conv_im_set_typing_state(im, state); - purple_conv_im_update_typing(im); } else { switch (state) { @@ -766,7 +765,6 @@ purple_conv_im_stop_typing_timeout(im); purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING); - purple_conv_im_update_typing(im); } else {
--- a/libpurple/util.c Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/util.c Sat Jun 06 06:21:39 2009 +0000 @@ -1041,6 +1041,35 @@ return ret; } +gboolean purple_markup_is_rtl(const char *html) +{ + GData *attributes; + const gchar *start, *end; + gboolean res = FALSE; + + if (purple_markup_find_tag("span", html, &start, &end, &attributes)) + { + /* tmp is a member of attributes and is free with g_datalist_clear call */ + const char *tmp = g_datalist_get_data(&attributes, "dir"); + if (tmp && !g_ascii_strcasecmp(tmp, "RTL")) + res = TRUE; + if (!res) + { + tmp = g_datalist_get_data(&attributes, "style"); + if (tmp) + { + char *tmp2 = purple_markup_get_css_property(tmp, "direction"); + if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL")) + res = TRUE; + g_free(tmp2); + } + + } + g_datalist_clear(&attributes); + } + return res; +} + gboolean purple_markup_find_tag(const char *needle, const char *haystack, const char **start, const char **end, GData **attributes) @@ -4395,6 +4424,37 @@ return g_string_free(workstr, FALSE); } +gchar * +purple_utf8_strip_unprintables(const gchar *str) +{ + gchar *workstr, *iter; + + if (str == NULL) + /* Act like g_strdup */ + return NULL; + + g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL); + + workstr = iter = g_new(gchar, strlen(str) + 1); + while (*str) { + gunichar c = g_utf8_get_char(str); + const gchar *next = g_utf8_next_char(str); + size_t len = next - str; + + if (g_unichar_isprint(c)) { + memcpy(iter, str, len); + iter += len; + } + + str = next; + } + + /* nul-terminate the new string */ + *iter = '\0'; + + return workstr; +} + /* * This function is copied from g_strerror() but changed to use * gai_strerror().
--- a/libpurple/util.h Sun May 31 18:39:19 2009 +0000 +++ b/libpurple/util.h Sat Jun 06 06:21:39 2009 +0000 @@ -506,8 +506,6 @@ * * @return The text with HTML entities literalized. You must g_free * this string when finished with it. - * - * @see purple_escape_html */ char *purple_unescape_html(const char *html); @@ -581,6 +579,16 @@ */ char * purple_markup_get_css_property(const gchar *style, const gchar *opt); +/** + * Check if the given HTML contains RTL text. + * + * @param html The HTML text. + * + * @return TRUE if the text contains RTL text, FALSE otherwise. + * + * @since 2.6.0 + */ +gboolean purple_markup_is_rtl(const char *html); /*@}*/ @@ -1240,6 +1248,22 @@ gchar *purple_utf8_salvage(const char *str); /** + * Removes unprintable characters from a UTF-8 string. These characters + * (in particular low-ASCII characters) are invalid in XML 1.0 and thus + * are not allowed in XMPP and are rejected by libxml2 by default. This + * function uses g_unichar_isprint to determine what characters should + * be stripped. The returned string must be freed by the caller. + * + * @param str A valid UTF-8 string. + * + * @return A newly allocated UTF-8 string without the unprintable characters. + * @since 2.6.0 + * + * @see g_unichar_isprint + */ +gchar *purple_utf8_strip_unprintables(const gchar *str); + +/** * Return the UTF-8 version of gai_strerror(). It calls gai_strerror() * then converts the result to UTF-8. This function is analogous to * g_strerror().
--- a/pidgin/gtkblist.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtkblist.c Sat Jun 06 06:21:39 2009 +0000 @@ -1611,8 +1611,9 @@ { PurpleBlistNode *node; GValue val; - GtkTreeIter iter; + GtkTreeIter iter, parent; GtkTreeSelection *sel; + GtkTreePath *path; sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)); if(!gtk_tree_selection_get_selected(sel, NULL, &iter)) @@ -1636,8 +1637,62 @@ } if(buddy) pidgin_retrieve_user_info(buddy->account->gc, buddy->name); - } else if (event->keyval == GDK_F2) { - gtk_blist_menu_alias_cb(tv, node); + } else { + switch (event->keyval) { + case GDK_F2: + gtk_blist_menu_alias_cb(tv, node); + break; + + case GDK_Left: + path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter); + if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) { + /* Collapse the Group */ + gtk_tree_view_collapse_row(GTK_TREE_VIEW(tv), path); + gtk_tree_path_free(path); + return TRUE; + } else { + /* Select the Parent */ + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path)) { + if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(gtkblist->treemodel), &parent, &iter)) { + gtk_tree_path_free(path); + path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &parent); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE); + gtk_tree_path_free(path); + return TRUE; + } + } + } + gtk_tree_path_free(path); + break; + + case GDK_Right: + path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter); + if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) { + /* Expand the Group */ + if (PURPLE_BLIST_NODE_IS_CONTACT(node)) { + pidgin_blist_expand_contact_cb(NULL, node); + gtk_tree_path_free(path); + return TRUE; + } else if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + gtk_tree_view_expand_row(GTK_TREE_VIEW(tv), path, FALSE); + gtk_tree_path_free(path); + return TRUE; + } + } else { + /* Select the First Child */ + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &parent, path)) { + if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gtkblist->treemodel), &iter, &parent, 0)) { + gtk_tree_path_free(path); + path = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &iter); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv), path, NULL, FALSE); + gtk_tree_path_free(path); + return TRUE; + } + } + } + gtk_tree_path_free(path); + break; + } } return FALSE; @@ -5556,9 +5611,12 @@ gtkblist = PIDGIN_BLIST(list); priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + if (priv->current_theme) + g_object_unref(priv->current_theme); + theme_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/blist/theme"); if (theme_name && *theme_name) - priv->current_theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist")); + priv->current_theme = g_object_ref(PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(theme_name, "blist"))); else priv->current_theme = NULL; @@ -6164,10 +6222,9 @@ if (count > 0 || purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups")) show = TRUE; - else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { /* Or chat? */ - if (buddy_is_displayable((PurpleBuddy*)node)) - show = TRUE; - } else if (!show_offline && PURPLE_BLIST_NODE_IS_GROUP(node)) { + else if (PURPLE_BLIST_NODE_IS_BUDDY(node) && buddy_is_displayable((PurpleBuddy*)node)) { /* Or chat? */ + show = TRUE; + } else if (!show_offline) { show = pidgin_blist_group_has_show_offline_buddy(group); } @@ -6691,6 +6748,8 @@ gtkblist->arrow_cursor = NULL; priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + if (priv->current_theme) + g_object_unref(priv->current_theme); g_free(priv); g_free(gtkblist); @@ -7261,7 +7320,10 @@ else purple_prefs_set_string(PIDGIN_PREFS_ROOT "/blist/theme", ""); - priv->current_theme = theme; + if (priv->current_theme) + g_object_unref(priv->current_theme); + + priv->current_theme = theme ? g_object_ref(theme) : NULL; pidgin_blist_build_layout(list);
--- a/pidgin/gtkconv.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtkconv.c Sat Jun 06 06:21:39 2009 +0000 @@ -5629,38 +5629,6 @@ #endif } -/* Returns true if the given HTML contains RTL text */ -static gboolean -html_is_rtl(const char *html) -{ - GData *attributes; - const gchar *start, *end; - gboolean res = FALSE; - - if (purple_markup_find_tag("span", html, &start, &end, &attributes)) - { - /* tmp is a member of attributes and is free with g_datalist_clear call */ - const char *tmp = g_datalist_get_data(&attributes, "dir"); - if (tmp && !g_ascii_strcasecmp(tmp, "RTL")) - res = TRUE; - if (!res) - { - tmp = g_datalist_get_data(&attributes, "style"); - if (tmp) - { - char *tmp2 = purple_markup_get_css_property(tmp, "direction"); - if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL")) - res = TRUE; - g_free(tmp2); - } - - } - g_datalist_clear(&attributes); - } - return res; -} - - static void pidgin_conv_write_conv(PurpleConversation *conv, const char *name, const char *alias, const char *message, PurpleMessageFlags flags, @@ -5822,7 +5790,7 @@ } /* Bi-Directional support - set timestamp direction using unicode characters */ - is_rtl_message = html_is_rtl(message); + is_rtl_message = purple_markup_is_rtl(message); /* Enforce direction only if message is RTL - doesn't effect LTR users */ if (is_rtl_message) str_embed_direction_chars(&mdate);
--- a/pidgin/gtkdialogs.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtkdialogs.c Sat Jun 06 06:21:39 2009 +0000 @@ -74,7 +74,7 @@ static const struct developer developers[] = { {"Daniel 'datallah' Atallah", NULL, NULL}, {"Paul 'darkrain42' Aurich", NULL, NULL }, - {"John 'rekkanoryo' Bailey", N_("bug master"), "rekkanoryo@pidgin.im"}, + {"John 'rekkanoryo' Bailey", N_("bug master"), NULL}, {"Ethan 'Paco-Paco' Blanton", NULL, NULL}, {"Hylke Bons", N_("artist"), "h.bons@student.rug.nl"}, {"Thomas Butter", NULL, NULL},
--- a/pidgin/gtkimhtml.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtkimhtml.c Sat Jun 06 06:21:39 2009 +0000 @@ -3867,12 +3867,15 @@ } static void -gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImage *image) -{ +gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImageSave *save) +{ + GtkIMHtmlImage *image = (GtkIMHtmlImage *)save->image; + /* Create an add dialog */ PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL); pidgin_smiley_editor_set_shortcut(editor, image->filename); pidgin_smiley_editor_set_image(editor, image->pixbuf); + pidgin_smiley_editor_set_data(editor, save->data, save->datasize); } /* @@ -3907,7 +3910,7 @@ item = gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley...")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img); g_signal_connect(G_OBJECT(item), "activate", - G_CALLBACK(gtk_imhtml_custom_smiley_save), image); + G_CALLBACK(gtk_imhtml_custom_smiley_save), save); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); }
--- a/pidgin/gtknotify.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtknotify.c Sat Jun 06 06:21:39 2009 +0000 @@ -120,7 +120,7 @@ { PIDGIN_NOTIFY_MAIL, PIDGIN_NOTIFY_POUNCE, - PIDGIN_NOTIFY_TYPES + PIDGIN_NOTIFY_TYPES } PidginNotifyType; static PidginNotifyDialog *mail_dialog = NULL; @@ -1379,7 +1379,6 @@ spec_dialog = g_new0(PidginNotifyDialog, 1); spec_dialog->dialog = dialog; - spec_dialog->open_button = button; spec_dialog->treemodel = treemodel; spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(spec_dialog->treemodel)); @@ -1399,6 +1398,7 @@ button = gtk_dialog_add_button(GTK_DIALOG(dialog), PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES); + spec_dialog->open_button = button; gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE); @@ -1496,7 +1496,7 @@ mail_dialog = spec_dialog; else if (type == PIDGIN_NOTIFY_POUNCE) { pounce_dialog = spec_dialog; - } + } return spec_dialog->dialog;
--- a/pidgin/gtkprefs.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtkprefs.c Sat Jun 06 06:21:39 2009 +0000 @@ -1163,14 +1163,15 @@ static void prefs_set_blist_theme_cb(GtkComboBox *combo_box, gpointer user_data) { - PidginBlistTheme *theme; + PidginBlistTheme *theme = NULL; GtkTreeIter iter; gchar *name = NULL; g_return_if_fail(gtk_combo_box_get_active_iter(combo_box, &iter)); gtk_tree_model_get(GTK_TREE_MODEL(prefs_blist_themes), &iter, 2, &name, -1); - theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(name, "blist")); + if (name && *name) + theme = PIDGIN_BLIST_THEME(purple_theme_manager_find_theme(name, "blist")); g_free(name); pidgin_blist_set_theme(theme);
--- a/pidgin/gtksmiley.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtksmiley.c Sat Jun 06 06:21:39 2009 +0000 @@ -47,6 +47,8 @@ GtkWidget *smiley_image; gchar *filename; GdkPixbuf *custom_pixbuf; + gpointer data; /** @since 2.6.0 */ + gsize datasize; /** @since 2.6.0 */ }; typedef struct @@ -277,7 +279,6 @@ purple_debug_info("gtksmiley", "adding a new smiley\n"); if (s->filename == NULL) { - /* Get the smiley from the custom pixbuf */ gchar *buffer = NULL; gsize size = 0; gchar *filename; @@ -296,8 +297,16 @@ } } - gdk_pixbuf_save_to_buffer(s->custom_pixbuf, &buffer, &size, - "png", NULL, "compression", "9", NULL, NULL); + if (s->data && s->datasize) { + /* Cached data & size in memory */ + buffer = s->data; + size = s->datasize; + } + else { + /* Get the smiley from the custom pixbuf */ + 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(dirname, filename, NULL); purple_util_write_data_to_file_absolute(s->filename, buffer, size); @@ -465,6 +474,13 @@ gtk_image_set_from_pixbuf(GTK_IMAGE(editor->smiley_image), image); } +void +pidgin_smiley_editor_set_data(PidginSmiley *editor, gpointer *data, gsize datasize) +{ + editor->data = data; + editor->datasize = datasize; +} + /****************************************************************************** * Delete smiley *****************************************************************************/
--- a/pidgin/gtksmiley.h Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtksmiley.h Sat Jun 06 06:21:39 2009 +0000 @@ -100,4 +100,15 @@ */ void pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image); +/** + * Sets the image data in a smiley add dialog + * + * @param editor A smiley editor dialog + * @param data A pointer to smiley's data + * @param datasize The size of smiley's data + * + * @since 2.6.0 + */ +void pidgin_smiley_editor_set_data(PidginSmiley *editor, gpointer *data, gsize datasize); + #endif /* PIDGIN_GTKSMILEY_H */
--- a/pidgin/gtkutils.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/gtkutils.c Sat Jun 06 06:21:39 2009 +0000 @@ -525,7 +525,7 @@ GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu)); if (p_item) (*p_item) = item; - return g_object_get_data(G_OBJECT(item), "aop_per_item_data"); + return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL; } static void
--- a/pidgin/plugins/Makefile.am Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/plugins/Makefile.am Sat Jun 06 06:21:39 2009 +0000 @@ -1,4 +1,4 @@ -DIST_SUBDIRS = cap gestures gevolution musicmessaging perl ticker +DIST_SUBDIRS = cap disco gestures gevolution musicmessaging perl ticker if BUILD_GEVOLUTION GEVOLUTION_DIR = gevolution @@ -26,6 +26,7 @@ $(GEVOLUTION_DIR) \ $(MUSICMESSAGING_DIR) \ $(PERL_DIR) \ + disco \ ticker plugindir = $(libdir)/pidgin
--- a/pidgin/plugins/convcolors.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/plugins/convcolors.c Sat Jun 06 06:21:39 2009 +0000 @@ -101,6 +101,7 @@ gboolean bold, italic, underline; int f; const char *color; + gboolean rtl = FALSE; for (i = 0; formats[i].prefix; i++) if (flags & formats[i].flag) @@ -126,6 +127,7 @@ bold = (f & FONT_BOLD); italic = (f & FONT_ITALIC); underline = (f & FONT_UNDERLINE); + rtl = purple_markup_is_rtl(*displaying); if (purple_prefs_get_bool(PREF_IGNORE)) { @@ -156,11 +158,13 @@ } t = *displaying; - *displaying = g_strdup_printf("%s%s%s%s%s%s%s", + *displaying = g_strdup_printf("%s%s%s%s%s%s%s%s%s", bold ? "<B>" : "</B>", italic ? "<I>" : "</I>", underline ? "<U>" : "</U>", - t, + rtl ? "<SPAN style=\"direction:rtl;text-align:right;\">" : "", + t, + rtl ? "</SPAN>" : "", bold ? "</B>" : "<B>", italic ? "</I>" : "<I>", underline ? "</U>" : "<U>"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/Makefile.am Sat Jun 06 06:21:39 2009 +0000 @@ -0,0 +1,23 @@ +plugindir = $(libdir)/pidgin + +xmppdisco_la_LDFLAGS = -module -avoid-version + +if PLUGINS + +plugin_LTLIBRARIES = xmppdisco.la + +xmppdisco_la_SOURCES = \ + gtkdisco.c \ + xmppdisco.c + +xmppdisco_la_LIBADD = $(GTK_LIBS) + +endif + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ + -I$(top_srcdir)/pidgin \ + $(DEBUG_CFLAGS) \ + $(GTK_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/gtkdisco.c Sat Jun 06 06:21:39 2009 +0000 @@ -0,0 +1,592 @@ +/** + * @file gtkdisco.c GTK+ Service Discovery UI + * @ingroup pidgin + */ + +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" +#include "gtkutils.h" +#include "pidgin.h" +#include "request.h" + +#include "gtkdisco.h" +#include "xmppdisco.h" + +GList *dialogs = NULL; + +struct _menu_cb_info { + PidginDiscoList *list; + XmppDiscoService *service; +}; + +enum { + PIXBUF_COLUMN = 0, + NAME_COLUMN, + DESCRIPTION_COLUMN, + SERVICE_COLUMN, + NUM_OF_COLUMNS +}; + +static void +pidgin_disco_list_destroy(PidginDiscoList *list) +{ + g_hash_table_destroy(list->services); + if (list->dialog && list->dialog->discolist == list) + list->dialog->discolist = NULL; + + if (list->tree) { + gtk_widget_destroy(list->tree); + list->tree = NULL; + } + + g_free((gchar*)list->server); + g_free(list); +} + +PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + ++list->ref; + purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list->ref); + + return list; +} + +void pidgin_disco_list_unref(PidginDiscoList *list) +{ + g_return_if_fail(list != NULL); + + --list->ref; + + purple_debug_misc("xmppdisco", "unreffing list, ref count now %d\n", list->ref); + if (list->ref == 0) + pidgin_disco_list_destroy(list); +} + +void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress) +{ + PidginDiscoDialog *dialog = list->dialog; + + if (!dialog) + return; + + list->in_progress = in_progress; + + if (in_progress) { + gtk_widget_set_sensitive(dialog->account_widget, FALSE); + gtk_widget_set_sensitive(dialog->stop_button, TRUE); + gtk_widget_set_sensitive(dialog->browse_button, FALSE); + } else { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), 0.0); + + gtk_widget_set_sensitive(dialog->account_widget, TRUE); + + gtk_widget_set_sensitive(dialog->stop_button, FALSE); + gtk_widget_set_sensitive(dialog->browse_button, TRUE); +/* + gtk_widget_set_sensitive(dialog->register_button, FALSE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); +*/ + } +} + +static void pidgin_disco_create_tree(PidginDiscoList *pdl); + +static void dialog_select_account_cb(GObject *w, PurpleAccount *account, + PidginDiscoDialog *dialog) +{ + dialog->account = account; + gtk_widget_set_sensitive(dialog->browse_button, account != NULL); +} + +static void register_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info"); + + xmpp_disco_service_register(info->service); +} + +static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server) +{ + pidgin_disco_list_set_in_progress(pdl, FALSE); + pidgin_disco_list_unref(pdl); +} + +static void discolist_ok_cb(PidginDiscoList *pdl, const char *server) +{ + gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE); + + if (!server || !*server) { + purple_notify_error(my_plugin, _("Invalid Server"), _("Invalid Server"), + NULL); + + pidgin_disco_list_set_in_progress(pdl, FALSE); + pidgin_disco_list_unref(pdl); + return; + } + + pdl->server = g_strdup(server); + pidgin_disco_list_set_in_progress(pdl, TRUE); + xmpp_disco_start(pdl); +} + +static void browse_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + PurpleConnection *pc; + PidginDiscoList *pdl; + const char *username; + const char *at, *slash; + char *server = NULL; + + pc = purple_account_get_connection(dialog->account); + if (!pc) + return; + + gtk_widget_set_sensitive(dialog->browse_button, FALSE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + + if (dialog->discolist != NULL) { + if (dialog->discolist->tree) { + gtk_widget_destroy(dialog->discolist->tree); + dialog->discolist->tree = NULL; + } + pidgin_disco_list_unref(dialog->discolist); + } + + pdl = dialog->discolist = g_new0(PidginDiscoList, 1); + pdl->services = g_hash_table_new_full(NULL, NULL, NULL, + (GDestroyNotify)gtk_tree_row_reference_free); + pdl->pc = pc; + /* We keep a copy... */ + pidgin_disco_list_ref(pdl); + + pdl->dialog = dialog; + pidgin_disco_create_tree(pdl); + + if (dialog->account_widget) + gtk_widget_set_sensitive(dialog->account_widget, FALSE); + + username = purple_account_get_username(dialog->account); + at = g_utf8_strchr(username, -1, '@'); + slash = g_utf8_strchr(username, -1, '/'); + if (at && !slash) { + server = g_strdup_printf("%s", at + 1); + } else if (at && slash && at + 1 < slash) { + server = g_strdup_printf("%.*s", (int)(slash - (at + 1)), at + 1); + } + + if (server == NULL) + /* This shouldn't ever happen since the account is connected */ + server = g_strdup("jabber.org"); + + purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"), + _("Select an XMPP server to query"), + server, FALSE, FALSE, NULL, + _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb), + _("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb), + purple_connection_get_account(pc), NULL, NULL, pdl); + + g_free(server); +} + +static void add_room_to_blist_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info"); + PurpleAccount *account; + const char *name; + + g_return_if_fail(info != NULL); + + account = purple_connection_get_account(info->list->pc); + name = info->service->name; + + if (info->service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) + purple_blist_request_add_chat(account, NULL, NULL, name); + else + purple_blist_request_add_buddy(account, name, NULL, NULL); +} + +static void +selection_changed_cb(GtkTreeSelection *selection, PidginDiscoList *pdl) +{ + XmppDiscoService *service; + GtkTreeIter iter; + GValue val; + static struct _menu_cb_info *info; + PidginDiscoDialog *dialog = pdl->dialog; + + if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { + val.g_type = 0; + gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN, &val); + service = g_value_get_pointer(&val); + if (!service) { + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + return; + } + + info = g_new0(struct _menu_cb_info, 1); + info->list = dialog->discolist; + info->service = service; + + g_object_set_data_full(G_OBJECT(dialog->add_button), "disco-info", + info, g_free); + g_object_set_data(G_OBJECT(dialog->register_button), "disco-info", info); + + gtk_widget_set_sensitive(dialog->add_button, service->flags & XMPP_DISCO_ADD); + gtk_widget_set_sensitive(dialog->register_button, service->flags & XMPP_DISCO_REGISTER); + } else { + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + } +} + +static void +row_expanded_cb(GtkTreeView *tree, GtkTreeIter *arg1, GtkTreePath *rg2, + gpointer user_data) +{ + PidginDiscoList *pdl; + XmppDiscoService *service; + GValue val; + + pdl = user_data; + + val.g_type = 0; + gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), arg1, SERVICE_COLUMN, + &val); + service = g_value_get_pointer(&val); + xmpp_disco_service_expand(service); +} + +static void +destroy_win_cb(GtkWidget *window, gpointer d) +{ + PidginDiscoDialog *dialog = d; + PidginDiscoList *list = dialog->discolist; + + if (list) { + list->dialog = NULL; + + if (list->in_progress) + list->in_progress = FALSE; + + pidgin_disco_list_unref(list); + } + + dialogs = g_list_remove(dialogs, d); + g_free(dialog); +} + +static void stop_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + pidgin_disco_list_set_in_progress(dialog->discolist, FALSE); +} + +static void close_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + GtkWidget *window = dialog->window; + + gtk_widget_destroy(window); +} + +static gboolean account_filter_func(PurpleAccount *account) +{ + return purple_strequal(purple_account_get_protocol_id(account), XMPP_PLUGIN_ID); +} + +static void pidgin_disco_create_tree(PidginDiscoList *pdl) +{ + GtkCellRenderer *text_renderer, *pixbuf_renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + pdl->model = gtk_tree_store_new(NUM_OF_COLUMNS, + GDK_TYPE_PIXBUF, /* PIXBUF_COLUMN */ + G_TYPE_STRING, /* NAME_COLUMN */ + G_TYPE_STRING, /* DESCRIPTION_COLUMN */ + G_TYPE_POINTER /* SERVICE_COLUMN */ + ); + + pdl->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pdl->model)); + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pdl->tree), TRUE); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pdl->tree)); + g_signal_connect(G_OBJECT(selection), "changed", + G_CALLBACK(selection_changed_cb), pdl); + + g_object_unref(pdl->model); + + gtk_container_add(GTK_CONTAINER(pdl->dialog->sw), pdl->tree); + gtk_widget_show(pdl->tree); + + text_renderer = gtk_cell_renderer_text_new(); + pixbuf_renderer = gtk_cell_renderer_pixbuf_new(); + + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, _("Name")); + + gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE); + gtk_tree_view_column_set_attributes(column, pixbuf_renderer, + "pixbuf", PIXBUF_COLUMN, NULL); + + gtk_tree_view_column_pack_start(column, text_renderer, TRUE); + gtk_tree_view_column_set_attributes(column, text_renderer, + "text", NAME_COLUMN, NULL); + + gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column), + GTK_TREE_VIEW_COLUMN_GROW_ONLY); + gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE); + gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), NAME_COLUMN); + gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column); + + column = gtk_tree_view_column_new_with_attributes(_("Description"), text_renderer, + "text", DESCRIPTION_COLUMN, NULL); + gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column), + GTK_TREE_VIEW_COLUMN_GROW_ONLY); + gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE); + gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), DESCRIPTION_COLUMN); + gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column); + + g_signal_connect(G_OBJECT(pdl->tree), "row-expanded", G_CALLBACK(row_expanded_cb), pdl); +} + +void pidgin_disco_signed_off_cb(PurpleConnection *pc) +{ + GList *node; + + for (node = dialogs; node; node = node->next) { + PidginDiscoDialog *dialog = node->data; + PidginDiscoList *list = dialog->discolist; + + if (list && list->pc == pc) { + if (list->in_progress) + pidgin_disco_list_set_in_progress(list, FALSE); + + if (list->tree) { + gtk_widget_destroy(list->tree); + list->tree = NULL; + } + + pidgin_disco_list_unref(list); + dialog->discolist = NULL; + + gtk_widget_set_sensitive(dialog->browse_button, + pidgin_account_option_menu_get_selected(dialog->account_widget) != NULL); + + gtk_widget_set_sensitive(dialog->register_button, FALSE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); + } + } +} + +void pidgin_disco_dialogs_destroy_all(void) +{ + while (dialogs) { + PidginDiscoDialog *dialog = dialogs->data; + + gtk_widget_destroy(dialog->window); + /* destroy_win_cb removes the dialog from the list */ + } +} + +PidginDiscoDialog *pidgin_disco_dialog_new(void) +{ + PidginDiscoDialog *dialog; + GtkWidget *window, *vbox, *vbox2, *bbox; + + dialog = g_new0(PidginDiscoDialog, 1); + dialogs = g_list_prepend(dialogs, dialog); + + /* Create the window. */ + dialog->window = window = pidgin_create_dialog(_("Service Discovery"), PIDGIN_HIG_BORDER, "service discovery", TRUE); + + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(destroy_win_cb), dialog); + + /* Create the parent vbox for everything. */ + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER); + + vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); + gtk_container_add(GTK_CONTAINER(vbox), vbox2); + gtk_widget_show(vbox2); + + /* accounts dropdown list */ + dialog->account_widget = pidgin_account_option_menu_new(NULL, FALSE, + G_CALLBACK(dialog_select_account_cb), account_filter_func, dialog); + dialog->account = pidgin_account_option_menu_get_selected(dialog->account_widget); + pidgin_add_widget_to_vbox(GTK_BOX(vbox2), _("_Account:"), NULL, dialog->account_widget, TRUE, NULL); + + /* scrolled window */ + dialog->sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(dialog->sw), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dialog->sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start(GTK_BOX(vbox2), dialog->sw, TRUE, TRUE, 0); + gtk_widget_set_size_request(dialog->sw, -1, 250); + gtk_widget_show(dialog->sw); + + /* progress bar */ + dialog->progress = gtk_progress_bar_new(); + gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog->progress), 0.1); + gtk_box_pack_start(GTK_BOX(vbox2), dialog->progress, FALSE, FALSE, 0); + gtk_widget_show(dialog->progress); + + /* button box */ + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window)); + gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + + /* stop button */ + dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, + G_CALLBACK(stop_button_cb), dialog); + gtk_widget_set_sensitive(dialog->stop_button, FALSE); + + /* browse button */ + dialog->browse_button = pidgin_pixbuf_button_from_stock(_("_Browse"), GTK_STOCK_REFRESH, + PIDGIN_BUTTON_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(bbox), dialog->browse_button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(dialog->browse_button), "clicked", + G_CALLBACK(browse_button_cb), dialog); + gtk_widget_set_sensitive(dialog->browse_button, dialog->account != NULL); + gtk_widget_show(dialog->browse_button); + + /* register button */ + dialog->register_button = pidgin_dialog_add_button(GTK_DIALOG(dialog->window), _("Register"), + G_CALLBACK(register_button_cb), dialog); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + + /* add button */ + dialog->add_button = pidgin_pixbuf_button_from_stock(_("_Add"), GTK_STOCK_ADD, + PIDGIN_BUTTON_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(bbox), dialog->add_button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(dialog->add_button), "clicked", + G_CALLBACK(add_room_to_blist_cb), dialog); + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_show(dialog->add_button); + + /* close button */ + dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, + G_CALLBACK(close_button_cb), dialog); + + /* show the dialog window and return the dialog */ + gtk_widget_show(dialog->window); + + return dialog; +} + +void pidgin_disco_add_service(PidginDiscoList *pdl, XmppDiscoService *service, XmppDiscoService *parent) +{ + PidginDiscoDialog *dialog; + GtkTreeIter iter, parent_iter, child; + char *filename = NULL; + GdkPixbuf *pixbuf = NULL; + gboolean append = TRUE; + + dialog = pdl->dialog; + g_return_if_fail(dialog != NULL); + + if (service != NULL) + purple_debug_info("xmppdisco", "Adding service \"%s\"\n", service->name); + else + purple_debug_info("xmppdisco", "Service \"%s\" has no childrens\n", parent->name); + + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress)); + + if (parent) { + GtkTreeRowReference *rr; + GtkTreePath *path; + + rr = g_hash_table_lookup(pdl->services, parent); + path = gtk_tree_row_reference_get_path(rr); + if (path) { + gtk_tree_model_get_iter(GTK_TREE_MODEL(pdl->model), &parent_iter, path); + gtk_tree_path_free(path); + + if (gtk_tree_model_iter_children(GTK_TREE_MODEL(pdl->model), &child, + &parent_iter)) { + PidginDiscoList *tmp; + gtk_tree_model_get(GTK_TREE_MODEL(pdl->model), &child, + SERVICE_COLUMN, &tmp, -1); + if (!tmp) + append = FALSE; + } + } + } + + if (service == NULL) { + if (parent != NULL && !append) + gtk_tree_store_remove(pdl->model, &child); + return; + } + + if (append) + gtk_tree_store_append(pdl->model, &iter, (parent ? &parent_iter : NULL)); + else + iter = child; + + if (service->flags & XMPP_DISCO_BROWSE) { + GtkTreeRowReference *rr; + GtkTreePath *path; + + gtk_tree_store_append(pdl->model, &child, &iter); + + path = gtk_tree_model_get_path(GTK_TREE_MODEL(pdl->model), &iter); + rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(pdl->model), path); + g_hash_table_insert(pdl->services, service, rr); + gtk_tree_path_free(path); + } + + if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY && service->gateway_type) { + char *tmp = g_strconcat(service->gateway_type, ".png", NULL); + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", tmp, NULL); + g_free(tmp); +#if 0 + } else if (service->type == XMPP_DISCO_SERVICE_TYPE_USER) { + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "person.png", NULL); +#endif + } else if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "chat.png", NULL); + + if (filename) { + pixbuf = gdk_pixbuf_new_from_file(filename, NULL); + g_free(filename); + } + + gtk_tree_store_set(pdl->model, &iter, + PIXBUF_COLUMN, pixbuf, + NAME_COLUMN, service->name, + DESCRIPTION_COLUMN, service->description, + SERVICE_COLUMN, service, + -1); + + if (pixbuf) + g_object_unref(pixbuf); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/gtkdisco.h Sat Jun 06 06:21:39 2009 +0000 @@ -0,0 +1,79 @@ +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef PIDGIN_XMPP_DISCO_UI_H +#define PIDGIN_XMPP_DISCO_UI_H + +typedef struct _PidginDiscoDialog PidginDiscoDialog; +typedef struct _PidginDiscoList PidginDiscoList; + +#include "xmppdisco.h" + +struct _PidginDiscoDialog { + GtkWidget *window; + GtkWidget *account_widget; + + GtkWidget *sw; + GtkWidget *progress; + + GtkWidget *stop_button; + GtkWidget *browse_button; + GtkWidget *register_button; + GtkWidget *add_button; + GtkWidget *close_button; + + PurpleAccount *account; + PidginDiscoList *discolist; +}; + +struct _PidginDiscoList { + PurpleConnection *pc; + gboolean in_progress; + const gchar *server; + + gint ref; + guint fetch_count; + + PidginDiscoDialog *dialog; + GtkTreeStore *model; + GtkWidget *tree; + GHashTable *services; +}; + +/** + * Shows a new service discovery dialog. + */ +PidginDiscoDialog *pidgin_disco_dialog_new(void); + +/** + * Destroy all the open dialogs (called when unloading the plugin). + */ +void pidgin_disco_dialogs_destroy_all(void); +void pidgin_disco_signed_off_cb(PurpleConnection *pc); + +void pidgin_disco_add_service(PidginDiscoList *list, XmppDiscoService *service, + XmppDiscoService *parent); + +PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list); +void pidgin_disco_list_unref(PidginDiscoList *list); + +void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress); +#endif /* PIDGIN_XMPP_DISCO_UI_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/xmppdisco.c Sat Jun 06 06:21:39 2009 +0000 @@ -0,0 +1,665 @@ +/* + * Purple - XMPP Service Disco Browser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +/* TODO list (a little bit of a brain dump): + * Support more actions than "register" and "add" based on context. + - Subscribe to pubsub nodes (just...because?) + - Execute ad-hoc commands + - Change 'Register' to 'Unregister' if we're registered? + - Administer MUCs + * Enumerate pubsub node contents. + - PEP too? (useful development tool at times) + * See if we can better handle the ad-hoc commands that ejabberd returns + when disco'ing a server as an administrator: +from disco#items: + <item jid='darkrain42.org' node='announce' name='Announcements'/> +disco#info: + <iq from='darkrain42.org' type='result'> + <query xmlns='http://jabber.org/protocol/disco#info' node='announce'/> + </iq> + * For services that are a JID w/o a node, handle fetching ad-hoc commands? +*/ + +#include "internal.h" +#include "pidgin.h" + +#include "debug.h" +#include "signals.h" +#include "version.h" + +#include "gtkconv.h" +#include "gtkimhtml.h" +#include "gtkplugin.h" + +#include "xmppdisco.h" +#include "gtkdisco.h" + +/* Variables */ +PurplePlugin *my_plugin = NULL; +static GHashTable *iq_callbacks = NULL; +static gboolean iq_listening = FALSE; + +typedef void (*XmppIqCallback)(PurpleConnection *pc, const char *type, + const char *id, const char *from, xmlnode *iq, + gpointer data); + +struct xmpp_iq_cb_data +{ + gpointer context; + PurpleConnection *pc; + XmppIqCallback cb; +}; + +struct item_data { + PidginDiscoList *list; + XmppDiscoService *parent; + char *name; + char *node; /* disco#info replies don't always include the node */ +}; + +static char* +generate_next_id() +{ + static guint32 index = 0; + + if (index == 0) { + do { + index = g_random_int(); + } while (index == 0); + } + + return g_strdup_printf("purpledisco%x", index++); +} + +static gboolean +remove_iq_callbacks_by_pc(gpointer key, gpointer value, gpointer user_data) +{ + struct xmpp_iq_cb_data *cb_data = value; + + if (cb_data && cb_data->pc == user_data) { + /* + * This is a hack. All the IQ callback datas in this code are + * the same structure so that we can free them here. Ideally they'd + * be objects and this would be polymorphic. That's overkill, here. + */ + struct item_data *item_data = cb_data->context; + + if (item_data) { + pidgin_disco_list_unref(item_data->list); + g_free(item_data->name); + g_free(item_data->node); + g_free(item_data); + } + + return TRUE; + } else + return FALSE; +} + +static gboolean +xmpp_iq_received(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq) +{ + struct xmpp_iq_cb_data *cb_data; + + cb_data = g_hash_table_lookup(iq_callbacks, id); + if (!cb_data) + return FALSE; + + cb_data->cb(cb_data->pc, type, id, from, iq, cb_data->context); + + g_hash_table_remove(iq_callbacks, id); + if (g_hash_table_size(iq_callbacks) == 0) { + PurplePlugin *prpl = purple_connection_get_prpl(pc); + iq_listening = FALSE; + purple_signal_disconnect(prpl, "jabber-receiving-iq", my_plugin, + PURPLE_CALLBACK(xmpp_iq_received)); + } + + /* Om nom nom nom */ + return TRUE; +} + +static void +xmpp_iq_register_callback(PurpleConnection *pc, gchar *id, gpointer data, + XmppIqCallback cb) +{ + struct xmpp_iq_cb_data *cbdata = g_new0(struct xmpp_iq_cb_data, 1); + + cbdata->context = data; + cbdata->cb = cb; + cbdata->pc = pc; + + g_hash_table_insert(iq_callbacks, id, cbdata); + + if (!iq_listening) { + PurplePlugin *prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID); + iq_listening = TRUE; + purple_signal_connect(prpl, "jabber-receiving-iq", my_plugin, + PURPLE_CALLBACK(xmpp_iq_received), NULL); + } +} + +static void +xmpp_disco_info_do(PurpleConnection *pc, gpointer cbdata, const char *jid, + const char *node, XmppIqCallback cb) +{ + xmlnode *iq, *query; + char *id = generate_next_id(); + + iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "type", "get"); + xmlnode_set_attrib(iq, "to", jid); + xmlnode_set_attrib(iq, "id", id); + + query = xmlnode_new_child(iq, "query"); + xmlnode_set_namespace(query, NS_DISCO_INFO); + if (node) + xmlnode_set_attrib(query, "node", node); + + /* Steals id */ + xmpp_iq_register_callback(pc, id, cbdata, cb); + + purple_signal_emit(purple_connection_get_prpl(pc), "jabber-sending-xmlnode", + pc, &iq); + if (iq != NULL) + xmlnode_free(iq); +} + +static void +xmpp_disco_items_do(PurpleConnection *pc, gpointer cbdata, const char *jid, + const char *node, XmppIqCallback cb) +{ + xmlnode *iq, *query; + char *id = generate_next_id(); + + iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "type", "get"); + xmlnode_set_attrib(iq, "to", jid); + xmlnode_set_attrib(iq, "id", id); + + query = xmlnode_new_child(iq, "query"); + xmlnode_set_namespace(query, NS_DISCO_ITEMS); + if (node) + xmlnode_set_attrib(query, "node", node); + + /* Steals id */ + xmpp_iq_register_callback(pc, id, cbdata, cb); + + purple_signal_emit(purple_connection_get_prpl(pc), "jabber-sending-xmlnode", + pc, &iq); + if (iq != NULL) + xmlnode_free(iq); +} + +static XmppDiscoServiceType +disco_service_type_from_identity(xmlnode *identity) +{ + const char *category, *type; + + if (!identity) + return XMPP_DISCO_SERVICE_TYPE_OTHER; + + category = xmlnode_get_attrib(identity, "category"); + type = xmlnode_get_attrib(identity, "type"); + + if (!category) + return XMPP_DISCO_SERVICE_TYPE_OTHER; + + if (g_str_equal(category, "conference")) + return XMPP_DISCO_SERVICE_TYPE_CHAT; + else if (g_str_equal(category, "directory")) + return XMPP_DISCO_SERVICE_TYPE_DIRECTORY; + else if (g_str_equal(category, "gateway")) + return XMPP_DISCO_SERVICE_TYPE_GATEWAY; + else if (g_str_equal(category, "pubsub")) { + if (!type || g_str_equal(type, "collection")) + return XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION; + else if (g_str_equal(type, "leaf")) + return XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF; + else if (g_str_equal(type, "service")) + return XMPP_DISCO_SERVICE_TYPE_OTHER; + else { + purple_debug_warning("xmppdisco", "Unknown pubsub type '%s'\n", type); + return XMPP_DISCO_SERVICE_TYPE_OTHER; + } + } + + return XMPP_DISCO_SERVICE_TYPE_OTHER; +} + +static const struct { + const char *from; + const char *to; +} disco_type_mappings[] = { + { "gadu-gadu", "gadu-gadu" }, /* the prpl is prpl-gg, but list_icon returns "gadu-gadu" */ + { "sametime", "meanwhile" }, + { "myspaceim", "myspace" }, + { "xmpp", "jabber" }, /* prpl-jabber (mentioned in case the prpl is renamed so this line will match) */ + { NULL, NULL } +}; + +static const gchar * +disco_type_from_string(const gchar *str) +{ + int i = 0; + + g_return_val_if_fail(str != NULL, ""); + + for ( ; disco_type_mappings[i].from; ++i) { + if (!strcasecmp(str, disco_type_mappings[i].from)) + return disco_type_mappings[i].to; + } + + /* fallback to the string itself */ + return str; +} + +static void +got_info_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *item_data = data; + PidginDiscoList *list = item_data->list; + xmlnode *query; + + --list->fetch_count; + + if (!list->in_progress) + goto out; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *identity = xmlnode_get_child(query, "identity"); + XmppDiscoService *service; + xmlnode *feature; + + service = g_new0(XmppDiscoService, 1); + service->list = item_data->list; + purple_debug_info("xmppdisco", "parent for %s is %p\n", from, item_data->parent); + service->parent = item_data->parent; + service->flags = XMPP_DISCO_ADD; + service->type = disco_service_type_from_identity(identity); + + if (item_data->node) { + if (item_data->name) { + service->name = item_data->name; + item_data->name = NULL; + } else + service->name = g_strdup(item_data->node); + + service->node = item_data->node; + item_data->node = NULL; + + if (service->type == XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION) + service->flags |= XMPP_DISCO_BROWSE; + } else + service->name = g_strdup(from); + + if (item_data->name) { + service->description = item_data->name; + item_data->name = NULL; + } else if (identity) + service->description = g_strdup(xmlnode_get_attrib(identity, "name")); + + /* TODO: Overlap with service->name a bit */ + service->jid = g_strdup(from); + + for (feature = xmlnode_get_child(query, "feature"); feature; + feature = xmlnode_get_next_twin(feature)) { + const char *var; + if (!(var = xmlnode_get_attrib(feature, "var"))) + continue; + + if (g_str_equal(var, NS_REGISTER)) + service->flags |= XMPP_DISCO_REGISTER; + else if (g_str_equal(var, NS_DISCO_ITEMS)) + service->flags |= XMPP_DISCO_BROWSE; + else if (g_str_equal(var, NS_MUC)) { + service->flags |= XMPP_DISCO_BROWSE; + service->type = XMPP_DISCO_SERVICE_TYPE_CHAT; + } + } + + if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY) + service->gateway_type = g_strdup(disco_type_from_string( + xmlnode_get_attrib(identity, "type"))); + + pidgin_disco_add_service(list, service, service->parent); + } + +out: + if (list->fetch_count == 0) + pidgin_disco_list_set_in_progress(list, FALSE); + + g_free(item_data->name); + g_free(item_data->node); + g_free(item_data); + pidgin_disco_list_unref(list); +} + +static void +got_items_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *item_data = data; + PidginDiscoList *list = item_data->list; + xmlnode *query; + gboolean has_items = FALSE; + + --list->fetch_count; + + if (!list->in_progress) + goto out; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *item; + + for (item = xmlnode_get_child(query, "item"); item; + item = xmlnode_get_next_twin(item)) { + const char *jid = xmlnode_get_attrib(item, "jid"); + const char *name = xmlnode_get_attrib(item, "name"); + const char *node = xmlnode_get_attrib(item, "node"); + + has_items = TRUE; + + if (item_data->parent->type == XMPP_DISCO_SERVICE_TYPE_CHAT) { + /* This is a hacky first-order approximation. Any MUC + * component that has a >1 level hierarchy (a Yahoo MUC + * transport component probably does) will violate this. + * + * On the other hand, this is better than querying all the + * chats at conference.jabber.org to enumerate them. + */ + XmppDiscoService *service = g_new0(XmppDiscoService, 1); + service->list = item_data->list; + service->parent = item_data->parent; + service->flags = XMPP_DISCO_ADD; + service->type = XMPP_DISCO_SERVICE_TYPE_CHAT; + + service->name = g_strdup(name); + service->jid = g_strdup(jid); + service->node = g_strdup(node); + pidgin_disco_add_service(list, service, item_data->parent); + } else { + struct item_data *item_data2 = g_new0(struct item_data, 1); + + item_data2->list = item_data->list; + item_data2->parent = item_data->parent; + item_data2->name = g_strdup(name); + item_data2->node = g_strdup(node); + + ++list->fetch_count; + pidgin_disco_list_ref(list); + xmpp_disco_info_do(pc, item_data2, jid, node, got_info_cb); + } + } + } + + if (!has_items) + pidgin_disco_add_service(list, NULL, item_data->parent); + +out: + if (list->fetch_count == 0) + pidgin_disco_list_set_in_progress(list, FALSE); + + g_free(item_data); + pidgin_disco_list_unref(list); +} + +static void +server_items_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *cb_data = data; + PidginDiscoList *list = cb_data->list; + xmlnode *query; + + g_free(cb_data); + --list->fetch_count; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *item; + + for (item = xmlnode_get_child(query, "item"); item; + item = xmlnode_get_next_twin(item)) { + const char *jid = xmlnode_get_attrib(item, "jid"); + const char *name = xmlnode_get_attrib(item, "name"); + const char *node = xmlnode_get_attrib(item, "node"); + struct item_data *item_data; + + if (!jid) + continue; + + item_data = g_new0(struct item_data, 1); + item_data->list = list; + item_data->name = g_strdup(name); + item_data->node = g_strdup(node); + + ++list->fetch_count; + pidgin_disco_list_ref(list); + xmpp_disco_info_do(pc, item_data, jid, node, got_info_cb); + } + } + + if (list->fetch_count == 0) + pidgin_disco_list_set_in_progress(list, FALSE); + + pidgin_disco_list_unref(list); +} + +static void +server_info_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *cb_data = data; + PidginDiscoList *list = cb_data->list; + xmlnode *query; + gboolean items = FALSE; + + --list->fetch_count; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *feature; + + for (feature = xmlnode_get_child(query, "feature"); feature; + feature = xmlnode_get_next_twin(feature)) { + const char *var = xmlnode_get_attrib(feature, "var"); + if (purple_strequal(var, NS_DISCO_ITEMS)) { + items = TRUE; + break; + } + } + } + + if (items) { + xmpp_disco_items_do(pc, cb_data, from, NULL /* node */, server_items_cb); + ++list->fetch_count; + pidgin_disco_list_ref(list); + } else { + purple_notify_error(my_plugin, _("Error"), + _("Server does not support service discovery"), + NULL); + pidgin_disco_list_set_in_progress(list, FALSE); + g_free(cb_data); + } + + pidgin_disco_list_unref(list); +} + +void xmpp_disco_start(PidginDiscoList *list) +{ + struct item_data *cb_data; + + g_return_if_fail(list != NULL); + + ++list->fetch_count; + pidgin_disco_list_ref(list); + + cb_data = g_new0(struct item_data, 1); + cb_data->list = list; + + xmpp_disco_info_do(list->pc, cb_data, list->server, NULL, server_info_cb); +} + +void xmpp_disco_service_expand(XmppDiscoService *service) +{ + struct item_data *item_data; + + g_return_if_fail(service != NULL); + + if (service->expanded) + return; + + item_data = g_new0(struct item_data, 1); + item_data->list = service->list; + item_data->parent = service; + + ++service->list->fetch_count; + pidgin_disco_list_ref(service->list); + + pidgin_disco_list_set_in_progress(service->list, TRUE); + + xmpp_disco_items_do(service->list->pc, item_data, service->jid, service->node, + got_items_cb); + service->expanded = TRUE; +} + +void xmpp_disco_service_register(XmppDiscoService *service) +{ + xmlnode *iq, *query; + char *id = generate_next_id(); + + iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "type", "get"); + xmlnode_set_attrib(iq, "to", service->jid); + xmlnode_set_attrib(iq, "id", id); + + query = xmlnode_new_child(iq, "query"); + xmlnode_set_namespace(query, NS_REGISTER); + + purple_signal_emit(purple_connection_get_prpl(service->list->pc), + "jabber-sending-xmlnode", service->list->pc, &iq); + if (iq != NULL) + xmlnode_free(iq); + g_free(id); +} + +static void +create_dialog(PurplePluginAction *action) +{ + pidgin_disco_dialog_new(); +} + +static GList * +actions(PurplePlugin *plugin, gpointer context) +{ + GList *l = NULL; + PurplePluginAction *action = NULL; + + action = purple_plugin_action_new(_("XMPP Service Discovery"), + create_dialog); + l = g_list_prepend(l, action); + + return l; +} + +static void +signed_off_cb(PurpleConnection *pc, gpointer unused) +{ + /* Deal with any dialogs */ + pidgin_disco_signed_off_cb(pc); + + /* Remove all the IQ callbacks for this connection */ + g_hash_table_foreach_remove(iq_callbacks, remove_iq_callbacks_by_pc, pc); +} + +static gboolean +plugin_load(PurplePlugin *plugin) +{ + PurplePlugin *xmpp_prpl; + + my_plugin = plugin; + + xmpp_prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID); + if (NULL == xmpp_prpl) + return FALSE; + + purple_signal_connect(purple_connections_get_handle(), "signing-off", + plugin, PURPLE_CALLBACK(signed_off_cb), NULL); + + iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + return TRUE; +} + +static gboolean +plugin_unload(PurplePlugin *plugin) +{ + g_hash_table_destroy(iq_callbacks); + iq_callbacks = NULL; + + purple_signals_disconnect_by_handle(plugin); + pidgin_disco_dialogs_destroy_all(); + + return TRUE; +} + +static PurplePluginInfo info = +{ + PURPLE_PLUGIN_MAGIC, + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_PLUGIN_STANDARD, + PIDGIN_PLUGIN_TYPE, + 0, + NULL, + PURPLE_PRIORITY_DEFAULT, + "gtk-xmppdisco", + N_("XMPP Service Discovery"), + DISPLAY_VERSION, + N_("Allows browsing and registering services."), + N_("This plugin is useful for registering with legacy transports or other " + "XMPP services."), + "Paul Aurich <paul@darkrain42.org>", + PURPLE_WEBSITE, + plugin_load, + plugin_unload, + NULL, /**< destroy */ + NULL, /**< ui_info */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + actions, + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + +static void +init_plugin(PurplePlugin *plugin) +{ +} + +PURPLE_INIT_PLUGIN(xmppdisco, init_plugin, info)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/xmppdisco.h Sat Jun 06 06:21:39 2009 +0000 @@ -0,0 +1,107 @@ +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef PIDGIN_XMPP_DISCO_H +#define PIDGIN_XMPP_DISCO_H + +typedef struct _XmppDiscoService XmppDiscoService; + +#include "gtkdisco.h" + +#define XMPP_PLUGIN_ID "prpl-jabber" +#define NS_DISCO_INFO "http://jabber.org/protocol/disco#info" +#define NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" +#define NS_MUC "http://jabber.org/protocol/muc" +#define NS_REGISTER "jabber:iq:register" + +#include "plugin.h" +extern PurplePlugin *my_plugin; + +/** + * The types of services. + */ +typedef enum +{ + XMPP_DISCO_SERVICE_TYPE_UNSET, + /** + * A registerable gateway to another protocol. An example would be + * XMPP legacy transports. + */ + XMPP_DISCO_SERVICE_TYPE_GATEWAY, + + /** + * A directory (e.g. allows the user to search for other users). + */ + XMPP_DISCO_SERVICE_TYPE_DIRECTORY, + + /** + * A chat (multi-user conversation). + */ + XMPP_DISCO_SERVICE_TYPE_CHAT, + + /** + * A pubsub collection (contains nodes) + */ + XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION, + + /** + * A pubsub leaf (contains stuff, not nodes). + */ + XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF, + + /** + * Something else. Do we need more categories? + */ + XMPP_DISCO_SERVICE_TYPE_OTHER +} XmppDiscoServiceType; + +/** + * The flags of services. + */ +typedef enum +{ + XMPP_DISCO_NONE = 0x0000, + XMPP_DISCO_ADD = 0x0001, /**< Supports an 'add' operation */ + XMPP_DISCO_BROWSE = 0x0002, /**< Supports browsing */ + XMPP_DISCO_REGISTER = 0x0004 /**< Supports a 'register' operation */ +} XmppDiscoServiceFlags; + +struct _XmppDiscoService { + PidginDiscoList *list; + gchar *name; + gchar *description; + + gchar *gateway_type; + XmppDiscoServiceType type; + XmppDiscoServiceFlags flags; + + XmppDiscoService *parent; + gchar *jid; + gchar *node; + gboolean expanded; +}; + +void xmpp_disco_start(PidginDiscoList *list); + +void xmpp_disco_service_expand(XmppDiscoService *service); +void xmpp_disco_service_register(XmppDiscoService *service); + +#endif /* PIDGIN_XMPP_DISCO_H */
--- a/pidgin/plugins/pidginrc.c Sun May 31 18:39:19 2009 +0000 +++ b/pidgin/plugins/pidginrc.c Sat Jun 06 06:21:39 2009 +0000 @@ -28,34 +28,31 @@ static guint pref_callback; static const gchar *color_prefs[] = { - "/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", - "/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color" + "/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::typing-ntofication-color" }; static const gchar *color_prefs_set[] = { - "/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", - "/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color" + "/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::typing-notification-color" }; static const gchar *color_names[] = { - 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"), - N_("Action Message Name Color") + N_("Action Message Name Color"), + N_("Typing Notification Color") }; static GtkWidget *color_widgets[G_N_ELEMENTS(color_prefs)]; @@ -126,6 +123,10 @@ g_string_append(style_string, "style \"purplerc_style\"\n{"); + if(purple_prefs_get_bool("/plugins/gtk/purplerc/set/disable-typing-notification")) { + g_string_append(style_string, "\tGtkIMHtml::typing-notification-enable = 0\n"); + } + for (i = 0; i < G_N_ELEMENTS(color_prefs); i++) { if (purple_prefs_get_bool(color_prefs_set[i])) { const gchar *pref; @@ -349,103 +350,27 @@ } static GtkWidget * -purplerc_get_config_frame(PurplePlugin *plugin) +purplerc_make_interface_vbox(void) { - /* Note: Intentionally not using the size group argument to the - * pidgin_prefs_labeled_* functions they only add the text label to - * the size group not the whole thing, which isn't what I want. */ + GtkWidget *vbox = NULL, *hbox = NULL, *check = NULL; + GtkSizeGroup *labelsg = NULL; gint i; - gchar *tmp; - GtkWidget *check = NULL, *widget = NULL; - GtkWidget *ret = NULL, *hbox = NULL, *frame = NULL; - GtkSizeGroup *labelsg = NULL, *widgetsg = NULL, *buttonsg = NULL; -#ifndef _WIN32 - const gchar *homepath = "$HOME"; -#else - const gchar *homepath = "\%APPDATA\%"; -#endif - - ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER); - - labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - widgetsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - buttonsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - - frame = pidgin_make_frame(ret, _("General")); - /* interface font */ - hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0); - - check = pidgin_prefs_checkbox(_("GTK+ Interface Font"), - "/plugins/gtk/purplerc/set/gtk-font-name", - hbox); - gtk_size_group_add_widget(labelsg, check); - - widget = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_FONT, - PIDGIN_BUTTON_HORIZONTAL); - gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0); - gtk_size_group_add_widget(widgetsg, widget); - gtk_widget_set_sensitive(widget, - purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-font-name")); - g_signal_connect(G_OBJECT(check), "toggled", - G_CALLBACK(pidgin_toggle_sensitive), widget); - g_signal_connect(G_OBJECT(widget), "clicked", - G_CALLBACK(purplerc_set_font), GINT_TO_POINTER(-1)); - /* key theme name */ - hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0); - - check = pidgin_prefs_checkbox(_("GTK+ Text Shortcut Theme"), - "/plugins/gtk/purplerc/set/gtk-key-theme-name", - hbox); - gtk_size_group_add_widget(labelsg, check); - - widget = pidgin_prefs_labeled_entry(hbox, "", - "/plugins/gtk/purplerc/gtk-key-theme-name", - NULL); - /* - gtk_size_group_add_widget(widgetsg, widget); - */ - gtk_widget_set_sensitive(widget, - purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-key-theme-name")); - g_signal_connect(G_OBJECT(check), "toggled", - G_CALLBACK(pidgin_toggle_sensitive), widget); + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - /* - for (i = 0; i < G_N_ELEMENTS(widget_bool_prefs); i++) { - hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0); - - check = pidgin_prefs_checkbox(_(widget_bool_names[i]), - widget_bool_prefs_set[i], hbox); - gtk_size_group_add_widget(labelsg, check); + gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BORDER); - widget_bool_widgets[i] = pidgin_prefs_checkbox("", widget_bool_prefs[i], hbox); - * - gtk_size_group_add_widget(widgetsb, widget_bool_widgets[i]); - * - gtk_widget_set_sensitive(widget_bool_widgets[i], - purple_prefs_get_bool(widget_bool_prefs_set[i])); - g_signal_connect(G_OBJECT(check), "toggled", - G_CALLBACK(pidgin_toggle_sensitive), - widget_bool_widgets[i]); - } - */ - - frame = pidgin_make_frame(ret, _("Interface colors")); - /* imhtml stuff */ for (i = 0; i < G_N_ELEMENTS(color_prefs); i++) { hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); check = pidgin_prefs_checkbox(_(color_names[i]), color_prefs_set[i], hbox); gtk_size_group_add_widget(labelsg, check); - color_widgets[i] = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_COLOR, PIDGIN_BUTTON_HORIZONTAL); - gtk_size_group_add_widget(widgetsg, color_widgets[i]); + color_widgets[i] = pidgin_pixbuf_button_from_stock("", + GTK_STOCK_SELECT_COLOR, PIDGIN_BUTTON_HORIZONTAL); gtk_box_pack_start(GTK_BOX(hbox), color_widgets[i], FALSE, FALSE, 0); gtk_widget_set_sensitive(color_widgets[i], @@ -458,39 +383,51 @@ GINT_TO_POINTER(i)); } - frame = pidgin_make_frame(ret, _("Widget Sizes")); - /* widget size stuff */ - for (i = 0; i < G_N_ELEMENTS(widget_size_prefs); i++) { - hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0); + g_object_unref(labelsg); + + return vbox; +} - check = pidgin_prefs_checkbox(_(widget_size_names[i]), - widget_size_prefs_set[i], hbox); - gtk_size_group_add_widget(labelsg, check); +static GtkWidget * +purplerc_make_fonts_vbox(void) +{ + GtkWidget *vbox = NULL, *hbox = NULL, *check = NULL, *widget = NULL; + GtkSizeGroup *labelsg = NULL; + int i; + + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BORDER); - widget_size_widgets[i] = pidgin_prefs_labeled_spin_button(hbox, "", widget_size_prefs[i], 0, 50, NULL); - /* - gtk_size_group_add_widget(widgetsg, widget_size_widgets[i]); - */ - gtk_widget_set_sensitive(widget_size_widgets[i], - purple_prefs_get_bool(widget_size_prefs_set[i])); - g_signal_connect(G_OBJECT(check), "toggled", - G_CALLBACK(pidgin_toggle_sensitive), - widget_size_widgets[i]); - } + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + check = pidgin_prefs_checkbox(_("GTK+ Interface Font"), + "/plugins/gtk/purplerc/set/gtk-font-name", + hbox); + gtk_size_group_add_widget(labelsg, check); - frame = pidgin_make_frame(ret, _("Fonts")); - /* imhtml font stuff */ + widget = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_FONT, + PIDGIN_BUTTON_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0); + gtk_widget_set_sensitive(widget, + purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-font-name")); + g_signal_connect(G_OBJECT(check), "toggled", + G_CALLBACK(pidgin_toggle_sensitive), widget); + g_signal_connect(G_OBJECT(widget), "clicked", + G_CALLBACK(purplerc_set_font), GINT_TO_POINTER(-1)); + for (i = 0; i < G_N_ELEMENTS(font_prefs); i++) { hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); - gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); check = pidgin_prefs_checkbox(_(font_names[i]), font_prefs_set[i], hbox); gtk_size_group_add_widget(labelsg, check); - font_widgets[i] = pidgin_pixbuf_button_from_stock("", GTK_STOCK_SELECT_FONT, PIDGIN_BUTTON_HORIZONTAL); - gtk_size_group_add_widget(widgetsg, font_widgets[i]); + font_widgets[i] = pidgin_pixbuf_button_from_stock("", + GTK_STOCK_SELECT_FONT, PIDGIN_BUTTON_HORIZONTAL); gtk_box_pack_start(GTK_BOX(hbox), font_widgets[i], FALSE, FALSE, 0); gtk_widget_set_sensitive(font_widgets[i], @@ -503,6 +440,127 @@ GINT_TO_POINTER(i)); } + g_object_unref(labelsg); + + return vbox; +} + +static GtkWidget * +purplerc_make_misc_vbox(void) +{ + /* Note: Intentionally not using the size group argument to the + * pidgin_prefs_labeled_* functions they only add the text label to + * the size group not the whole thing, which isn't what I want. */ + GtkWidget *vbox = NULL, *hbox = NULL, *check = NULL, *widget = NULL; + GtkSizeGroup *labelsg = NULL; + int i; + + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + labelsg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BORDER); + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + check = pidgin_prefs_checkbox(_("GTK+ Text Shortcut Theme"), + "/plugins/gtk/purplerc/set/gtk-key-theme-name", + hbox); + gtk_size_group_add_widget(labelsg, check); + + widget = pidgin_prefs_labeled_entry(hbox, "", + "/plugins/gtk/purplerc/gtk-key-theme-name", + NULL); + gtk_widget_set_sensitive(widget, + purple_prefs_get_bool("/plugins/gtk/purplerc/set/gtk-key-theme-name")); + g_signal_connect(G_OBJECT(check), "toggled", + G_CALLBACK(pidgin_toggle_sensitive), widget); + + for (i = 0; i < G_N_ELEMENTS(widget_size_prefs); i++) { + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + check = pidgin_prefs_checkbox(_(widget_size_names[i]), + widget_size_prefs_set[i], hbox); + gtk_size_group_add_widget(labelsg, check); + + widget_size_widgets[i] = pidgin_prefs_labeled_spin_button(hbox, "", widget_size_prefs[i], 0, 50, NULL); + gtk_widget_set_sensitive(widget_size_widgets[i], + purple_prefs_get_bool(widget_size_prefs_set[i])); + g_signal_connect(G_OBJECT(check), "toggled", + G_CALLBACK(pidgin_toggle_sensitive), + widget_size_widgets[i]); + } + + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + check = pidgin_prefs_checkbox(_("Disable Typing Notification Text"), + "/plugins/gtk/purplerc/set/disable-typing-notification", hbox); + + /* Widget boolean stuff */ + /* + for (i = 0; i < G_N_ELEMENTS(widget_bool_prefs); i++) { + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + check = pidgin_prefs_checkbox(_(widget_bool_names[i]), + widget_bool_prefs_set[i], hbox); + gtk_size_group_add_widget(labelsg, check); + + widget_bool_widgets[i] = pidgin_prefs_checkbox("", widget_bool_prefs[i], hbox); + + gtk_widget_set_sensitive(widget_bool_widgets[i], + purple_prefs_get_bool(widget_bool_prefs_set[i])); + g_signal_connect(G_OBJECT(check), "toggled", + G_CALLBACK(pidgin_toggle_sensitive), + widget_bool_widgets[i]); + } + */ + + g_object_unref(labelsg); + + return vbox; +} + +static GtkWidget * +purplerc_get_config_frame(PurplePlugin *plugin) +{ + gchar *tmp; + GtkWidget *check = NULL, *label = NULL; + GtkWidget *ret = NULL, *hbox = NULL, *frame = NULL, *note = NULL; +#ifndef _WIN32 + const gchar *homepath = "$HOME"; +#else + const gchar *homepath = "\%APPDATA\%"; +#endif + + ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + note = gtk_notebook_new(); + label = gtk_label_new(NULL); + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); + + gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER); + + tmp = g_strdup_printf("<span weight=\"bold\">%s</span>", _("GTK+ Theme Control Settings")); + gtk_label_set_markup(GTK_LABEL(label), tmp); + g_free(tmp); + + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ret), hbox, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ret), note, FALSE, FALSE, 0); + + label = gtk_label_new(_("Colors")); + gtk_notebook_insert_page(GTK_NOTEBOOK(note), purplerc_make_interface_vbox(), label, -1); + + label = gtk_label_new(_("Fonts")); + gtk_notebook_insert_page(GTK_NOTEBOOK(note), purplerc_make_fonts_vbox(), label, -1); + + label = gtk_label_new(_("Miscellaneous")); + gtk_notebook_insert_page(GTK_NOTEBOOK(note), purplerc_make_misc_vbox(), label, -1); + + gtk_box_pack_start(GTK_BOX(ret), gtk_hseparator_new(), TRUE, TRUE, 0); + frame = pidgin_make_frame(ret, _("Gtkrc File Tools")); hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); @@ -512,22 +570,17 @@ homepath, G_DIR_SEPARATOR_S ".purple" G_DIR_SEPARATOR_S); check = gtk_button_new_with_label(tmp); g_free(tmp); - gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0); - gtk_size_group_add_widget(buttonsg, check); + gtk_box_pack_start(GTK_BOX(hbox), check, TRUE, TRUE, 0); g_signal_connect(G_OBJECT(check), "clicked", G_CALLBACK(purplerc_write), NULL); check = gtk_button_new_with_label(_("Re-read gtkrc files")); - gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0); - gtk_size_group_add_widget(buttonsg, check); + gtk_box_pack_start(GTK_BOX(hbox), check, TRUE, TRUE, 0); g_signal_connect(G_OBJECT(check), "clicked", G_CALLBACK(purplerc_reread), NULL); gtk_widget_show_all(ret); - g_object_unref(labelsg); - g_object_unref(widgetsg); - g_object_unref(buttonsg); return ret; } @@ -621,6 +674,15 @@ purple_prefs_add_bool(widget_bool_prefs_set[i], FALSE); } */ + + purple_prefs_add_bool("/plugins/gtk/purplerc/disable-typing-notification", FALSE); + purple_prefs_add_bool("/plugins/gtk/purplerc/set/disable-typing-notification", FALSE); + + /* remove old cursor color prefs */ + purple_prefs_remove("/plugins/gtk/purplerc/color/GtkWidget::cursor-color"); + purple_prefs_remove("/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color"); + purple_prefs_remove("/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color"); + purple_prefs_remove("/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color"); } PURPLE_INIT_PLUGIN(purplerc, purplerc_init, purplerc_info)
--- a/po/de.po Sun May 31 18:39:19 2009 +0000 +++ b/po/de.po Sat Jun 06 06:21:39 2009 +0000 @@ -11,9 +11,9 @@ msgstr "" "Project-Id-Version: de\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-05-15 19:56+0200\n" -"PO-Revision-Date: 2009-05-15 19:56+0200\n" -"Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n" +"POT-Creation-Date: 2009-06-01 12:31+0200\n" +"PO-Revision-Date: 2009-06-01 12:31+0200\n" +"Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n" "Language-Team: Deutsch <de@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1788,6 +1788,8 @@ msgid "+++ %s signed off" msgstr "+++ %s hat sich abgemeldet" +#. Unknown error +#. Unknown error! msgid "Unknown error" msgstr "Unbekannter Fehler" @@ -3257,8 +3259,8 @@ msgid "_Password:" msgstr "_Passwort:" -msgid "IRC nicks may not contain whitespace" -msgstr "IRC-Nicknamen dürfen keine Leerzeichen enthalten" +msgid "IRC nick and server may not contain whitespace" +msgstr "IRC- Server und -Nicknamen dürfen keine Leerzeichen enthalten" #. 1. connect to server #. connect to the server @@ -3741,6 +3743,10 @@ msgid "Server does not use any supported authentication method" msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden" +msgid "You require encryption, but it is not available on this server." +msgstr "" +"Sie fordern Verschlüsselung, aber diese ist auf dem Server nicht verfügbar." + msgid "Invalid challenge from server" msgstr "Ungültige Challenge vom Server" @@ -3748,15 +3754,13 @@ msgstr "SASL-Fehler" msgid "The BOSH connection manager terminated your session." -msgstr "" - -#, fuzzy +msgstr "Der BOSH-Verbindungsmanager hat Ihre Sitzung beendet." + msgid "No session ID given" -msgstr "Kein Grund angegeben" - -#, fuzzy +msgstr "Keine Sitzungs-ID angegeben" + msgid "Unsupported version of BOSH protocol" -msgstr "Nicht-unterstützte Version" +msgstr "Nicht-unterstützte Version des BOSH-Protokolls" msgid "Unable to establish a connection with the server" msgstr "Die Verbindung mit dem Server konnte nicht hergestellt werden" @@ -4006,7 +4010,7 @@ msgid "Resource" msgstr "Ressource" -#, fuzzy, c-format +#, c-format msgid "%s ago" msgstr "vor %s" @@ -4194,10 +4198,6 @@ msgid "Roles:" msgstr "Funktion" -msgid "You require encryption, but it is not available on this server." -msgstr "" -"Sie fordern Verschlüsselung, aber diese ist auf dem Server nicht verfügbar." - msgid "Ping timeout" msgstr "Ping-Zeitüberschreitung" @@ -5424,17 +5424,6 @@ msgstr "Windows Live ID-Authentifikation:Ungültige Antwort" #, c-format -msgid "%s is not a valid group." -msgstr "%s ist keine gültige Gruppe." - -msgid "Unknown error." -msgstr "Unbekannter Fehler." - -#, c-format -msgid "%s on %s (%s)" -msgstr "%s auf %s (%s)" - -#, c-format msgid "%s just sent you a Nudge!" msgstr "%s hat Sie gerade angestoßen!" @@ -5475,15 +5464,12 @@ msgid "Service Temporarily Unavailable." msgstr "Dienst momentan nicht verfügbar." +msgid "Unknown error." +msgstr "Unbekannter Fehler." + msgid "Mobile message was not sent because it was too long." msgstr "Mobile Nachricht wurde nicht gesendet, da sie zu lang war." -msgid "Unable to rename group" -msgstr "Kann die Gruppe nicht umbenennen" - -msgid "Unable to delete group" -msgstr "Kann die Gruppe nicht löschen" - #, c-format msgid "" "The MSN server will shut down for maintenance in %d minute. You will " @@ -5647,14 +5633,6 @@ "Nachricht konnte nicht gesendet werden, da ein unbekannter Fehler " "aufgetreten ist:" -#, c-format -msgid "%s has added you to his or her buddy list." -msgstr "Der Benutzer %s hat Sie zu seiner Buddy-Liste hinzugefügt." - -#, c-format -msgid "%s has removed you from his or her buddy list." -msgstr "Der Benutzer %s hat Sie von seiner Buddy-Liste gelöscht." - msgid "Delete Buddy from Address Book?" msgstr "Buddy aus dem Adressbuch löschen?" @@ -5684,6 +5662,28 @@ msgstr "MSN-Protokoll-Plugin" #, c-format +msgid "%s is not a valid group." +msgstr "%s ist keine gültige Gruppe." + +#, c-format +msgid "%s on %s (%s)" +msgstr "%s auf %s (%s)" + +msgid "Unable to rename group" +msgstr "Kann die Gruppe nicht umbenennen" + +msgid "Unable to delete group" +msgstr "Kann die Gruppe nicht löschen" + +#, c-format +msgid "%s has added you to his or her buddy list." +msgstr "Der Benutzer %s hat Sie zu seiner Buddy-Liste hinzugefügt." + +#, c-format +msgid "%s has removed you from his or her buddy list." +msgstr "Der Benutzer %s hat Sie von seiner Buddy-Liste gelöscht." + +#, c-format msgid "No such user: %s" msgstr "Kein solcher Benutzer: %s" @@ -6557,6 +6557,9 @@ msgid "iChat AV" msgstr "iChat AV" +msgid "Live Video" +msgstr "Live-Video" + msgid "Camera" msgstr "Kamera" @@ -9556,6 +9559,30 @@ msgid "Add buddy rejected" msgstr "Hinzufügen des Buddys zurückgewiesen" +#. Some error in the received stream +msgid "Received invalid data" +msgstr "Ungültige Daten empfangen" + +#. Password incorrect +msgid "Incorrect Password" +msgstr "Falsches Passwort" + +#. security lock from too many failed login attempts +msgid "Account locked: Too many failed login attempts" +msgstr "Konto gesperrt: Zu viele erfolglose Login-Versuche" + +#. the username does not exist +msgid "Username does not exist" +msgstr "Benutzername existiert nicht" + +#. indicates a lock of some description +msgid "Account locked: See the debug log" +msgstr "Konto gesperrt: Sehen Sie in den Debug-Mitschnitt" + +#. username or password missing +msgid "Username or password missing" +msgstr "Benutzername oder Passwort fehlt" + #, c-format msgid "" "The Yahoo server has requested the use of an unrecognized authentication " @@ -10465,8 +10492,9 @@ msgid "Please update the necessary fields." msgstr "Bitte aktualisieren Sie die erforderlichen Felder." -msgid "Room _List" -msgstr "Ra_umliste" +#, fuzzy +msgid "A_ccount" +msgstr "Konto" msgid "" "Please enter the appropriate information about the chat you would like to " @@ -10475,8 +10503,8 @@ "Bitte geben Sie geeignete Informationen über den Chat ein, den Sie betreten " "wollen.\n" -msgid "_Account:" -msgstr "_Konto:" +msgid "Room _List" +msgstr "Ra_umliste" msgid "_Block" msgstr "_Sperren" @@ -10596,7 +10624,7 @@ msgstr "/Buddys/Anzeigen/_Offline-Buddys" msgid "/Buddies/Show/_Empty Groups" -msgstr "/Buddys/Anzeigen/_leere Gruppen" +msgstr "/Buddys/Anzeigen/_Leere Gruppen" msgid "/Buddies/Show/Buddy _Details" msgstr "/Buddys/Anzeigen/Buddy-_Details" @@ -10837,7 +10865,7 @@ msgstr "/Buddys/Anzeigen/Offline-Buddys" msgid "/Buddies/Show/Empty Groups" -msgstr "/Buddys/Anzeigen/leere Gruppen" +msgstr "/Buddys/Anzeigen/Leere Gruppen" msgid "/Buddies/Show/Buddy Details" msgstr "/Buddys/Anzeigen/Buddy-Details" @@ -11410,6 +11438,9 @@ msgid "Hungarian" msgstr "Ungarisch" +msgid "Armenian" +msgstr "Armenisch" + msgid "Indonesian" msgstr "Indonesisch" @@ -11506,6 +11537,9 @@ msgid "Swedish" msgstr "Schwedisch" +msgid "Swahili" +msgstr "Swahili" + msgid "Tamil" msgstr "Tamilisch" @@ -12365,6 +12399,9 @@ msgid "Pounce on Whom" msgstr "Bei wem alarmieren" +msgid "_Account:" +msgstr "_Konto:" + msgid "_Buddy name:" msgstr "_Buddy-Name:" @@ -13025,6 +13062,9 @@ msgid "Custom Smiley Manager" msgstr "Verwaltung für benutzerdefinierte Smileys" +msgid "Select Buddy Icon" +msgstr "Buddy-Icon auswählen" + msgid "Click to change your buddyicon for this account." msgstr "Klicken Sie hier, um Ihr Buddy-Icon für dieses Konto zu ändern." @@ -14183,19 +14223,3 @@ msgid "This plugin is useful for debbuging XMPP servers or clients." msgstr "" "Dieses Plugin ist nützlich zur Fehlersuche in XMPP-Servern oder -Clients." - -#~ msgid "Live Video" -#~ msgstr "Live-Video" - -#~ msgid "Invite message" -#~ msgstr "Einladungsnachricht" - -#~ msgid "" -#~ "Please enter the name of the user you wish to invite,\n" -#~ "along with an optional invite message." -#~ msgstr "" -#~ "Bitte geben Sie den Benutzernamen der Person ein, die Sie einladen " -#~ "möchten zusammen mit einer optionalen Einladungsnachricht." - -#~ msgid "ST_UN server:" -#~ msgstr "ST_UN Server:"