# HG changeset patch # User Kevin Stange # Date 1188155309 0 # Node ID a650c8294bd218a1e92e0475af9ca0d3b4dd0883 # Parent 90a41215bcb112fdfd89e55a59ce1a36cdd3df13# Parent 52a67d7b82ac6c1ee62e487c659a1334726e0c4e merge of '0bf01031d132e0c8d3ab091041ec704fcfc2b152' and '5b0dfe36f723f3de64d94f962693b9df13b36a40' diff -r 90a41215bcb1 -r a650c8294bd2 ChangeLog --- a/ChangeLog Sun Aug 26 19:07:22 2007 +0000 +++ b/ChangeLog Sun Aug 26 19:08:29 2007 +0000 @@ -1,5 +1,12 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.1.2: + * New protocol plugin: MySpaceIM (Jeff Connelly, Google Summer of + Code) + + Finch: + * Per-conversation mute and logging options (accessible from the menu) + version 2.1.1 (08/20/2007): Yahoo: * Added an account action to open your inbox in the yahoo prpl. diff -r 90a41215bcb1 -r a650c8294bd2 ChangeLog.API --- a/ChangeLog.API Sun Aug 26 19:07:22 2007 +0000 +++ b/ChangeLog.API Sun Aug 26 19:08:29 2007 +0000 @@ -6,6 +6,18 @@ * pidgin_set_accessible_relations, sets up label-for and labelled-by ATK relations (broken out from pidgin_set_accessible_label) + Finch: + Added: + * finch_sound_is_enabled + * The reserved field in the FinchConv is now used to store information + about the conversation (using FinchConversationFlag) + + libgnt: + * gnt_slider_set_small_step, gnt_slider_set_large_step to allow more + fine tuned updates of a GntSlider + * gnt_util_parse_xhtml_to_textview to parse XHTML strings in a + GntTextView (this works only if libxml2 is available) + Version 2.1.1 (08/20/2007): libpurple: Changed: diff -r 90a41215bcb1 -r a650c8294bd2 finch/gntaccount.c --- a/finch/gntaccount.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/gntaccount.c Sun Aug 26 19:08:29 2007 +0000 @@ -222,6 +222,11 @@ /* XXX: Proxy options */ + if (accounts.window && accounts.tree) { + gnt_tree_set_selected(GNT_TREE(accounts.tree), account); + gnt_box_give_focus_to_child(GNT_BOX(accounts.window), accounts.tree); + } + gnt_widget_destroy(dialog->window); } diff -r 90a41215bcb1 -r a650c8294bd2 finch/gntconv.c --- a/finch/gntconv.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/gntconv.c Sun Aug 26 19:08:29 2007 +0000 @@ -37,6 +37,7 @@ #include "gntdebug.h" #include "gntplugin.h" #include "gntprefs.h" +#include "gntsound.h" #include "gntstatus.h" #include "gntpounce.h" @@ -347,6 +348,53 @@ } static void +toggle_logging_cb(GntMenuItem *item, gpointer ggconv) +{ + FinchConv *fc = ggconv; + PurpleConversation *conv = fc->active_conv; + gboolean logging = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item)); + GList *iter; + + if (logging == purple_conversation_is_logging(conv)) + return; + + /* Xerox */ + if (logging) { + /* Enable logging first so the message below can be logged. */ + purple_conversation_set_logging(conv, TRUE); + + purple_conversation_write(conv, NULL, + _("Logging started. Future messages in this conversation will be logged."), + conv->logs ? (PURPLE_MESSAGE_SYSTEM) : + (PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG), + time(NULL)); + } else { + purple_conversation_write(conv, NULL, + _("Logging stopped. Future messages in this conversation will not be logged."), + conv->logs ? (PURPLE_MESSAGE_SYSTEM) : + (PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG), + time(NULL)); + + /* Disable the logging second, so that the above message can be logged. */ + purple_conversation_set_logging(conv, FALSE); + } + + /* Each conversation with the same person will have the same logging setting */ + for (iter = fc->list; iter; iter = iter->next) { + if (iter->data == conv) + continue; + purple_conversation_set_logging(iter->data, logging); + } +} + +static void +toggle_sound_cb(GntMenuItem *item, gpointer ggconv) +{ + FinchConv *fc = ggconv; + fc->flags ^= FINCH_CONV_NO_SOUND; +} + +static void send_to_cb(GntMenuItem *m, gpointer n) { PurpleAccount *account = g_object_get_data(G_OBJECT(m), "purple_account"); @@ -452,6 +500,18 @@ generate_send_to_menu(ggc); } + + item = gnt_menuitem_check_new(_("Enable Logging")); + gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), + purple_conversation_is_logging(ggc->active_conv)); + gnt_menu_add_item(GNT_MENU(sub), item); + gnt_menuitem_set_callback(item, toggle_logging_cb, ggc); + + item = gnt_menuitem_check_new(_("Enable Sounds")); + gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item), + !(ggc->flags & FINCH_CONV_NO_SOUND)); + gnt_menu_add_item(GNT_MENU(sub), item); + gnt_menuitem_set_callback(item, toggle_sound_cb, ggc); } static void @@ -497,6 +557,12 @@ else ggc = g_new0(FinchConv, 1); + /* Each conversation with the same person will have the same logging setting */ + if (ggc->list) { + purple_conversation_set_logging(conv, + purple_conversation_is_logging(ggc->list->data)); + } + ggc->list = g_list_prepend(ggc->list, conv); ggc->active_conv = conv; conv->ui_data = ggc; @@ -586,6 +652,9 @@ g_signal_connect(G_OBJECT(ggc->entry), "text_changed", G_CALLBACK(send_typing_notification), ggc); } + if (!finch_sound_is_enabled()) + ggc->flags |= FINCH_CONV_NO_SOUND; + gg_create_menu(ggc); g_free(title); @@ -830,6 +899,23 @@ gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags)); } +static void +finch_conv_present(PurpleConversation *conv) +{ + FinchConv *fc = FINCH_CONV(conv); + if (fc && fc->window) + return gnt_window_present(fc->window); +} + +static gboolean +finch_conv_has_focus(PurpleConversation *conv) +{ + FinchConv *fc = FINCH_CONV(conv); + if (fc && fc->window) + return gnt_widget_has_focus(fc->window); + return FALSE; +} + static PurpleConversationUiOps conv_ui_ops = { finch_create_conversation, @@ -841,8 +927,8 @@ finch_chat_rename_user, finch_chat_remove_users, finch_chat_update_user, - NULL, /* present */ - NULL, /* has_focus */ + finch_conv_present, /* present */ + finch_conv_has_focus, /* has_focus */ NULL, /* custom_smiley_add */ NULL, /* custom_smiley_write */ NULL, /* custom_smiley_close */ diff -r 90a41215bcb1 -r a650c8294bd2 finch/gntconv.h --- a/finch/gntconv.h Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/gntconv.h Sun Aug 26 19:08:29 2007 +0000 @@ -43,6 +43,11 @@ typedef struct _FinchConvChat FinchConvChat; typedef struct _FinchConvIm FinchConvIm; +typedef enum +{ + FINCH_CONV_NO_SOUND = 1 << 0, +} FinchConversationFlag; + struct _FinchConv { GList *list; @@ -53,7 +58,7 @@ GntWidget *tv; /* text-view */ GntWidget *menu; GntWidget *info; - void *pad; + FinchConversationFlag flags; union { diff -r 90a41215bcb1 -r a650c8294bd2 finch/gntsound.c --- a/finch/gntsound.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/gntsound.c Sun Aug 26 19:08:29 2007 +0000 @@ -40,6 +40,8 @@ #include "sound.h" #include "util.h" +#include "gntconv.h" + #include "gntbox.h" #include "gntwindow.h" #include "gntcombobox.h" @@ -173,7 +175,8 @@ has_focus = purple_conversation_has_focus(conv); - if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus"))) + if ((gntconv->flags & FINCH_CONV_NO_SOUND) || + (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))) { return; } @@ -409,14 +412,14 @@ GError *err = NULL; switch (GST_MESSAGE_TYPE (msg)) { - case GST_MESSAGE_EOS: - gst_element_set_state(play, GST_STATE_NULL); - gst_object_unref(GST_OBJECT(play)); - break; case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &err, NULL); purple_debug_error("gstreamer", "%s\n", err->message); g_error_free(err); + /* fall-through and clean up */ + case GST_MESSAGE_EOS: + gst_element_set_state(play, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(play)); break; case GST_MESSAGE_WARNING: gst_message_parse_warning(msg, &err, NULL); @@ -670,28 +673,34 @@ { PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events))); FinchSoundEvent * event = &sounds[id]; - char *enabled, *file, *tmpfile; + char *enabled, *file, *tmpfile, *volpref; gboolean temp_value; + int volume; enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", finch_sound_get_active_profile(), event->pref); file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), event->pref); + volpref = g_strdup(make_pref("/volume")); temp_value = purple_prefs_get_bool(enabled); tmpfile = g_strdup(purple_prefs_get_string(file)); + volume = purple_prefs_get_int(volpref); purple_prefs_set_string(file, event->file); if (!temp_value) purple_prefs_set_bool(enabled, TRUE); + purple_prefs_set_int(volpref, gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume))); purple_sound_play_event(id, NULL); if (!temp_value) purple_prefs_set_bool(enabled, FALSE); purple_prefs_set_string(file, tmpfile); + purple_prefs_set_int(volpref, volume); g_free(enabled); g_free(file); g_free(tmpfile); + g_free(volpref); } static void @@ -990,6 +999,8 @@ pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0); gnt_slider_set_step(GNT_SLIDER(slider), 5); + gnt_slider_set_small_step(GNT_SLIDER(slider), 1); + gnt_slider_set_large_step(GNT_SLIDER(slider), 20); label = gnt_label_new(""); gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label)); gnt_box_set_pad(GNT_BOX(tmpbox), 1); @@ -1053,7 +1064,22 @@ load_pref_window(finch_sound_get_active_profile()); gnt_widget_show(win); -} +} + +gboolean finch_sound_is_enabled(void) +{ + const char *pref = make_pref("/method"); + const char *method = purple_prefs_get_string(pref); + + if (!method) + return FALSE; + if (strcmp(method, "nosound") == 0) + return FALSE; + if (purple_prefs_get_int(make_pref("/volume")) <= 0) + return FALSE; + + return TRUE; +} static PurpleSoundUiOps sound_ui_ops = { diff -r 90a41215bcb1 -r a650c8294bd2 finch/gntsound.h --- a/finch/gntsound.h Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/gntsound.h Sun Aug 26 19:08:29 2007 +0000 @@ -55,6 +55,14 @@ GList *finch_sound_get_profiles(void); /** + * Determine whether any sound will be played or not. + * + * @return Returns FALSE if preference is set to 'No sound', or if volume is + * set to zero. + */ +gboolean finch_sound_is_enabled(void); + +/** * Gets GNT sound UI ops. * * @return The UI operations structure. diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntcolors.c --- a/finch/libgnt/gntcolors.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntcolors.c Sun Aug 26 19:08:29 2007 +0000 @@ -133,6 +133,7 @@ restore_colors(); } +#if GLIB_CHECK_VERSION(2,6,0) static int get_color(char *key) { @@ -164,7 +165,6 @@ return color; } -#if GLIB_CHECK_VERSION(2,6,0) void gnt_colors_parse(GKeyFile *kfile) { GError *error = NULL; diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntfilesel.c --- a/finch/libgnt/gntfilesel.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntfilesel.c Sun Aug 26 19:08:29 2007 +0000 @@ -200,7 +200,7 @@ const char *tmp; tmp = sel->suggest ? sel->suggest : (const char*)gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files)); - old = g_strdup_printf("%s%s%s", sel->current, sel->current[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : ""); + old = g_strdup_printf("%s%s%s", SAFE(sel->current), SAFE(sel->current)[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : ""); gnt_entry_set_text(GNT_ENTRY(sel->location), old); g_free(old); } diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntslider.c --- a/finch/libgnt/gntslider.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntslider.c Sun Aug 26 19:08:29 2007 +0000 @@ -125,22 +125,66 @@ step_back(GntBindable *bindable, GList *null) { GntSlider *slider = GNT_SLIDER(bindable); - if (slider->current <= slider->min) - return FALSE; gnt_slider_advance_step(slider, -1); return TRUE; } static gboolean +small_step_back(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current - slider->smallstep); + return TRUE; +} + +static gboolean +large_step_back(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current - slider->largestep); + return TRUE; +} + +static gboolean step_forward(GntBindable *bindable, GList *list) { GntSlider *slider = GNT_SLIDER(bindable); - if (slider->current >= slider->max) - return FALSE; gnt_slider_advance_step(slider, 1); return TRUE; } +static gboolean +small_step_forward(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current + slider->smallstep); + return TRUE; +} + +static gboolean +large_step_forward(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->current + slider->largestep); + return TRUE; +} + +static gboolean +move_min_value(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->min); + return TRUE; +} + +static gboolean +move_max_value(GntBindable *bindable, GList *null) +{ + GntSlider *slider = GNT_SLIDER(bindable); + gnt_slider_set_value(slider, slider->max); + return TRUE; +} + static void gnt_slider_class_init(GntSliderClass *klass) { @@ -165,8 +209,14 @@ gnt_bindable_register_binding(bindable, "step-backward", GNT_KEY_DOWN, NULL); gnt_bindable_class_register_action(bindable, "step-forward", step_forward, GNT_KEY_RIGHT, NULL); gnt_bindable_register_binding(bindable, "step-forward", GNT_KEY_UP, NULL); - - /* XXX: how would home/end work? */ + gnt_bindable_class_register_action(bindable, "small-step-backward", small_step_back, GNT_KEY_CTRL_LEFT, NULL); + gnt_bindable_register_binding(bindable, "small-step-backward", GNT_KEY_CTRL_DOWN, NULL); + gnt_bindable_class_register_action(bindable, "small-step-forward", small_step_forward, GNT_KEY_CTRL_RIGHT, NULL); + gnt_bindable_register_binding(bindable, "small-step-forward", GNT_KEY_CTRL_UP, NULL); + gnt_bindable_class_register_action(bindable, "large-step-backward", large_step_back, GNT_KEY_PGDOWN, NULL); + gnt_bindable_class_register_action(bindable, "large-step-forward", large_step_forward, GNT_KEY_PGUP, NULL); + gnt_bindable_class_register_action(bindable, "min-value", move_min_value, GNT_KEY_HOME, NULL); + gnt_bindable_class_register_action(bindable, "max-value", move_max_value, GNT_KEY_END, NULL); gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass)); } @@ -233,10 +283,14 @@ void gnt_slider_set_value(GntSlider *slider, int value) { + int old; if (slider->current == value) return; + old = slider->current; slider->current = value; sanitize_value(slider); + if (old == slider->current) + return; redraw_slider(slider); slider_value_changed(slider); } @@ -248,10 +302,7 @@ int gnt_slider_advance_step(GntSlider *slider, int steps) { - slider->current += steps * slider->step; - sanitize_value(slider); - redraw_slider(slider); - slider_value_changed(slider); + gnt_slider_set_value(slider, slider->current + steps * slider->step); return slider->current; } @@ -260,6 +311,16 @@ slider->step = step; } +void gnt_slider_set_small_step(GntSlider *slider, int step) +{ + slider->smallstep = step; +} + +void gnt_slider_set_large_step(GntSlider *slider, int step) +{ + slider->largestep = step; +} + void gnt_slider_set_range(GntSlider *slider, int max, int min) { slider->max = MAX(max, min); diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntslider.h --- a/finch/libgnt/gntslider.h Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntslider.h Sun Aug 26 19:08:29 2007 +0000 @@ -56,6 +56,8 @@ int min; /* minimum value */ int step; /* amount to change at each step */ int current; /* current value */ + int smallstep; + int largestep; }; struct _GntSliderClass @@ -103,11 +105,27 @@ * Sets the amount of change at each step. * * @param slider The slider - * @param step The amount for each ste + * @param step The amount for each step */ void gnt_slider_set_step(GntSlider *slider, int step); /** + * Sets the amount of change a small step. + * + * @param slider The slider + * @param step The amount for a small step (for the slider) + */ +void gnt_slider_set_small_step(GntSlider *slider, int step); + +/** + * Sets the amount of change a large step. + * + * @param slider The slider + * @param step The amount for a large step (for the slider) + */ +void gnt_slider_set_large_step(GntSlider *slider, int step); + +/** * Advance the slider forward or backward. * * @param slider The slider diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntstyle.c --- a/finch/libgnt/gntstyle.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntstyle.c Sun Aug 26 19:08:29 2007 +0000 @@ -55,6 +55,8 @@ if (!group) group = "general"; return g_key_file_get_value(gkfile, group, key, NULL); +#else + return NULL; #endif } @@ -93,6 +95,7 @@ return def; } +#if GLIB_CHECK_VERSION(2,6,0) static void refine(char *text) { @@ -133,6 +136,7 @@ { return (char *)gnt_key_translate(key); } +#endif void gnt_style_read_workspaces(GntWM *wm) { diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntutils.c --- a/finch/libgnt/gntutils.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntutils.c Sun Aug 26 19:08:29 2007 +0000 @@ -385,6 +385,7 @@ xmlNode *ch; gboolean processed = FALSE; char *url = NULL; + gboolean insert_nl_s = FALSE, insert_nl_e = FALSE; if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE) return; @@ -398,19 +399,34 @@ } else if (g_ascii_strcasecmp(name, "u") == 0) { flag |= GNT_TEXT_FLAG_UNDERLINE; } else if (g_ascii_strcasecmp(name, "br") == 0) { - gnt_text_view_append_text_with_flags(tv, "\n", flag); + insert_nl_e = TRUE; } else if (g_ascii_strcasecmp(name, "a") == 0) { flag |= GNT_TEXT_FLAG_UNDERLINE; url = (char *)xmlGetProp(node, (xmlChar*)"href"); + } else if (g_ascii_strcasecmp(name, "h1") == 0 || + g_ascii_strcasecmp(name, "h2") == 0 || + g_ascii_strcasecmp(name, "h3") == 0 || + g_ascii_strcasecmp(name, "h4") == 0 || + g_ascii_strcasecmp(name, "h5") == 0 || + g_ascii_strcasecmp(name, "h6") == 0) { + insert_nl_s = TRUE; + insert_nl_e = TRUE; + } else if (g_ascii_strcasecmp(name, "title") == 0) { + insert_nl_s = TRUE; + insert_nl_e = TRUE; + flag |= GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_UNDERLINE; } else { /* XXX: Process other possible tags */ } + if (insert_nl_s) + gnt_text_view_append_text_with_flags(tv, "\n", flag); + for (ch = node->children; ch; ch = ch->next) { if (ch->type == XML_ELEMENT_NODE) { processed = TRUE; + util_parse_html_to_tv(ch, tv, flag); } - util_parse_html_to_tv(ch, tv, flag); } if (!processed) { @@ -425,6 +441,9 @@ g_free(href); xmlFree(url); } + + if (insert_nl_e) + gnt_text_view_append_text_with_flags(tv, "\n", flag); } #endif diff -r 90a41215bcb1 -r a650c8294bd2 finch/libgnt/gntwm.c --- a/finch/libgnt/gntwm.c Sun Aug 26 19:07:22 2007 +0000 +++ b/finch/libgnt/gntwm.c Sun Aug 26 19:08:29 2007 +0000 @@ -738,7 +738,7 @@ print = ch; #ifndef NO_WIDECHAR if (wch.chars[0] > 255) { - snprintf(unicode, sizeof(unicode), "&#x%x;", wch.chars[0]); + snprintf(unicode, sizeof(unicode), "&#x%x;", (unsigned int)wch.chars[0]); print = unicode; } #endif diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/Makefile.am --- a/libpurple/Makefile.am Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/Makefile.am Sun Aug 26 19:08:29 2007 +0000 @@ -22,9 +22,9 @@ win32/giowin32.c \ win32/win32dep.h -if USE_GCONFTOOL -GCONF_DIR=gconf -endif +# if USE_GCONFTOOL +# GCONF_DIR=gconf +# endif pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = purple.pc diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/blist.c --- a/libpurple/blist.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/blist.c Sun Aug 26 19:08:29 2007 +0000 @@ -1190,6 +1190,12 @@ group = purple_group_new(_("Chats")); purple_blist_add_group(group, purple_blist_get_last_sibling(purplebuddylist->root)); + } else { + /* Add group to blist if isn't already on it. Fixes #2752. */ + if (!purple_find_group(group->name)) { + purple_blist_add_group(group, + purple_blist_get_last_sibling(purplebuddylist->root)); + } } } else { group = (PurpleGroup*)node->parent; @@ -1284,6 +1290,12 @@ g = (PurpleGroup *)((PurpleBlistNode *)c)->parent; } else { if (group) { + /* Add chat to blist if isn't already on it. Fixes #2752. */ + if (!purple_find_group(group->name)) { + purple_blist_add_group(group, + purple_blist_get_last_sibling(purplebuddylist->root)); + } + g = group; } else { g = purple_group_new(_("Buddies")); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/idle.c --- a/libpurple/idle.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/idle.c Sun Aug 26 19:08:29 2007 +0000 @@ -163,8 +163,8 @@ { if (!no_away) { + no_away = TRUE; purple_savedstatus_set_idleaway(FALSE); - no_away = TRUE; } time_until_next_idle_event = 0; return; diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/network.c --- a/libpurple/network.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/network.c Sun Aug 26 19:08:29 2007 +0000 @@ -436,7 +436,7 @@ static gint wpurple_get_connected_network_count(void) { - guint net_cnt = 0; + gint net_cnt = 0; WSAQUERYSET qs; HANDLE h; @@ -521,7 +521,7 @@ HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL; - + if (!(MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) { g_thread_exit(NULL); return NULL; @@ -636,7 +636,7 @@ purple_network_get_handle(void) { static int handle; - + return &handle; } @@ -675,7 +675,7 @@ purple_signal_register(purple_network_get_handle(), "network-configuration-changed", purple_marshal_VOID, NULL, 0); - + purple_pmp_init(); purple_upnp_init(); } diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/Makefile.mingw --- a/libpurple/protocols/Makefile.mingw Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/Makefile.mingw Sun Aug 26 19:08:29 2007 +0000 @@ -8,7 +8,7 @@ PIDGIN_TREE_TOP := ../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak -SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour +SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour myspace .PHONY: all install clean diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Sun Aug 26 19:08:29 2007 +0000 @@ -82,8 +82,8 @@ BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); bconv->socket = -1; bconv->tx_buf = purple_circ_buffer_new(512); - bconv->tx_handler = -1; - bconv->rx_handler = -1; + bconv->tx_handler = 0; + bconv->rx_handler = 0; return bconv; } @@ -234,7 +234,7 @@ if (writelen == 0) { purple_input_remove(bconv->tx_handler); - bconv->tx_handler = -1; + bconv->tx_handler = 0; return; } @@ -272,7 +272,7 @@ BonjourJabberConversation *bconv = bb->conversation; /* If we're not ready to actually send, append it to the buffer */ - if (bconv->tx_handler != -1 + if (bconv->tx_handler != 0 || bconv->connect_data != NULL || !bconv->sent_stream_start || !bconv->recv_stream_start @@ -304,7 +304,7 @@ } if (ret < len) { - if (bconv->tx_handler == -1) + if (bconv->tx_handler == 0) bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE, _send_data_write_cb, pb); purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret); @@ -455,7 +455,7 @@ /* Stream started; process the send buffer if there is one */ purple_input_remove(bconv->tx_handler); - bconv->tx_handler= -1; + bconv->tx_handler= 0; bconv->sent_stream_start = TRUE; bonjour_jabber_stream_started(pb); @@ -779,7 +779,7 @@ /* TODO: We're really supposed to wait for "" before closing the socket */ close(bconv->socket); } - if (bconv->rx_handler != -1) + if (bconv->rx_handler != 0) purple_input_remove(bconv->rx_handler); if (bconv->tx_handler > 0) purple_input_remove(bconv->tx_handler); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/bonjour/mdns_win32.c --- a/libpurple/protocols/bonjour/mdns_win32.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Sun Aug 26 19:08:29 2007 +0000 @@ -99,7 +99,7 @@ /* We've got what we need; stop listening */ purple_input_remove(idata->null_query_handler); - idata->null_query_handler = -1; + idata->null_query_handler = 0; DNSServiceRefDeallocate(idata->null_query); idata->null_query = NULL; } diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/msn/servconn.c --- a/libpurple/protocols/msn/servconn.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/msn/servconn.c Sun Aug 26 19:08:29 2007 +0000 @@ -51,7 +51,7 @@ servconn->num = session->servconns_count++; servconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN); - servconn->tx_handler = -1; + servconn->tx_handler = 0; return servconn; } @@ -303,7 +303,7 @@ if (writelen == 0) { purple_input_remove(servconn->tx_handler); - servconn->tx_handler = -1; + servconn->tx_handler = 0; return; } @@ -328,7 +328,7 @@ if (!servconn->session->http_method) { - if (servconn->tx_handler == -1) { + if (servconn->tx_handler == 0) { switch (servconn->type) { case MSN_SERVCONN_NS: @@ -353,7 +353,7 @@ if (ret < 0 && errno == EAGAIN) ret = 0; if (ret >= 0 && ret < len) { - if (servconn->tx_handler == -1) + if (servconn->tx_handler == 0) servconn->tx_handler = purple_input_add( servconn->fd, PURPLE_INPUT_WRITE, servconn_write_cb, servconn); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/CHANGES --- a/libpurple/protocols/myspace/CHANGES Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/CHANGES Sun Aug 26 19:08:29 2007 +0000 @@ -1,3 +1,28 @@ +2007-08-23 Jeff Connelly - 0.16 +* Add option to add all friends from myspace.com to your buddy list (#2660) +* If a user doesn't have a picture, don't display an icon (instead of + displaying MySpace's "no photo" icon) +* Fix #2725, a common crash related to buddy icon data +* Fix #2752, which led to duplicate groups +* Fix #2720, crash/disconnect when adding a buddy that doesn't exist + (You'll now receive an error when looking up invalid usernames). + +2007-08-22 Jeff Connelly - 0.15 +* Incomplete implementation of adding friends from myspace.com. +* Change msim_msg_get() to start at the given node instead of the beginning. +* Add msim_msg_get_*_from_element() to access data in MsimMessagElement *'s. +* Use MsimMessage dictionaries everywhere in incoming messages, instead of + the old GHashTable method. Dictionary type is now fully implemented. +* Add functions to loop over MsimMessages. +* Link to myspace.com profile in Get Info. +* Conditionally use my proposed attention API if defined. +* Propagate to im.pidgin.pidgin branch for 2.1.2. +* GSoC ended on 2007-08-20. The code in this release hasn't changed since + then. I did, however, bump the version number to 0.15 to distinguish this + release from the previous one. But there were no code changes. I updated + the text files, too. +* Note: msimprpl will continue to be developed as time permits. + 2007-08-12 Jeff Connelly - 0.14 * Full emoticon support (except no difference between nerd and geek emoticons), thanks to a number of new icons from Hylke Bons. diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/Makefile.am --- a/libpurple/protocols/myspace/Makefile.am Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/Makefile.am Sun Aug 26 19:08:29 2007 +0000 @@ -2,7 +2,18 @@ pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) -SOURCES = myspace.c message.c +SOURCES = myspace.c \ + myspace.h \ + persist.h \ + message.c \ + message.h \ + zap.c \ + session.c \ + session.h \ + markup.c \ + markup.h \ + user.c \ + user.h AM_CFLAGS = $(st) diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/Makefile.mingw --- a/libpurple/protocols/myspace/Makefile.mingw Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/Makefile.mingw Sun Aug 26 19:08:29 2007 +0000 @@ -37,7 +37,7 @@ ## ## SOURCES, OBJECTS ## -C_SRC = myspace.c message.c +C_SRC = myspace.c message.c zap.c session.c markup.c user.c OBJECTS = $(C_SRC:%.c=%.o) diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/README --- a/libpurple/protocols/myspace/README Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/README Sun Aug 26 19:08:29 2007 +0000 @@ -1,4 +1,4 @@ -MySpaceIM Protocol Plugin by Jeff Connelly 20070807 +MySpaceIM Protocol Plugin for Libpurple by Jeff Connelly 20070807 Greetings. This package contains a plugin for libpurple (as used in @@ -6,7 +6,7 @@ network and send/receive messages. Functionality is only basic as of yet, and this code should be considered alpha quality. -This code is being developed under Google Summer of Code 2007. +This code was initially developed under Google Summer of Code 2007. For features and TODO, see http://developer.pidgin.im/wiki/MySpaceIM @@ -28,7 +28,5 @@ Enjoy, -Jeff Connelly -California Polytechnic State University at San Luis Obispo -myspaceim@xyzzy.cjb.net -jeff2@soc.pidgin.im +msimprpl@xyzzy.cjb.net diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/markup.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/markup.c Sun Aug 26 19:08:29 2007 +0000 @@ -0,0 +1,689 @@ +/* MySpaceIM Protocol Plugin - markup + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "myspace.h" + +typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); + +/* Internal functions */ + +static guint msim_point_to_purple_size(MsimSession *session, guint point); +static guint msim_purple_size_to_point(MsimSession *session, guint size); +static guint msim_height_to_point(MsimSession *session, guint height); +static guint msim_point_to_height(MsimSession *session, guint point); + +static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end); +static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end); +static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f); +static gchar *msim_convert_smileys_to_markup(gchar *before); +static double msim_round(double round); + + +/* Globals */ + +/* The names in in emoticon_names (for ) map to corresponding + * entries in emoticon_symbols (for the ASCII representation of the emoticon). + * + * Multiple emoticon symbols in Pidgin can map to one name. List the + * canonical form, as inserted by the "Smile!" dialog, first. For example, + * :) comes before :-), because although both are recognized as 'happy', + * the first is inserted by the smiley button (first symbol in theme). + * + * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */ +static struct MSIM_EMOTICON +{ + gchar *name; + gchar *symbol; +} msim_emoticons[] = { + /* Unfortunately, this list duplicates much of the file + * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because + * that file is part of Pidgin, but we're part of libpurple. + */ + { "bigsmile", ":D" }, + { "bigsmile", ":-D" }, + { "devil", "}:)" }, + { "frazzled", ":Z" }, + { "geek", "B)" }, + { "googles", "%)" }, + { "growl", ":E" }, + { "laugh", ":))" }, /* Must be before ':)' */ + { "happy", ":)" }, + { "happy", ":-)" }, + { "happi", ":)" }, + { "heart", ":X" }, + { "mohawk", "-:" }, + { "mad", "X(" }, + { "messed", "X)" }, + { "nerd", "Q)" }, + { "oops", ":G" }, + { "pirate", "P)" }, + { "scared", ":O" }, + { "sidefrown", ":{" }, + { "sinister", ":B" }, + { "smirk", ":," }, + { "straight", ":|" }, + { "tongue", ":P" }, + { "tongue", ":p" }, + { "tongy", ":P" }, + { "upset", "B|" }, + { "wink", ";-)" }, + { "wink", ";)" }, + { "winc", ";)" }, + { "worried", ":[" }, + { "kiss", ":x" }, + { NULL, NULL } +}; + + + +/* Indexes of this array + 1 map HTML font size to scale of normal font size. * + * Based on _point_sizes from libpurple/gtkimhtml.c + * 1 2 3 4 5 6 7 */ +static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; + +#define MAX_FONT_SIZE 7 /* Purple maximum font size */ +#define POINTS_PER_INCH 72 /* How many pt's in an inch */ + +/* Text formatting bits for */ +#define MSIM_TEXT_BOLD 1 +#define MSIM_TEXT_ITALIC 2 +#define MSIM_TEXT_UNDERLINE 4 + +/* Default baseline size of purple's fonts, in points. What is size 3 in points. + * _font_scale specifies scaling factor relative to this point size. Note this + * is only the default; it is configurable in account options. */ +#define MSIM_BASE_FONT_POINT_SIZE 8 + +/* Default display's DPI. 96 is common but it can differ. Also configurable + * in account options. */ +#define MSIM_DEFAULT_DPI 96 + + +/* round is part of C99, but sometimes is unavailable before then. + * Based on http://forums.belution.com/en/cpp/000/050/13.shtml + */ +double msim_round(double value) +{ + if (value < 0) { + return -(floor(-value + 0.5)); + } else { + return floor( value + 0.5); + } +} + + +/** Convert typographical font point size to HTML font size. + * Based on libpurple/gtkimhtml.c */ +static guint +msim_point_to_purple_size(MsimSession *session, guint point) +{ + guint size, this_point, base; + gdouble scale; + + base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); + + for (size = 0; + size < sizeof(_font_scale) / sizeof(_font_scale[0]); + ++size) { + scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; + this_point = (guint)msim_round(scale * base); + + if (this_point >= point) { + purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", + point, size); + return size; + } + } + + /* No HTML font size was this big; return largest possible. */ + return this_point; +} + +/** Convert HTML font size to point size. */ +static guint +msim_purple_size_to_point(MsimSession *session, guint size) +{ + gdouble scale; + guint point; + guint base; + + scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; + + base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); + + point = (guint)msim_round(scale * base); + + purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n", + size, point); + + return point; +} + +/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ +static guint +msim_height_to_point(MsimSession *session, guint height) +{ + guint dpi; + + dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); + + return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height); + + /* See also: libpurple/protocols/bonjour/jabber.c + * _font_size_ichat_to_purple */ +} + +/** Convert point size to msim pixel height font size specification, for outgoing messages. */ +static guint +msim_point_to_height(MsimSession *session, guint point) +{ + guint dpi; + + dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); + + return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); +} + +/** Convert the msim markup (font) tag into HTML. */ +static void +msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *face, *height_str, *decor_str; + GString *gs_end, *gs_begin; + guint decor, height; + + face = xmlnode_get_attrib(root, "f"); + height_str = xmlnode_get_attrib(root, "h"); + decor_str = xmlnode_get_attrib(root, "s"); + + if (height_str) { + height = atol(height_str); + } else { + height = 12; + } + + if (decor_str) { + decor = atol(decor_str); + } else { + decor = 0; + } + + gs_begin = g_string_new(""); + /* TODO: get font size working */ + if (height && !face) { + g_string_printf(gs_begin, "", + msim_point_to_purple_size(session, msim_height_to_point(session, height))); + } else if (height && face) { + g_string_printf(gs_begin, "", face, + msim_point_to_purple_size(session, msim_height_to_point(session, height))); + } else { + g_string_printf(gs_begin, ""); + } + + /* No support for font-size CSS? */ + /* g_string_printf(gs_begin, "", face, + msim_height_to_point(height)); */ + + gs_end = g_string_new(""); + + if (decor & MSIM_TEXT_BOLD) { + g_string_append(gs_begin, ""); + g_string_prepend(gs_end, ""); + } + + if (decor & MSIM_TEXT_ITALIC) { + g_string_append(gs_begin, ""); + g_string_append(gs_end, ""); + } + + if (decor & MSIM_TEXT_UNDERLINE) { + g_string_append(gs_begin, ""); + g_string_append(gs_end, ""); + } + + + *begin = gs_begin->str; + *end = gs_end->str; +} + +/** Convert a msim markup color to a color suitable for libpurple. + * + * @param msim Either a color name, or an rgb(x,y,z) code. + * + * @return A new string, either a color name or #rrggbb code. Must g_free(). + */ +static char * +msim_color_to_purple(const char *msim) +{ + guint red, green, blue; + + if (!msim) { + return g_strdup("black"); + } + + if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) { + /* Color name. */ + return g_strdup(msim); + } + /* TODO: rgba (alpha). */ + + return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); +} + +/** Convert the msim markup (anchor) tag into HTML. */ +static void +msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *href; + + href = xmlnode_get_attrib(root, "h"); + if (!href) { + href = ""; + } + + *begin = g_strdup_printf("%s", href, href); + *end = g_strdup(""); +} + +/** Convert the msim markup

