# HG changeset patch # User Rob Flynn # Date 1013969292 0 # Node ID b68c648618a3bb8c3e8471f2592044f77415256d # Parent 8b03506b8c1e8c39f9139d9fe014823dddc43b45 [gaim-migrate @ 2969] I Love Rock & Roll.... committer: Tailor Script diff -r 8b03506b8c1e -r b68c648618a3 src/buddy_chat.c --- a/src/buddy_chat.c Wed Feb 13 15:23:13 2002 +0000 +++ b/src/buddy_chat.c Sun Feb 17 18:08:12 2002 +0000 @@ -808,8 +808,30 @@ static void chat_press_info(GtkObject *obj, struct conversation *b) { - if (b->gc) - b->gc->prpl->get_info(b->gc, gtk_object_get_user_data(obj)); + if (b->gc) { + /* + * If there are special needs for getting info on users in + * buddy chat "rooms"... + */ + if(b->gc->prpl->get_cb_info != NULL) { + b->gc->prpl->get_cb_info(b->gc, b->id, gtk_object_get_user_data(obj)); + } else { + b->gc->prpl->get_info(b->gc, gtk_object_get_user_data(obj)); + } + } +} + + +static void chat_press_away(GtkObject *obj, struct conversation *b) +{ + if (b->gc) { + /* + * May want to expand this to work similarly to chat_press_info? + */ + if(b->gc->prpl->get_cb_away != NULL) { + b->gc->prpl->get_cb_away(b->gc, b->id, gtk_object_get_user_data(obj)); + } + } } static gint right_click_chat(GtkObject *obj, GdkEventButton *event, struct conversation *b) @@ -849,34 +871,53 @@ gtk_widget_show(button); } + if (b->gc && b->gc->prpl->get_cb_away) { + button = gtk_menu_item_new_with_label(_("Get Away Msg")); + gtk_signal_connect(GTK_OBJECT(button), "activate", + GTK_SIGNAL_FUNC(chat_press_away), b); + gtk_object_set_user_data(GTK_OBJECT(button), gtk_object_get_user_data(obj)); + gtk_menu_append(GTK_MENU(menu), button); + gtk_widget_show(button); + } + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return TRUE; } +/* + * Common code for adding a chat buddy to the list + */ +static void add_chat_buddy_common(struct conversation *b, char *name, int pos) +{ + char tmp[BUF_LONG]; + GtkWidget *list_item; + + if (ignored(b, name)) { + g_snprintf(tmp, sizeof(tmp), "X %s", name); + list_item = gtk_list_item_new_with_label(tmp); + } else + list_item = gtk_list_item_new_with_label(name); + + gtk_object_set_user_data(GTK_OBJECT(list_item), name); + gtk_signal_connect(GTK_OBJECT(list_item), "button_press_event", + GTK_SIGNAL_FUNC(right_click_chat), b); + gtk_list_insert_items(GTK_LIST(b->list), g_list_append(NULL, list_item), pos); + gtk_widget_show(list_item); +} + void add_chat_buddy(struct conversation *b, char *buddy) { char *name = g_strdup(buddy); char tmp[BUF_LONG]; - GtkWidget *list_item; int pos; plugin_event(event_chat_buddy_join, b->gc, (void *)b->id, name, 0); b->in_room = g_list_insert_sorted(b->in_room, name, insertname); pos = g_list_index(b->in_room, name); - if (ignored(b, buddy)) { - g_snprintf(tmp, sizeof(tmp), "X %s", name); - list_item = gtk_list_item_new_with_label(tmp); - } else - list_item = gtk_list_item_new_with_label(name); - - gtk_object_set_user_data(GTK_OBJECT(list_item), name); - gtk_signal_connect(GTK_OBJECT(list_item), "button_press_event", - GTK_SIGNAL_FUNC(right_click_chat), b); - gtk_list_insert_items(GTK_LIST(b->list), g_list_append(NULL, list_item), pos); - gtk_widget_show(list_item); + add_chat_buddy_common(b, name, pos); g_snprintf(tmp, sizeof(tmp), _("%d %s in room"), g_list_length(b->in_room), g_list_length(b->in_room) == 1 ? "person" : "people"); @@ -898,7 +939,6 @@ GList *items = GTK_LIST(b->list)->children; char *name = g_strdup(new); - GtkWidget *list_item; char *ign; int pos; @@ -940,8 +980,6 @@ if (!ignored(b, new)) b->ignored = g_list_append(b->ignored, g_strdup(name)); - g_snprintf(tmp, sizeof(tmp), "X %s", name); - list_item = gtk_list_item_new_with_label(tmp); } else { if ((ign = ignored(b, new)) != NULL) { /* if they weren't ignored and change to someone who is ignored, @@ -950,14 +988,9 @@ g_free(ign); b->ignored = g_list_remove(b->ignored, ign); } - list_item = gtk_list_item_new_with_label(name); } - gtk_object_set_user_data(GTK_OBJECT(list_item), name); - gtk_signal_connect(GTK_OBJECT(list_item), "button_press_event", - GTK_SIGNAL_FUNC(right_click_chat), b); - gtk_list_insert_items(GTK_LIST(b->list), g_list_append(NULL, list_item), pos); - gtk_widget_show(list_item); + add_chat_buddy_common(b, name, pos); if (chat_options & OPT_CHAT_LOGON) { g_snprintf(tmp, sizeof(tmp), _("%s is now known as %s"), old, new); @@ -1050,8 +1083,6 @@ GList *i; char *ign; int pos; - GtkWidget *list_item; - char tmp[80]; i = GTK_LIST(b->list)->selection; if (i) { @@ -1067,19 +1098,12 @@ if (ign) { g_free(ign); b->ignored = g_list_remove(b->ignored, ign); - g_snprintf(tmp, sizeof tmp, "%s", name); } else { b->ignored = g_list_append(b->ignored, g_strdup(name)); - g_snprintf(tmp, sizeof tmp, "X %s", name); } - list_item = gtk_list_item_new_with_label(tmp); - gtk_object_set_user_data(GTK_OBJECT(list_item), name); - gtk_list_insert_items(GTK_LIST(b->list), g_list_append(NULL, list_item), pos); gtk_widget_destroy(i->data); - gtk_widget_show(list_item); - gtk_signal_connect(GTK_OBJECT(list_item), "button_press_event", - GTK_SIGNAL_FUNC(right_click_chat), b); + add_chat_buddy_common(b, name, pos); } void show_new_buddy_chat(struct conversation *b) @@ -1476,21 +1500,8 @@ while (r) { char *name = r->data; - GtkWidget *list_item; - char tmp[BUF_LONG]; - if (ignored(c, name)) { - g_snprintf(tmp, sizeof(tmp), "X %s", name); - list_item = gtk_list_item_new_with_label(tmp); - } else - list_item = gtk_list_item_new_with_label(name); - - gtk_object_set_user_data(GTK_OBJECT(list_item), name); - gtk_signal_connect(GTK_OBJECT(list_item), "button_press_event", - GTK_SIGNAL_FUNC(right_click_chat), c); - gtk_list_insert_items(GTK_LIST(c->list), - g_list_append(NULL, list_item), pos); - gtk_widget_show(list_item); + add_chat_buddy_common(c, name, pos); r = r->next; pos++; @@ -1522,21 +1533,8 @@ while (r) { char *name = r->data; - GtkWidget *list_item; - char tmp[BUF_LONG]; - if (ignored(c, name)) { - g_snprintf(tmp, sizeof(tmp), "X %s", name); - list_item = gtk_list_item_new_with_label(tmp); - } else - list_item = gtk_list_item_new_with_label(name); - - gtk_object_set_user_data(GTK_OBJECT(list_item), name); - gtk_signal_connect(GTK_OBJECT(list_item), "button_press_event", - GTK_SIGNAL_FUNC(right_click_chat), c); - gtk_list_insert_items(GTK_LIST(c->list), - g_list_append(NULL, list_item), pos); - gtk_widget_show(list_item); + add_chat_buddy_common(c, name, pos); r = r->next; pos++; diff -r 8b03506b8c1e -r b68c648618a3 src/dialogs.c --- a/src/dialogs.c Wed Feb 13 15:23:13 2002 +0000 +++ b/src/dialogs.c Sun Feb 17 18:08:12 2002 +0000 @@ -4209,3 +4209,523 @@ return 0; } + +/*------------------------------------------------------------------------*/ +/* The dialog for setting V-Card info */ +/*------------------------------------------------------------------------*/ +/* + * There are actually two "chunks" of code following: generic "multi-entry dialog" + * support and V-Card dialog specific support. + * + * At first blush, this may seem like an unnecessary duplication of effort given + * that a "set dir info" dialog already exists. However, this is not so because: + * + * 1. V-Cards can have a lot more data in them than what the current + * "set dir" dialog supports. + * + * 2. V-Card data, at least with respect to Jabber, is currently in a + * state of flux. As the data and format changes, all that need be + * changed with the V-Card support I've written is the "template" + * data. + * + * 3. The "multi entry dialog" support itself was originally written + * to support Jabber server user registration (TBD). A "dynamically + * configurable" multi-entry dialog is needed for that, as different + * servers may require different registration information. It just + * turned out to be well-suited to adding V-Card setting support, as + * well :-). + * + * TBD: Add check-box support to the generic multi-entry dialog support so that + * it can be used to "replace" the "set dir info" support? + * + * Multiple-language support. Currently Not In There. I think this should + * be easy. Note that when it's added: if anybody saved their data in + * English, it'll be lost when MLS is added and they'll have to re-enter it. + * + * More "TBDs" noted in the code. + */ + + +/*------------------------------------*/ +/* generic multi-entry dialog support */ +/*------------------------------------*/ + +/* + * Print all multi-entry items + * + * Note: Simply a debug helper + */ +void multi_entry_item_print_all(const GSList *list) { + + int cnt = 0; + + /* While there's something to print... */ + while(list != NULL) { + fprintf(stderr, "label %2d: \"%s\"", ++cnt, ((MultiEntryData *) (list->data))->label); + if(((MultiEntryData *) (list->data))->text != NULL) { + fprintf(stderr, ", text: \"%s\"", ((MultiEntryData *) (list->data))->text); + } + fputs("\n", stderr); + list = list->next; + } +} + +/* + * Print all multi-text items + * + * Note: Simply a debug helper + */ +void multi_text_item_print_all(const GSList *list) { + + int cnt = 0; + + /* While there's something to print... */ + while(list != NULL) { + fprintf(stderr, "label %2d: \"%s\"", ++cnt, ((MultiTextData *) (list->data))->label); + if(((MultiTextData *) (list->data))->text != NULL) { + fprintf(stderr, ", text: \"%s\"", ((MultiTextData *) (list->data))->text); + } + fputs("\n", stderr); + list = list->next; + } +} + + +/* + * Free all multi-entry item allocs and NULL the list pointer + */ +void multi_entry_items_free_all(GSList **list) +{ + + GSList *next = *list; + MultiEntryData *data; + + /* While there's something to free() ... */ + while(next != NULL) { + data = (MultiEntryData *) next->data; + g_free(data->label); + g_free(data->text); + g_free(data); + next = next->next; + } + g_slist_free(*list); + *list = NULL; +} + +/* + * Free all multi-text item allocs and NULL the list pointer + */ +void multi_text_items_free_all(GSList **list) +{ + + GSList *next = *list; + MultiTextData *data; + + /* While there's something to free() ... */ + while(next != NULL) { + data = (MultiTextData *) next->data; + g_free(data->label); + g_free(data->text); + g_free(data); + next = next->next; + } + g_slist_free(*list); + *list = NULL; +} + +/* + * See if a MultiEntryData item contains a given label + * + * See: glib docs for g_slist_compare_custom() for details + */ +static gint multi_entry_data_label_compare(gconstpointer data, gconstpointer label) +{ + return(strcmp(((MultiEntryData *) (data))->label, (char *) label)); +} + +/* + * Add a new multi-entry item to list + * + * If adding to existing list: will search the list for existence of + * "label" and change/create "text" entry if necessary. + */ + +MultiEntryData *multi_entry_list_update(GSList **list, const char *label, const char *text, int add_it) +{ + GSList *found; + MultiEntryData *data; + + if((found = g_slist_find_custom(*list, label, multi_entry_data_label_compare)) == NULL) { + if(add_it) { + data = (MultiEntryData *) g_slist_last(*list = + g_slist_append(*list, g_malloc(sizeof(MultiEntryData))))->data; + data->label = strcpy(g_malloc(strlen(label) +1), label); + data->text = NULL; + /* + * default to setting "visible" and editable to TRUE - they can be + * overridden later, of course. + */ + data->visible = TRUE; + data->editable = TRUE; + } else { + data = NULL; + } + } else { + data = found->data; + } + + if(data != NULL && text != NULL && text[0] != '\0') { + if(data->text == NULL) { + data->text = g_malloc(strlen(text) + 1); + } else { + data->text = g_realloc(data->text, strlen(text) + 1); + } + strcpy(data->text, text); + } + + return(data); +} + +/* + * See if a MultiTextData item contains a given label + * + * See: glib docs for g_slist_compare_custom() for details + */ +static gint multi_text_data_label_compare(gconstpointer data, gconstpointer label) +{ + return(strcmp(((MultiTextData *) (data))->label, (char *) label)); +} + +/* + * Add a new multi-text item to list + * + * If adding to existing list: will search the list for existence of + * "label" and change/create "text" text if necessary. + */ + +MultiTextData *multi_text_list_update(GSList **list, const char *label, const char *text, int add_it) +{ + GSList *found; + MultiTextData *data; + + if((found = g_slist_find_custom(*list, label, multi_text_data_label_compare)) == NULL) { + if(add_it) { + data = (MultiTextData *) g_slist_last(*list = + g_slist_append(*list, g_malloc(sizeof(MultiTextData))))->data; + data->label = strcpy(g_malloc(strlen(label) +1), label); + data->text = NULL; + } else { + data = NULL; + } + } else { + data = found->data; + } + + if(data != NULL && text != NULL && text[0] != '\0') { + if(data->text == NULL) { + data->text = g_malloc(strlen(text) + 1); + } else { + data->text = g_realloc(data->text, strlen(text) + 1); + } + strcpy(data->text, text); + } + + return(data); +} + +/* + * Free-up the multi-entry item list and the MultiEntryDlg + * struct alloc. + */ +void multi_entry_free(struct multi_entry_dlg *b) +{ + multi_entry_items_free_all(&(b->multi_entry_items)); + multi_text_items_free_all(&(b->multi_text_items)); + g_free(b->instructions->text); + g_free(b->instructions); + g_free(b); +} + +/* + * Multi-Entry dialog "destroyed" catcher + * + * Free-up the multi-entry item list, destroy the dialog widget + * and free the MultiEntryDlg struct alloc. + * + */ +void multi_entry_dialog_destroy(GtkWidget *widget, gpointer data) +{ + MultiEntryDlg *b = data; + + multi_entry_free(b); +} + +/* + * Show/Re-show instructions + */ +void re_show_multi_entry_instr(MultiInstrData *instructions) +{ + if(instructions->label != NULL) { + if(instructions->text == NULL) { + gtk_widget_hide(instructions->label); + } else { + gtk_label_set_text(GTK_LABEL (instructions->label), instructions->text); + gtk_widget_show(instructions->label); + } + } +} + +/* + * Show/Re-show entry boxes + */ +void re_show_multi_entry_entries(GtkWidget **entries_table, + GtkWidget *entries_frame, + GSList *multi_entry_items) +{ + GtkWidget *label; + GSList *multi_entry; + MultiEntryData *med; + int rows = 0; + int rowNum; + + /* Figure-out number of rows needed for table */ + rows = g_slist_length(multi_entry_items); + + if(*entries_table != NULL) { + gtk_widget_destroy(GTK_WIDGET (*entries_table)); + } + *entries_table = gtk_table_new(rows, 3, FALSE); + gtk_container_add(GTK_CONTAINER (entries_frame), *entries_table); + + for(rowNum = 0, multi_entry = multi_entry_items; + multi_entry != NULL; ++rowNum, multi_entry = multi_entry->next) { + + med = (MultiEntryData *) multi_entry->data; + + label = gtk_label_new(med->label); + gtk_misc_set_alignment(GTK_MISC(label), (gfloat) 1.0, (gfloat) 0.5); + gtk_table_attach_defaults(GTK_TABLE (*entries_table), label, 0, 1, rowNum, rowNum +1); + gtk_widget_show(label); + + label = gtk_label_new(": "); + gtk_misc_set_alignment(GTK_MISC(label), (gfloat) 0.0, (gfloat) 0.5); + gtk_table_attach_defaults(GTK_TABLE (*entries_table), label, 1, 2, rowNum, rowNum +1); + gtk_widget_show(label); + + med->widget = gtk_entry_new_with_max_length(50); + if(med->text != NULL) { + gtk_entry_set_text(GTK_ENTRY (med->widget), med->text); + } + gtk_entry_set_visibility(GTK_ENTRY (med->widget), med->visible); + gtk_entry_set_editable(GTK_ENTRY (med->widget), med->editable); + gtk_table_attach_defaults(GTK_TABLE (*entries_table), + med->widget, 2, 3, rowNum, rowNum +1); + gtk_widget_show(med->widget); + } + + gtk_widget_show(*entries_table); +} + +/* + * Show/Re-show textboxes + */ +void re_show_multi_entry_textboxes(GtkWidget **texts_ibox, + GtkWidget *texts_obox, + GSList *multi_text_items) +{ + GSList *multi_text; + MultiTextData *mtd; + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *vscrollbar; + + if(*texts_ibox != NULL) { + gtk_widget_destroy(GTK_WIDGET (*texts_ibox)); + } + *texts_ibox = gtk_vbox_new(FALSE, 5); + gtk_container_add(GTK_CONTAINER (texts_obox), *texts_ibox); + + for(multi_text = multi_text_items; multi_text != NULL; multi_text = multi_text->next) { + mtd = (MultiTextData *) multi_text->data; + frame = gtk_frame_new(mtd->label); + gtk_container_add(GTK_CONTAINER (*texts_ibox), frame); + hbox = gtk_hbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER (frame), hbox); + mtd->textbox = gtk_text_new(NULL, NULL); + gtk_text_set_editable(GTK_TEXT(mtd->textbox), TRUE); + gtk_text_set_word_wrap(GTK_TEXT(mtd->textbox), TRUE); + gtk_widget_set_usize(mtd->textbox, 300, 100); + gtk_text_insert(GTK_TEXT(mtd->textbox), NULL, NULL, NULL, mtd->text, -1); + gtk_box_pack_start(GTK_BOX (hbox), mtd->textbox, FALSE, FALSE, 0); + vscrollbar = gtk_vscrollbar_new (GTK_TEXT(mtd->textbox)->vadj); + gtk_box_pack_start(GTK_BOX (hbox), vscrollbar, FALSE, FALSE, 0); + gtk_widget_show(mtd->textbox); + gtk_widget_show (vscrollbar); + gtk_widget_show(hbox); + gtk_widget_show(frame); + } + + gtk_widget_show(*texts_ibox); +} + +/* + * Create and initialize a new Multi-Entry Dialog struct + */ +MultiEntryDlg *multi_entry_dialog_new() +{ + MultiEntryDlg *b = g_new0(MultiEntryDlg, 1); + b->instructions = g_new0(MultiInstrData, 1); + b->multi_entry_items = NULL; + b->multi_text_items = NULL; + return(b); +} + +/* + * Instantiate a new multi-entry dialog + * + * data == pointer to MultiEntryDlg with the following + * initialized: + * + * wmclass_name + * wmclass_class + * title + * user + * multi_entry_items - pointers to MultiEntryData list + * and MultiTextData list + * instructions (optional) + * ok function pointer + * cancel function pointer (actually used to set + * window destroy signal--cancel asserts destroy) + * + * sets the following in the MultiEntryDialog struct: + * + * window + */ +void show_multi_entry_dialog(gpointer data) +{ + GtkWidget *vbox, *hbox; + GtkWidget *button; + MultiEntryDlg *b = data; + + GAIM_DIALOG(b->window); + gtk_window_set_wmclass(GTK_WINDOW(b->window), b->wmclass_name, b->wmclass_class); + gtk_window_set_title(GTK_WINDOW (b->window), b->title); + /* Clean up if user dismisses window via window manager! */ + gtk_signal_connect(GTK_OBJECT(b->window), "destroy", GTK_SIGNAL_FUNC(b->cancel), (gpointer) b); + gtk_widget_realize(b->window); + aol_icon(b->window->window); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_container_add(GTK_CONTAINER (b->window), vbox); + + b->instructions->label = gtk_label_new(NULL); + gtk_label_set_line_wrap(GTK_LABEL (b->instructions->label), TRUE); + gtk_box_pack_start(GTK_BOX (vbox), b->instructions->label, TRUE, TRUE, 5); + re_show_multi_entry_instr(b->instructions); + + b->entries_frame = gtk_frame_new(NULL); + gtk_box_pack_start(GTK_BOX (vbox), b->entries_frame, TRUE, TRUE, 5); + gtk_widget_show(b->entries_frame); + b->entries_table = NULL; + re_show_multi_entry_entries(&(b->entries_table), b->entries_frame, b->multi_entry_items); + + b->texts_obox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX (vbox), b->texts_obox, TRUE, TRUE, 0); + gtk_widget_show(b->texts_obox); + b->texts_ibox = NULL; + re_show_multi_entry_textboxes(&(b->texts_ibox), b->texts_obox, b->multi_text_items); + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show(hbox); + + button = picture_button(b->window, _("Cancel"), cancel_xpm); + /* Let "destroy handling" (set above) handle cleanup */ + gtk_signal_connect_object(GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (b->window)); + gtk_box_pack_end(GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + button = picture_button(b->window, _("Save"), save_xpm); + gtk_signal_connect(GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (b->ok), (gpointer) b); + gtk_box_pack_end(GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + gtk_widget_show(vbox); + gtk_widget_show(b->window); +} + + +/*------------------------------------*/ +/* V-Card dialog specific support */ +/*------------------------------------*/ + +/* + * V-Card "set info" dialog "Save" clicked + * + * Copy data from GTK+ dialogs into GSLists, call protocol-specific + * formatter and save the user info data. + */ +void set_vcard_dialog_ok_clicked(GtkWidget *widget, gpointer data) +{ + MultiEntryDlg *b = (MultiEntryDlg *) data; + struct gaim_connection *gc; + gchar *tmp; + GSList *list; + + for(list = b->multi_entry_items; list != NULL; list = list->next) { + if(((MultiEntryData *) list->data)->text != NULL) { + g_free(((MultiEntryData *) list->data)->text); + } + ((MultiEntryData *) list->data)->text = + g_strdup(gtk_entry_get_text(GTK_ENTRY(((MultiEntryData *) list->data)->widget))); + } + + for(list = b->multi_text_items; list != NULL; list = list->next) { + if(((MultiTextData *) list->data)->text != NULL) { + g_free(((MultiTextData *) list->data)->text); + } + ((MultiTextData *) list->data)->text = + gtk_editable_get_chars((GtkEditable *) (((MultiTextData *) list->data)->textbox), + 0, -1); + } + + + tmp = b->custom(b); + + /* + * Set the user info and (possibly) send to the server + */ + if (b->user) { + strncpy(b->user->user_info, tmp, sizeof b->user->user_info); + gc = b->user->gc; + + save_prefs(); + + if (gc) + serv_set_info(gc, b->user->user_info); + } + + g_free(tmp); + + /* Let multi-edit dialog window "destroy" event catching handle remaining cleanup */ + gtk_widget_destroy(GTK_WIDGET (b->window)); +} + +/* + * Instantiate a v-card dialog + */ +void show_set_vcard(MultiEntryDlg *b) +{ + b->ok = set_vcard_dialog_ok_clicked; + b->cancel = multi_entry_dialog_destroy; + + show_multi_entry_dialog(b); +} + + +/*------------------------------------------------------------------------*/ +/* End dialog for setting v-card info */ +/*------------------------------------------------------------------------*/ + diff -r 8b03506b8c1e -r b68c648618a3 src/gaim.h --- a/src/gaim.h Wed Feb 13 15:23:13 2002 +0000 +++ b/src/gaim.h Sun Feb 17 18:08:12 2002 +0000 @@ -411,6 +411,7 @@ extern char *gaim_user_dir(); extern void strncpy_nohtml(gchar *, const gchar *, size_t); extern void strncpy_withhtml(gchar *, const gchar *, size_t); +extern gchar *strdup_withhtml(const gchar *); extern void away_on_login(char *); extern void system_log(enum log_event, struct gaim_connection *, struct buddy *, int); extern unsigned char *utf8_to_str(unsigned char *); @@ -419,4 +420,83 @@ extern void strip_linefeed(char *); extern time_t get_time(int, int, int, int, int, int); +/*------------------------------------------------------------------------*/ +/* Multi-Entry dialog and vCard dialog support */ +/*------------------------------------------------------------------------*/ + +/* + * Struct for "instructions" dialog data + */ +typedef struct multi_instr_dlg { + GtkWidget *label; /* dialog instructions widget */ + gchar *text; /* dialog instructions */ +} MultiInstrData; + +/* + * Struct for multiple-entry dialog data + */ +typedef struct multi_entry_data { + GtkWidget *widget; /* entry widget object */ + char *label; /* label text pointer */ + char *text; /* entry text pointer */ + int visible; /* should entry field be "visible?" */ + int editable; /* should entry field be editable? */ +} MultiEntryData; + +/* + * Struct for multiple-textbox dialog data + */ +typedef struct multi_text_data { + char *label; /* frame label */ + GtkWidget *textbox; /* text entry widget object */ + char *text; /* textbox text pointer */ +} MultiTextData; + +/* + * Struct to create a multi-entry dialog + */ +typedef struct multi_entry_dlg { + GtkWidget *window; /* dialog main window */ + gchar *wmclass_name; /* window name */ + gchar *wmclass_class; /* window class */ + char *title; /* window title */ + + struct aim_user *user; /* user info - needed for most everything */ + + MultiInstrData *instructions; /* instructions (what else?) */ + + GtkWidget *entries_table; /* table widget containing m-e lables & entries */ + GtkWidget *entries_frame; /* frame widget containing the table widget */ + GSList *multi_entry_items; /* entry dialogs parameters */ + + GtkWidget *texts_ibox; /* inner vbox containing multi-text frames */ + GtkWidget *texts_obox; /* outer vbox containing multi-text frames */ + GSList *multi_text_items; /* text dialogs parameters */ + + void * (*custom)(struct multi_entry_dlg *); /* Custom function that may be used by */ + /* multi-entry dialog "wrapper" functions */ + /* (Not used by multi-entry dialog routines) */ + + void (*ok)(GtkWidget *, gpointer); /* "Save/OK" action */ + void (*cancel)(GtkWidget *, gpointer); /* "Cancel" action */ +} MultiEntryDlg; + +extern MultiTextData *multi_text_list_update(GSList **, const char *, const char *, int); +extern void multi_text_items_free_all(GSList **); +extern MultiEntryData *multi_entry_list_update(GSList **, const char *, const char *, int); +extern void multi_entry_items_free_all(GSList **); + +extern void re_show_multi_entry_instr(MultiInstrData *); +extern void re_show_multi_entry_entries(GtkWidget **, GtkWidget *, GSList *); +extern void re_show_multi_entry_textboxes(GtkWidget **, GtkWidget *, GSList *); + +extern MultiEntryDlg *multi_entry_dialog_new(void); +extern void show_multi_entry_dialog(gpointer); + +extern void show_set_vcard(MultiEntryDlg *); + +/*------------------------------------------------------------------------*/ +/* End Multi-Entry dialog and vCard dialog support */ +/*------------------------------------------------------------------------*/ + #endif /* _GAIM_H_ */ diff -r 8b03506b8c1e -r b68c648618a3 src/multi.c --- a/src/multi.c Wed Feb 13 15:23:13 2002 +0000 +++ b/src/multi.c Sun Feb 17 18:08:12 2002 +0000 @@ -238,6 +238,7 @@ const char *txt; int i; struct aim_user *a; + struct prpl *p; if (!u->user) { txt = gtk_entry_get_text(GTK_ENTRY(u->name)); @@ -278,9 +279,24 @@ gtk_widget_destroy(u->icondlg); u->icondlg = NULL; - gtk_widget_destroy(u->mod); + /* + * See if user registration is supported/required + */ + if((p = find_prpl(u->protocol)) == NULL) { + /* TBD: error dialog here! (This should never happen, you know...) */ + fprintf(stderr, "dbg: couldn't find protocol for protocol number %d!\n", u->protocol); + fflush(stderr); + } else { + if(p->register_user != NULL && + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(u->register_user)) == TRUE) { + + p->register_user(a); + } + } save_prefs(); + + gtk_widget_destroy(u->mod); } static void cancel_mod(GtkWidget *w, struct mod_user *u) @@ -648,6 +664,14 @@ g_free(puo); op = op->next; } + + if(p->register_user != NULL) { + u->register_user = gtk_check_button_new_with_label("Register with server"); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(u->register_user), FALSE); + gtk_box_pack_start(GTK_BOX(vbox), u->register_user, FALSE, FALSE, 0); + gtk_widget_show(u->register_user); + } + g_list_free(tmp); } @@ -1218,17 +1242,23 @@ g_free(k); } -void hide_login_progress(struct gaim_connection *gc, char *why) +/* + * Common code for hide_login_progress(), and hide_login_progress_info() + */ +static void hide_login_progress_common(struct gaim_connection *gc, + char *details, + char *title, + char *prologue) { char buf[2048]; struct kick_dlg *k = find_kick_dlg(gc->user); struct signon_meter *meter = find_signon_meter(gc); - sprintf(buf, _("%s\n%s was unable to sign on: %s"), full_date(), gc->username, why); + sprintf(buf, _("%s\n%s: %s"), full_date(), prologue, details); if (k) gtk_widget_destroy(k->dlg); k = g_new0(struct kick_dlg, 1); k->user = gc->user; - k->dlg = do_error_dialog(buf, _("Signon Error")); + k->dlg = do_error_dialog(buf, title); kicks = g_slist_append(kicks, k); gtk_signal_connect(GTK_OBJECT(k->dlg), "destroy", GTK_SIGNAL_FUNC(set_kick_null), k); if (meter) { @@ -1238,6 +1268,24 @@ } } +void hide_login_progress(struct gaim_connection *gc, char *why) +{ + char buf[2048]; + + sprintf(buf, _("%s was unable to sign on"), gc->username); + hide_login_progress_common(gc, why, _("Signon Error"), buf); +} + +/* + * Like hide_login_progress(), but for informational, not error/warning, + * messages. + * + */ +void hide_login_progress_notice(struct gaim_connection *gc, char *why) +{ + hide_login_progress_common(gc, why, _("Notice"), gc->username); +} + void signoff_all() { GSList *c = connections; diff -r 8b03506b8c1e -r b68c648618a3 src/multi.h --- a/src/multi.h Wed Feb 13 15:23:13 2002 +0000 +++ b/src/multi.h Sun Feb 17 18:08:12 2002 +0000 @@ -111,5 +111,6 @@ void set_login_progress(struct gaim_connection *, float, char *); void hide_login_progress(struct gaim_connection *, char *); +void hide_login_progress_notice(struct gaim_connection *, char *); #endif /* _MULTI_H_ */ diff -r 8b03506b8c1e -r b68c648618a3 src/protocols/jabber/jabber.c --- a/src/protocols/jabber/jabber.c Wed Feb 13 15:23:13 2002 +0000 +++ b/src/protocols/jabber/jabber.c Sun Feb 17 18:08:12 2002 +0000 @@ -92,56 +92,86 @@ xmlnode current; /* Current node in parsing instance.. */ /* Event callback ptrs */ - void (*on_state)(struct gjconn_struct *j, int state); - void (*on_packet)(struct gjconn_struct *j, jpacket p); + void (*on_state)(struct gjconn_struct *gjc, int state); + void (*on_packet)(struct gjconn_struct *gjc, jpacket p); + + GHashTable *queries; /* query tracker */ void *priv; } *gjconn, gjconn_struct; -typedef void (*gjconn_state_h)(gjconn j, int state); -typedef void (*gjconn_packet_h)(gjconn j, jpacket p); +typedef void (*gjconn_state_h)(gjconn gjc, int state); +typedef void (*gjconn_packet_h)(gjconn gjc, jpacket p); static gjconn gjab_new(char *user, char *pass, void *priv); -static void gjab_delete(gjconn j); -static void gjab_state_handler(gjconn j, gjconn_state_h h); -static void gjab_packet_handler(gjconn j, gjconn_packet_h h); -static void gjab_start(gjconn j); -static void gjab_stop(gjconn j); +static void gjab_delete(gjconn gjc); +static void gjab_state_handler(gjconn gjc, gjconn_state_h h); +static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h); +static void gjab_start(gjconn gjc); +static void gjab_stop(gjconn gjc); /* -static int gjab_getfd(gjconn j); -static jid gjab_getjid(gjconn j); -static char *gjab_getsid(gjconn j); +static int gjab_getfd(gjconn gjc); +static jid gjab_getjid(gjconn gjc); +static char *gjab_getsid(gjconn gjc); */ -static char *gjab_getid(gjconn j); -static void gjab_send(gjconn j, xmlnode x); -static void gjab_send_raw(gjconn j, const char *str); -static void gjab_recv(gjconn j); -static void gjab_auth(gjconn j); - +static char *gjab_getid(gjconn gjc); +static void gjab_send(gjconn gjc, xmlnode x); +static void gjab_send_raw(gjconn gjc, const char *str); +static void gjab_recv(gjconn gjc); +static void gjab_auth(gjconn gjc); + +/* + * It is *this* to which we point the gaim_connection proto_data + */ struct jabber_data { - gjconn jc; + gjconn gjc; gboolean did_import; - GSList *pending_chats; - GSList *existing_chats; + GSList *chats; GHashTable *hash; time_t idle; gboolean die; }; +/* + * Jabber "chat group" info. Pointers to these go in jabber_data + * pending and existing chats lists. + */ struct jabber_chat { jid Jid; struct gaim_connection *gc; struct conversation *b; int id; + int state; }; +/* + * Jabber chat states... + * + * Note: due to a bug in one version of the Jabber server, subscriptions + * to chat groups aren't (always?) properly removed at the server. The + * result is clients receive Jabber "presence" notifications for JIDs + * they no longer care about. The problem with such vestigial notifies is + * that we really have no way of telling if it's vestigial or if it's a + * valid "buddy" presence notification. So we keep jabber_chat structs + * around after leaving a chat group and simply mark them "closed." That + * way we can test for such errant presence notifications. I.e.: if we + * get a presence notfication from a JID that matches a chat group JID, + * we disregard it. + */ +#define JCS_PENDING 1 /* pending */ +#define JCS_ACTIVE 2 /* active */ +#define JCS_CLOSED 3 /* closed */ + + static char *jabber_name() { return "Jabber"; } -#define STATE_EVT(arg) if(j->on_state) { (j->on_state)(j, (arg) ); } +#define STATE_EVT(arg) if(gjc->on_state) { (gjc->on_state)(gjc, (arg) ); } + +static void jabber_handlevcard(gjconn, xmlnode, char *); static char *create_valid_jid(const char *given, char *server, char *resource) { @@ -160,7 +190,7 @@ static gjconn gjab_new(char *user, char *pass, void *priv) { pool p; - gjconn j; + gjconn gjc; if (!user) return (NULL); @@ -168,158 +198,166 @@ p = pool_new(); if (!p) return (NULL); - j = pmalloc_x(p, sizeof(gjconn_struct), 0); - if (!j) + gjc = pmalloc_x(p, sizeof(gjconn_struct), 0); + if (!gjc) { + pool_free(p); /* no need for this anymore! */ return (NULL); - j->p = p; - - j->user = jid_new(p, user); - j->pass = pstrdup(p, pass); - - j->state = JCONN_STATE_OFF; - j->id = 1; - j->fd = -1; - - j->priv = priv; - - return j; + } + gjc->p = p; + + gjc->user = jid_new(p, user); + gjc->pass = pstrdup(p, pass); + + gjc->state = JCONN_STATE_OFF; + gjc->id = 1; + gjc->fd = -1; + + gjc->priv = priv; + + return gjc; } -static void gjab_delete(gjconn j) +static void gjab_delete(gjconn gjc) { - if (!j) + if (!gjc) return; - gjab_stop(j); - pool_free(j->p); + gjab_stop(gjc); + pool_free(gjc->p); } -static void gjab_state_handler(gjconn j, gjconn_state_h h) +static void gjab_state_handler(gjconn gjc, gjconn_state_h h) { - if (!j) + if (!gjc) return; - j->on_state = h; + gjc->on_state = h; } -static void gjab_packet_handler(gjconn j, gjconn_packet_h h) +static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h) { - if (!j) + if (!gjc) return; - j->on_packet = h; + gjc->on_packet = h; } -static void gjab_stop(gjconn j) +static void gjab_stop(gjconn gjc) { - if (!j || j->state == JCONN_STATE_OFF) + if (!gjc || gjc->state == JCONN_STATE_OFF) return; - j->state = JCONN_STATE_OFF; - gjab_send_raw(j, ""); - close(j->fd); - j->fd = -1; - XML_ParserFree(j->parser); - j->parser = NULL; + gjab_send_raw(gjc, ""); + gjc->state = JCONN_STATE_OFF; + close(gjc->fd); + gjc->fd = -1; + XML_ParserFree(gjc->parser); + gjc->parser = NULL; } /* -static int gjab_getfd(gjconn j) +static int gjab_getfd(gjconn gjc) { - if (j) - return j->fd; + if (gjc) + return gjc->fd; else return -1; } -static jid gjab_getjid(gjconn j) +static jid gjab_getjid(gjconn gjc) { - if (j) - return (j->user); + if (gjc) + return (gjc->user); else return NULL; } -static char *gjab_getsid(gjconn j) +static char *gjab_getsid(gjconn gjc) { - if (j) - return (j->sid); + if (gjc) + return (gjc->sid); else return NULL; } */ -static char *gjab_getid(gjconn j) +static char *gjab_getid(gjconn gjc) { - snprintf(j->idbuf, 8, "%d", j->id++); - return &j->idbuf[0]; + snprintf(gjc->idbuf, 8, "%d", gjc->id++); + return &gjc->idbuf[0]; } -static void gjab_send(gjconn j, xmlnode x) +static void gjab_send(gjconn gjc, xmlnode x) { - if (j && j->state != JCONN_STATE_OFF) { + if (gjc && gjc->state != JCONN_STATE_OFF) { char *buf = xmlnode2str(x); if (buf) - write(j->fd, buf, strlen(buf)); + write(gjc->fd, buf, strlen(buf)); debug_printf("gjab_send: %s\n", buf); } } -static void gjab_send_raw(gjconn j, const char *str) +static void gjab_send_raw(gjconn gjc, const char *str) { - if (j && j->state != JCONN_STATE_OFF) { - write(j->fd, str, strlen(str)); + if (gjc && gjc->state != JCONN_STATE_OFF) { + /* + * JFIXME: No error detection?!?! + */ + if(write(gjc->fd, str, strlen(str)) < 0) { + fprintf(stderr, "DBG: Problem sending. Error: %d\n", errno); + fflush(stderr); + } debug_printf("gjab_send_raw: %s\n", str); } } -static void gjab_reqroster(gjconn j) +static void gjab_reqroster(gjconn gjc) { xmlnode x; x = jutil_iqnew(JPACKET__GET, NS_ROSTER); - xmlnode_put_attrib(x, "id", gjab_getid(j)); - - gjab_send(j, x); + xmlnode_put_attrib(x, "id", gjab_getid(gjc)); + + gjab_send(gjc, x); xmlnode_free(x); } -static void gjab_reqauth(gjconn j) +static void gjab_reqauth(gjconn gjc) { xmlnode x, y, z; char *user; - if (!j) + if (!gjc) return; x = jutil_iqnew(JPACKET__GET, NS_AUTH); xmlnode_put_attrib(x, "id", IQID_AUTH); y = xmlnode_get_tag(x, "query"); - user = j->user->user; + user = gjc->user->user; if (user) { z = xmlnode_insert_tag(y, "username"); xmlnode_insert_cdata(z, user, -1); } - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); } -static void gjab_auth(gjconn j) +static void gjab_auth(gjconn gjc) { xmlnode x, y, z; char *hash, *user; - if (!j) + if (!gjc) return; x = jutil_iqnew(JPACKET__SET, NS_AUTH); xmlnode_put_attrib(x, "id", IQID_AUTH); y = xmlnode_get_tag(x, "query"); - user = j->user->user; + user = gjc->user->user; if (user) { z = xmlnode_insert_tag(y, "username"); @@ -327,42 +365,42 @@ } z = xmlnode_insert_tag(y, "resource"); - xmlnode_insert_cdata(z, j->user->resource, -1); - - if (j->sid) { - debug_printf("digest authentication (sid %s)\n", j->sid); + xmlnode_insert_cdata(z, gjc->user->resource, -1); + + if (gjc->sid) { + debug_printf("digest authentication (sid %s)\n", gjc->sid); z = xmlnode_insert_tag(y, "digest"); - hash = pmalloc(x->p, strlen(j->sid) + strlen(j->pass) + 1); - strcpy(hash, j->sid); - strcat(hash, j->pass); + hash = pmalloc(x->p, strlen(gjc->sid) + strlen(gjc->pass) + 1); + strcpy(hash, gjc->sid); + strcat(hash, gjc->pass); hash = shahash(hash); xmlnode_insert_cdata(z, hash, 40); } else { z = xmlnode_insert_tag(y, "password"); - xmlnode_insert_cdata(z, j->pass, -1); + xmlnode_insert_cdata(z, gjc->pass, -1); } - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); return; } -static void gjab_recv(gjconn j) +static void gjab_recv(gjconn gjc) { static char buf[4096]; int len; - if (!j || j->state == JCONN_STATE_OFF) + if (!gjc || gjc->state == JCONN_STATE_OFF) return; - if ((len = read(j->fd, buf, sizeof(buf) - 1))) { - struct jabber_data *jd = GJ_GC(j)->proto_data; + if ((len = read(gjc->fd, buf, sizeof(buf) - 1))) { + struct jabber_data *jd = GJ_GC(gjc)->proto_data; buf[len] = '\0'; debug_printf("input (len %d): %s\n", len, buf); - XML_Parse(j->parser, buf, len, 0); + XML_Parse(gjc->parser, buf, len, 0); if (jd->die) - signoff(GJ_GC(j)); + signoff(GJ_GC(gjc)); } else if (len <= 0) { STATE_EVT(JCONN_STATE_OFF) } @@ -371,54 +409,54 @@ static void startElement(void *userdata, const char *name, const char **attribs) { xmlnode x; - gjconn j = (gjconn) userdata; - - if (j->current) { + gjconn gjc = (gjconn) userdata; + + if (gjc->current) { /* Append the node to the current one */ - x = xmlnode_insert_tag(j->current, name); + x = xmlnode_insert_tag(gjc->current, name); xmlnode_put_expat_attribs(x, attribs); - j->current = x; + gjc->current = x; } else { x = xmlnode_new_tag(name); xmlnode_put_expat_attribs(x, attribs); if (strcmp(name, "stream:stream") == 0) { /* special case: name == stream:stream */ /* id attrib of stream is stored for digest auth */ - j->sid = g_strdup(xmlnode_get_attrib(x, "id")); + gjc->sid = g_strdup(xmlnode_get_attrib(x, "id")); /* STATE_EVT(JCONN_STATE_AUTH) */ xmlnode_free(x); } else { - j->current = x; + gjc->current = x; } } } static void endElement(void *userdata, const char *name) { - gjconn j = (gjconn) userdata; + gjconn gjc = (gjconn) userdata; xmlnode x; jpacket p; - if (j->current == NULL) { + if (gjc->current == NULL) { /* we got */ STATE_EVT(JCONN_STATE_OFF) return; } - x = xmlnode_get_parent(j->current); + x = xmlnode_get_parent(gjc->current); if (!x) { /* it is time to fire the event */ - p = jpacket_new(j->current); - - if (j->on_packet) - (j->on_packet) (j, p); + p = jpacket_new(gjc->current); + + if (gjc->on_packet) + (gjc->on_packet) (gjc, p); else - xmlnode_free(j->current); + xmlnode_free(gjc->current); } - j->current = x; + gjc->current = x; } static void jabber_callback(gpointer data, gint source, GaimInputCondition condition) @@ -426,15 +464,15 @@ struct gaim_connection *gc = (struct gaim_connection *)data; struct jabber_data *jd = (struct jabber_data *)gc->proto_data; - gjab_recv(jd->jc); + gjab_recv(jd->gjc); } static void charData(void *userdata, const char *s, int slen) { - gjconn j = (gjconn) userdata; - - if (j->current) - xmlnode_insert_cdata(j->current, s, slen); + gjconn gjc = (gjconn) userdata; + + if (gjc->current) + xmlnode_insert_cdata(gjc->current, s, slen); } static void gjab_connected(gpointer data, gint source, GaimInputCondition cond) @@ -443,7 +481,7 @@ char *t, *t2; struct gaim_connection *gc = data; struct jabber_data *jd; - gjconn j; + gjconn gjc; if (!g_slist_find(connections, gc)) { close(source); @@ -451,61 +489,64 @@ } jd = gc->proto_data; - j = jd->jc; - - if (j->fd != source) - j->fd = source; + gjc = jd->gjc; + + if (gjc->fd != source) + gjc->fd = source; if (source == -1) { STATE_EVT(JCONN_STATE_OFF) return; } - j->state = JCONN_STATE_CONNECTED; + gjc->state = JCONN_STATE_CONNECTED; STATE_EVT(JCONN_STATE_CONNECTED) /* start stream */ - x = jutil_header(NS_CLIENT, j->user->server); + x = jutil_header(NS_CLIENT, gjc->user->server); t = xmlnode2str(x); /* this is ugly, we can create the string here instead of jutil_header */ /* what do you think about it? -madcat */ t2 = strstr(t, "/>"); *t2++ = '>'; *t2 = '\0'; - gjab_send_raw(j, ""); - gjab_send_raw(j, t); + gjab_send_raw(gjc, ""); + gjab_send_raw(gjc, t); xmlnode_free(x); - j->state = JCONN_STATE_ON; + gjc->state = JCONN_STATE_ON; STATE_EVT(JCONN_STATE_ON); - gc = GJ_GC(j); - gc->inpa = gaim_input_add(j->fd, GAIM_INPUT_READ, jabber_callback, gc); + gc = GJ_GC(gjc); + gc->inpa = gaim_input_add(gjc->fd, GAIM_INPUT_READ, jabber_callback, gc); } -static void gjab_start(gjconn j) +static void gjab_start(gjconn gjc) { struct aim_user *user; int port; - if (!j || j->state != JCONN_STATE_OFF) + if (!gjc || gjc->state != JCONN_STATE_OFF) return; - user = GJ_GC(j)->user; + user = GJ_GC(gjc)->user; port = user->proto_opt[USEROPT_PORT][0] ? atoi(user->proto_opt[USEROPT_PORT]) : DEFAULT_PORT; - j->parser = XML_ParserCreate(NULL); - XML_SetUserData(j->parser, (void *)j); - XML_SetElementHandler(j->parser, startElement, endElement); - XML_SetCharacterDataHandler(j->parser, charData); - - j->fd = proxy_connect(j->user->server, port, gjab_connected, GJ_GC(j)); - if (!user->gc || (j->fd < 0)) { + gjc->parser = XML_ParserCreate(NULL); + XML_SetUserData(gjc->parser, (void *)gjc); + XML_SetElementHandler(gjc->parser, startElement, endElement); + XML_SetCharacterDataHandler(gjc->parser, charData); + + gjc->fd = proxy_connect(gjc->user->server, port, gjab_connected, GJ_GC(gjc)); + if (!user->gc || (gjc->fd < 0)) { STATE_EVT(JCONN_STATE_OFF) return; } } +/* + * Find chat by chat group name + */ static struct conversation *find_chat(struct gaim_connection *gc, char *name) { GSList *bcs = gc->buddy_chats; @@ -524,33 +565,99 @@ return b; } -static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat) +/* + * Find chat by "chat id" + * + * Returns: 0 on success and jabber_chat pointer set + * or -EINVAL on error and jabber_chat pointer is + * undefined. + * + * TBD: Slogging through the buddy_chats list seems + * redundant since the chat i.d. is mirrored in the + * jabber_chat struct list. But that's the way it + * was, so that's the way I'm leaving it--for now. + */ +static int jabber_find_chat_by_convo_id(struct gaim_connection *gc, int id, struct jabber_chat **jc) { - GSList *bcs = ((struct jabber_data *)gc->proto_data)->existing_chats; + GSList *bcs = gc->buddy_chats; + struct conversation *b = NULL; + struct jabber_data *jd = gc->proto_data; + + *jc = NULL; + + while(bcs != NULL) { + b = bcs->data; + if (id == b->id) + break; + bcs = bcs->next; + } + + if (bcs != NULL) { + bcs = jd->chats; + while (bcs != NULL) { + *jc = bcs->data; + if ((*jc)->state == JCS_ACTIVE && (*jc)->b == b) + break; + bcs = bcs->next; + } + } + + return(bcs == NULL? -EINVAL : 0); +} + +/* + * Find any chat + */ +static struct jabber_chat *find_any_chat(struct gaim_connection *gc, jid chat) +{ + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; struct jabber_chat *jc = NULL; - while (bcs) { - jc = bcs->data; + while (jcs) { + jc = jcs->data; if (!jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER)) break; jc = NULL; - bcs = bcs->next; + jcs = jcs->next; } return jc; } + +/* + * Find existing/active Jabber chat + */ +static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat) +{ + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; + struct jabber_chat *jc = NULL; + + while (jcs) { + jc = jcs->data; + if (jc->state == JCS_ACTIVE && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER)) + break; + jc = NULL; + jcs = jcs->next; + } + + return jc; +} + +/* + * Find pending chat + */ static struct jabber_chat *find_pending_chat(struct gaim_connection *gc, jid chat) { - GSList *bcs = ((struct jabber_data *)gc->proto_data)->pending_chats; + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; struct jabber_chat *jc = NULL; - while (bcs) { - jc = bcs->data; - if (!jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER)) + while (jcs) { + jc = jcs->data; + if (jc->state == JCS_PENDING && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER)) break; jc = NULL; - bcs = bcs->next; + jcs = jcs->next; } return jc; @@ -569,7 +676,49 @@ return FALSE; } -static void jabber_handlemessage(gjconn j, jpacket p) +/* + * keep track of away msg same as yahoo plugin + */ +static void jabber_track_away(gjconn gjc, jpacket p, char *name) +{ + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + gpointer val = g_hash_table_lookup(jd->hash, name); + char *show; + char *vshow = NULL; + char *status = NULL; + char *msg = NULL; + + if((show = xmlnode_get_tag_data(p->x, "show")) != NULL) { + if (!strcasecmp(show, "away")) { + vshow = "Away"; + } else if (!strcasecmp(show, "chat")) { + vshow = "Online"; + } else if (!strcasecmp(show, "xa")) { + vshow = "Extended Away"; + } else if (!strcasecmp(show, "dnd")) { + vshow = "Do Not Disturb"; + } + } + + status = xmlnode_get_tag_data(p->x, "status"); + + if(vshow != NULL || status != NULL ) { + /* kinda hokey, but it works :-) */ + msg = g_strdup_printf("%s%s%s", + (vshow == NULL? "" : vshow), + (vshow == NULL || status == NULL? "" : ": "), + (status == NULL? "" : status)); + } + + if (val) { + g_free(val); + g_hash_table_insert(jd->hash, name, msg); + } else { + g_hash_table_insert(jd->hash, g_strdup(name), msg); + } +} + +static void jabber_handlemessage(gjconn gjc, jpacket p) { xmlnode y, xmlns, subj; @@ -608,24 +757,29 @@ data = g_strsplit(room, "@", 2); m = g_list_append(m, g_strdup(data[0])); m = g_list_append(m, g_strdup(data[1])); - m = g_list_append(m, g_strdup(j->user->user)); + m = g_list_append(m, g_strdup(gjc->user->user)); g_strfreev(data); - serv_got_chat_invite(GJ_GC(j), room, from, msg, m); + serv_got_chat_invite(GJ_GC(gjc), room, from, msg, m); } else if (msg) { /* whisper */ struct jabber_chat *jc; g_snprintf(m, sizeof(m), "%s", msg); - if (((jc = find_existing_chat(GJ_GC(j), p->from)) != NULL) && jc->b) - serv_got_chat_in(GJ_GC(j), jc->b->id, p->from->resource, 1, m, time(NULL)); + if (((jc = find_existing_chat(GJ_GC(gjc), p->from)) != NULL) && jc->b) + serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 1, m, time(NULL)); else { int flags = 0; if (xmlnode_get_tag(p->x, "gaim")) flags = IM_FLAG_GAIMUSER; if (find_conversation(jid_full(p->from))) - serv_got_im(GJ_GC(j), jid_full(p->from), m, flags, time(NULL), -1); + serv_got_im(GJ_GC(gjc), jid_full(p->from), m, flags, time(NULL), -1); else { - from = g_strdup_printf("%s@%s", p->from->user, p->from->server); - serv_got_im(GJ_GC(j), from, m, flags, time(NULL), -1); + if(p->from->user) { + from = g_strdup_printf("%s@%s", p->from->user, p->from->server); + } else { + /* server message? */ + from = g_strdup(p->from->server); + } + serv_got_im(GJ_GC(gjc), from, m, flags, time(NULL), -1); g_free(from); } } @@ -665,16 +819,14 @@ } topic = utf8_to_str(topic); - jc = find_existing_chat(GJ_GC(j), p->from); + jc = find_existing_chat(GJ_GC(gjc), p->from); if (!jc) { /* we're not in this chat. are we supposed to be? */ - struct jabber_data *jd = GJ_GC(j)->proto_data; - if ((jc = find_pending_chat(GJ_GC(j), p->from)) != NULL) { + if ((jc = find_pending_chat(GJ_GC(gjc), p->from)) != NULL) { /* yes, we're supposed to be. so now we are. */ - jc->b = serv_got_joined_chat(GJ_GC(j), i++, p->from->user); + jc->b = serv_got_joined_chat(GJ_GC(gjc), i++, p->from->user); jc->id = jc->b->id; - jd->existing_chats = g_slist_append(jd->existing_chats, jc); - jd->pending_chats = g_slist_remove(jd->pending_chats, jc); + jc->state = JCS_ACTIVE; } else { /* no, we're not supposed to be. */ g_free(msg); @@ -683,14 +835,15 @@ } if (p->from->resource) { if (!y) { - if (!find_chat_buddy(jc->b, p->from->resource)) + if (!find_chat_buddy(jc->b, p->from->resource)) { add_chat_buddy(jc->b, p->from->resource); - else if ((y = xmlnode_get_tag(p->x, "status"))) { + } else if ((y = xmlnode_get_tag(p->x, "status"))) { char buf[8192]; - msg = xmlnode_get_data(y); - g_snprintf(buf, sizeof(buf), "%s now has status: %s", - p->from->resource, msg); - write_to_conv(jc->b, buf, WFLAG_SYSTEM, NULL, time(NULL), -1); + + g_snprintf(buf, sizeof(buf), "%s@%s/%s", + p->from->user, p->from->server, p->from->resource); + jabber_track_away(gjc, p, buf); + } } else if (jc->b && msg) { char buf[8192]; @@ -703,7 +856,7 @@ g_snprintf(buf, sizeof(buf), "%s", msg); - serv_got_chat_in(GJ_GC(j), jc->b->id, p->from->resource, 0, buf, time(NULL)); + serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 0, buf, time(NULL)); } } else { /* message from the server */ if(jc->b && topic) { @@ -721,7 +874,7 @@ } } -static void jabber_handlepresence(gjconn j, jpacket p) +static void jabber_handlepresence(gjconn gjc, jpacket p) { char *to, *from, *type; struct buddy *b = NULL; @@ -756,7 +909,7 @@ state = 0; } - who = jid_new(j->p, from); + who = jid_new(gjc->p, from); if (who->user == NULL) { /* FIXME: transport */ return; @@ -765,18 +918,17 @@ buddy = g_strdup_printf("%s@%s", who->user, who->server); /* um. we're going to check if it's a chat. if it isn't, and there are pending - * chats, create the chat. if there aren't pending chats, add the buddy. */ - if ((cnv = find_chat(GJ_GC(j), who->user)) == NULL) { + * chats, create the chat. if there aren't pending chats and we don't have the + * buddy on our list, simply bail out. */ + if ((cnv = find_chat(GJ_GC(gjc), who->user)) == NULL) { static int i = 0x70; - struct jabber_data *jd = GJ_GC(j)->proto_data; - if ((jc = find_pending_chat(GJ_GC(j), who)) != NULL) { - jc->b = cnv = serv_got_joined_chat(GJ_GC(j), i++, who->user); + if ((jc = find_pending_chat(GJ_GC(gjc), who)) != NULL) { + jc->b = cnv = serv_got_joined_chat(GJ_GC(gjc), i++, who->user); jc->id = jc->b->id; - jd->existing_chats = g_slist_append(jd->existing_chats, jc); - jd->pending_chats = g_slist_remove(jd->pending_chats, jc); - } else if (!(b = find_buddy(GJ_GC(j), buddy))) { - b = add_buddy(GJ_GC(j), "Buddies", buddy, buddy); - do_export(GJ_GC(j)); + jc->state = JCS_ACTIVE; + } else if ((b = find_buddy(GJ_GC(gjc), buddy)) == NULL) { + g_free(buddy); + return; } } @@ -796,58 +948,54 @@ b->proto_data = g_slist_remove(b->proto_data, resources->data); } if (!b->proto_data) { - serv_got_update(GJ_GC(j), buddy, 0, 0, 0, 0, 0, 0); + serv_got_update(GJ_GC(gjc), buddy, 0, 0, 0, 0, 0, 0); } } else { /* keep track of away msg same as yahoo plugin */ - struct jabber_data *jd = GJ_GC(j)->proto_data; - gpointer val = g_hash_table_lookup(jd->hash, normalize(b->name)); - if (val) { - g_free(val); - g_hash_table_insert(jd->hash, normalize(b->name), - g_strdup(xmlnode_get_tag_data(p->x, "status"))); - } else - g_hash_table_insert(jd->hash, g_strdup(normalize(b->name)), - g_strdup(xmlnode_get_tag_data(p->x, "status"))); - + jabber_track_away(gjc, p, normalize(b->name)); if (!resources) { b->proto_data = g_slist_append(b->proto_data, g_strdup(res)); } - serv_got_update(GJ_GC(j), buddy, 1, 0, 0, 0, state, 0); + serv_got_update(GJ_GC(gjc), buddy, 1, 0, 0, 0, state, 0); } } else { if (who->resource) { if (type && !strcasecmp(type, "unavailable")) { struct jabber_data *jd; - if (!jc && !(jc = find_existing_chat(GJ_GC(j), who))) { + if (!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) { g_free(buddy); return; } jd = jc->gc->proto_data; + /* if it's not ourselves...*/ if (strcmp(who->resource, jc->Jid->resource) && jc->b) { remove_chat_buddy(jc->b, who->resource, NULL); - return; - } - - jd->existing_chats = g_slist_remove(jd->existing_chats, jc); - serv_got_chat_left(GJ_GC(j), jc->id); - g_free(jc); - } else { - if ((!jc && !(jc = find_existing_chat(GJ_GC(j), who))) || !jc->b) { g_free(buddy); return; } - if (!find_chat_buddy(jc->b, who->resource)) + + jc->state = JCS_CLOSED; + serv_got_chat_left(GJ_GC(gjc), jc->id); + /* + * TBD: put back some day? + jd->chats = g_slist_remove(jd->chats, jc); + g_free(jc); + */ + } else { + if ((!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) || !jc->b) { + g_free(buddy); + return; + } + if (!find_chat_buddy(jc->b, who->resource)) { add_chat_buddy(jc->b, who->resource); - else if ((y = xmlnode_get_tag(p->x, "status"))) { + } else { char buf[8192]; - char *msg = xmlnode_get_data(y); - g_snprintf(buf, sizeof(buf), "%s now has status: %s", - p->from->resource, msg); - write_to_conv(jc->b, buf, WFLAG_SYSTEM, NULL, time(NULL), -1); + g_snprintf(buf, sizeof(buf), "%s@%s/%s", + who->user, who->server, who->resource); + jabber_track_away(gjc, p, buf); } } } @@ -858,7 +1006,7 @@ return; } -static void jabber_handles10n(gjconn j, jpacket p) +static void jabber_handles10n(gjconn gjc, jpacket p) { xmlnode g; char *Jid = xmlnode_get_attrib(p->x, "from"); @@ -870,159 +1018,150 @@ xmlnode_put_attrib(g, "type", "subscribed"); else if (!strcmp(ask, "unsubscribe")) xmlnode_put_attrib(g, "type", "unsubscribed"); - else + else { + xmlnode_free(g); return; - - gjab_send(j, g); + } + + gjab_send(gjc, g); + xmlnode_free(g); } -static void jabber_handleroster(gjconn j, xmlnode querynode) +/* + * Pending subscription to a buddy? + */ +#define BUD_SUB_TO_PEND(sub, ask) ((!strcasecmp((sub), "none") || !strcasecmp((sub), "from")) && \ + (ask) != NULL && !strcasecmp((ask), "subscribe")) + +/* + * Subscribed to a buddy? + */ +#define BUD_SUBD_TO(sub, ask) ((!strcasecmp((sub), "to") || !strcasecmp((sub), "both")) && \ + ((ask) == NULL || !strcasecmp((ask), "subscribe"))) + +/* + * Pending unsubscription to a buddy? + */ +#define BUD_USUB_TO_PEND(sub, ask) ((!strcasecmp((sub), "to") || !strcasecmp((sub), "both")) && \ + (ask) != NULL && !strcasecmp((ask), "unsubscribe")) + +/* + * Unsubscribed to a buddy? + */ +#define BUD_USUBD_TO(sub, ask) ((!strcasecmp((sub), "none") || !strcasecmp((sub), "from")) && \ + ((ask) == NULL || !strcasecmp((ask), "unsubscribe"))) + +/* + * If a buddy is added or removed from the roster on another resource + * jabber_handlebuddy is called + * + * Called with roster item node. + */ +static void jabber_handlebuddy(gjconn gjc, xmlnode x) +{ + xmlnode g; + char *Jid, *name, *sub, *ask; + jid who; + struct buddy *b = NULL; + char *buddyname, *groupname; + + Jid = xmlnode_get_attrib(x, "jid"); + name = xmlnode_get_attrib(x, "name"); + sub = xmlnode_get_attrib(x, "subscription"); + ask = xmlnode_get_attrib(x, "ask"); + who = jid_new(gjc->p, Jid); + + /* JFIXME: jabber_handleroster() had a "FIXME: transport" at this + * equivilent point. So... + * + * We haven't allocated any memory or done anything interesting to + * this point, so we'll violate Good Coding Structure here by + * simply bailing out. + */ + if (!who || !who->user) { + return; + } + + buddyname = g_strdup_printf("%s@%s", who->user, who->server); + + if((g = xmlnode_get_tag(x, "group")) == NULL || (groupname = xmlnode_get_data(g)) == NULL) { + groupname = "Buddies"; + } + + + if (BUD_SUB_TO_PEND(sub, ask) || BUD_SUBD_TO(sub, ask)) { + if ((b = find_buddy(GJ_GC(gjc), buddyname)) == NULL) { + debug_printf("adding buddy [4]: %s\n", buddyname); + b = add_buddy(GJ_GC(gjc), groupname, buddyname, + name ? name : buddyname); + do_export(GJ_GC(gjc)); + } + /* + * TBD: this is what we *would* do if we could. But the + * remove_buddy() code doesn't update the UI. And if the + * user selects and tries to remove a buddy already removed + * via here: *ka-boom*! + * + } else if (BUD_USUB_TO_PEND(sub, ask) || BUD_USUBD_TO(sub, ask) || !strcasecmp(sub, "remove")) { + if ((b = find_buddy(GJ_GC(gjc), buddyname)) != NULL) { + struct group *group; + + group = find_group_by_buddy(GJ_GC(gjc), buddyname); + debug_printf("removing buddy [1]: %s, from group: %s\n", + buddyname, group->name); + remove_buddy(GJ_GC(gjc), group, b); + do_export(GJ_GC(gjc)); + } + */ + } + g_free(buddyname); + +} + +static void jabber_handleroster(gjconn gjc, xmlnode querynode) { xmlnode x; x = xmlnode_get_firstchild(querynode); while (x) { - xmlnode g; - char *Jid, *name, *sub, *ask; - jid who; - - Jid = xmlnode_get_attrib(x, "jid"); - name = xmlnode_get_attrib(x, "name"); - sub = xmlnode_get_attrib(x, "subscription"); - ask = xmlnode_get_attrib(x, "ask"); - who = jid_new(j->p, Jid); - - if ((g = xmlnode_get_firstchild(x))) { - while (g) { - if (xmlnode_get_name(g) && - g_strncasecmp(xmlnode_get_name(g), "group", 5) == 0) { - struct buddy *b = NULL; - char *groupname, *buddyname; - - if (!who || !who->user) { - /* FIXME: transport */ - g = xmlnode_get_nextsibling(g); - continue; - } - buddyname = g_strdup_printf("%s@%s", who->user, who->server); - groupname = xmlnode_get_data(xmlnode_get_firstchild(g)); - if (groupname == NULL) - groupname = "Buddies"; - if (strcasecmp(sub, "from") && strcasecmp(sub, "none") && - !(b = find_buddy(GJ_GC(j), buddyname))) { - debug_printf("adding buddy: %s\n", buddyname); - b = add_buddy(GJ_GC(j), groupname, buddyname, - name ? name : buddyname); - do_export(GJ_GC(j)); - /* - } else if (b) { - debug_printf("updating buddy: %s/%s\n", buddyname, name); - g_snprintf(b->name, sizeof(b->name), "%s", buddyname); - g_snprintf(b->show, sizeof(b->show), "%s", - name ? name : buddyname); - */ - } - g_free(buddyname); - } - g = xmlnode_get_nextsibling(g); - } - } else { - struct buddy *b; - char *buddyname; - - if (!who || !who->user) { - /* FIXME: transport */ - x = xmlnode_get_nextsibling(x); - continue; - } - buddyname = g_strdup_printf("%s@%s", who->user, who->server); - if (strcasecmp(sub, "from") && strcasecmp(sub, "none") && - !(b = find_buddy(GJ_GC(j), buddyname))) { - debug_printf("adding buddy: %s\n", buddyname); - b = add_buddy(GJ_GC(j), "Buddies", buddyname, name ? name : Jid); - do_export(GJ_GC(j)); - } - g_free(buddyname); - } - + jabber_handlebuddy(gjc, x); x = xmlnode_get_nextsibling(x); } x = jutil_presnew(0, NULL, "Online"); - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); } -static void jabber_handlevcard(gjconn j, xmlnode querynode, char *from) -{ - struct gaim_connection *gc = GJ_GC(j); - char buf[1024]; - char *fn, *url, *email, *nickname, *status, *desc; - jid who; - char *buddy; - struct jabber_data *jd = GJ_GC(j)->proto_data; - int at = 0; - - who = jid_new(j->p, from); - buddy = g_strdup_printf("%s@%s", who->user, who->server); - - fn = xmlnode_get_tag_data(querynode, "FN"); - url = xmlnode_get_tag_data(querynode, "URL"); - email = xmlnode_get_tag_data(querynode, "EMAIL"); - nickname = xmlnode_get_tag_data(querynode, "NICKNAME"); - desc = xmlnode_get_tag_data(querynode, "DESC"); - status = g_hash_table_lookup(jd->hash, buddy); - if (!status) - status = "Online"; - - at = g_snprintf(buf, sizeof buf, "Jabber ID: %s
", buddy); - if (fn) - at += g_snprintf(buf + at, sizeof(buf) - at, "Full Name: %s
", fn); - if (nickname) - at += g_snprintf(buf + at, sizeof(buf) - at, "Nickname: %s
", nickname); - if (url) - at += g_snprintf(buf + at, sizeof(buf) - at, "URL: %s
", - url, url); - if (email) - at += g_snprintf(buf + at, sizeof(buf) - at, - "Email: %s
", email, email); - at += g_snprintf(buf + at, sizeof(buf) - at, "Status: %s\n", status); - if (desc) - at += g_snprintf(buf + at, sizeof(buf) - at, "
%s
\n", desc); - - g_show_info_text(gc, buddy, 2, buf, NULL); - g_free(buddy); -} - -static void jabber_handleauthresp(gjconn j, jpacket p) +static void jabber_handleauthresp(gjconn gjc, jpacket p) { if (jpacket_subtype(p) == JPACKET__RESULT) { if (xmlnode_has_children(p->x)) { xmlnode query = xmlnode_get_tag(p->x, "query"); - set_login_progress(GJ_GC(j), 4, "Authenticating"); + set_login_progress(GJ_GC(gjc), 4, "Authenticating"); if (!xmlnode_get_tag(query, "digest")) { - g_free(j->sid); - j->sid = NULL; + g_free(gjc->sid); + gjc->sid = NULL; } - gjab_auth(j); + gjab_auth(gjc); } else { debug_printf("auth success\n"); - account_online(GJ_GC(j)); - serv_finish_login(GJ_GC(j)); - - if (bud_list_cache_exists(GJ_GC(j))) - do_import(GJ_GC(j), NULL); - - ((struct jabber_data *)GJ_GC(j)->proto_data)->did_import = TRUE; - - gjab_reqroster(j); + account_online(GJ_GC(gjc)); + serv_finish_login(GJ_GC(gjc)); + + if (bud_list_cache_exists(GJ_GC(gjc))) + do_import(GJ_GC(gjc), NULL); + + ((struct jabber_data *)GJ_GC(gjc)->proto_data)->did_import = TRUE; + + gjab_reqroster(gjc); } } else { xmlnode xerr; char *errmsg = NULL; int errcode = 0; - struct jabber_data *jd = GJ_GC(j)->proto_data; + struct jabber_data *jd = GJ_GC(gjc)->proto_data; debug_printf("auth failed\n"); xerr = xmlnode_get_tag(p->x, "error"); @@ -1034,16 +1173,16 @@ g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg); } else g_snprintf(msg, sizeof(msg), "%s", errmsg); - hide_login_progress(GJ_GC(j), msg); + hide_login_progress(GJ_GC(gjc), msg); } else { - hide_login_progress(GJ_GC(j), "Unknown login error"); + hide_login_progress(GJ_GC(gjc), "Unknown login error"); } jd->die = TRUE; } } -static void jabber_handleversion(gjconn j, xmlnode iqnode) { +static void jabber_handleversion(gjconn gjc, xmlnode iqnode) { xmlnode querynode, x; char *id, *from; char os[1024]; @@ -1064,12 +1203,12 @@ xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "version"), VERSION, -1); xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "os"), os, -1); - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); } -static void jabber_handletime(gjconn j, xmlnode iqnode) { +static void jabber_handletime(gjconn gjc, xmlnode iqnode) { xmlnode querynode, x; char *id, *from; time_t now_t; @@ -1095,15 +1234,15 @@ strftime(buf, 1024, "%d %b %Y %T", now); xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "display"), buf, -1); - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); } -static void jabber_handlelast(gjconn j, xmlnode iqnode) { +static void jabber_handlelast(gjconn gjc, xmlnode iqnode) { xmlnode x, querytag; char *id, *from; - struct jabber_data *jd = GJ_GC(j)->proto_data; + struct jabber_data *jd = GJ_GC(gjc)->proto_data; char idle_time[32]; id = xmlnode_get_attrib(iqnode, "id"); @@ -1117,58 +1256,116 @@ g_snprintf(idle_time, sizeof idle_time, "%ld", jd->idle ? time(NULL) - jd->idle : 0); xmlnode_put_attrib(querytag, "seconds", idle_time); - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); } -static void jabber_handlepacket(gjconn j, jpacket p) +/* + * delete == TRUE: delete found entry + * + * returns pointer to (local) copy of value if found, NULL otherwise + * + * Note: non-reentrant! Local static storage re-used on subsequent calls. + * If you're going to need to keep the returned value, make a copy! + */ +static gchar *jabber_track_queries(GHashTable *queries, gchar *key, gboolean delete) +{ + gpointer my_key, my_val; + static gchar *ret_val = NULL; + + if(ret_val != NULL) { + g_free(ret_val); + ret_val = NULL; + } + + /* self-protection */ + if(queries != NULL && key != NULL) { + if(g_hash_table_lookup_extended(queries, key, &my_key, &my_val)) { + ret_val = g_strdup((gchar *) my_val); + if(delete) { + g_hash_table_remove(queries, key); + g_free(my_key); + g_free(my_val); + } + } + } + + return(ret_val); +} + +static void jabber_handlepacket(gjconn gjc, jpacket p) { char *id; switch (p->type) { case JPACKET_MESSAGE: - jabber_handlemessage(j, p); + jabber_handlemessage(gjc, p); break; case JPACKET_PRESENCE: - jabber_handlepresence(j, p); + jabber_handlepresence(gjc, p); break; case JPACKET_IQ: debug_printf("jpacket_subtype: %d\n", jpacket_subtype(p)); - if (((id = xmlnode_get_attrib(p->x, "id")) != NULL) && !strcmp(id, IQID_AUTH)) { - jabber_handleauthresp(j, p); + id = xmlnode_get_attrib(p->x, "id"); + if (id != NULL && !strcmp(id, IQID_AUTH)) { + jabber_handleauthresp(gjc, p); break; } if (jpacket_subtype(p) == JPACKET__SET) { + xmlnode querynode; + querynode = xmlnode_get_tag(p->x, "query"); + if (NSCHECK(querynode, "jabber:iq:roster")) { + jabber_handlebuddy(gjc, xmlnode_get_firstchild(querynode)); + } } else if (jpacket_subtype(p) == JPACKET__GET) { xmlnode querynode; querynode = xmlnode_get_tag(p->x, "query"); if (NSCHECK(querynode, NS_VERSION)) { - jabber_handleversion(j, p->x); + jabber_handleversion(gjc, p->x); } else if (NSCHECK(querynode, NS_TIME)) { - jabber_handletime(j, p->x); + jabber_handletime(gjc, p->x); } else if (NSCHECK(querynode, "jabber:iq:last")) { - jabber_handlelast(j, p->x); + jabber_handlelast(gjc, p->x); } } else if (jpacket_subtype(p) == JPACKET__RESULT) { xmlnode querynode, vcard; char *xmlns, *from; + /* + * TBD: ISTM maybe this part could use a serious re-work? + */ from = xmlnode_get_attrib(p->x, "from"); querynode = xmlnode_get_tag(p->x, "query"); - xmlns = xmlnode_get_attrib(querynode, "xmlns"); vcard = xmlnode_get_tag(p->x, "vCard"); if (!vcard) vcard = xmlnode_get_tag(p->x, "VCARD"); if (NSCHECK(querynode, NS_ROSTER)) { - jabber_handleroster(j, querynode); + jabber_handleroster(gjc, querynode); } else if (NSCHECK(querynode, NS_VCARD)) { - jabber_handlevcard(j, querynode, from); + jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */ + jabber_handlevcard(gjc, querynode, from); } else if (vcard) { - jabber_handlevcard(j, vcard, from); + jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */ + jabber_handlevcard(gjc, vcard, from); + } else if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL) { + debug_printf("jabber:iq:query: %s\n", xmlns); } else { - /* debug_printf("jabber:iq:query: %s\n", xmlns); */ + char *val; + + debug_printf("jabber:iq: %s\n", xmlnode2str(p->x)); + + /* handle "null" query results */ + if((val = jabber_track_queries(gjc->queries, id, TRUE)) != NULL) { + if(strcmp((char *) val, "vCard") == 0) { + /* + * No actual vCard, but there's other stuff. This + * way the user always gets some kind of response. + */ + jabber_handlevcard(gjc, NULL, from); + } + } } } else if (jpacket_subtype(p) == JPACKET__ERROR) { @@ -1193,7 +1390,7 @@ break; case JPACKET_S10N: - jabber_handles10n(j, p); + jabber_handles10n(gjc, p); break; default: debug_printf("jabber: packet type %d (%s)\n", p->type, xmlnode2str(p->x)); @@ -1204,19 +1401,19 @@ return; } -static void jabber_handlestate(gjconn j, int state) +static void jabber_handlestate(gjconn gjc, int state) { switch (state) { case JCONN_STATE_OFF: - hide_login_progress(GJ_GC(j), "Unable to connect"); - signoff(GJ_GC(j)); + hide_login_progress(GJ_GC(gjc), "Unable to connect"); + signoff(GJ_GC(gjc)); break; case JCONN_STATE_CONNECTED: - set_login_progress(GJ_GC(j), 2, "Connected"); + set_login_progress(GJ_GC(gjc), 2, "Connected"); break; case JCONN_STATE_ON: - set_login_progress(GJ_GC(j), 3, "Requesting Authentication Method"); - gjab_reqauth(j); + set_login_progress(GJ_GC(gjc), 3, "Requesting Authentication Method"); + gjab_reqauth(gjc); break; default: debug_printf("state change: %d\n", state); @@ -1231,10 +1428,11 @@ char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "GAIM"); jd->hash = g_hash_table_new(g_str_hash, g_str_equal); + jd->chats = NULL; /* we have no chats yet */ set_login_progress(gc, 1, "Connecting"); - if (!(jd->jc = gjab_new(loginname, user->password, gc))) { + if (!(jd->gjc = gjab_new(loginname, user->password, gc))) { g_free(loginname); debug_printf("jabber: unable to connect (jab_new failed)\n"); hide_login_progress(gc, "Unable to connect"); @@ -1243,9 +1441,10 @@ } g_free(loginname); - gjab_state_handler(jd->jc, jabber_handlestate); - gjab_packet_handler(jd->jc, jabber_handlepacket); - gjab_start(jd->jc); + gjab_state_handler(jd->gjc, jabber_handlestate); + gjab_packet_handler(jd->gjc, jabber_handlepacket); + jd->gjc->queries = g_hash_table_new(g_str_hash, g_str_equal); + gjab_start(jd->gjc); } static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) { @@ -1256,23 +1455,51 @@ static gboolean jabber_free(gpointer data) { - gjab_delete(data); + struct jabber_data *jd = data; + + gjab_delete(jd->gjc); + g_free(jd->gjc->sid); + jd->gjc = NULL; + g_free(jd); + return FALSE; } static void jabber_close(struct gaim_connection *gc) { struct jabber_data *jd = gc->proto_data; - g_hash_table_foreach_remove(jd->hash, jabber_destroy_hash, NULL); - g_hash_table_destroy(jd->hash); + + if(jd) { + GSList *jcs = jd->chats; + + /* Free-up the jabber_chat struct allocs and the list */ + while (jcs) { + g_free(jcs->data); + jcs = jcs->next; + } + g_slist_free(jd->chats); + + /* Free-up the away status memories and the list */ + if(jd->hash != NULL) { + g_hash_table_foreach_remove(jd->hash, jabber_destroy_hash, NULL); + g_hash_table_destroy(jd->hash); + jd->hash = NULL; + } + + /* Free-up the pending queries memories and the list */ + if(jd->gjc->queries != NULL) { + g_hash_table_foreach_remove(jd->gjc->queries, jabber_destroy_hash, NULL); + g_hash_table_destroy(jd->gjc->queries); + jd->gjc->queries = NULL; + } + } if (gc->inpa) gaim_input_remove(gc->inpa); - close(jd->jc->fd); - g_timeout_add(50, jabber_free, jd->jc); - xmlnode_free(jd->jc->current); - g_free(jd->jc->sid); - jd->jc = NULL; - g_free(jd); + + if(jd) { + g_timeout_add(50, jabber_free, jd); + xmlnode_free(jd->gjc->current); + } gc->proto_data = NULL; } @@ -1280,14 +1507,15 @@ { xmlnode x, y; char *realwho; - gjconn j = ((struct jabber_data *)gc->proto_data)->jc; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; if (!who || !message) return 0; x = xmlnode_new_tag("message"); - if (!strchr(who, '@')) - realwho = g_strdup_printf("%s@%s", who, j->user->server); + /* Bare username and "username" not the server itself? */ + if (!strchr(who, '@') && strcmp(who, gjc->user->server) != 0) + realwho = g_strdup_printf("%s@%s", who, gjc->user->server); else realwho = g_strdup(who); xmlnode_put_attrib(x, "to", realwho); @@ -1303,7 +1531,7 @@ g_free(utf8); } - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); xmlnode_free(x); return 1; } @@ -1312,7 +1540,9 @@ { xmlnode x, y; char *realwho; - gjconn j = ((struct jabber_data *)gc->proto_data)->jc; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + struct buddy *buddy = NULL; + struct group *buddy_group = NULL; if (!((struct jabber_data *)gc->proto_data)->did_import) return; @@ -1324,9 +1554,9 @@ return; if (!strchr(name, '@')) - realwho = g_strdup_printf("%s@%s", name, j->user->server); + realwho = g_strdup_printf("%s@%s", name, gjc->user->server); else { - jid who = jid_new(j->p, name); + jid who = jid_new(gjc->p, name); if (who->user == NULL) { /* FIXME: transport */ return; @@ -1334,16 +1564,38 @@ realwho = g_strdup_printf("%s@%s", who->user, who->server); } + x = jutil_iqnew(JPACKET__SET, NS_ROSTER); y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item"); xmlnode_put_attrib(y, "jid", realwho); - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); + + /* If we can find the buddy, there's an alias for him and + * it's not 0-length, add the "name" attribute. + */ + if((buddy = find_buddy(gc, realwho)) != NULL && + buddy->show != NULL && (buddy->show)[0] != '\0') { + + xmlnode_put_attrib(y, "name", buddy->show); + } + + /* + * Find out what group the buddy's in and send that along + * with the roster item. + */ + if((buddy_group = find_group_by_buddy(gc, realwho)) != NULL) { + xmlnode z; + z = xmlnode_insert_tag(y, "group"); + xmlnode_insert_cdata(z, buddy_group->name, -1); + } + + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); xmlnode_free(x); x = xmlnode_new_tag("presence"); xmlnode_put_attrib(x, "to", realwho); xmlnode_put_attrib(x, "type", "subscribe"); - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + xmlnode_free(x); g_free(realwho); } @@ -1352,22 +1604,20 @@ { xmlnode x, y; char *realwho; - gjconn j = ((struct jabber_data *)gc->proto_data)->jc; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; if (!name) return; if (!strchr(name, '@')) - realwho = g_strdup_printf("%s@%s", name, j->user->server); + realwho = g_strdup_printf("%s@%s", name, gjc->user->server); else realwho = g_strdup(name); - x = jutil_iqnew(JPACKET__SET, NS_ROSTER); - y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item"); - xmlnode_put_attrib(y, "jid", realwho); - xmlnode_put_attrib(y, "subscription", "remove"); - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); - + x = xmlnode_new_tag("presence"); + xmlnode_put_attrib(x, "to", realwho); + xmlnode_put_attrib(x, "type", "unsubscribe"); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); g_free(realwho); xmlnode_free(x); } @@ -1390,23 +1640,66 @@ static GList *jabber_chat_info(struct gaim_connection *gc) { - gjconn j = ((struct jabber_data *)gc->proto_data)->jc; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + + static char *confserv = NULL; /* this pointer must be persistent */ + gchar *server; GList *m = NULL; struct proto_chat_entry *pce; + /* This is a scientific wild-ass guess... + * + * If there are more than two "components" to the current server name, + * lop-off the left-most component and replace with "conference." + */ + if(confserv != NULL) { + g_free(confserv); /* dispose of the old value */ + } + + if((server = g_strdup(gjc->user->server)) == NULL) { + confserv = g_strdup(DEFAULT_GROUPCHAT); + } else { + gchar **splits, **index; + gchar *tmp; + int cnt = 0; + + + index = splits = g_strsplit(server, ".", -1); /* split the connected server */ + + while(*(index++)) /* index to the end--counting the parts */ + ++cnt; + + /* + * If we've more than two parts, point to the second part. Else point + * to the start. + */ + if(cnt > 2) { + index -= cnt; + } else { + index = splits; + } + + /* Put it together */ + confserv = g_strjoin(".", "conference", (tmp = g_strjoinv(".", index)), NULL); + + g_free(server); /* we don't need this stuff no more */ + g_free(tmp); + g_strfreev(splits); + } + pce = g_new0(struct proto_chat_entry, 1); pce->label = _("Room:"); m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("Server:"); - pce->def = DEFAULT_GROUPCHAT; + pce->def = confserv; m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("Handle:"); - pce->def = j->user->user; + pce->def = gjc->user->user; m = g_list_append(m, pce); return m; @@ -1416,35 +1709,60 @@ { xmlnode x; char *realwho; - gjconn j = ((struct jabber_data *)gc->proto_data)->jc; - GSList *pc = ((struct jabber_data *)gc->proto_data)->pending_chats; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; struct jabber_chat *jc; + jid Jid; if (!data || !data->next || !data->next->next) return; - jc = g_new0(struct jabber_chat, 1); realwho = create_valid_jid(data->data, data->next->data, data->next->next->data); - jc->Jid = jid_new(j->p, realwho); - jc->gc = gc; debug_printf("%s\n", realwho); + Jid = jid_new(gjc->p, realwho); + + if((jc = find_any_chat(gc, Jid)) != NULL) { + free(Jid); /* don't need it, after all */ + switch(jc->state) { + case JCS_PENDING: + debug_printf("attempt to re-join already pending Jabber chat! (ignoring)\n"); + g_free(realwho); /* yuck! */ + return; + case JCS_ACTIVE: + debug_printf("attempt to re-join already active Jabber chat! (ignoring)\n"); + g_free(realwho); /* yuck! */ + return; + case JCS_CLOSED: + debug_printf("rejoining previously closed Jabber chat\n"); + break; + default: + debug_printf("found Jabber chat in unknown state! (ignoring)\n"); + g_free(realwho); /* yuck! */ + return; + } + } else { + debug_printf("joining completely new Jabber chat\n"); + jc = g_new0(struct jabber_chat, 1); + jc->Jid = Jid; + jc->gc = gc; + ((struct jabber_data *)gc->proto_data)->chats = g_slist_append(jcs, jc); + } + + jc->state = JCS_PENDING; + x = jutil_presnew(0, realwho, NULL); - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); g_free(realwho); - - ((struct jabber_data *)gc->proto_data)->pending_chats = g_slist_append(pc, jc); } static void jabber_chat_invite(struct gaim_connection *gc, int id, char *message, char *name) { xmlnode x, y; - GSList *bcs = gc->buddy_chats; - struct conversation *b = NULL; struct jabber_data *jd = gc->proto_data; - gjconn j = jd->jc; + gjconn gjc = jd->gjc; struct jabber_chat *jc = NULL; char *realwho, *subject; @@ -1452,28 +1770,12 @@ return; /* find which chat we're inviting to */ - while (bcs) { - b = bcs->data; - if (id == b->id) - break; - bcs = bcs->next; - } - if (!bcs) - return; - - bcs = jd->existing_chats; - while (bcs) { - jc = bcs->data; - if (jc->b == b) - break; - bcs = bcs->next; - } - if (!bcs) + if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0) return; x = xmlnode_new_tag("message"); if (!strchr(name, '@')) - realwho = g_strdup_printf("%s@%s", name, j->user->server); + realwho = g_strdup_printf("%s@%s", name, gjc->user->server); else realwho = g_strdup(name); xmlnode_put_attrib(x, "to", realwho); @@ -1492,75 +1794,41 @@ g_free(utf8); } - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); xmlnode_free(x); } static void jabber_chat_leave(struct gaim_connection *gc, int id) { - GSList *bcs = gc->buddy_chats; - struct conversation *b = NULL; struct jabber_data *jd = gc->proto_data; - gjconn j = jd->jc; + gjconn gjc = jd->gjc; struct jabber_chat *jc = NULL; char *realwho; xmlnode x; - while (bcs) { - b = bcs->data; - if (id == b->id) - break; - bcs = bcs->next; - } - if (!bcs) - return; - - bcs = jd->existing_chats; - while (bcs) { - jc = bcs->data; - if (jc->b == b) - break; - bcs = bcs->next; - } - if (!bcs) + /* Find out which chat we're leaving */ + if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0) return; realwho = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server); x = jutil_presnew(0, realwho, NULL); g_free(realwho); xmlnode_put_attrib(x, "type", "unavailable"); - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); jc->b = NULL; } static int jabber_chat_send(struct gaim_connection *gc, int id, char *message) { - GSList *bcs = gc->buddy_chats; - struct conversation *b = NULL; - struct jabber_data *jd = gc->proto_data; xmlnode x, y; struct jabber_chat *jc = NULL; char *chatname; - - while (bcs) { - b = bcs->data; - if (id == b->id) - break; - bcs = bcs->next; - } - if (!bcs) - return -EINVAL; - - bcs = jd->existing_chats; - while (bcs) { - jc = bcs->data; - if (jc->b == b) - break; - bcs = bcs->next; - } - if (!bcs) - return -EINVAL; + int retval = 0; + + /* Find out which chat we're sending to */ + if((retval = jabber_find_chat_by_convo_id(gc, id, &jc)) != 0) + return(retval); x = xmlnode_new_tag("message"); xmlnode_put_attrib(x, "from", jid_full(jc->Jid)); @@ -1586,37 +1854,19 @@ g_free(utf8); } - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); xmlnode_free(x); return 0; } static void jabber_chat_whisper(struct gaim_connection *gc, int id, char *who, char *message) { - GSList *bcs = gc->buddy_chats; - struct conversation *b = NULL; - struct jabber_data *jd = gc->proto_data; xmlnode x, y; struct jabber_chat *jc = NULL; char *chatname; - while (bcs) { - b = bcs->data; - if (id == b->id) - break; - bcs = bcs->next; - } - if (!bcs) - return; - - bcs = jd->existing_chats; - while (bcs) { - jc = bcs->data; - if (jc->b == b) - break; - bcs = bcs->next; - } - if (!bcs) + /* Find out which chat we're whispering to */ + if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0) return; x = xmlnode_new_tag("message"); @@ -1633,7 +1883,7 @@ g_free(utf8); } - gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); xmlnode_free(x); } @@ -1645,44 +1895,124 @@ g_return_val_if_fail((s != NULL), NULL); - u = t = g_strdup(s); - - g_strdown(t); - - while (*t && (x < BUF_LEN - 1)) { - if (*t != ' ') - buf[x++] = *t; - t++; + /* Somebody called us with s == NULL once... */ + if(s == NULL) { + return(NULL); + } else { + u = t = g_strdup(s); + + g_strdown(t); + + while (*t && (x < BUF_LEN - 1)) { + if (*t != ' ') + buf[x++] = *t; + t++; + } + buf[x] = '\0'; + g_free(u); + + if (!strchr(buf, '@')) { + strcat(buf, "@jabber.org"); /* this isn't always right, but eh */ + } else if ((u = strchr(strchr(buf, '@'), '/')) != NULL) { + *u = '\0'; + } + + return buf; } - buf[x] = '\0'; - g_free(u); - - if (!strchr(buf, '@')) { - strcat(buf, "@jabber.org"); /* this isn't always right, but eh */ - } else if ((u = strchr(strchr(buf, '@'), '/')) != NULL) { - *u = '\0'; - } - - return buf; } static void jabber_get_info(struct gaim_connection *gc, char *who) { xmlnode x; char *id; + char *realwho; struct jabber_data *jd = gc->proto_data; - gjconn j = jd->jc; + gjconn gjc = jd->gjc; x = jutil_iqnew(JPACKET__GET, NS_VCARD); - xmlnode_put_attrib(x, "to", who); - id = gjab_getid(j); + /* Bare username? */ + if (!strchr(who, '@')) { + realwho = g_strdup_printf("%s@%s", who, gjc->user->server); + } else { + realwho = g_strdup(who); + } + xmlnode_put_attrib(x, "to", realwho); + g_free(realwho); + + id = gjab_getid(gjc); xmlnode_put_attrib(x, "id", id); - gjab_send(j, x); + g_hash_table_insert(jd->gjc->queries, g_strdup(id), g_strdup("vCard")); + + gjab_send(gjc, x); xmlnode_free(x); } +static void jabber_get_away_msg(struct gaim_connection *gc, char *who) { + struct jabber_data *jd = gc->proto_data; + gjconn gjc = jd->gjc; + char *status; + + /* space for all elements: Jabber I.D. + "status" + NULL (list terminator) */ + gchar **str_arr = (gchar **) g_new(gpointer, 3); + gchar **ap = str_arr; + gchar *realwho, *final; + + /* Bare username? */ + if (!strchr(who, '@')) { + realwho = g_strdup_printf("%s@%s", who, gjc->user->server); + } else { + realwho = g_strdup(who); + } + *ap++ = g_strdup_printf("Jabber ID: %s
\n", realwho); + + if((status = g_hash_table_lookup(jd->hash, realwho)) == NULL) { + status = "Online"; + } + *ap++ = g_strdup_printf("Status: %s
\n", status); + + *ap = NULL; + + final= g_strjoinv(NULL, str_arr); + g_strfreev(str_arr); + + g_show_info_text(gc, realwho, 2, final, NULL); + g_free(realwho); + g_free(final); + +} + +static void jabber_get_cb_info(struct gaim_connection *gc, int cid, char *who) { + struct jabber_chat *jc = NULL; + char *realwho; + + /* Find out which chat */ + if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0) + return; + + realwho = g_strdup_printf("%s@%s/%s", jc->Jid->user, jc->Jid->server, who); + + jabber_get_info(gc, realwho); + g_free(realwho); +} + +static void jabber_get_cb_away_msg(struct gaim_connection *gc, int cid, char *who) { + struct jabber_chat *jc = NULL; + char *realwho; + + /* Find out which chat */ + if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0) + return; + + realwho = g_strdup_printf("%s@%s/%s", jc->Jid->user, jc->Jid->server, who); + + jabber_get_away_msg(gc, realwho); + + g_free(realwho); + +} + static GList *jabber_buddy_menu(struct gaim_connection *gc, char *who) { GList *m = NULL; struct proto_buddy_menu *pbm; @@ -1692,6 +2022,11 @@ pbm->callback = jabber_get_info; pbm->gc = gc; m = g_list_append(m, pbm); + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Get Away Msg"); + pbm->callback = jabber_get_away_msg; + pbm->gc = gc; + m = g_list_append(m, pbm); return m; } @@ -1712,7 +2047,7 @@ { xmlnode x, y; struct jabber_data *jd = gc->proto_data; - gjconn j = jd->jc; + gjconn gjc = jd->gjc; gc->away = NULL; /* never send an auto-response */ @@ -1753,7 +2088,7 @@ } } - gjab_send(j, x); + gjab_send(gjc, x); xmlnode_free(x); } @@ -1765,7 +2100,7 @@ static void jabber_keepalive(struct gaim_connection *gc) { struct jabber_data *jd = (struct jabber_data *)gc->proto_data; - gjab_send_raw(jd->jc, " \t "); + gjab_send_raw(jd->gjc, " \t "); } static GList *jabber_user_opts() @@ -1790,6 +2125,807 @@ } } +/*---------------------------------------*/ +/* Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/* + * V-Card format: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * <ROLE/> + * <DESC/> + * <BDAY/> + * </vCard> + * + * See also: + * + * http://docs.jabber.org/proto/html/vcard-temp.html + * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd + */ + +/* + * Cross-reference user-friendly V-Card entry labels to vCard XML tags + * and attributes. + * + * Order is (or should be) unimportant. For example: we have no way of + * knowing in what order real data will arrive. + * + * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag + * name, XML tag's parent tag "path" (relative to vCard node). + * + * List is terminated by a NULL label pointer. + * + * Entries with no label text, but with XML tag and parent tag + * entries, are used by V-Card XML construction routines to + * "automagically" construct the appropriate XML node tree. + * + * Thoughts on future direction/expansion + * + * This is a "simple" vCard. + * + * It is possible for nodes other than the "vCard" node to have + * attributes. Should that prove necessary/desirable, add an + * "attributes" pointer to the vcard_template struct, create the + * necessary tag_attr structs, and add 'em to the vcard_dflt_data + * array. + * + * The above changes will (obviously) require changes to the vCard + * construction routines. + */ + +struct vcard_template { + char *label; /* label text pointer */ + char *text; /* entry text pointer */ + int visible; /* should entry field be "visible?" */ + int editable; /* should entry field be editable? */ + char *tag; /* tag text */ + char *ptag; /* parent tag "path" text */ + char *url; /* vCard display format if URL */ +} vcard_template_data[] = { + {"Full Name", NULL, TRUE, TRUE, "FN", NULL, NULL}, + {"Family Name", NULL, TRUE, TRUE, "FAMILY", "N", NULL}, + {"Given Name", NULL, TRUE, TRUE, "GIVEN", "N", NULL}, + {"Nickname", NULL, TRUE, TRUE, "NICKNAME", NULL, NULL}, + {"URL", NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"}, + {"Street Address", NULL, TRUE, TRUE, "STREET", "ADR", NULL}, + {"Extended Address", NULL, TRUE, TRUE, "EXTADD", "ADR", NULL}, + {"Locality", NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL}, + {"Region", NULL, TRUE, TRUE, "REGION", "ADR", NULL}, + {"Postal Code", NULL, TRUE, TRUE, "PCODE", "ADR", NULL}, + {"Country", NULL, TRUE, TRUE, "COUNTRY", "ADR", NULL}, + {"Telephone", NULL, TRUE, TRUE, "TELEPHONE", NULL, NULL}, + {"Email", NULL, TRUE, TRUE, "EMAIL", NULL, "<A HREF=\"mailto:%s\">%s</A>"}, + {"Organization Name", NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL}, + {"Organization Unit", NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL}, + {"Title", NULL, TRUE, TRUE, "TITLE", NULL, NULL}, + {"Role", NULL, TRUE, TRUE, "ROLE", NULL, NULL}, + {"Birthday", NULL, TRUE, TRUE, "BDAY", NULL, NULL}, + {"Description", NULL, TRUE, TRUE, "DESC", NULL, NULL}, + {"", NULL, TRUE, TRUE, "N", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ADR", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ORG", NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL, NULL} +}; + +/* + * The "vCard" tag's attibute list... + */ +struct tag_attr { + char *attr; + char *value; +} vcard_tag_attr_list[] = { + {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"}, + {"version", "2.0", }, + {"xmlns", "vcard-temp", }, + {NULL, NULL}, +}; + + +/* + * V-Card user instructions + */ +static char *multi_entry_instructions = + "Enter the stuff you feel comfortable with. Leave the rest blank."; + +/* + * Used by routines to parse an XML-encoded string into an xmlnode tree + */ +typedef struct { + XML_Parser parser; + xmlnode current; +} *xmlstr2xmlnode_parser, xmlstr2xmlnode_parser_struct; + + +/* + * Display a Jabber vCard + */ +static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from) +{ + struct gaim_connection *gc = GJ_GC(gjc); + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + jid who = jid_new(gjc->p, from); + char *cdata, *status; + + struct vcard_template *vc_tp = vcard_template_data; + + /* space for all vCard elements + Jabber I.D. + "status" + NULL (list terminator) */ + gchar **str_arr = (gchar **) g_new(gpointer, + (sizeof(vcard_template_data)/sizeof(struct vcard_template)) + 3); + gchar **ap = str_arr; + gchar *buddy, *final; + + if(who->resource != NULL && (who->resource)[0] != '\0') { + buddy = g_strdup_printf("%s@%s/%s", who->user, who->server, who->resource); + } else { + buddy = g_strdup_printf("%s@%s", who->user, who->server); + } + *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", buddy); + + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + if(strcmp(vc_tp->tag, "DESC") == 0) + continue; /* special handling later */ + if(vc_tp->ptag == NULL) { + cdata = xmlnode_get_tag_data(querynode, vc_tp->tag); + } else { + gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag); + cdata = xmlnode_get_tag_data(querynode, tag); + g_free(tag); + } + if(cdata != NULL) { + if(vc_tp->url == NULL) { + *ap++ = g_strdup_printf("<B>%s:</B> %s<BR>\n", vc_tp->label, cdata); + } else { + gchar *fmt = g_strdup_printf("<B>%%s:</B> %s<BR>\n", vc_tp->url); + *ap++ = g_strdup_printf(fmt, vc_tp->label, cdata, cdata); + g_free(fmt); + } + } + } + + if((status = g_hash_table_lookup(jd->hash, buddy)) == NULL) { + status = "Online"; + } + *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", status); + + /* + * "Description" handled as a special case: get a copy of the + * string and HTML-ize. + */ + if((cdata = xmlnode_get_tag_data(querynode, "DESC")) != NULL) { + gchar *tmp = g_strdup_printf("<HR>%s<BR>", cdata); + *ap++ = strdup_withhtml(tmp); + g_free(tmp); + } + + *ap = NULL; + + final= g_strjoinv(NULL, str_arr); + g_strfreev(str_arr); + + g_show_info_text(gc, buddy, 2, final, NULL); + g_free(buddy); + g_free(final); +} + +/* + * Used by XML_Parse on parsing CDATA + */ +static void xmlstr2xmlnode_charData(void *userdata, const char *s, int slen) +{ + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + + if (xmlp->current) + xmlnode_insert_cdata(xmlp->current, s, slen); +} + +/* + * Used by XML_Parse to start or append to an xmlnode + */ +static void xmlstr2xmlnode_startElement(void *userdata, const char *name, const char **attribs) +{ + xmlnode x; + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + + if (xmlp->current) { + /* Append the node to the current one */ + x = xmlnode_insert_tag(xmlp->current, name); + xmlnode_put_expat_attribs(x, attribs); + + xmlp->current = x; + } else { + x = xmlnode_new_tag(name); + xmlnode_put_expat_attribs(x, attribs); + xmlp->current = x; + } +} + +/* + * Used by XML_Parse to end an xmlnode + */ +static void xmlstr2xmlnode_endElement(void *userdata, const char *name) +{ + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + xmlnode x; + + if (xmlp->current != NULL && (x = xmlnode_get_parent(xmlp->current)) != NULL) { + xmlp->current = x; + } +} + +/* + * Parse an XML-encoded string into an xmlnode tree + * + * Caller is responsible for freeing the returned xmlnode + */ +static xmlnode xmlstr2xmlnode(char *xmlstring) +{ + xmlstr2xmlnode_parser my_parser = g_new(xmlstr2xmlnode_parser_struct, 1); + xmlnode x = NULL; + + my_parser->parser = XML_ParserCreate(NULL); + my_parser->current = NULL; + + XML_SetUserData(my_parser->parser, (void *)my_parser); + XML_SetElementHandler(my_parser->parser, xmlstr2xmlnode_startElement, xmlstr2xmlnode_endElement); + XML_SetCharacterDataHandler(my_parser->parser, xmlstr2xmlnode_charData); + XML_Parse(my_parser->parser, xmlstring, strlen(xmlstring), 0); + + x = my_parser->current; + + XML_ParserFree(my_parser->parser); + g_free(my_parser); + + return(x); +} + +/* + * Insert a tag node into an xmlnode tree, recursively inserting parent tag + * nodes as necessary + * + * Returns pointer to inserted node + * + * Note to hackers: this code is designed to be re-entrant (it's recursive--it + * calls itself), so don't put any "static"s in here! + */ +static xmlnode insert_tag_to_parent_tag(xmlnode start, const char *parent_tag, const char *new_tag) +{ + xmlnode x = NULL; + + /* + * If the parent tag wasn't specified, see if we can get it + * from the vCard template struct. + */ + if(parent_tag == NULL) { + struct vcard_template *vc_tp = vcard_template_data; + + while(vc_tp->label != NULL) { + if(strcmp(vc_tp->tag, new_tag) == 0) { + parent_tag = vc_tp->ptag; + break; + } + ++vc_tp; + } + } + + /* + * If we have a parent tag... + */ + if(parent_tag != NULL ) { + /* + * Try to get the parent node for a tag + */ + if((x = xmlnode_get_tag(start, parent_tag)) == NULL) { + /* + * Descend? + */ + char *grand_parent = strcpy(g_malloc(strlen(parent_tag) + 1), parent_tag); + char *parent; + + if((parent = strrchr(grand_parent, '/')) != NULL) { + *(parent++) = '\0'; + x = insert_tag_to_parent_tag(start, grand_parent, parent); + } else { + x = xmlnode_insert_tag(start, grand_parent); + } + g_free(grand_parent); + } else { + /* + * We found *something* to be the parent node. + * Note: may be the "root" node! + */ + xmlnode y; + if((y = xmlnode_get_tag(x, new_tag)) != NULL) { + return(y); + } + } + } + + /* + * insert the new tag into its parent node + */ + return(xmlnode_insert_tag((x == NULL? start : x), new_tag)); +} + +/* + * Find the tag name for a label + * + * Returns NULL on not found + */ +static char *tag_for_label(const char *label) +{ + struct vcard_template *vc_tp = vcard_template_data; + char *p = NULL; + + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + if(strcmp(label, vc_tp->label) == 0) { + p = vc_tp->tag; + break; + } + } + + return(p); +} + +/* + * Send vCard info to Jabber server + */ +static void jabber_set_info(struct gaim_connection *gc, char *info) +{ + xmlnode x, vc_node; + char *id; + struct jabber_data *jd = gc->proto_data; + gjconn gjc = jd->gjc; + + x = xmlnode_new_tag("iq"); + xmlnode_put_attrib(x,"type","set"); + + id = gjab_getid(gjc); + + xmlnode_put_attrib(x, "id", id); + + /* + * Send only if there's actually any *information* to send + */ + if((vc_node = xmlstr2xmlnode(info)) != NULL && xmlnode_get_name(vc_node) != NULL && + g_strncasecmp(xmlnode_get_name(vc_node), "vcard", 5) == 0) { + xmlnode_insert_tag_node(x, vc_node); + debug_printf("jabber: vCard packet: %s\n", xmlnode2str(x)); + gjab_send(gjc, x); + } + + xmlnode_free(x); +} + +/* + * This is the callback from the "ok clicked" for "set vCard" + * + * Formats GSList data into XML-encoded string and returns a pointer + * to said string. + * + * g_free()'ing the returned string space is the responsibility of + * the caller. + */ +static gchar *jabber_format_info(MultiEntryDlg *b) +{ + xmlnode vc_node; + GSList *list; + MultiEntryData *med; + MultiTextData *mtd; + char *p; + + struct tag_attr *tag_attr; + + vc_node = xmlnode_new_tag("vCard"); + + for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr) + xmlnode_put_attrib(vc_node, tag_attr->attr, tag_attr->value); + + for(list = b->multi_entry_items; list != NULL; list = list->next) { + med = (MultiEntryData *) list->data; + if(med->label != NULL && med->text != NULL && (med->text)[0] != '\0') { + if((p = tag_for_label(med->label)) != NULL) { + xmlnode xp; + if((xp = insert_tag_to_parent_tag(vc_node, NULL, p)) != NULL) { + xmlnode_insert_cdata(xp, med->text, -1); + } + } + } + } + + for(list = b->multi_text_items; list != NULL; list = list->next) { + mtd = (MultiTextData *) list->data; + if(mtd->label != NULL && mtd->text != NULL && (mtd->text)[0] != '\0') { + if((p = tag_for_label(mtd->label)) != NULL) { + xmlnode xp; + if((xp = insert_tag_to_parent_tag(vc_node, NULL, p)) != NULL) { + xmlnode_insert_cdata(xp, mtd->text, -1); + } + } + } + } + + + p = g_strdup(xmlnode2str(vc_node)); + xmlnode_free(vc_node); + + return(p); +} + +/* + * This gets executed by the proto action + * + * Creates a new MultiEntryDlg struct, gets the XML-formatted user_info + * string (if any) into GSLists for the (multi-entry) edit dialog and + * calls the set_vcard dialog. + */ +static void jabber_setup_set_info(struct gaim_connection *gc) +{ + MultiEntryData *data; + const struct vcard_template *vc_tp; + char *user_info; + MultiEntryDlg *b = multi_entry_dialog_new(); + char *cdata; + xmlnode x_vc_data = NULL; + struct aim_user *tmp = gc->user; + b->user = tmp; + + + /* + * Get existing, XML-formatted, user info + */ + if((user_info = g_malloc(strlen(tmp->user_info) + 1)) != NULL) { + strcpy(user_info, tmp->user_info); + x_vc_data = xmlstr2xmlnode(user_info); + } + + /* + * Set up GSLists for edit with labels from "template," data from user info + */ + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + if((vc_tp->label)[0] == '\0') + continue; + if(vc_tp->ptag == NULL) { + cdata = xmlnode_get_tag_data(x_vc_data, vc_tp->tag); + } else { + gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag); + cdata = xmlnode_get_tag_data(x_vc_data, tag); + g_free(tag); + } + if(strcmp(vc_tp->tag, "DESC") == 0) { + multi_text_list_update(&(b->multi_text_items), + vc_tp->label, cdata, TRUE); + } else { + data = multi_entry_list_update(&(b->multi_entry_items), + vc_tp->label, cdata, TRUE); + data->visible = vc_tp->visible; + data->editable = vc_tp->editable; + } + } + + + if(x_vc_data != NULL) { + xmlnode_free(x_vc_data); + } else { + /* + * Early Beta versions had a different user_info storage format--let's + * see if that works. + * + * This goes away RSN. + */ + const char *record_separator = "<BR>"; + const char *field_separator = ": "; + gchar **str_list, **str_list_ptr, **str_list2; + + if((str_list = g_strsplit(user_info, record_separator, 0)) != NULL) { + for(str_list_ptr = str_list; *str_list_ptr != NULL; ++str_list_ptr) { + str_list2 = g_strsplit(*str_list_ptr, field_separator, 2); + if(str_list2[0] != NULL && str_list2[1] != NULL) { + g_strstrip(str_list2[0]); + g_strstrip(str_list2[1]); + /* this is ugly--so far */ + if(strcmp(str_list2[0], "Description") == 0) { + multi_text_list_update(&(b->multi_text_items), + str_list2[0], str_list2[1], FALSE); + } else { + multi_entry_list_update(&(b->multi_entry_items), + str_list2[0], str_list2[1], FALSE); + } + } + g_strfreev(str_list2); + } + g_strfreev(str_list); + } + } + + if(user_info != NULL) { + g_free(user_info); + } + + b->title = "Gaim - vCard Wizzard"; + b->wmclass_name = "set_info"; + b->wmclass_class = "Gaim"; + b->instructions->text = g_strdup(multi_entry_instructions); + + b->custom = (void *) jabber_format_info; + + show_set_vcard(b); +} + +/*---------------------------------------*/ +/* End Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/*----------------------------------------*/ +/* Jabber "user registration" support */ +/*----------------------------------------*/ + +/* + * Three of the following four functions duplicate much of what + * exists elsewhere: + * + * jabber_handleregresp() + * gjab_reqreg() + * jabber_handle_registration_state() + * + * It may be that an additional flag could be added to one of + * the "local" structs and the duplicated code modified to + * account for it--thus eliminating the duplication. Then again: + * doing it the way it is may be much cleaner. + * + * TBD: Code to support requesting additional information server + * wants at registration--incl. dialog. + */ + +/* + * Like jabber_handlepacket(), only different + */ +static void jabber_handleregresp(gjconn gjc, jpacket p) +{ + if (jpacket_subtype(p) == JPACKET__RESULT) { + xmlnode querynode; + + if((querynode = xmlnode_get_tag(p->x, "query")) != NULL) { + char *xmlns; + + /* we damn well *better* have this! */ + if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL && + strcmp(xmlns, NS_REGISTER) == 0) { + + char *tag; + xmlnode child = xmlnode_get_firstchild(querynode); + + debug_printf("got registration requirments response!\n"); + + while(child != NULL) { + if((tag = xmlnode_get_name(child)) != NULL) { + char *data; + + fprintf(stderr, "DBG: got node: \"%s\"\n", tag); + fflush(stderr); + + if((data = xmlnode_get_data(child)) != NULL) { + fprintf(stderr, "DBG: got data: \"%s\"\n", data); + fflush(stderr); + } + } + child = xmlnode_get_nextsibling(child); + } + } + } else { + debug_printf("registration successful!\n"); + + hide_login_progress_notice(GJ_GC(gjc), "Server Registration successful!"); + /* + * TBD: is this the correct way to do away with a + * gaim_connection and all its associated memory + * allocs, etc.? + */ + signoff(GJ_GC(gjc)); + } + + } else { + xmlnode xerr; + char *errmsg = NULL; + int errcode = 0; + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + + debug_printf("registration failed\n"); + xerr = xmlnode_get_tag(p->x, "error"); + if (xerr) { + char msg[BUF_LONG]; + errmsg = xmlnode_get_data(xerr); + if (xmlnode_get_attrib(xerr, "code")) { + errcode = atoi(xmlnode_get_attrib(xerr, "code")); + g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg); + } else + g_snprintf(msg, sizeof(msg), "%s", errmsg); + hide_login_progress(GJ_GC(gjc), msg); + } else { + hide_login_progress(GJ_GC(gjc), "Unknown registration error"); + } + + jd->die = TRUE; + } +} + +/* + * Like gjab_reqauth(), only different + */ +static void gjab_reqreg(gjconn gjc) +{ + xmlnode x, y, z; + char *user; + + if (!gjc) + return; + + x = jutil_iqnew(JPACKET__SET, NS_REGISTER); + y = xmlnode_get_tag(x, "query"); + + user = gjc->user->user; + + if (user) { + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); + } + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, gjc->pass, -1); + + debug_printf("jabber: registration packet: %s\n", xmlnode2str(x)); + gjab_send(gjc, x); + xmlnode_free(x); +} + +/* + * Like gjab_reqauth(), only different + */ +static void gjab_reqregreqs(gjconn gjc) +{ + xmlnode x, y, z; + char *user; + + if (!gjc) + return; + + x = jutil_iqnew(JPACKET__GET, NS_REGISTER); + y = xmlnode_get_tag(x, "query"); + + xmlnode_put_attrib(x, "to", "jimsun.linxnet.com"); + /* + xmlnode_put_attrib(x, "from", "jseymour@jimsun.linxnet.com"); + */ + /* + user = gjc->user->user; + + if (user) { + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); + } + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, gjc->pass, -1); + */ + + debug_printf("jabber: registration packet: %s\n", xmlnode2str(x)); + gjab_send(gjc, x); + xmlnode_free(x); +} + +/* + * Like jabber_handlestate(), only different + */ +static void jabber_handle_registration_state(gjconn gjc, int state) +{ + switch (state) { + case JCONN_STATE_OFF: + hide_login_progress(GJ_GC(gjc), "Unable to connect"); + signoff(GJ_GC(gjc)); + break; + case JCONN_STATE_CONNECTED: + /* + * TBD? + set_login_progress(GJ_GC(gjc), 2, "Connected"); + */ + break; + case JCONN_STATE_ON: + /* + * TBD? + set_login_progress(GJ_GC(gjc), 3, "Requesting Authentication Method"); + */ + gjab_reqreg(gjc); + /* + * TBD: A work-in-progress + gjab_reqregreqs(gjc); + */ + break; + default: + debug_printf("state change: %d\n", state); + } + return; +} + +/* + * Like jabber_login(), only different + */ +void jabber_register_user(struct aim_user *au) +{ + struct gaim_connection *gc = new_gaim_conn(au); + struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1); + char *loginname = create_valid_jid(au->username, DEFAULT_SERVER, "GAIM"); + + /* + * These do nothing during registration + */ + jd->hash = NULL; + jd->chats = NULL; + + if ((jd->gjc = gjab_new(loginname, au->password, gc)) == NULL) { + g_free(loginname); + debug_printf("jabber: unable to connect (jab_new failed)\n"); + hide_login_progress(gc, "Unable to connect"); + signoff(gc); + } else { + gjab_state_handler(jd->gjc, jabber_handle_registration_state); + gjab_packet_handler(jd->gjc, jabber_handleregresp); + jd->gjc->queries = NULL; + gjab_start(jd->gjc); + } + + g_free(loginname); +} + +/*----------------------------------------*/ +/* End Jabber "user registration" support */ +/*----------------------------------------*/ + +static void jabber_do_action(struct gaim_connection *gc, char *act) +{ + if (!strcmp(act, "Set User Info")) { + jabber_setup_set_info(gc); + /* + } else if (!strcmp(act, "Set Dir Info")) { + show_set_dir(gc); + } else if (!strcmp(act, "Change Password")) { + show_change_passwd(gc); + */ + } +} + +static GList *jabber_actions() +{ + GList *m = NULL; + + m = g_list_append(m, "Set User Info"); + /* + m = g_list_append(m, "Set Dir Info"); + m = g_list_append(m, "Change Password"); + */ + + return m; +} + static struct prpl *my_protocol = NULL; void jabber_init(struct prpl *ret) @@ -1800,13 +2936,17 @@ ret->name = jabber_name; ret->list_icon = jabber_list_icon; ret->away_states = jabber_away_states; + ret->actions = jabber_actions; + ret->do_action = jabber_do_action; ret->buddy_menu = jabber_buddy_menu; ret->user_opts = jabber_user_opts; ret->login = jabber_login; ret->close = jabber_close; ret->send_im = jabber_send_im; - ret->set_info = NULL; + ret->set_info = jabber_set_info; ret->get_info = jabber_get_info; + ret->get_cb_info = jabber_get_cb_info; + ret->get_cb_away = jabber_get_cb_away_msg; ret->set_away = jabber_set_away; ret->set_dir = NULL; ret->get_dir = NULL; @@ -1831,6 +2971,7 @@ ret->keepalive = jabber_keepalive; ret->normalize = jabber_normalize; ret->buddy_free = jabber_buddy_free; + ret->register_user = jabber_register_user; my_protocol = ret; } diff -r 8b03506b8c1e -r b68c648618a3 src/prpl.h --- a/src/prpl.h Wed Feb 13 15:23:13 2002 +0000 +++ b/src/prpl.h Sun Feb 17 18:08:12 2002 +0000 @@ -144,7 +144,11 @@ void (* keepalive) (struct gaim_connection *); /* new user registration */ - void (* register_user) (struct gaim_connection *); + void (* register_user) (struct aim_user *); + + /* get "chat buddy" info and away message */ + void (* get_cb_info) (struct gaim_connection *, int, char *who); + void (* get_cb_away) (struct gaim_connection *, int, char *who); void (* buddy_free) (struct buddy *); diff -r 8b03506b8c1e -r b68c648618a3 src/ui.h --- a/src/ui.h Wed Feb 13 15:23:13 2002 +0000 +++ b/src/ui.h Sun Feb 17 18:08:12 2002 +0000 @@ -232,6 +232,9 @@ /* stuff for mail check prompt */ GtkWidget *checkmail; + + /* stuff for register with server */ + GtkWidget *register_user; }; #define EDIT_GC 0 diff -r 8b03506b8c1e -r b68c648618a3 src/util.c --- a/src/util.c Wed Feb 13 15:23:13 2002 +0000 +++ b/src/util.c Sun Feb 17 18:08:12 2002 +0000 @@ -912,6 +912,33 @@ } +/* + * Like strncpy_withhtml (above), but malloc()'s the necessary space + * + * The caller is responsible for freeing the space pointed to by the + * return value. + */ + +gchar *strdup_withhtml(const gchar *src) +{ + char *sp, *dest; + gulong destsize; + + /* + * All we need do is multiply the number of newlines by 3 (the + * additional length of "<BR>" over "\n"), account for the + * terminator, malloc the space and call strncpy_withhtml. + */ + for(destsize = 0, sp = src; (sp = strchr(sp, '\n')) != NULL; ++sp, ++destsize) + ; + destsize *= 3; + destsize += strlen(src) + 1; + dest = g_malloc(destsize); + strncpy_withhtml(dest, src, destsize); + + return(dest); +} + void away_on_login(char *mesg) { GSList *awy = away_messages;