(paragraph) tag into HTML. */ +static void +msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + /* Just pass through unchanged. + * + * Note: attributes currently aren't passed, if there are any. */ + *begin = g_strdup("

"); + *end = g_strdup("

"); +} + +/** Convert the msim markup tag (text color) into HTML. TODO: Test */ +static void +msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *color; + gchar *purple_color; + + color = xmlnode_get_attrib(root, "v"); + if (!color) { + purple_debug_info("msim", "msim_markup_c_to_html: tag w/o v attr"); + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: log as unrecognized */ + return; + } + + purple_color = msim_color_to_purple(color); + + *begin = g_strdup_printf("", purple_color); + + g_free(purple_color); + + /* *begin = g_strdup_printf("", color); */ + *end = g_strdup(""); +} + +/** Convert the msim markup tag (background color) into HTML. TODO: Test */ +static void +msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *color; + gchar *purple_color; + + color = xmlnode_get_attrib(root, "v"); + if (!color) { + *begin = g_strdup(""); + *end = g_strdup(""); + purple_debug_info("msim", "msim_markup_b_to_html: w/o v attr"); + /* TODO: log as unrecognized. */ + return; + } + + purple_color = msim_color_to_purple(color); + + /* TODO: find out how to set background color. */ + *begin = g_strdup_printf("", + purple_color); + g_free(purple_color); + + *end = g_strdup("

"); +} + +/** Convert the msim markup tag (emoticon image) into HTML. */ +static void +msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) +{ + const gchar *name; + guint i; + struct MSIM_EMOTICON *emote; + + name = xmlnode_get_attrib(root, "n"); + if (!name) { + purple_debug_info("msim", "msim_markup_i_to_html: w/o n"); + *begin = g_strdup(""); + *end = g_strdup(""); + /* TODO: log as unrecognized */ + return; + } + + /* Find and use canonical form of smiley symbol. */ + for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { + if (g_str_equal(name, emote->name)) { + *begin = g_strdup(emote->symbol); + *end = g_strdup(""); + return; + } + } + + /* Couldn't find it, sorry. Try to degrade gracefully. */ + *begin = g_strdup_printf("**%s**", name); + *end = g_strdup(""); +} + +/** Convert an individual msim markup tag to HTML. */ +static void +msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, + gchar **end) +{ + if (g_str_equal(root->name, "f")) { + msim_markup_f_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "a")) { + msim_markup_a_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "p")) { + msim_markup_p_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "c")) { + msim_markup_c_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "b")) { + msim_markup_b_to_html(session, root, begin, end); + } else if (g_str_equal(root->name, "i")) { + msim_markup_i_to_html(session, root, begin, end); + } else { + purple_debug_info("msim", "msim_markup_tag_to_html: " + "unknown tag name=%s, ignoring", + (root && root->name) ? root->name : "(NULL)"); + *begin = g_strdup(""); + *end = g_strdup(""); + } +} + +/** Convert an individual HTML tag to msim markup. */ +static void +html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, + gchar **end) +{ + /* TODO: Coalesce nested tags into one tag! + * Currently, the 's' value will be overwritten when b/i/u is nested + * within another one, and only the inner-most formatting will be + * applied to the text. */ + if (!purple_utf8_strcasecmp(root->name, "root")) { + *begin = g_strdup(""); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "b")) { + *begin = g_strdup_printf("", MSIM_TEXT_BOLD); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "i")) { + *begin = g_strdup_printf("", MSIM_TEXT_ITALIC); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "u")) { + *begin = g_strdup_printf("", MSIM_TEXT_UNDERLINE); + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "a")) { + const gchar *href, *link_text; + + href = xmlnode_get_attrib(root, "href"); + + if (!href) { + href = xmlnode_get_attrib(root, "HREF"); + } + + link_text = xmlnode_get_data(root); + + if (href) { + if (g_str_equal(link_text, href)) { + /* Purple gives us: URL + * Translate to + * Displayed as text of URL with link to URL + */ + *begin = g_strdup_printf("", href); + } else { + /* But if we get: text + * Translate to: text: + * + * Because official client only supports self-closed + * tags; you can't change the link text. + */ + *begin = g_strdup_printf("%s: ", link_text, href); + } + } else { + *begin = g_strdup(""); + } + + /* Sorry, kid. MySpace doesn't support you within tags. */ + xmlnode_free(root->child); + root->child = NULL; + + *end = g_strdup(""); + } else if (!purple_utf8_strcasecmp(root->name, "font")) { + const gchar *size; + const gchar *face; + + size = xmlnode_get_attrib(root, "size"); + face = xmlnode_get_attrib(root, "face"); + + if (face && size) { + *begin = g_strdup_printf("", face, + msim_point_to_height(session, + msim_purple_size_to_point(session, atoi(size)))); + } else if (face) { + *begin = g_strdup_printf("", face); + } else if (size) { + *begin = g_strdup_printf("", + msim_point_to_height(session, + msim_purple_size_to_point(session, atoi(size)))); + } else { + *begin = g_strdup(""); + } + + *end = g_strdup(""); + + /* TODO: color (bg uses ), emoticons */ + } else { + *begin = g_strdup_printf("[%s]", root->name); + *end = g_strdup_printf("[/%s]", root->name); + } +} + +/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup. + * + * @param f Function to convert tags. + * + * @return An HTML string. Caller frees. + */ +static gchar * +msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f) +{ + xmlnode *node; + gchar *begin, *inner, *end; + GString *final; + + if (!root || !root->name) { + return g_strdup(""); + } + + purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", + root->name); + + begin = inner = end = NULL; + + final = g_string_new(""); + + f(session, root, &begin, &end); + + g_string_append(final, begin); + + /* Loop over all child nodes. */ + for (node = root->child; node != NULL; node = node->next) { + switch (node->type) { + case XMLNODE_TYPE_ATTRIB: + /* Attributes handled above. */ + break; + + case XMLNODE_TYPE_TAG: + /* A tag or tag with attributes. Recursively descend. */ + inner = msim_convert_xmlnode(session, node, f); + g_return_val_if_fail(inner != NULL, NULL); + + purple_debug_info("msim", " ** node name=%s\n", + (node && node->name) ? node->name : "(NULL)"); + break; + + case XMLNODE_TYPE_DATA: + /* Literal text. */ + inner = g_new0(char, node->data_sz + 1); + strncpy(inner, node->data, node->data_sz); + inner[node->data_sz] = 0; + + purple_debug_info("msim", " ** node data=%s\n", + inner ? inner : "(NULL)"); + break; + + default: + purple_debug_info("msim", + "msim_convert_xmlnode: strange node\n"); + inner = g_strdup(""); + } + + if (inner) { + g_string_append(final, inner); + } + } + + /* TODO: Note that msim counts each piece of text enclosed by as + * a paragraph and will display each on its own line. You actually have + * to _nest_ tags to intersperse different text in one paragraph! + * Comment out this line below to see. */ + g_string_append(final, end); + + purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n", + (final && final->str) ? final->str : "(NULL)"); + + return final->str; +} + +/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ +static gchar * +msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) +{ + xmlnode *root; + gchar *str; + gchar *enclosed_raw; + + g_return_val_if_fail(raw != NULL, NULL); + + /* Enclose text in one root tag, to try to make it valid XML for parsing. */ + enclosed_raw = g_strconcat("", raw, "", NULL); + + root = xmlnode_from_str(enclosed_raw, -1); + + if (!root) { + purple_debug_info("msim", "msim_markup_to_html: couldn't parse " + "%s as XML, returning raw: %s\n", enclosed_raw, raw); + /* TODO: msim_unrecognized */ + g_free(enclosed_raw); + return g_strdup(raw); + } + + g_free(enclosed_raw); + + str = msim_convert_xmlnode(session, root, f); + g_return_val_if_fail(str != NULL, NULL); + purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str); + + xmlnode_free(root); + + return str; +} + +/** Convert plaintext smileys to markup tags. + * + * @param before Original text with ASCII smileys. Will be freed. + * @return A new string with tags, if applicable. Must be g_free()'d. + */ +static gchar * +msim_convert_smileys_to_markup(gchar *before) +{ + gchar *old, *new, *replacement; + guint i; + struct MSIM_EMOTICON *emote; + + old = before; + new = NULL; + + for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { + gchar *name, *symbol; + + name = emote->name; + symbol = emote->symbol; + + replacement = g_strdup_printf("", name); + + purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", + symbol ? symbol : "(NULL)", + replacement ? replacement : "(NULL)"); + new = str_replace(old, symbol, replacement); + + g_free(replacement); + g_free(old); + + old = new; + } + + return new; +} + + +/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. + * + * @return Purple markup string, must be g_free()'d. */ +gchar * +msim_markup_to_html(MsimSession *session, const gchar *raw) +{ + return msim_convert_xml(session, raw, + (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); +} + +/** High-level function to convert Purple (HTML) to MySpaceIM markup. + * + * @return HTML markup string, must be g_free()'d. */ +gchar * +html_to_msim_markup(MsimSession *session, const gchar *raw) +{ + gchar *markup; + + markup = msim_convert_xml(session, raw, + (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); + + if (purple_account_get_bool(session->account, "emoticons", TRUE)) { + /* Frees markup and allocates a new one. */ + markup = msim_convert_smileys_to_markup(markup); + } + + return markup; +} + + diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/markup.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/markup.h Sun Aug 26 19:08:29 2007 +0000 @@ -0,0 +1,27 @@ +/* MySpaceIM Protocol Plugin - markup + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MYSPACE_MARKUP_H +#define _MYSPACE_MARKUP_H + +/* High-level msim markup <=> Purple html conversion functions. */ +gchar *msim_markup_to_html(MsimSession *, const gchar *raw); +gchar *html_to_msim_markup(MsimSession *, const gchar *raw); + +#endif /* !_MYSPACE_MARKUP_H */ diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/message.c --- a/libpurple/protocols/myspace/message.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/message.c Sun Aug 26 19:08:29 2007 +0000 @@ -250,13 +250,21 @@ return new_list; } -/** Free a GList * of gchar * strings. */ +/** Free a GList * of MsimMessageElement *'s. */ void msim_msg_list_free(GList *l) { for (; l != NULL; l = g_list_next(l)) { - g_free((gchar *)(l->data)); + MsimMessageElement *elem; + + elem = (MsimMessageElement *)l->data; + + /* Note that name is almost never dynamically allocated elsewhere; + * it is usually a static string, but not in lists. So cast it. */ + g_free((gchar *)elem->name); + g_free(elem->data); + g_free(elem); } g_list_free(l); } @@ -275,7 +283,19 @@ /* TODO: escape/unescape /3 <-> | within list elements */ for (i = 0; array[i] != NULL; ++i) { - list = g_list_append(list, g_strdup(array[i])); + MsimMessageElement *elem; + + /* Freed in msim_msg_list_free() */ + elem = g_new0(MsimMessageElement, 1); + + /* Give the element a name for debugging purposes. + * Not supposed to be looked up by this name; instead, + * lookup the elements by indexing the array. */ + elem->name = g_strdup_printf("(list item #%d)", i); + elem->type = MSIM_TYPE_RAW; + elem->data = g_strdup(array[i]); + + list = g_list_append(list, elem); } g_strfreev(array); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.c Sun Aug 26 19:08:29 2007 +0000 @@ -33,135 +33,37 @@ #define PURPLE_PLUGIN -#include "message.h" -#include "persist.h" #include "myspace.h" -/* Globals */ - -/* The names in in emoticon_names (for ) map to corresponding - * entries in emoticon_symbols (for the ASCII representation of the emoticon). - * - * Multiple emoticon symbols in Pidgin can map to one name. List the - * canonical form, as inserted by the "Smile!" dialog, first. For example, - * :) comes before :-), because although both are recognized as 'happy', - * the first is inserted by the smiley button (first symbol in theme). - * - * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */ -static struct MSIM_EMOTICON -{ - gchar *name; - gchar *symbol; -} msim_emoticons[] = { - /* Unfortunately, this list duplicates much of the file - * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because - * that file is part of Pidgin, but we're part of libpurple. - */ - { "bigsmile", ":D" }, - { "bigsmile", ":-D" }, - { "devil", "}:)" }, - { "frazzled", ":Z" }, - { "geek", "B)" }, - { "googles", "%)" }, - { "growl", ":E" }, - { "laugh", ":))" }, /* Must be before ':)' */ - { "happy", ":)" }, - { "happy", ":-)" }, - { "happi", ":)" }, - { "heart", ":X" }, - { "mohawk", "-:" }, - { "mad", "X(" }, - { "messed", "X)" }, - { "nerd", "Q)" }, - { "oops", ":G" }, - { "pirate", "P)" }, - { "scared", ":O" }, - { "sidefrown", ":{" }, - { "sinister", ":B" }, - { "smirk", ":," }, - { "straight", ":|" }, - { "tongue", ":P" }, - { "tongue", ":p" }, - { "tongy", ":P" }, - { "upset", "B|" }, - { "wink", ";-)" }, - { "wink", ";)" }, - { "winc", ";)" }, - { "worried", ":[" }, - { "kiss", ":x" }, - { NULL, NULL } -}; - /* Internal functions */ -static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code); -static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr); #ifdef MSIM_DEBUG_MSG static void print_hash_item(gpointer key, gpointer value, gpointer user_data); #endif -static int msim_send_really_raw(PurpleConnection *gc, const char *buf, - int total_bytes); +static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes); static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg); -static const gchar *msim_compute_login_response( - const gchar nonce[2 * NONCE_SIZE], const gchar *email, - const gchar *password, guint *response_len); -static gboolean msim_send_bm(MsimSession *session, const gchar *who, - const gchar *text, int type); - -static guint msim_point_to_purple_size(MsimSession *session, guint point); -static guint msim_purple_size_to_point(MsimSession *session, guint size); -static guint msim_height_to_point(MsimSession *session, guint height); -static guint msim_point_to_height(MsimSession *session, guint point); - -static void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note); - -static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, - gchar **begin, gchar **end); -static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, - gchar **begin, gchar **end); -static gchar *msim_convert_xml(MsimSession *, const gchar *raw, - MSIM_XMLNODE_CONVERT f); -static gchar *msim_convert_smileys_to_markup(gchar *before); - -/* High-level msim markup <=> html conversion functions. */ -static gchar *msim_markup_to_html(MsimSession *, const gchar *raw); -static gchar *html_to_msim_markup(MsimSession *, const gchar *raw); - -static MsimUser *msim_get_user_from_buddy(PurpleBuddy *buddy); -static MsimUser *msim_find_user(MsimSession *session, const gchar *username); - -static gboolean msim_incoming_bm_record_cv(MsimSession *session, - MsimMessage *msg); +static const gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len); + +static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg); static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg); static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg); static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg); -static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); +/* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */ static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg); static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg); static gboolean msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg); #ifdef MSIM_SEND_CLIENT_VERSION -static gboolean msim_send_unofficial_client(MsimSession *session, - gchar *username); +static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username); #endif static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); -static gchar *msim_format_now_playing(gchar *band, gchar *song); - -static void msim_set_status_code(MsimSession *session, guint code, - gchar *statstring); - -static void msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full); - -static void msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, - gsize len, const gchar *error_message); - -static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user); -static gboolean msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user); -static gboolean msim_process_server_info(MsimSession *session, - MsimMessage *msg); + +static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring); + +static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg); static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg); @@ -175,51 +77,22 @@ static gboolean msim_process(MsimSession *session, MsimMessage *msg); -static MsimMessage *msim_do_postprocessing(MsimMessage *msg, - const gchar *uid_field_name, const gchar *uid_before, guint uid); -static void msim_postprocess_outgoing_cb(MsimSession *session, - MsimMessage *userinfo, gpointer data); -static gboolean msim_postprocess_outgoing(MsimSession *session, - MsimMessage *msg, const gchar *username, const gchar *uid_field_name, - const gchar *uid_before); +static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid); +static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); +static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); static gboolean msim_error(MsimSession *session, MsimMessage *msg); -static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, - gpointer data); +static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); static gboolean msim_check_inbox(gpointer data); -static void msim_input_cb(gpointer gc_uncasted, gint source, - PurpleInputCondition cond); - -static guint msim_new_reply_callback(MsimSession *session, - MSIM_USER_LOOKUP_CB cb, gpointer data); - -static void msim_connect_cb(gpointer data, gint source, - const gchar *error_message); - -static gboolean msim_is_userid(const gchar *user); -static gboolean msim_is_email(const gchar *user); - -static void msim_lookup_user(MsimSession *session, const gchar *user, - MSIM_USER_LOOKUP_CB cb, gpointer data); +static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond); + + +static void msim_connect_cb(gpointer data, gint source, const gchar *error_message); static void msim_import_friends(PurplePluginAction *action); -double msim_round(double round); - -/* round is part of C99, but sometimes is unavailable before then. - * Based on http://forums.belution.com/en/cpp/000/050/13.shtml - */ -double msim_round(double value) -{ - if (value < 0) { - return -(floor(-value + 0.5)); - } else { - return floor( value + 0.5); - } -} - /** * Load the plugin. */ @@ -283,195 +156,6 @@ return types; } -/** Get zap types. */ -GList * -msim_attention_types(PurpleAccount *acct) -{ - static GList *types = NULL; - MsimAttentionType* attn; - - if (!types) { -#define _MSIM_ADD_NEW_ATTENTION(icn, des, incoming, outgoing) \ - attn = g_new0(MsimAttentionType, 1); \ - attn->icon = icn; \ - attn->description = des; \ - attn->incoming_description = incoming; \ - attn->outgoing_description = outgoing; \ - types = g_list_append(types, attn); - - /* TODO: icons for each zap */ - _MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking")); - _MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing")); - } - - return types; -} - -/** Send a zap */ -gboolean -msim_send_attention(PurpleConnection *gc, gchar *username, guint code) -{ - GList *types; - MsimSession *session; - MsimAttentionType *attn; - PurpleBuddy *buddy; - - session = (MsimSession *)gc->proto_data; - - /* Look for this attention type, by the code index given. */ - types = msim_attention_types(gc->account); - attn = (MsimAttentionType *)g_list_nth_data(types, code); - - if (!attn) { - purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code); - return FALSE; - } - - buddy = purple_find_buddy(session->account, username); - if (!buddy) { - return FALSE; - } - - /* TODO: make use of the MsimAttentionType we found, instead of - * doing it all over in msim_send_zap_from_menu. */ - msim_send_zap_from_menu(&buddy->node, GUINT_TO_POINTER(code)); - - return TRUE; -} - -/** Send a zap to a user. */ -static gboolean -msim_send_zap(MsimSession *session, const gchar *username, guint code) -{ - gchar *zap_string; -#ifndef MSIM_USE_ATTENTION_API - gchar *zap_description; -#endif - GList *types; - MsimAttentionType *attn; - gboolean rc; - - g_return_val_if_fail(session != NULL, FALSE); - g_return_val_if_fail(username != NULL, FALSE); - - types = msim_attention_types(session->account); - - attn = g_list_nth_data(types, code); - if (!attn) { - return FALSE; - } - - -#ifdef MSIM_USE_ATTENTION_API - serv_got_attention(session->gc, username, attn, FALSE); -#else - zap_description = g_strdup_printf("*** Attention: %s %s ***", attn->outgoing_description, - username); - - serv_got_im(session->gc, username, zap_description, - PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM, time(NULL)); - - g_free(zap_description); -#endif - - /* 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)) { - purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s", - username, zap_string); - rc = FALSE; - } else { - rc = TRUE; - } - - g_free(zap_string); - - return rc; - -} - -/** Zap someone. Callback from msim_blist_node_menu zap menu. */ -static void -msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr) -{ - PurpleBuddy *buddy; - PurpleAccount *account; - PurpleConnection *gc; - MsimSession *session; - guint zap; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { - /* Only know about buddies for now. */ - return; - } - - g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); - - buddy = (PurpleBuddy *)node; - - /* Find the session */ - account = buddy->account; - gc = purple_account_get_connection(account); - session = (MsimSession *)gc->proto_data; - - zap = GPOINTER_TO_INT(zap_num_ptr); - - g_return_if_fail(msim_send_zap(session, buddy->name, zap)); -} - - -/** Return menu, if any, for a buddy list node. */ -GList * -msim_blist_node_menu(PurpleBlistNode *node) -{ - GList *menu, *zap_menu; - GList *types; - PurpleMenuAction *act; - /* Warning: hardcoded to match that in msim_attention_types. */ - const gchar *zap_names[10]; - guint i; - - if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { - /* Only know about buddies for now. */ - return NULL; - } - - /* Names from official client. */ - types = msim_attention_types(NULL); - i = 0; - do - { - MsimAttentionType *attn; - - attn = (MsimAttentionType *)types->data; - zap_names[i] = attn->description; - ++i; - } while ((types = g_list_next(types))); - - menu = zap_menu = NULL; - - /* TODO: get rid of once is accessible directly in GUI */ - for (i = 0; i < sizeof(zap_names) / sizeof(zap_names[0]); ++i) { - act = purple_menu_action_new(zap_names[i], PURPLE_CALLBACK(msim_send_zap_from_menu), - GUINT_TO_POINTER(i), NULL); - zap_menu = g_list_append(zap_menu, act); - } - - act = purple_menu_action_new(_("Zap"), NULL, NULL, zap_menu); - menu = g_list_append(menu, act); - - return menu; -} - /** * Return the icon name for a buddy and account. * @@ -911,7 +595,7 @@ * Buddy messages ('bm') include instant messages, action messages, status messages, etc. * */ -static gboolean +gboolean msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, int type) { @@ -945,613 +629,6 @@ return rc; } -/* Indexes of this array + 1 map HTML font size to scale of normal font size. * - * Based on _point_sizes from libpurple/gtkimhtml.c - * 1 2 3 4 5 6 7 */ -static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; - -#define MAX_FONT_SIZE 7 /* Purple maximum font size */ -#define POINTS_PER_INCH 72 /* How many pt's in an inch */ - -/** Convert typographical font point size to HTML font size. - * Based on libpurple/gtkimhtml.c */ -static guint -msim_point_to_purple_size(MsimSession *session, guint point) -{ - guint size, this_point, base; - gdouble scale; - - base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); - - for (size = 0; - size < sizeof(_font_scale) / sizeof(_font_scale[0]); - ++size) { - scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; - this_point = (guint)msim_round(scale * base); - - if (this_point >= point) { - purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", - point, size); - return size; - } - } - - /* No HTML font size was this big; return largest possible. */ - return this_point; -} - -/** Convert HTML font size to point size. */ -static guint -msim_purple_size_to_point(MsimSession *session, guint size) -{ - gdouble scale; - guint point; - guint base; - - scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; - - base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); - - point = (guint)msim_round(scale * base); - - purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n", - size, point); - - return point; -} - -/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ -static guint -msim_height_to_point(MsimSession *session, guint height) -{ - guint dpi; - - dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); - - return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height); - - /* See also: libpurple/protocols/bonjour/jabber.c - * _font_size_ichat_to_purple */ -} - -/** Convert point size to msim pixel height font size specification, for outgoing messages. */ -static guint -msim_point_to_height(MsimSession *session, guint point) -{ - guint dpi; - - dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); - - return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); -} - -/** Convert the msim markup (font) tag into HTML. */ -static void -msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *face, *height_str, *decor_str; - GString *gs_end, *gs_begin; - guint decor, height; - - face = xmlnode_get_attrib(root, "f"); - height_str = xmlnode_get_attrib(root, "h"); - decor_str = xmlnode_get_attrib(root, "s"); - - if (height_str) { - height = atol(height_str); - } else { - height = 12; - } - - if (decor_str) { - decor = atol(decor_str); - } else { - decor = 0; - } - - gs_begin = g_string_new(""); - /* TODO: get font size working */ - if (height && !face) { - g_string_printf(gs_begin, "", - msim_point_to_purple_size(session, msim_height_to_point(session, height))); - } else if (height && face) { - g_string_printf(gs_begin, "", face, - msim_point_to_purple_size(session, msim_height_to_point(session, height))); - } else { - g_string_printf(gs_begin, ""); - } - - /* No support for font-size CSS? */ - /* g_string_printf(gs_begin, "", face, - msim_height_to_point(height)); */ - - gs_end = g_string_new(""); - - if (decor & MSIM_TEXT_BOLD) { - g_string_append(gs_begin, ""); - g_string_prepend(gs_end, ""); - } - - if (decor & MSIM_TEXT_ITALIC) { - g_string_append(gs_begin, ""); - g_string_append(gs_end, ""); - } - - if (decor & MSIM_TEXT_UNDERLINE) { - g_string_append(gs_begin, ""); - g_string_append(gs_end, ""); - } - - - *begin = gs_begin->str; - *end = gs_end->str; -} - -/** Convert a msim markup color to a color suitable for libpurple. - * - * @param msim Either a color name, or an rgb(x,y,z) code. - * - * @return A new string, either a color name or #rrggbb code. Must g_free(). - */ -static char * -msim_color_to_purple(const char *msim) -{ - guint red, green, blue; - - if (!msim) { - return g_strdup("black"); - } - - if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) { - /* Color name. */ - return g_strdup(msim); - } - /* TODO: rgba (alpha). */ - - return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); -} - -/** Convert the msim markup (anchor) tag into HTML. */ -static void -msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *href; - - href = xmlnode_get_attrib(root, "h"); - if (!href) { - href = ""; - } - - *begin = g_strdup_printf("%s", href, href); - *end = g_strdup(""); -} - -/** Convert the msim markup

(paragraph) tag into HTML. */ -static void -msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - /* Just pass through unchanged. - * - * Note: attributes currently aren't passed, if there are any. */ - *begin = g_strdup("

"); - *end = g_strdup("

"); -} - -/** Convert the msim markup tag (text color) into HTML. TODO: Test */ -static void -msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *color; - gchar *purple_color; - - color = xmlnode_get_attrib(root, "v"); - if (!color) { - purple_debug_info("msim", "msim_markup_c_to_html: tag w/o v attr"); - *begin = g_strdup(""); - *end = g_strdup(""); - /* TODO: log as unrecognized */ - return; - } - - purple_color = msim_color_to_purple(color); - - *begin = g_strdup_printf("", purple_color); - - g_free(purple_color); - - /* *begin = g_strdup_printf("", color); */ - *end = g_strdup(""); -} - -/** Convert the msim markup tag (background color) into HTML. TODO: Test */ -static void -msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *color; - gchar *purple_color; - - color = xmlnode_get_attrib(root, "v"); - if (!color) { - *begin = g_strdup(""); - *end = g_strdup(""); - purple_debug_info("msim", "msim_markup_b_to_html: w/o v attr"); - /* TODO: log as unrecognized. */ - return; - } - - purple_color = msim_color_to_purple(color); - - /* TODO: find out how to set background color. */ - *begin = g_strdup_printf("", - purple_color); - g_free(purple_color); - - *end = g_strdup("

"); -} - -/** Convert the msim markup tag (emoticon image) into HTML. */ -static void -msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) -{ - const gchar *name; - guint i; - struct MSIM_EMOTICON *emote; - - name = xmlnode_get_attrib(root, "n"); - if (!name) { - purple_debug_info("msim", "msim_markup_i_to_html: w/o n"); - *begin = g_strdup(""); - *end = g_strdup(""); - /* TODO: log as unrecognized */ - return; - } - - /* Find and use canonical form of smiley symbol. */ - for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { - if (!strcmp(name, emote->name)) { - *begin = g_strdup(emote->symbol); - *end = g_strdup(""); - return; - } - } - - /* Couldn't find it, sorry. Try to degrade gracefully. */ - *begin = g_strdup_printf("**%s**", name); - *end = g_strdup(""); -} - -/** Convert an individual msim markup tag to HTML. */ -static void -msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, - gchar **end) -{ - if (!strcmp(root->name, "f")) { - msim_markup_f_to_html(session, root, begin, end); - } else if (!strcmp(root->name, "a")) { - msim_markup_a_to_html(session, root, begin, end); - } else if (!strcmp(root->name, "p")) { - msim_markup_p_to_html(session, root, begin, end); - } else if (!strcmp(root->name, "c")) { - msim_markup_c_to_html(session, root, begin, end); - } else if (!strcmp(root->name, "b")) { - msim_markup_b_to_html(session, root, begin, end); - } else if (!strcmp(root->name, "i")) { - msim_markup_i_to_html(session, root, begin, end); - } else { - purple_debug_info("msim", "msim_markup_tag_to_html: " - "unknown tag name=%s, ignoring", - (root && root->name) ? root->name : "(NULL)"); - *begin = g_strdup(""); - *end = g_strdup(""); - } -} - -/** Convert an individual HTML tag to msim markup. */ -static void -html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, - gchar **end) -{ - /* TODO: Coalesce nested tags into one tag! - * Currently, the 's' value will be overwritten when b/i/u is nested - * within another one, and only the inner-most formatting will be - * applied to the text. */ - if (!purple_utf8_strcasecmp(root->name, "root")) { - *begin = g_strdup(""); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "b")) { - *begin = g_strdup_printf("", MSIM_TEXT_BOLD); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "i")) { - *begin = g_strdup_printf("", MSIM_TEXT_ITALIC); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "u")) { - *begin = g_strdup_printf("", MSIM_TEXT_UNDERLINE); - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "a")) { - const gchar *href, *link_text; - - href = xmlnode_get_attrib(root, "href"); - - if (!href) { - href = xmlnode_get_attrib(root, "HREF"); - } - - link_text = xmlnode_get_data(root); - - if (href) { - if (!strcmp(link_text, href)) { - /* Purple gives us: URL - * Translate to - * Displayed as text of URL with link to URL - */ - *begin = g_strdup_printf("", href); - } else { - /* But if we get: text - * Translate to: text: - * - * Because official client only supports self-closed - * tags; you can't change the link text. - */ - *begin = g_strdup_printf("%s: ", link_text, href); - } - } else { - *begin = g_strdup(""); - } - - /* Sorry, kid. MySpace doesn't support you within tags. */ - xmlnode_free(root->child); - root->child = NULL; - - *end = g_strdup(""); - } else if (!purple_utf8_strcasecmp(root->name, "font")) { - const gchar *size; - const gchar *face; - - size = xmlnode_get_attrib(root, "size"); - face = xmlnode_get_attrib(root, "face"); - - if (face && size) { - *begin = g_strdup_printf("", face, - msim_point_to_height(session, - msim_purple_size_to_point(session, atoi(size)))); - } else if (face) { - *begin = g_strdup_printf("", face); - } else if (size) { - *begin = g_strdup_printf("", - msim_point_to_height(session, - msim_purple_size_to_point(session, atoi(size)))); - } else { - *begin = g_strdup(""); - } - - *end = g_strdup(""); - - /* TODO: color (bg uses ), emoticons */ - } else { - *begin = g_strdup_printf("[%s]", root->name); - *end = g_strdup_printf("[/%s]", root->name); - } -} - -/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup. - * - * @param f Function to convert tags. - * - * @return An HTML string. Caller frees. - */ -static gchar * -msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f) -{ - xmlnode *node; - gchar *begin, *inner, *end; - GString *final; - - if (!root || !root->name) { - return g_strdup(""); - } - - purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", - root->name); - - begin = inner = end = NULL; - - final = g_string_new(""); - - f(session, root, &begin, &end); - - g_string_append(final, begin); - - /* Loop over all child nodes. */ - for (node = root->child; node != NULL; node = node->next) { - switch (node->type) { - case XMLNODE_TYPE_ATTRIB: - /* Attributes handled above. */ - break; - - case XMLNODE_TYPE_TAG: - /* A tag or tag with attributes. Recursively descend. */ - inner = msim_convert_xmlnode(session, node, f); - g_return_val_if_fail(inner != NULL, NULL); - - purple_debug_info("msim", " ** node name=%s\n", - (node && node->name) ? node->name : "(NULL)"); - break; - - case XMLNODE_TYPE_DATA: - /* Literal text. */ - inner = g_new0(char, node->data_sz + 1); - strncpy(inner, node->data, node->data_sz); - inner[node->data_sz] = 0; - - purple_debug_info("msim", " ** node data=%s\n", - inner ? inner : "(NULL)"); - break; - - default: - purple_debug_info("msim", - "msim_convert_xmlnode: strange node\n"); - inner = g_strdup(""); - } - - if (inner) { - g_string_append(final, inner); - } - } - - /* TODO: Note that msim counts each piece of text enclosed by as - * a paragraph and will display each on its own line. You actually have - * to _nest_ tags to intersperse different text in one paragraph! - * Comment out this line below to see. */ - g_string_append(final, end); - - purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n", - (final && final->str) ? final->str : "(NULL)"); - - return final->str; -} - -/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ -static gchar * -msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) -{ - xmlnode *root; - gchar *str; - gchar *enclosed_raw; - - g_return_val_if_fail(raw != NULL, NULL); - - /* Enclose text in one root tag, to try to make it valid XML for parsing. */ - enclosed_raw = g_strconcat("", raw, "", NULL); - - root = xmlnode_from_str(enclosed_raw, -1); - - if (!root) { - purple_debug_info("msim", "msim_markup_to_html: couldn't parse " - "%s as XML, returning raw: %s\n", enclosed_raw, raw); - /* TODO: msim_unrecognized */ - g_free(enclosed_raw); - return g_strdup(raw); - } - - g_free(enclosed_raw); - - str = msim_convert_xmlnode(session, root, f); - g_return_val_if_fail(str != NULL, NULL); - purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str); - - xmlnode_free(root); - - return str; -} - -/** Convert plaintext smileys to markup tags. - * - * @param before Original text with ASCII smileys. Will be freed. - * @return A new string with tags, if applicable. Must be g_free()'d. - */ -static gchar * -msim_convert_smileys_to_markup(gchar *before) -{ - gchar *old, *new, *replacement; - guint i; - struct MSIM_EMOTICON *emote; - - old = before; - new = NULL; - - for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { - gchar *name, *symbol; - - name = emote->name; - symbol = emote->symbol; - - replacement = g_strdup_printf("", name); - - purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", - symbol ? symbol : "(NULL)", - replacement ? replacement : "(NULL)"); - new = str_replace(old, symbol, replacement); - - g_free(replacement); - g_free(old); - - old = new; - } - - return new; -} - - -/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. - * - * @return Purple markup string, must be g_free()'d. */ -static gchar * -msim_markup_to_html(MsimSession *session, const gchar *raw) -{ - return msim_convert_xml(session, raw, - (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); -} - -/** High-level function to convert Purple (HTML) to MySpaceIM markup. - * - * @return HTML markup string, must be g_free()'d. */ -static gchar * -html_to_msim_markup(MsimSession *session, const gchar *raw) -{ - gchar *markup; - - markup = msim_convert_xml(session, raw, - (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); - - if (purple_account_get_bool(session->account, "emoticons", TRUE)) { - /* Frees markup and allocates a new one. */ - markup = msim_convert_smileys_to_markup(markup); - } - - return markup; -} - -/** Get the MsimUser from a PurpleBuddy, creating it if needed. */ -static MsimUser * -msim_get_user_from_buddy(PurpleBuddy *buddy) -{ - MsimUser *user; - - if (!buddy) { - return NULL; - } - - if (!buddy->proto_data) { - /* No MsimUser for this buddy; make one. */ - - /* TODO: where is this freed? */ - user = g_new0(MsimUser, 1); - user->buddy = buddy; - buddy->proto_data = (gpointer)user; - } - - user = (MsimUser *)(buddy->proto_data); - - return user; -} - -/** Find and return an MsimUser * representing a user on the buddy list, or NULL. */ -static MsimUser * -msim_find_user(MsimSession *session, const gchar *username) -{ - PurpleBuddy *buddy; - MsimUser *user; - - buddy = purple_find_buddy(session->account, username); - if (!buddy) { - return NULL; - } - - user = msim_get_user_from_buddy(buddy); - - return user; -} - /** Record the client version in the buddy list, from an incoming message. */ static gboolean @@ -1655,7 +732,7 @@ * @param msg An MsimMessage that was unrecognized, or NULL. * @param note Information on what was unrecognized, or NULL. */ -static void +void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note) { /* TODO: Some more context, outwardly equivalent to a backtrace, @@ -1669,8 +746,8 @@ * by Alexandr Shutko, who maintains OSCAR protocol documentation). */ purple_debug_info("msim", "Unrecognized data on account for %s\n", - session->account->username ? session->account->username - : "(NULL)"); + (session && session->account && session->account->username) ? + session->account->username : "(NULL)"); if (note) { purple_debug_info("msim", "(Note: %s)\n", note); } @@ -1680,60 +757,6 @@ } } -/** Process an incoming zap. */ -static gboolean -msim_incoming_zap(MsimSession *session, MsimMessage *msg) -{ - gchar *msg_text, *username; - gint zap; - const gchar *zap_past_tense[10]; -#ifdef MSIM_USE_ATTENTION_API - MsimAttentionType attn; -#else - gchar *zap_text; -#endif - - zap_past_tense[0] = _("zapped"); - zap_past_tense[1] = _("whacked"); - zap_past_tense[2] = _("torched"); - zap_past_tense[3] = _("smooched"); - zap_past_tense[4] = _("hugged"); - zap_past_tense[5] = _("bslapped"); - zap_past_tense[6] = _("goosed"); - zap_past_tense[7] = _("hi-fived"); - zap_past_tense[8] = _("punk'd"); - zap_past_tense[9] = _("raspberried"); - - msg_text = msim_msg_get_string(msg, "msg"); - username = msim_msg_get_string(msg, "_username"); - - g_return_val_if_fail(msg_text != NULL, FALSE); - g_return_val_if_fail(username != NULL, FALSE); - - g_return_val_if_fail(sscanf(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", &zap) == 1, FALSE); - - zap = CLAMP(zap, 0, sizeof(zap_past_tense) / sizeof(zap_past_tense[0])); - - /* TODO:ZAP: use msim_attention_types */ -#ifdef MSIM_USE_ATTENTION_API - attn.incoming_description = zap_past_tense[zap]; - attn.outgoing_description = NULL; - attn.icon = NULL; /* TODO: icon */ - - serv_got_attention(session->gc, username, &attn, TRUE); -#else - zap_text = g_strdup_printf(_("*** You have been %s! ***"), zap_past_tense[zap]); - serv_got_im(session->gc, username, zap_text, - PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, time(NULL)); - g_free(zap_text); -#endif - - g_free(msg_text); - g_free(username); - - return TRUE; -} - /** * Handle an incoming action message. * @@ -1761,13 +784,13 @@ purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", msg_text, username); - if (strcmp(msg_text, "%typing%") == 0) { + if (g_str_equal(msg_text, "%typing%")) { /* TODO: find out if msim repeatedly sends typing messages, so we can * give it a timeout. Right now, there does seem to be an inordinately * amount of time between typing stopped-typing notifications. */ serv_got_typing(session->gc, username, 0, PURPLE_TYPING); rc = TRUE; - } else if (strcmp(msg_text, "%stoptyping%") == 0) { + } else if (g_str_equal(msg_text, "%stoptyping%")) { serv_got_typing_stopped(session->gc, username); rc = TRUE; } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { @@ -1784,7 +807,7 @@ return rc; } -/* Process an incoming media (buddy icon) message. */ +/* Process an incoming media (message background?) message. */ static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg) { @@ -1902,95 +925,7 @@ return 0; } -/** Format the "now playing" indicator, showing the artist and song. - * @return Return a new string (must be g_free()'d), or NULL. - */ -static gchar * -msim_format_now_playing(gchar *band, gchar *song) -{ - if ((band && strlen(band)) || (song && strlen(song))) { - return g_strdup_printf("%s - %s", - (band && strlen(band)) ? band : "Unknown Artist", - (song && strlen(song)) ? song : "Unknown Song"); - } else { - return NULL; - } -} - -/** Append user information to a PurpleNotifyUserInfo, given an MsimUser. - * Used by msim_tooltip_text() and msim_get_info_cb() to show a user's profile. - */ -static void -msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full) -{ - gchar *str; - guint uid; - guint cv; - - /* Useful to identify the account the tooltip refers to. - * Other prpls show this. */ - if (user->username) { - purple_notify_user_info_add_pair(user_info, _("User"), user->username); - } - - uid = purple_blist_node_get_int(&user->buddy->node, "UserID"); - - if (full) { - /* TODO: link to username, if available */ - purple_notify_user_info_add_pair(user_info, _("Profile"), - g_strdup_printf("http://myspace.com/%d", - uid, uid)); - } - - - /* a/s/l...the vitals */ - if (user->age) { - purple_notify_user_info_add_pair(user_info, _("Age"), - g_strdup_printf("%d", user->age)); - } - - if (user->gender && strlen(user->gender)) { - purple_notify_user_info_add_pair(user_info, _("Gender"), user->gender); - } - - if (user->location && strlen(user->location)) { - purple_notify_user_info_add_pair(user_info, _("Location"), user->location); - } - - /* Other information */ - if (user->headline && strlen(user->headline)) { - purple_notify_user_info_add_pair(user_info, _("Headline"), user->headline); - } - - str = msim_format_now_playing(user->band_name, user->song_name); - if (str && strlen(str)) { - purple_notify_user_info_add_pair(user_info, _("Song"), str); - } - - /* Note: total friends only available if looked up by uid, not username. */ - if (user->total_friends) { - purple_notify_user_info_add_pair(user_info, _("Total Friends"), - g_strdup_printf("%d", user->total_friends)); - } - - if (full) { - /* Client information */ - - str = user->client_info; - cv = user->client_cv; - - if (str && cv != 0) { - purple_notify_user_info_add_pair(user_info, _("Client Version"), - g_strdup_printf("%s (build %d)", str, cv)); - } else if (str) { - purple_notify_user_info_add_pair(user_info, _("Client Version"), - g_strdup(str)); - } else if (cv) { - purple_notify_user_info_add_pair(user_info, _("Client Version"), - g_strdup_printf("Build %d", cv)); - } - } -} + /** Callback for msim_get_info(), for when user info is received. */ static void @@ -2377,42 +1312,27 @@ guint i, n; const gchar *froms[5], *tos[5], *urls[5], *subjects[5]; - /* Three parallel arrays for each new inbox message type. */ - static const gchar *inbox_keys[] = - { - "Mail", - "BlogComment", - "ProfileComment", - "FriendRequest", - "PictureComment" + /* Information for each new inbox message type. */ + static struct + { + const gchar *key; + guint bit; + const gchar *url; + const gchar *text; + } message_types[] = { + { "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL }, + { "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL }, + { "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }, + { "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL }, + { "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL } }; - static const guint inbox_bits[] = - { - MSIM_INBOX_MAIL, - MSIM_INBOX_BLOG_COMMENT, - MSIM_INBOX_PROFILE_COMMENT, - MSIM_INBOX_FRIEND_REQUEST, - MSIM_INBOX_PICTURE_COMMENT - }; - - static const gchar *inbox_urls[] = - { - "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", - "http://blog.myspace.com/index.cfm?fuseaction=blog", - "http://home.myspace.com/index.cfm?fuseaction=user", - "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", - "http://home.myspace.com/index.cfm?fuseaction=user" - }; - - static const gchar *inbox_text[5]; - /* Can't write _()'d strings in array initializers. Workaround. */ - inbox_text[0] = _("New mail messages"); - inbox_text[1] = _("New blog comments"); - inbox_text[2] = _("New profile comments"); - inbox_text[3] = _("New friend requests!"); - inbox_text[4] = _("New picture comments"); + message_types[0].text = _("New mail messages"); + message_types[1].text = _("New blog comments"); + message_types[2].text = _("New profile comments"); + message_types[3].text = _("New friend requests!"); + message_types[4].text = _("New picture comments"); g_return_if_fail(reply != NULL); @@ -2427,12 +1347,12 @@ n = 0; - for (i = 0; i < sizeof(inbox_keys) / sizeof(inbox_keys[0]); ++i) { + for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) { const gchar *key; guint bit; - key = inbox_keys[i]; - bit = inbox_bits[i]; + key = message_types[i].key; + bit = message_types[i].bit; if (msim_msg_get(body, key)) { /* Notify only on when _changes_ from no mail -> has mail @@ -2441,13 +1361,13 @@ purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n", key ? key : "(NULL)", n); - subjects[n] = inbox_text[i]; + subjects[n] = message_types[i].text; froms[n] = _("MySpace"); tos[n] = session->username; /* TODO: append token, web challenge, so automatically logs in. * Would also need to free strings because they won't be static */ - urls[n] = inbox_urls[i]; + urls[n] = message_types[i].url; ++n; } else { @@ -2520,6 +1440,9 @@ * some of the time, but can vary. This is our own user ID. */ session->userid = msim_msg_get_integer(msg, "userid"); + /* Save uid to account so this account can be looked up by uid. */ + purple_account_set_int(session->account, "uid", session->userid); + /* Not sure what profileid is used for. */ if (msim_msg_get_integer(msg, "profileid") != session->userid) { msim_unrecognized(session, msg, @@ -2597,10 +1520,12 @@ (GSourceFunc)msim_check_alive, session); #endif - purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, - (GSourceFunc)msim_check_inbox, session); - - msim_check_inbox(session); + if (purple_account_get_check_mail(session->account)) { + purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, + (GSourceFunc)msim_check_inbox, session); + msim_check_inbox(session); + } + return TRUE; } @@ -2641,166 +1566,6 @@ } } -/** Callback for when a buddy icon finished being downloaded. */ -static void -msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, - gpointer user_data, - const gchar *url_text, - gsize len, - const gchar *error_message) -{ - MsimUser *user; - - user = (MsimUser *)user_data; - - purple_debug_info("msim_downloaded_buddy_icon", - "Downloaded %d bytes\n", len); - - purple_buddy_icons_set_for_user(user->buddy->account, - user->buddy->name, - (gchar *)url_text, len, - /* Use URL itself as buddy icon "checksum" */ - user->image_url); -} - -/** Store a field of information about a buddy. */ -static void -msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user) -{ - if (!strcmp(key_str, "UserID") || !strcmp(key_str, "ContactID")) { - /* Save to buddy list, if it exists, for quick cached uid lookup with msim_uid2username_from_blist(). */ - if (user->buddy) - { - purple_debug_info("msim", "associating uid %s with username %s\n", key_str, user->buddy->name); - purple_blist_node_set_int(&user->buddy->node, "UserID", atol(value_str)); - } - /* Need to store in MsimUser, too? What if not on blist? */ - } else if (!strcmp(key_str, "Age")) { - user->age = atol(value_str); - } else if (!strcmp(key_str, "Gender")) { - user->gender = g_strdup(value_str); - } else if (!strcmp(key_str, "Location")) { - user->location = g_strdup(value_str); - } else if (!strcmp(key_str, "TotalFriends")) { - user->total_friends = atol(value_str); - } else if (!strcmp(key_str, "DisplayName")) { - user->display_name = g_strdup(value_str); - } else if (!strcmp(key_str, "BandName")) { - user->band_name = g_strdup(value_str); - } else if (!strcmp(key_str, "SongName")) { - user->song_name = g_strdup(value_str); - } else if (!strcmp(key_str, "UserName") || !strcmp(key_str, "IMName") || !strcmp(key_str, "NickName")) { - /* Ignore because PurpleBuddy knows this already */ - ; - } else if (!strcmp(key_str, "ImageURL") || !strcmp(key_str, "AvatarURL")) { - const gchar *previous_url; - - user->image_url = g_strdup(value_str); - - previous_url = purple_buddy_icons_get_checksum_for_user(user->buddy); - - /* Only download if URL changed */ - if (!previous_url || strcmp(previous_url, user->image_url)) { - purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user); - } - } else if (!strcmp(key_str, "Headline")) { - user->headline = g_strdup(value_str); - } else { - /* TODO: other fields in MsimUser */ - gchar *msg; - - msg = g_strdup_printf("msim_store_user_info_each: unknown field %s=%s", - key_str, value_str); - - msim_unrecognized(NULL, NULL, msg); - - g_free(msg); - } -} - -/** Save buddy information to the buddy list from a user info reply message. - * - * @param session - * @param msg The user information reply, with any amount of information. - * @param user The structure to save to, or NULL to save in PurpleBuddy->proto_data. - * - * Variable information is saved to the passed MsimUser structure. Permanent - * information (UserID) is stored in the blist node of the buddy list (and - * ends up in blist.xml, persisted to disk) if it exists. - * - * If the function has no buddy information, this function - * is a no-op (and returns FALSE). - * - */ -static gboolean -msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user) -{ - gchar *username; - MsimMessage *body, *body_node; - - g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); - g_return_val_if_fail(msg != NULL, FALSE); - - body = msim_msg_get_dictionary(msg, "body"); - if (!body) { - return FALSE; - } - - username = msim_msg_get_string(body, "UserName"); - - if (!username) { - purple_debug_info("msim", - "msim_process_reply: not caching body, no UserName\n"); - msim_msg_free(body); - g_free(username); - return FALSE; - } - - /* Null user = find and store in PurpleBuddy's proto_data */ - if (!user) { - user = msim_find_user(session, username); - if (!user) { - msim_msg_free(body); - g_free(username); - return FALSE; - } - } - - /* TODO: make looping over MsimMessage's easier. */ - for (body_node = body; - body_node != NULL; - body_node = msim_msg_get_next_element_node(body_node)) - { - const gchar *key_str; - gchar *value_str; - MsimMessageElement *elem; - - elem = (MsimMessageElement *)body_node->data; - key_str = elem->name; - - value_str = msim_msg_get_string_from_element(elem); - msim_store_user_info_each(key_str, value_str, user); - g_free(value_str); - } - - if (msim_msg_get_integer(msg, "dsn") == MG_OWN_IM_INFO_DSN && - msim_msg_get_integer(msg, "lid") == MG_OWN_IM_INFO_LID) { - /* TODO: do something with our own IM info, if we need it for some - * specific purpose. Otherwise it is available on the buddy list, - * if the user has themselves as their own buddy. - * - * However, much of the info is already available in MsimSession, - * stored in msim_we_are_logged_on(). */ - } else if (msim_msg_get_integer(msg, "dsn") == MG_OWN_MYSPACE_INFO_DSN && - msim_msg_get_integer(msg, "lid") == MG_OWN_MYSPACE_INFO_LID) { - /* TODO: same as above, but for MySpace info. */ - } - - msim_msg_free(body); - - return TRUE; -} - /** Process the initial server information from the server. */ static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg) @@ -2986,9 +1751,9 @@ */ list = msim_msg_get_list(msg, "msg"); - status_code = atoi(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE)); + status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE)); purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code); - status_headline = g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE); + status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE)); blist = purple_get_blist(); @@ -3012,7 +1777,8 @@ purple_debug_info("msim", "msim_status: found buddy %s\n", username); } - user->headline = g_strdup(status_headline); + /* don't copy; let the MsimUser own the headline, memory-wise */ + user->headline = status_headline; /* Set user status */ switch (status_code) { @@ -3034,9 +1800,9 @@ break; default: - purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n", + purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n", username, status_code); - purple_status_code = PURPLE_STATUS_AVAILABLE; + purple_status_code = PURPLE_STATUS_AVAILABLE; } purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL); @@ -3056,6 +1822,14 @@ } #endif + if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) { + /* Get information when they come online. + * TODO: periodically refresh? + */ + purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username); + msim_lookup_user(session, username, NULL, NULL); + } + g_free(username); msim_msg_list_free(list); @@ -3207,7 +1981,7 @@ msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) { - gchar *uid_field_name, *uid_before; + gchar *uid_field_name, *uid_before, *username; guint uid; MsimMessage *msg, *body; @@ -3222,6 +1996,19 @@ uid = msim_msg_get_integer(body, "UserID"); msim_msg_free(body); + username = msim_msg_get_string(msg, "_username"); + + if (!uid) { + gchar *msg; + + msg = g_strdup_printf(_("No such user: %s"), username); + purple_notify_error(NULL, NULL, _("User lookup"), msg); + g_free(msg); + g_free(username); + //msim_msg_free(msg); + return; + } + uid_field_name = msim_msg_get_string(msg, "_uid_field_name"); uid_before = msim_msg_get_string(msg, "_uid_before"); @@ -3238,6 +2025,7 @@ */ g_free(uid_field_name); g_free(uid_before); + g_free(username); //msim_msg_free(msg); } @@ -3532,7 +2320,7 @@ * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup * 2) data - make it an MsimMessage? */ -static guint +guint msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, gpointer data) { @@ -3580,79 +2368,7 @@ gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc); } -/* Session methods */ - -/** - * Create a new MSIM session. - * - * @param acct The account to create the session from. - * - * @return Pointer to a new session. Free with msim_session_destroy. - */ -MsimSession * -msim_session_new(PurpleAccount *acct) -{ - MsimSession *session; - - g_return_val_if_fail(acct != NULL, NULL); - - session = g_new0(MsimSession, 1); - - session->magic = MSIM_SESSION_STRUCT_MAGIC; - session->account = acct; - session->gc = purple_account_get_connection(acct); - session->sesskey = 0; - session->userid = 0; - session->username = NULL; - session->fd = -1; - - /* TODO: Remove. */ - session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, - g_direct_equal, NULL, NULL); /* do NOT free function pointers! (values) */ - session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, - g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are, - they could be integers inside gpointers - or strings, so I don't freed them. - Figure this out, once free cache. */ - - /* Created in msim_process_server_info() */ - session->server_info = NULL; - - session->rxoff = 0; - session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE); - session->next_rid = 1; - session->last_comm = time(NULL); - session->inbox_status = 0; - - return session; -} - -/** - * Free a session. - * - * @param session The session to destroy. - */ -void -msim_session_destroy(MsimSession *session) -{ - g_return_if_fail(MSIM_SESSION_VALID(session)); - - session->magic = -1; - - g_free(session->rxbuf); - g_free(session->username); - - /* TODO: Remove. */ - g_hash_table_destroy(session->user_lookup_cb); - g_hash_table_destroy(session->user_lookup_cb_data); - - if (session->server_info) { - msim_msg_free(session->server_info); - } - - g_free(session); -} - + /** * Close the connection. * @@ -3686,106 +2402,6 @@ /** - * Check if a string is a userid (all numeric). - * - * @param user The user id, email, or name. - * - * @return TRUE if is userid, FALSE if not. - */ -static gboolean -msim_is_userid(const gchar *user) -{ - g_return_val_if_fail(user != NULL, FALSE); - - return strspn(user, "0123456789") == strlen(user); -} - -/** - * Check if a string is an email address (contains an @). - * - * @param user The user id, email, or name. - * - * @return TRUE if is an email, FALSE if not. - * - * This function is not intended to be used as a generic - * means of validating email addresses, but to distinguish - * between a user represented by an email address from - * other forms of identification. - */ -static gboolean -msim_is_email(const gchar *user) -{ - g_return_val_if_fail(user != NULL, FALSE); - - return strchr(user, '@') != NULL; -} - - -/** - * Asynchronously lookup user information, calling callback when receive result. - * - * @param session - * @param user The user id, email address, or username. Not freed. - * @param cb Callback, called with user information when available. - * @param data An arbitray data pointer passed to the callback. - */ -/* TODO: change to not use callbacks */ -static void -msim_lookup_user(MsimSession *session, const gchar *user, - MSIM_USER_LOOKUP_CB cb, gpointer data) -{ - MsimMessage *body; - gchar *field_name; - guint rid, cmd, dsn, lid; - - g_return_if_fail(MSIM_SESSION_VALID(session)); - g_return_if_fail(user != NULL); - g_return_if_fail(cb != NULL); - - purple_debug_info("msim", "msim_lookup_userid: " - "asynchronously looking up <%s>\n", user); - - msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data); - - /* Setup callback. Response will be associated with request using 'rid'. */ - rid = msim_new_reply_callback(session, cb, data); - - /* Send request */ - - cmd = MSIM_CMD_GET; - - if (msim_is_userid(user)) { - field_name = "UserID"; - dsn = MG_MYSPACE_INFO_BY_ID_DSN; - lid = MG_MYSPACE_INFO_BY_ID_LID; - } else if (msim_is_email(user)) { - field_name = "Email"; - dsn = MG_MYSPACE_INFO_BY_STRING_DSN; - lid = MG_MYSPACE_INFO_BY_STRING_LID; - } else { - field_name = "UserName"; - dsn = MG_MYSPACE_INFO_BY_STRING_DSN; - lid = MG_MYSPACE_INFO_BY_STRING_LID; - } - - body = msim_msg_new( - field_name, MSIM_TYPE_STRING, g_strdup(user), - NULL); - - g_return_if_fail(msim_send(session, - "persist", MSIM_TYPE_INTEGER, 1, - "sesskey", MSIM_TYPE_INTEGER, session->sesskey, - "cmd", MSIM_TYPE_INTEGER, 1, - "dsn", MSIM_TYPE_INTEGER, dsn, - "uid", MSIM_TYPE_INTEGER, session->userid, - "lid", MSIM_TYPE_INTEGER, lid, - "rid", MSIM_TYPE_INTEGER, rid, - "body", MSIM_TYPE_DICTIONARY, body, - NULL)); -} - - -/** * Obtain the status text for a buddy. * * @param buddy The buddy to obtain status text for. @@ -3908,6 +2524,8 @@ group_name = msim_msg_get_string(contact_info, "GroupName"); if (group_name) { group = purple_group_new(group_name); + purple_debug_info("msim_add_contact_from_server_cb", + "adding to GroupName: %s\n", group_name); g_free(group_name); } else { group = purple_group_new(_("IM Friends")); @@ -3916,13 +2534,17 @@ /* 2. Get or create buddy */ buddy = purple_find_buddy(session->account, username); if (!buddy) { + purple_debug_info("msim_add_contact_from_server_cb", + "creating new buddy: %s\n", username); buddy = purple_buddy_new(session->account, username, NULL); } + /* Add group to beginning. See #2752. */ + purple_blist_add_group(group, NULL); + /* TODO: use 'Position' in contact_info to take into account where buddy is */ purple_blist_add_buddy(buddy, NULL, group, NULL /* insertion point */); - /* 3. Update buddy information */ user = msim_get_user_from_buddy(buddy); @@ -3943,14 +2565,14 @@ * * @return TRUE if added. * */ -static void +static gboolean msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info) { guint uid; const gchar *username; uid = msim_msg_get_integer(contact_info, "ContactID"); - g_return_if_fail(uid != 0); + g_return_val_if_fail(uid != 0, FALSE); /* Lookup the username, since NickName and IMName is unreliable */ username = msim_uid2username_from_blist(session, uid); @@ -3965,6 +2587,10 @@ } else { msim_add_contact_from_server_cb(session, NULL, (gpointer)msim_msg_clone(contact_info)); } + + /* Say that the contact was added, even if we're still looking up + * their username. */ + return TRUE; } /** Called when contact list is received from server. */ @@ -3972,12 +2598,16 @@ msim_got_contact_list(MsimSession *session, MsimMessage *reply, gpointer user_data) { MsimMessage *body, *body_node; + gchar *msg; + guint buddy_count; msim_msg_dump("msim_got_contact_list: reply=%s", reply); body = msim_msg_get_dictionary(reply, "body"); g_return_if_fail(body != NULL); + buddy_count = 0; + for (body_node = body; body_node != NULL; body_node = msim_msg_get_next_element_node(body_node)) @@ -3986,13 +2616,21 @@ elem = (MsimMessageElement *)body_node->data; - if (!strcmp(elem->name, "ContactID")) + if (g_str_equal(elem->name, "ContactID")) { /* Will look for first contact in body_node */ - msim_add_contact_from_server(session, body_node); + if (msim_add_contact_from_server(session, body_node)) { + ++buddy_count; + } } } + msg = g_strdup_printf(_("%d buddies were added or updated"), buddy_count); + + purple_notify_info(session->account, _("Add contacts from server"), msg, NULL); + + g_free(msg); + msim_msg_free(body); } @@ -4010,7 +2648,7 @@ completed = msim_msg_get_string(body, "Completed"); g_return_if_fail(body != NULL); msim_msg_free(body); - if (strcmp(completed, "True")) + if (!g_str_equal(completed, "True")) { purple_debug_info("msim_import_friends_cb", "failed to import friends: %s", completed); @@ -4263,7 +2901,7 @@ packed_expected = "\\bx\\WFhY\\k1\\v1\\k1\\42\\k1" "\\v43\\k1\\v52/1xxx/2yyy\\k1\\v7\\final\\"; - if (0 != strcmp(packed, packed_expected)) { + if (!g_str_equal(packed, packed_expected)) { purple_debug_info("msim", "!!!(%d), msim_msg_pack not what expected: %s != %s\n", ++failures, packed, packed_expected); } @@ -4273,7 +2911,7 @@ packed_cloned = msim_msg_pack(msg_cloned); purple_debug_info("msim", "msg cloned=%s\n", packed_cloned); - if (0 != strcmp(packed, packed_cloned)) { + if (!g_str_equal(packed, packed_cloned)) { purple_debug_info("msim", "!!!(%d), msim_msg_pack on cloned message not equal to original: %s != %s\n", ++failures, packed_cloned, packed); } @@ -4328,7 +2966,7 @@ escaped = msim_escape(raw); purple_debug_info("msim", "msim_test_escaping: raw=%s, escaped=%s\n", raw, escaped); expected = "hello/1world/2hello/1world"; - if (0 != strcmp(escaped, expected)) { + if (!g_str_equal(escaped, expected)) { purple_debug_info("msim", "!!!(%d), msim_escape failed: %s != %s\n", ++failures, escaped, expected); } @@ -4337,7 +2975,7 @@ unescaped = msim_unescape(escaped); g_free(escaped); purple_debug_info("msim", "msim_test_escaping: unescaped=%s\n", unescaped); - if (0 != strcmp(raw, unescaped)) { + if (!g_str_equal(raw, unescaped)) { purple_debug_info("msim", "!!!(%d), msim_unescape failed: %s != %s\n", ++failures, raw, unescaped); } @@ -4346,16 +2984,81 @@ } #endif +static gboolean +msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params) +{ + PurpleAccount *account; + GList *l; + gchar *uid_str, *cid_str; + guint uid, cid; + + if (g_ascii_strcasecmp(proto, "myim")) + return FALSE; + + /* Parameters are case-insensitive. */ + uid_str = g_hash_table_lookup(params, "uid"); + cid_str = g_hash_table_lookup(params, "cid"); + + uid = uid_str ? atol(uid_str) : 0; + cid = cid_str ? atol(cid_str) : 0; + + /* Need a contact. */ + g_return_val_if_fail(cid != 0, FALSE); + + /* Find our account with specified user id, or use first connected account if uid=0. */ + account = NULL; + l = purple_accounts_get_all(); + while (l) { + if (purple_account_is_connected(l->data) && + (uid == 0 || purple_account_get_int(l->data, "uid", 0) == uid)) { + account = l->data; + break; + } + l = l->next; + } + + if (!account) { + purple_notify_error(NULL, _("myim URL handler"), + _("No suitable MySpaceIM account could be found to open this myim URL."), + _("Enable the proper MySpaceIM account and try again.")); + return FALSE; + } + + /* TODO: msim_lookup_user() on cid, so can add by username? */ + + /* myim:sendIM?uID=USERID&cID=CONTACTID */ + if (!g_ascii_strcasecmp(cmd, "sendIM")) { + PurpleConversation *conv; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, cid_str, account); + if (!conv) { + purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", cid_str); + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, cid_str); + } + + /* Just open the window so the user can send an IM. */ + purple_conversation_present(conv); + + /* myim:addContact?uID=USERID&cID=CONTACTID */ + } else if (!g_ascii_strcasecmp(cmd, "addContact")) { + purple_blist_request_add_buddy(account, cid_str, _("Buddies"), NULL); + } + + return FALSE; +} + /** Initialize plugin. */ void init_plugin(PurplePlugin *plugin) { - PurpleAccountOption *option; #ifdef MSIM_SELF_TEST msim_test_all(); exit(0); #endif /* MSIM_SELF_TEST */ + PurpleAccountOption *option; + static gboolean initialized = FALSE; + /* TODO: default to automatically try different ports. Make the user be * able to set the first port to try (like LastConnectedPort in Windows client). */ @@ -4386,21 +3089,14 @@ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); #endif - /* TODO: /zap command. Problem with this is that there are different kinds of zaps, - * and the selection is best made available in a drop-down menu, instead of forcing - * the user to type the kind of zap and memorizing available zaps (or putting it in the - * help menu). A new "attention" API, for zap/buzz/nudge (different protocols) will - * solve this. */ -#if 0 - purple_cmd_register("zap", /* cmd */ - "w", /* args - accept a single word */ - PURPLE_CMD_P_PRPL, /* priority */ - PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY, /* flags */ - "prpl-myspace", /* prpl_id */ - msim_cmd_zap, /* func */ - _("zap: zap a user to get their attention"), /* helpstr */ - NULL); /* data */ -#endif + /* Code below only runs once. Based on oscar.c's oscar_init(). */ + if (initialized) + return; + + initialized = TRUE; + + purple_signal_connect(purple_get_core(), "uri-handler", &initialized, + PURPLE_CALLBACK(msim_uri_handler), NULL); } PURPLE_INIT_PLUGIN(myspace, init_plugin, info); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/myspace.h --- a/libpurple/protocols/myspace/myspace.h Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.h Sun Aug 26 19:08:29 2007 +0000 @@ -46,9 +46,15 @@ #include "util.h" /* for base64 */ #include "debug.h" /* for purple_debug_info */ #include "xmlnode.h" +#include "core.h" /* MySpaceIM includes */ +#include "persist.h" #include "message.h" +#include "session.h" +#include "zap.h" +#include "markup.h" +#include "user.h" /* Conditional compilation options */ /* Send third-party client version? (Recognized by us and Miranda's plugin) */ @@ -69,7 +75,7 @@ /* Use the attention API for zaps? */ /* Can't have until >=2.2.0, since is a new API. */ -/*#define MSIM_USE_ATTENTION_API */ +#define MSIM_USE_ATTENTION_API /* Constants */ @@ -90,7 +96,7 @@ #define MSIM_LANGUAGE_NAME_ENGLISH "ENGLISH" /* msimprpl version string of this plugin */ -#define MSIM_PRPL_VERSION_STRING "0.14" +#define MSIM_PRPL_VERSION_STRING "0.16" /* Default server */ #define MSIM_SERVER "im.myspace.akadns.net" @@ -157,23 +163,6 @@ #define MSIM_STATUS_CODE_IDLE 2 #define MSIM_STATUS_CODE_AWAY 5 -/* Text formatting bits for */ -#define MSIM_TEXT_BOLD 1 -#define MSIM_TEXT_ITALIC 2 -#define MSIM_TEXT_UNDERLINE 4 - -/* Default baseline size of purple's fonts, in points. What is size 3 in points. - * _font_scale specifies scaling factor relative to this point size. Note this - * is only the default; it is configurable in account options. */ -#define MSIM_BASE_FONT_POINT_SIZE 8 - -/* Default display's DPI. 96 is common but it can differ. Also configurable - * in account options. */ -#define MSIM_DEFAULT_DPI 96 - - -/* Random number in every MsimSession, to ensure it is valid. */ -#define MSIM_SESSION_STRUCT_MAGIC 0xe4a6752b /* Inbox status bitfield values for MsimSession.inbox_status */ #define MSIM_INBOX_MAIL (1 << 0) @@ -182,53 +171,6 @@ #define MSIM_INBOX_FRIEND_REQUEST (1 << 3) #define MSIM_INBOX_PICTURE_COMMENT (1 << 4) -/* Everything needed to keep track of a session (proto_data field in PurpleConnection) */ -typedef struct _MsimSession -{ - guint magic; /**< MSIM_SESSION_STRUCT_MAGIC */ - PurpleAccount *account; - PurpleConnection *gc; - guint sesskey; /**< Session key from server */ - guint userid; /**< This user's numeric user ID */ - gchar *username; /**< This user's unique username */ - gint fd; /**< File descriptor to/from server */ - - /* TODO: Remove. */ - GHashTable *user_lookup_cb; /**< Username -> userid lookup callback */ - GHashTable *user_lookup_cb_data; /**< Username -> userid lookup callback data */ - - MsimMessage *server_info; /**< Parameters from server */ - - gchar *rxbuf; /**< Receive buffer */ - guint rxoff; /**< Receive buffer offset */ - guint next_rid; /**< Next request/response ID */ - time_t last_comm; /**< Time received last communication */ - guint inbox_status; /**< Bit field of inbox notifications */ -} MsimSession; - -/* Check if an MsimSession is valid */ -#define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC) - -/* Hold ephemeral information about buddies, for proto_data of PurpleBuddy. */ -/* GHashTable? */ -typedef struct _MsimUser -{ - PurpleBuddy *buddy; - guint client_cv; - gchar *client_info; - guint age; - gchar *gender; - gchar *location; - guint total_friends; - gchar *headline; - gchar *display_name; - /* Note: uid is in &buddy->node (set_blist_node_int), since it never changes */ - gchar *username; - gchar *band_name, *song_name; - gchar *image_url; -} MsimUser; - - #ifdef MSIM_USE_ATTENTION_API #define MsimAttentionType PurpleAttentionType #else @@ -248,31 +190,17 @@ gchar *str_replace(const gchar *str, const gchar *old, const gchar *new); -/* Callback function pointer type for when a user's information is received, - * initiated from a user lookup. */ -typedef void (*MSIM_USER_LOOKUP_CB)(MsimSession *session, MsimMessage *userinfo, gpointer data); - /* Functions */ gboolean msim_load(PurplePlugin *plugin); GList *msim_status_types(PurpleAccount *acct); -GList *msim_attention_types(PurpleAccount *acct); -gboolean msim_send_attention(PurpleConnection *gc, gchar *username, guint code); - -GList *msim_blist_node_menu(PurpleBlistNode *node); - const gchar *msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy); - gboolean msim_send_raw(MsimSession *session, const gchar *msg); void msim_login(PurpleAccount *acct); - -int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, -PurpleMessageFlags flags); +int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags); +unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state); -typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); - -unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state); void msim_get_info(PurpleConnection *gc, const gchar *name); void msim_set_status(PurpleAccount *account, PurpleStatus *status); @@ -283,9 +211,6 @@ gboolean msim_offline_message(const PurpleBuddy *buddy); -MsimSession *msim_session_new(PurpleAccount *acct); -void msim_session_destroy(MsimSession *session); - void msim_close(PurpleConnection *gc); char *msim_status_text(PurpleBuddy *buddy); @@ -298,6 +223,13 @@ int msim_test_escaping(void); #endif +gboolean msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, int type); + + +void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note); +guint msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, gpointer data); + + void init_plugin(PurplePlugin *plugin); #endif /* !_MYSPACE_MYSPACE_H */ diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/persist.h --- a/libpurple/protocols/myspace/persist.h Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/persist.h Sun Aug 26 19:08:29 2007 +0000 @@ -42,8 +42,8 @@ /** Define a set of _DSN and _LID constants for a persistance request. */ #define MSIM_PERSIST_DSN_LID(name,dsn,lid) \ - const int name##_DSN = dsn; \ - const int name##_LID = lid; + static const int name##_DSN = dsn; \ + static const int name##_LID = lid; /* Can't do this, errors: * persist.h:51:3: error: '#' is not followed by a macro parameter diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/release.sh --- a/libpurple/protocols/myspace/release.sh Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/myspace/release.sh Sun Aug 26 19:08:29 2007 +0000 @@ -4,7 +4,7 @@ # Package a new msimprpl for release. Must be run with bash. -VERSION=0.14 +VERSION=0.16 make # Include 'myspace' directory in archive, so it can easily be unextracted # into ~/pidgin/libpurple/protocols at the correct location. diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/session.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/session.c Sun Aug 26 19:08:29 2007 +0000 @@ -0,0 +1,95 @@ +/* MySpaceIM Protocol Plugin, session + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "myspace.h" + +/* Session methods */ + +/** + * Create a new MSIM session. + * + * @param acct The account to create the session from. + * + * @return Pointer to a new session. Free with msim_session_destroy. + */ +MsimSession * +msim_session_new(PurpleAccount *acct) +{ + MsimSession *session; + + g_return_val_if_fail(acct != NULL, NULL); + + session = g_new0(MsimSession, 1); + + session->magic = MSIM_SESSION_STRUCT_MAGIC; + session->account = acct; + session->gc = purple_account_get_connection(acct); + session->sesskey = 0; + session->userid = 0; + session->username = NULL; + session->fd = -1; + + /* TODO: Remove. */ + session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, NULL); /* do NOT free function pointers! (values) */ + session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are, + they could be integers inside gpointers + or strings, so I don't freed them. + Figure this out, once free cache. */ + + /* Created in msim_process_server_info() */ + session->server_info = NULL; + + session->rxoff = 0; + session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE); + session->next_rid = 1; + session->last_comm = time(NULL); + session->inbox_status = 0; + + return session; +} + +/** + * Free a session. + * + * @param session The session to destroy. + */ +void +msim_session_destroy(MsimSession *session) +{ + g_return_if_fail(MSIM_SESSION_VALID(session)); + + session->magic = -1; + + g_free(session->rxbuf); + g_free(session->username); + + /* TODO: Remove. */ + g_hash_table_destroy(session->user_lookup_cb); + g_hash_table_destroy(session->user_lookup_cb_data); + + if (session->server_info) { + msim_msg_free(session->server_info); + } + + g_free(session); +} + diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/session.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/session.h Sun Aug 26 19:08:29 2007 +0000 @@ -0,0 +1,57 @@ +/* MySpaceIM Protocol Plugin, session + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MYSPACE_SESSION_H +#define _MYSPACE_SESSION_H + +/* Random number in every MsimSession, to ensure it is valid. */ +#define MSIM_SESSION_STRUCT_MAGIC 0xe4a6752b + +/* Everything needed to keep track of a session (proto_data field in PurpleConnection) */ +typedef struct _MsimSession +{ + guint magic; /**< MSIM_SESSION_STRUCT_MAGIC */ + PurpleAccount *account; + PurpleConnection *gc; + guint sesskey; /**< Session key from server */ + guint userid; /**< This user's numeric user ID */ + gchar *username; /**< This user's unique username */ + gint fd; /**< File descriptor to/from server */ + + /* TODO: Remove. */ + GHashTable *user_lookup_cb; /**< Username -> userid lookup callback */ + GHashTable *user_lookup_cb_data; /**< Username -> userid lookup callback data */ + + MsimMessage *server_info; /**< Parameters from server */ + + gchar *rxbuf; /**< Receive buffer */ + guint rxoff; /**< Receive buffer offset */ + guint next_rid; /**< Next request/response ID */ + time_t last_comm; /**< Time received last communication */ + guint inbox_status; /**< Bit field of inbox notifications */ +} MsimSession; + +/* Check if an MsimSession is valid */ +#define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC) + + +MsimSession *msim_session_new(PurpleAccount *acct); +void msim_session_destroy(MsimSession *session); + +#endif /* !_MYSPACE_SESSION_H */ diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/zap.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/zap.c Sun Aug 26 19:08:29 2007 +0000 @@ -0,0 +1,269 @@ +/* MySpaceIM Protocol Plugin - zap support + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "myspace.h" +#include "zap.h" + +static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code); +static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr); + + +/** Get zap types. */ +GList * +msim_attention_types(PurpleAccount *acct) +{ + static GList *types = NULL; + MsimAttentionType* attn; + + if (!types) { +#define _MSIM_ADD_NEW_ATTENTION(icn, nme, incoming, outgoing) \ + attn = g_new0(MsimAttentionType, 1); \ + attn->icon_name = icn; \ + attn->name = nme; \ + attn->incoming_description = incoming; \ + attn->outgoing_description = outgoing; \ + types = g_list_append(types, attn); + + /* TODO: icons for each zap */ + _MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking")); + _MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing")); + } + + return types; +} + +/** Send a zap */ +gboolean +msim_send_attention(PurpleConnection *gc, const gchar *username, guint code) +{ + GList *types; + MsimSession *session; + MsimAttentionType *attn; + PurpleBuddy *buddy; + + session = (MsimSession *)gc->proto_data; + + /* Look for this attention type, by the code index given. */ + types = msim_attention_types(gc->account); + attn = (MsimAttentionType *)g_list_nth_data(types, code); + + if (!attn) { + purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code); + return FALSE; + } + + buddy = purple_find_buddy(session->account, username); + if (!buddy) { + return FALSE; + } + + /* TODO: make use of the MsimAttentionType we found, instead of + * doing it all over in msim_send_zap_from_menu. */ + msim_send_zap_from_menu(&buddy->node, GUINT_TO_POINTER(code)); + + return TRUE; +} + +/** Send a zap to a user. */ +static gboolean +msim_send_zap(MsimSession *session, const gchar *username, guint code) +{ + gchar *zap_string; +#ifndef MSIM_USE_ATTENTION_API + gchar *zap_description; +#endif + GList *types; + MsimAttentionType *attn; + gboolean rc; + + g_return_val_if_fail(session != NULL, FALSE); + g_return_val_if_fail(username != NULL, FALSE); + + types = msim_attention_types(session->account); + + attn = g_list_nth_data(types, code); + if (!attn) { + return FALSE; + } + + +#ifdef MSIM_USE_ATTENTION_API + serv_got_attention(session->gc, username, attn, FALSE); +#else + zap_description = g_strdup_printf("*** Attention: %s %s ***", attn->outgoing_description, + username); + + serv_got_im(session->gc, username, zap_description, + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM, time(NULL)); + + g_free(zap_description); +#endif + + /* 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)) { + purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s", + username, zap_string); + rc = FALSE; + } else { + rc = TRUE; + } + + g_free(zap_string); + + return rc; + +} + +/** Zap someone. Callback from msim_blist_node_menu zap menu. */ +static void +msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr) +{ + PurpleBuddy *buddy; + PurpleAccount *account; + PurpleConnection *gc; + MsimSession *session; + guint zap; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + /* Only know about buddies for now. */ + return; + } + + g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); + + buddy = (PurpleBuddy *)node; + + /* Find the session */ + account = buddy->account; + gc = purple_account_get_connection(account); + session = (MsimSession *)gc->proto_data; + + zap = GPOINTER_TO_INT(zap_num_ptr); + + g_return_if_fail(msim_send_zap(session, buddy->name, zap)); +} + +/** Return menu, if any, for a buddy list node. */ +GList * +msim_blist_node_menu(PurpleBlistNode *node) +{ + GList *menu, *zap_menu; + GList *types; + PurpleMenuAction *act; + /* Warning: hardcoded to match that in msim_attention_types. */ + const gchar *zap_names[10]; + guint i; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) { + /* Only know about buddies for now. */ + return NULL; + } + + /* Names from official client. */ + types = msim_attention_types(NULL); + i = 0; + do + { + MsimAttentionType *attn; + + attn = (MsimAttentionType *)types->data; + zap_names[i] = attn->name; + ++i; + } while ((types = g_list_next(types))); + + menu = zap_menu = NULL; + + /* TODO: get rid of once is accessible directly in GUI */ + for (i = 0; i < sizeof(zap_names) / sizeof(zap_names[0]); ++i) { + act = purple_menu_action_new(zap_names[i], PURPLE_CALLBACK(msim_send_zap_from_menu), + GUINT_TO_POINTER(i), NULL); + zap_menu = g_list_append(zap_menu, act); + } + + act = purple_menu_action_new(_("Zap"), NULL, NULL, zap_menu); + menu = g_list_append(menu, act); + + return menu; +} + +/** Process an incoming zap. */ +gboolean +msim_incoming_zap(MsimSession *session, MsimMessage *msg) +{ + gchar *msg_text, *username; + gint zap; + const gchar *zap_past_tense[10]; +#ifdef MSIM_USE_ATTENTION_API + MsimAttentionType attn; +#else + gchar *zap_text; +#endif + + zap_past_tense[0] = _("zapped"); + zap_past_tense[1] = _("whacked"); + zap_past_tense[2] = _("torched"); + zap_past_tense[3] = _("smooched"); + zap_past_tense[4] = _("hugged"); + zap_past_tense[5] = _("bslapped"); + zap_past_tense[6] = _("goosed"); + zap_past_tense[7] = _("hi-fived"); + zap_past_tense[8] = _("punk'd"); + zap_past_tense[9] = _("raspberried"); + + msg_text = msim_msg_get_string(msg, "msg"); + username = msim_msg_get_string(msg, "_username"); + + g_return_val_if_fail(msg_text != NULL, FALSE); + g_return_val_if_fail(username != NULL, FALSE); + + g_return_val_if_fail(sscanf(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", &zap) == 1, FALSE); + + zap = CLAMP(zap, 0, sizeof(zap_past_tense) / sizeof(zap_past_tense[0])); + + /* TODO:ZAP: use msim_attention_types */ +#ifdef MSIM_USE_ATTENTION_API + attn.incoming_description = zap_past_tense[zap]; + attn.outgoing_description = NULL; + attn.icon_name = NULL; /* TODO: icon */ + + serv_got_attention(session->gc, username, &attn, TRUE); +#else + zap_text = g_strdup_printf(_("*** You have been %s! ***"), zap_past_tense[zap]); + serv_got_im(session->gc, username, zap_text, + PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(zap_text); +#endif + + g_free(msg_text); + g_free(username); + + return TRUE; +} + + diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/myspace/zap.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/myspace/zap.h Sun Aug 26 19:08:29 2007 +0000 @@ -0,0 +1,28 @@ +/* MySpaceIM Protocol Plugin - zap support + * + * Copyright (C) 2007, Jeff Connelly + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MYSPACE_ZAP_H +#define _MYSPACE_ZAP_H + +GList *msim_attention_types(PurpleAccount *acct); +gboolean msim_send_attention(PurpleConnection *gc, const gchar *username, guint code); +GList *msim_blist_node_menu(PurpleBlistNode *node); +gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); + +#endif /* !_MYSPACE_ZAP_H */ diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.c Sun Aug 26 19:08:29 2007 +0000 @@ -3529,6 +3529,7 @@ PurpleConnection *gc; PurpleAccount *account; PurpleStatus *status; + PurplePresence *presence; const char *message, *itmsurl; char *tmp; va_list ap; @@ -3572,7 +3573,8 @@ aim_srv_setextrainfo(od, FALSE, 0, TRUE, tmp, itmsurl); g_free(tmp); - aim_srv_setidle(od, 0); + presence = purple_status_get_presence(status); + aim_srv_setidle(od, purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence)); if (od->icq) { aim_icq_reqofflinemsgs(od); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/sametime/sametime.c --- a/libpurple/protocols/sametime/sametime.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/sametime/sametime.c Sun Aug 26 19:08:29 2007 +0000 @@ -3183,13 +3183,12 @@ PurpleConnection *gc; struct mwPurplePluginData *pd; struct mwAwareIdBlock t = { mwAware_USER, b->name, NULL }; - const char *ret; - - gc = b->account->gc; - pd = gc->proto_data; - - ret = mwServiceAware_getText(pd->srvc_aware, &t); - + const char *ret = NULL; + + if ((gc = purple_account_get_connection(b->account)) + && (pd = gc->proto_data)) + ret = mwServiceAware_getText(pd->srvc_aware, &t); + return (ret && g_utf8_validate(ret, -1, NULL)) ? g_markup_escape_text(ret, -1): NULL; } @@ -3242,17 +3241,17 @@ static void mw_prpl_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) { PurpleConnection *gc; - struct mwPurplePluginData *pd; + struct mwPurplePluginData *pd = NULL; struct mwAwareIdBlock idb = { mwAware_USER, b->name, NULL }; - const char *message; + const char *message = NULL; const char *status; char *tmp; - gc = b->account->gc; - pd = gc->proto_data; - - message = mwServiceAware_getText(pd->srvc_aware, &idb); + if ((gc = purple_account_get_connection(b->account)) + && (pd = gc->proto_data)) + message = mwServiceAware_getText(pd->srvc_aware, &idb); + status = status_text(b); if(message != NULL && g_utf8_validate(message, -1, NULL) && purple_utf8_strcasecmp(status, message)) { @@ -3264,7 +3263,7 @@ purple_notify_user_info_add_pair(user_info, _("Status"), status); } - if(full) { + if(full && pd != NULL) { tmp = user_supports_text(pd->srvc_aware, b->name); if(tmp) { purple_notify_user_info_add_pair(user_info, _("Supports"), tmp); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sun Aug 26 19:08:29 2007 +0000 @@ -238,14 +238,18 @@ case 8: /* how many online buddies we have */ break; case 7: /* the current buddy */ - if (name && f) /* update the previous buddy before changing the variables */ - yahoo_update_status(gc, name, f); - name = pair->value; - if (name && g_utf8_validate(name, -1, NULL)) + /* update the previous buddy before changing the variables */ + if (f) { + if (message) + yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode)); + if (name) + yahoo_update_status(gc, name, f); + } + name = message = NULL; + f = NULL; + if (pair->value && g_utf8_validate(pair->value, -1, NULL)) { + name = pair->value; f = yahoo_friend_find_or_new(gc, name); - else { - f = NULL; - name = NULL; } break; case 10: /* state */ @@ -779,7 +783,7 @@ purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL)); g_free(buf); } - + } @@ -2920,7 +2924,7 @@ purple_connection_set_display_name(gc, purple_account_get_username(account)); yd->fd = -1; - yd->txhandler = -1; + yd->txhandler = 0; /* TODO: Is there a good grow size for the buffer? */ yd->txbuf = purple_circ_buffer_new(0); yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/yahoo/yahoo_packet.c --- a/libpurple/protocols/yahoo/yahoo_packet.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.c Sun Aug 26 19:08:29 2007 +0000 @@ -294,7 +294,7 @@ if (writelen == 0) { purple_input_remove(yd->txhandler); - yd->txhandler = -1; + yd->txhandler = 0; return; } @@ -355,7 +355,7 @@ len = yahoo_packet_build(pkt, 0, yd->wm, yd->jp, &data); yahoo_packet_dump(data, len); - if (yd->txhandler == -1) + if (yd->txhandler == 0) ret = write(yd->fd, data, len); else { ret = -1; @@ -371,7 +371,7 @@ } if (ret < len) { - if (yd->txhandler == -1) + if (yd->txhandler == 0) yd->txhandler = purple_input_add(yd->fd, PURPLE_INPUT_WRITE, yahoo_packet_send_can_write, yd); purple_circ_buffer_append(yd->txbuf, data + ret, len - ret); diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/protocols/yahoo/yahoochat.c --- a/libpurple/protocols/yahoo/yahoochat.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoochat.c Sun Aug 26 19:08:29 2007 +0000 @@ -62,8 +62,9 @@ } pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE,0); - yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc), - 109, purple_connection_get_display_name(gc), 6, "abcde"); + yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), + 109, purple_connection_get_display_name(gc), 6, "abcde", + 135, "ym8.1.0.415"); yahoo_packet_send_and_free(pkt, yd); } @@ -155,7 +156,7 @@ if (members) { g_hash_table_replace(components, g_strdup("members"), g_strdup(members->str)); } - if (!yahoo_privacy_check(gc, who) || + if (!yahoo_privacy_check(gc, who) || (purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) { purple_debug_info("yahoo", "Invite to conference %s from %s has been dropped.\n", room, who); @@ -640,7 +641,7 @@ GList *w; purple_debug_misc("yahoo", "leaving conference %s\n", room); - + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash_str(pkt, 1, dn); @@ -732,7 +733,7 @@ continue; yahoo_packet_hash(pkt, "ss", 52, name, 53, name); } - + yahoo_packet_send_and_free(pkt, yd); g_free(msg2); } diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/prpl.h --- a/libpurple/prpl.h Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/prpl.h Sun Aug 26 19:08:29 2007 +0000 @@ -30,6 +30,7 @@ #define _PURPLE_PRPL_H_ typedef struct _PurplePluginProtocolInfo PurplePluginProtocolInfo; +typedef struct _PurpleAttentionType PurpleAttentionType; /**************************************************************************/ /** @name Basic Protocol Information */ @@ -91,6 +92,14 @@ gboolean secret; }; +struct _PurpleAttentionType +{ + const char *icon_name; /**< Icon to display (optional) */ + const char *name; /**< Shown in GUI elements */ + const char *incoming_description; /**< Shown when sent */ + const char *outgoing_description; /**< Shown when receied */ +}; + /** * Protocol options * @@ -332,8 +341,10 @@ /* room list serialize */ char *(*roomlist_room_serialize)(PurpleRoomlistRoom *room); - void (*_purple_reserved1)(void); - void (*_purple_reserved2)(void); + /* Attention API for sending & receiving zaps/nudges/buzzes etc. */ + gboolean (*send_attention)(PurpleConnection *gc, + const char *username, guint type); + GList *(*attention_types)(PurpleAccount *acct); void (*_purple_reserved3)(void); void (*_purple_reserved4)(void); }; diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/request.c --- a/libpurple/request.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/request.c Sun Aug 26 19:08:29 2007 +0000 @@ -1172,7 +1172,7 @@ void * purple_request_choice(void *handle, const char *title, const char *primary, - const char *secondary, unsigned int default_value, + const char *secondary, int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, @@ -1197,7 +1197,7 @@ void * purple_request_choice_varg(void *handle, const char *title, const char *primary, const char *secondary, - unsigned int default_value, + int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, @@ -1233,7 +1233,7 @@ void * purple_request_action(void *handle, const char *title, const char *primary, - const char *secondary, unsigned int default_action, + const char *secondary, int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, ...) { @@ -1254,7 +1254,7 @@ void * purple_request_action_varg(void *handle, const char *title, const char *primary, const char *secondary, - unsigned int default_action, + int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, va_list actions) { diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/request.h --- a/libpurple/request.h Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/request.h Sun Aug 26 19:08:29 2007 +0000 @@ -190,13 +190,13 @@ PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data); void *(*request_choice)(const char *title, const char *primary, - const char *secondary, unsigned int default_value, + const char *secondary, int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, va_list choices); void *(*request_action)(const char *title, const char *primary, - const char *secondary, unsigned int default_action, + const char *secondary, int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, va_list actions); @@ -1215,7 +1215,7 @@ * @param cancel_cb The callback for the @c Cancel button. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * @param ... The choices. This argument list should be * terminated with a NULL parameter. @@ -1224,7 +1224,7 @@ */ void *purple_request_choice(void *handle, const char *title, const char *primary, const char *secondary, - unsigned int default_value, + int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, @@ -1246,7 +1246,7 @@ * @param cancel_cb The callback for the @c Cancel button. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * @param choices The choices. This argument list should be * terminated with a @c NULL parameter. @@ -1255,7 +1255,7 @@ */ void *purple_request_choice_varg(void *handle, const char *title, const char *primary, const char *secondary, - unsigned int default_value, + int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, @@ -1275,7 +1275,7 @@ * @param default_action The default value. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * @param action_count The number of actions. * @param ... A list of actions. These are pairs of @@ -1290,7 +1290,7 @@ */ void *purple_request_action(void *handle, const char *title, const char *primary, const char *secondary, - unsigned int default_action, + int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, ...); @@ -1308,7 +1308,7 @@ * @param default_action The default value. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * @param action_count The number of actions. * @param actions A list of actions and callbacks. @@ -1317,7 +1317,7 @@ */ void *purple_request_action_varg(void *handle, const char *title, const char *primary, const char *secondary, - unsigned int default_action, + int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, va_list actions); @@ -1338,7 +1338,7 @@ * @param cancel_cb The callback for the @c Cancel button. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy associated with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * * @return A UI-specific handle. @@ -1411,7 +1411,7 @@ * @param cancel_cb The callback for the @c Cancel button. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * * @return A UI-specific handle. @@ -1435,7 +1435,7 @@ * @param cancel_cb The callback for the @c Cancel button. * @param account The PurpleAccount associated with this request, or NULL if none is * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param conv The PurpleConversation associated with this request, or NULL if none is * @param user_data The data to pass to the callback. * * @return A UI-specific handle. diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/server.c --- a/libpurple/server.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/server.c Sun Aug 26 19:08:29 2007 +0000 @@ -242,6 +242,75 @@ } } +/** Indicate that an attention message was sent or received. */ +void +serv_got_attention(PurpleConnection *gc, const char *who, PurpleAttentionType *attn, gboolean incoming) +{ + PurpleConversation *conv; + PurpleMessageFlags flags; + gchar *description; + int plugin_return; + + + /* For incoming messages, block the attention message if requested (privacy) */ + if (incoming) { + gchar *who_copy; + + if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->set_permit_deny == NULL) + if (!purple_privacy_check(gc->account, who)) + return; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, gc->account); + + who_copy = g_strdup(who); + + plugin_return = GPOINTER_TO_INT( + purple_signal_emit_return_1(purple_conversations_get_handle(), + "receiving-im-msg", gc->account, + &who_copy, &attn, conv)); + + if (!attn || !who_copy || plugin_return) { + g_free(who_copy); + return; + } + + purple_signal_emit(purple_conversations_get_handle(), "received-im-msg", gc->account, + who, attn, conv); + } + + /* The attention message was allowed. Create a string representing the message. */ + flags = PURPLE_MESSAGE_SYSTEM; + + /* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display + * it next to the attention command. And if it is null, display a generic icon. */ + + if (incoming) { + if (attn->incoming_description) { + description = g_strdup_printf(_("Attention! You have been %s."), attn->incoming_description); + } else { + description = g_strdup(_("Attention!")); + } + flags |= PURPLE_MESSAGE_RECV; + } else { + if (attn->outgoing_description) { + description = g_strdup_printf(_("Attention! %s %s."), attn->outgoing_description, who); + } else { + description = g_strdup(_("Attention!")); + } + flags |= PURPLE_MESSAGE_SEND; + } + + /* Display it in the conversation window to the user. */ + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, gc->account); + if (!conv) + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, gc->account, who); + + purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, description, flags, time(NULL)); + + g_free(description); +} + + /* * Move a buddy from one group to another on server. * diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/server.h --- a/libpurple/server.h Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/server.h Sun Aug 26 19:08:29 2007 +0000 @@ -67,6 +67,7 @@ int serv_chat_send(PurpleConnection *, int, const char *, PurpleMessageFlags flags); void serv_alias_buddy(PurpleBuddy *); void serv_got_alias(PurpleConnection *gc, const char *who, const char *alias); +void serv_got_attention(PurpleConnection *gc, const char *who, struct _PurpleAttentionType *attn, gboolean incoming); /** * Receive a typing message from a remote user. Either PURPLE_TYPING diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/util.c --- a/libpurple/util.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/util.c Sun Aug 26 19:08:29 2007 +0000 @@ -1374,7 +1374,7 @@ g_string_free(cdata, TRUE); cdata = NULL; } - + } if(tags == tag) break; @@ -1425,7 +1425,7 @@ ALLOW_TAG("strong"); ALLOW_TAG("ul"); - + /* we skip
because it's not legal in XHTML-IM. However, * we still want to send something sensible, so we put a * linebreak in its place.
also needs special handling @@ -2539,7 +2539,7 @@ * people's settings if there is a problem writing the new values. */ gboolean -purple_util_write_data_to_file(const char *filename, const char *data, size_t size) +purple_util_write_data_to_file(const char *filename, const char *data, gssize size) { const char *user_dir = purple_user_dir(); gchar *filename_temp, *filename_full; @@ -4305,7 +4305,7 @@ } } -gboolean purple_message_meify(char *message, size_t len) +gboolean purple_message_meify(char *message, gssize len) { char *c; gboolean inside_html = FALSE; diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/util.h --- a/libpurple/util.h Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/util.h Sun Aug 26 19:08:29 2007 +0000 @@ -586,7 +586,7 @@ * @return TRUE if the file was written successfully. FALSE otherwise. */ gboolean purple_util_write_data_to_file(const char *filename, const char *data, - size_t size); + gssize size); /** * Read the contents of a given file and parse the results into an @@ -1113,7 +1113,7 @@ * @return TRUE if it starts with "/me ", and it has been removed, otherwise * FALSE */ -gboolean purple_message_meify(char *message, size_t len); +gboolean purple_message_meify(char *message, gssize len); /** * Removes the underscore characters from a string used identify the mnemonic diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/win32/global.mak --- a/libpurple/win32/global.mak Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/win32/global.mak Sun Aug 26 19:08:29 2007 +0000 @@ -11,7 +11,7 @@ # Locations of our various dependencies WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev ASPELL_TOP ?= $(WIN32_DEV_TOP)/aspell-dev-0-50-3-3 -GTKSPELL_TOP ?= $(WIN32_DEV_TOP)/gtkspell-2.0.6 +GTKSPELL_TOP ?= $(WIN32_DEV_TOP)/gtkspell-2.0.11 GTK_TOP ?= $(WIN32_DEV_TOP)/gtk_2_0 GTK_BIN ?= $(GTK_TOP)/bin BONJOUR_TOP ?= $(WIN32_DEV_TOP)/Bonjour_SDK diff -r 90a41215bcb1 -r a650c8294bd2 libpurple/xmlnode.c --- a/libpurple/xmlnode.c Sun Aug 26 19:07:22 2007 +0000 +++ b/libpurple/xmlnode.c Sun Aug 26 19:08:29 2007 +0000 @@ -272,6 +272,8 @@ if(NULL != node->parent) { if(node->parent->child == node) { node->parent->child = node->next; + if (node->parent->lastchild == node) + node->parent->lastchild = node->next; } else { xmlnode *prev = node->parent->child; while(prev && prev->next != node) { @@ -279,6 +281,8 @@ } if(prev) { prev->next = node->next; + if (node->parent->lastchild == node) + node->parent->lastchild = prev; } } } diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/gtkaccount.c Sun Aug 26 19:08:29 2007 +0000 @@ -272,7 +272,8 @@ add_user_options(dialog, dialog->top_vbox); add_protocol_options(dialog, dialog->bottom_vbox); - if (!dialog->prpl_info || !dialog->prpl_info->register_user) { + if (!dialog->prpl_info || !dialog->prpl_info->register_user || + g_object_get_data(G_OBJECT(item), "fake")) { gtk_widget_hide(dialog->register_button); } else { if (dialog->prpl_info != NULL && @@ -1394,7 +1395,9 @@ purple_signal_emit(pidgin_account_get_handle(), "account-modified", account); /* If this is a new account, then sign on! */ - if (new && !dialog->registering) { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->register_button))) { + purple_account_register(account); + } else if (new) { const PurpleSavedStatus *saved_status; saved_status = purple_savedstatus_get_current(); @@ -1410,19 +1413,6 @@ return account; } -static void -register_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog) -{ - PurpleAccount *account; - - dialog->registering = TRUE; - - account = ok_account_prefs_cb(NULL, dialog); - - purple_account_register(account); -} - - static const GtkTargetEntry dnd_targets[] = { {"text/plain", 0, 0}, {"text/uri-list", 0, 1}, @@ -1501,6 +1491,18 @@ add_login_options(dialog, vbox); add_user_options(dialog, vbox); + button = gtk_check_button_new_with_label(_("Create this new account on the server")); + gtk_box_pack_start(GTK_BOX(main_vbox), button, FALSE, FALSE, 0); + gtk_widget_show(button); + dialog->register_button = button; + if (dialog->account == NULL) + gtk_widget_set_sensitive(button, FALSE); + + if (!dialog->prpl_info || !dialog->prpl_info->register_user) + gtk_widget_hide(button); + + + /* Setup the page with 'Advanced'. */ dialog->bottom_vbox = dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_container_set_border_width(GTK_CONTAINER(dbox), PIDGIN_HIG_BORDER); @@ -1519,22 +1521,6 @@ gtk_box_pack_end(GTK_BOX(main_vbox), bbox, FALSE, TRUE, 0); gtk_widget_show(bbox); - /* Register button */ - button = gtk_button_new_with_label(_("Register")); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(register_account_prefs_cb), dialog); - - dialog->register_button = button; - - if (dialog->account == NULL) - gtk_widget_set_sensitive(button, FALSE); - - if (!dialog->prpl_info || !dialog->prpl_info->register_user) - gtk_widget_hide(button); - /* Cancel button */ button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/gtkrequest.c --- a/pidgin/gtkrequest.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/gtkrequest.c Sun Aug 26 19:08:29 2007 +0000 @@ -442,7 +442,7 @@ static void * pidgin_request_choice(const char *title, const char *primary, - const char *secondary, unsigned int default_value, + const char *secondary, int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, @@ -548,7 +548,7 @@ static void * pidgin_request_action(const char *title, const char *primary, - const char *secondary, unsigned int default_action, + const char *secondary, int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, va_list actions) { @@ -1083,7 +1083,7 @@ data->cbs[0] = ok_cb; data->cbs[1] = cancel_cb; - + #ifdef _WIN32 data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; #else /* !_WIN32 */ diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/gtksavedstatuses.c --- a/pidgin/gtksavedstatuses.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/gtksavedstatuses.c Sun Aug 26 19:08:29 2007 +0000 @@ -1450,7 +1450,9 @@ GtkWidget *win; GtkTreeIter iter; GtkCellRenderer *rend; - const char *status_id = NULL; + char *status_id = NULL; + char *message = NULL; + gboolean parent_dialog_has_substatus = FALSE; GList *list; gboolean select = FALSE; @@ -1553,25 +1555,29 @@ G_CALLBACK(substatus_editor_ok_cb), dialog); /* Seed the input widgets with the current values */ - /* TODO: Get the current values from our parent's list store, not the saved_status! */ - if (status_editor->original_title != NULL) - { + + /* Only look at the saved status if we can't find it in the parent status dialog's substatuses model */ + gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter, + STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &parent_dialog_has_substatus, -1); + if (parent_dialog_has_substatus) { + gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter, + STATUS_EDITOR_COLUMN_STATUS_ID, &status_id, + STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message, -1); + } else if (status_editor->original_title != NULL) { PurpleSavedStatus *saved_status = NULL; PurpleSavedStatusSub *substatus = NULL; - saved_status = purple_savedstatus_find(status_editor->original_title); - if (saved_status != NULL) - substatus = purple_savedstatus_get_substatus(saved_status, account); + if ((saved_status = purple_savedstatus_find(status_editor->original_title)) != NULL) { + if ((substatus = purple_savedstatus_get_substatus(saved_status, account)) != NULL) { + message = (char *)purple_savedstatus_substatus_get_message(substatus); + status_id = (char *)purple_status_type_get_id(purple_savedstatus_substatus_get_type(substatus)); + } + } + } + /* TODO: Else get the generic status type from our parent */ - if (substatus != NULL) - { - gtk_imhtml_append_text(dialog->message, - purple_savedstatus_substatus_get_message(substatus), - 0); - status_id = purple_status_type_get_id(purple_savedstatus_substatus_get_type(substatus)); - } - /* TODO: Else get the generic status type from our parent */ - } + if (message) + gtk_imhtml_append_text(dialog->message, message, 0); for (list = purple_account_get_status_types(account); list; list = list->next) { @@ -1607,6 +1613,12 @@ if (!select) gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); + if (parent_dialog_has_substatus) { + /* These two were gotten from the parent tree model, so they need to be freed */ + g_free(status_id); + g_free(message); + } + gtk_widget_show_all(win); } diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/gtksound.c --- a/pidgin/gtksound.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/gtksound.c Sun Aug 26 19:08:29 2007 +0000 @@ -364,14 +364,14 @@ GError *err = NULL; switch (GST_MESSAGE_TYPE (msg)) { - case GST_MESSAGE_EOS: - gst_element_set_state(play, GST_STATE_NULL); - gst_object_unref(GST_OBJECT(play)); - break; case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &err, NULL); purple_debug_error("gstreamer", "%s\n", err->message); g_error_free(err); + /* fall-through and clean up */ + case GST_MESSAGE_EOS: + gst_element_set_state(play, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(play)); break; case GST_MESSAGE_WARNING: gst_message_parse_warning(msg, &err, NULL); diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/gtkstatusbox.c --- a/pidgin/gtkstatusbox.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/gtkstatusbox.c Sun Aug 26 19:08:29 2007 +0000 @@ -1415,15 +1415,15 @@ } -static void -toggled_cb(GtkWidget *widget, PidginStatusBox *box) +static +gboolean +toggled_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box) { - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { if (!box->popup_in_progress) pidgin_status_box_popup (box); - } else { - pidgin_status_box_popdown(box); - } + else + pidgin_status_box_popdown(box); +return TRUE; } static void @@ -1773,7 +1773,7 @@ g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event", G_CALLBACK(button_released_cb), status_box); #endif - g_signal_connect(G_OBJECT(status_box->toggle_button), "toggled", + g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event", G_CALLBACK(toggled_cb), status_box); g_signal_connect(G_OBJECT(buffer), "changed", G_CALLBACK(imhtml_changed_cb), status_box); g_signal_connect(G_OBJECT(status_box->imhtml), "format_function_toggle", diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/plugins/musicmessaging/musicmessaging.c --- a/pidgin/plugins/musicmessaging/musicmessaging.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/plugins/musicmessaging/musicmessaging.c Sun Aug 26 19:08:29 2007 +0000 @@ -71,10 +71,10 @@ /* Globals */ /* List of sessions */ -GList *conversations; +static GList *conversations; /* Pointer to this plugin */ -PurplePlugin *plugin_pointer; +static PurplePlugin *plugin_pointer; /* Define types needed for DBus */ DBusGConnection *connection; @@ -350,7 +350,16 @@ static gboolean intercept_received(PurpleAccount *account, char **sender, char **message, PurpleConversation *conv, int *flags) { - MMConversation *mmconv = mmconv_from_conv(conv); + MMConversation *mmconv; + + if (conv == NULL) { + /* XXX: This is just to avoid a crash (#2726). + * We may want to create the conversation instead of returning from here + */ + return FALSE; + } + + mmconv = mmconv_from_conv(conv); purple_debug_misc("purple-musicmessaging", "Intercepted: %s\n", *message); if (strstr(*message, MUSICMESSAGING_PREFIX)) diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/plugins/spellchk.c --- a/pidgin/plugins/spellchk.c Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/plugins/spellchk.c Sun Aug 26 19:08:29 2007 +0000 @@ -667,7 +667,7 @@ return; } -static int buf_get_line(char *ibuf, char **buf, int *position, int len) +static int buf_get_line(char *ibuf, char **buf, int *position, gsize len) { int pos = *position; int spos = pos; diff -r 90a41215bcb1 -r a650c8294bd2 pidgin/win32/nsis/pidgin-installer.nsi --- a/pidgin/win32/nsis/pidgin-installer.nsi Sun Aug 26 19:07:22 2007 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Sun Aug 26 19:08:29 2007 +0000 @@ -554,6 +554,10 @@ Push "msnim" Call RegisterURIHandler SectionEnd + Section /o "myim:" SecURI_MYIM + Push "myim" + Call RegisterURIHandler + SectionEnd Section /o "ymsgr:" SecURI_YMSGR Push "ymsgr" Call RegisterURIHandler @@ -706,6 +710,7 @@ Delete "$INSTDIR\plugins\libicq.dll" Delete "$INSTDIR\plugins\libirc.dll" Delete "$INSTDIR\plugins\libmsn.dll" + Delete "$INSTDIR\plugins\libmyspace.dll" Delete "$INSTDIR\plugins\libnapster.dll" Delete "$INSTDIR\plugins\libnovell.dll" Delete "$INSTDIR\plugins\libqq.dll"