# HG changeset patch # User Rob Flynn # Date 1015044741 0 # Node ID 7239a392486caec7485a15858e084b3e8e213843 # Parent d16a0504f1c83ea89f24d7f43b352ae4aa3e263a [gaim-migrate @ 3006] 0.53 :) committer: Tailor Script diff -r d16a0504f1c8 -r 7239a392486c AUTHORS --- a/AUTHORS Sat Mar 02 01:37:01 2002 +0000 +++ b/AUTHORS Sat Mar 02 04:52:21 2002 +0000 @@ -10,8 +10,12 @@ Yahoo: IBRRob Jabber: robflynn@jabber.org -Sean Egan +Sean Egan - Developer AIM: SeanEgn + ICQ: 96642211 + MSN: SeanEgn@hotmail.com + Yahoo: SeanEgn + Jabber: SeanEgn@jabber.org Syd Logan - Hacker and Designated Driver (lazy bum) diff -r d16a0504f1c8 -r 7239a392486c ChangeLog --- a/ChangeLog Sat Mar 02 01:37:01 2002 +0000 +++ b/ChangeLog Sat Mar 02 04:52:21 2002 +0000 @@ -1,6 +1,6 @@ Gaim: The Pimpin' Penguin IM Clone thats good for the soul! -version 0.53: +version 0.53 (02/28/2002): * Updated Polish Translation (thanks Przemyslaw Sulek) * Slovak translation added (Thanks Daniel Rezny) * Minor bug fixes re: queued away messages @@ -11,6 +11,9 @@ * Editable buddy pounces (Thanks Jason Willis) * Server side buddy lists in Oscar (Thanks KingAnt :-)) * Fix for the chatlist plugin + * Typing Notification (AIM Direct Connect, Yahoo, MSN) + * IM Images (Receive Only) + * Prettier GtkImHtml selection version 0.52 (02/17/2002): * Better buddy icon transparency (thanks SeanEgan) diff -r d16a0504f1c8 -r 7239a392486c NEWS --- a/NEWS Sat Mar 02 01:37:01 2002 +0000 +++ b/NEWS Sat Mar 02 04:52:21 2002 +0000 @@ -1,6 +1,17 @@ -=[ Gaim ]=- The Pimpin' Penguin AIM Clone That's Good For The Soul! -0.53: +0.53 (02/28/2002): + Rob: Well, we missed yesterday's release. That's Okay, I head a + nasty headache. You can all just deal. ;-) + + + Sean: Neat Goodies! Whee!! Oscar got a lot of great additions. + It can do Screen Name formatting, it can save and store your buddy + list on the server, it can do typing notifications in Direct + Connections, and yes, it can receive IM Images! Sending images will + be added in the next release. MSN and Yahoo! can do typing + notification too. Hooray! + 0.52 (02/17/2002): Rob: Well, after a long delay we're finally ready for another release. diff -r d16a0504f1c8 -r 7239a392486c doc/CREDITS --- a/doc/CREDITS Sat Mar 02 01:37:01 2002 +0000 +++ b/doc/CREDITS Sat Mar 02 04:52:21 2002 +0000 @@ -9,6 +9,7 @@ Jim Duchek jimduchek@ou.edu Eric Warmenhoven warmenhoven@yahoo.com Mark Spencer markster@marko.net +Sean Egan bj91704@binghamton.edu Other Contributors: @@ -30,8 +31,6 @@ onees and pointed me to those. Props to the boys at Helix Code. Thanks guys. -Sean Egan - Lots of IRC things here and there. Thanks, man =) Nathan Walp A healthy amount of patches for the Jabber plugin Neil Sanchala diff -r d16a0504f1c8 -r 7239a392486c gaim.spec.in --- a/gaim.spec.in Sat Mar 02 01:37:01 2002 +0000 +++ b/gaim.spec.in Sat Mar 02 04:52:21 2002 +0000 @@ -99,6 +99,21 @@ rm -r $RPM_BUILD_ROOT %changelog +* Fri Feb 28 2002 Rob Flynn (0.53 release) +- Updated Polish Translation (thanks Przemyslaw Sulek) +- Slovak translation added (Thanks Daniel Rezny) +- Minor bug fixes re: queued away messages +- Better buddy icon transparency (for real this time ;-)) +- Ability to change formatting of Oscar screen name +- Better selection in HTML widget (Thanks BMiller) +- New icons for ICQ (Thanks Kevin Miller) +- Editable buddy pounces (Thanks Jason Willis) +- Server side buddy lists in Oscar (Thanks KingAnt :-)) +- Fix for the chatlist plugin +- Typing Notification (AIM Direct Connect, Yahoo, MSN) +- IM Images (Receive Only) +- Prettier GtkImHtml selection + * Sun Feb 17 2002 Rob Flynn (0.52 release) - Better buddy icon transparency (thanks SeanEgan) - Updated Polish Translation (thanks Przemyslaw Sulek) diff -r d16a0504f1c8 -r 7239a392486c plugins/SIGNALS --- a/plugins/SIGNALS Sat Mar 02 01:37:01 2002 +0000 +++ b/plugins/SIGNALS Sat Mar 02 04:52:21 2002 +0000 @@ -26,7 +26,8 @@ event_draw_menu, event_im_displayed_sent, event_im_displayed_rcvd, - event_chat_send_invite + event_chat_send_invite, + event_got_typing, }; To add a signal handler, call the fuction gaim_signal_connect with the @@ -314,3 +315,16 @@ 'who' is who you're inviting. 'msg' is the message they'll receive when they're invited. It may be NULL. Setting this to NULL won't stop the invitation from going thru. + +event_got_typing: + struct gaim_connection *gc, char *who + + This is called when a buddy starts typing you and is called + differently depending on the protocol. MSN requires that a + conversation is already in progress, and may send more than + one notification while typing. OSCAR can receive typing + notifications in direct IMs, and Yahoo can receive them any + time. + + 'gc' is the connection the typing is sent to. + 'who' is the person typing to you. diff -r d16a0504f1c8 -r 7239a392486c plugins/chatlist.c --- a/plugins/chatlist.c Sat Mar 02 01:37:01 2002 +0000 +++ b/plugins/chatlist.c Sat Mar 02 04:52:21 2002 +0000 @@ -11,7 +11,7 @@ #include #include -#define AOL_SRCHSTR "/community/aimcheck.adp/url=" +#define AOL_SRCHSTR "aim:GoChat?RoomName=" struct chat_page { GtkWidget *list1; diff -r d16a0504f1c8 -r 7239a392486c src/about.c --- a/src/about.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/about.c Sat Mar 02 04:52:21 2002 +0000 @@ -134,7 +134,7 @@ text = gtk_text_new(NULL, NULL); gtk_text_insert (GTK_TEXT (text), NULL, NULL, NULL, - _("Active Developers\n====================\nRob Flynn (maintainer) [ rob@marko.net ]\nSean Egan (coder) [ bj91704@binghamton.edu ]\n\nCrazy Patch Writers\n===================\nBenjamin Miller\nDecklin Foster\n\nRetired Developers\n===================\nJim Duchek\nEric Warmenhoven [ warmenhoven@yahoo.com ]\nMark Spencer (original author) [ markster@marko.net ]"), 369); + _("Active Developers\n====================\nRob Flynn (maintainer) [ rob@marko.net ]\nSean Egan (coder) [ bj91704@binghamton.edu ]\n\nCrazy Patch Writers\n===================\nBenjamin Miller\nDecklin Foster\nMark Doliner\n\nRetired Developers\n===================\nJim Duchek\nEric Warmenhoven [ warmenhoven@yahoo.com ]\nMark Spencer (original author) [ markster@marko.net ]"), 382); sw = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); diff -r d16a0504f1c8 -r 7239a392486c src/aim.c --- a/src/aim.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/aim.c Sat Mar 02 04:52:21 2002 +0000 @@ -401,7 +401,7 @@ "and post the backtrace from the core file. If you do not know\n" "how to get the backtrace, please get instructions at\n" WEBSITE "gdb.php. If you need further\n" - "assistance, please IM either EWarmenhoven or RobFlynn and\n" + "assistance, please IM either RobFlynn or SeanEgn and\n" "they can help you.\n"); #endif abort(); diff -r d16a0504f1c8 -r 7239a392486c src/buddy.c --- a/src/buddy.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/buddy.c Sat Mar 02 04:52:21 2002 +0000 @@ -714,6 +714,8 @@ show_ee_dialog(3); else if (!g_strcasecmp("markster97", normalize (b->name))) show_ee_dialog(4); + else if (!g_strcasecmp("seanegn", normalize (b->name))) + show_ee_dialog(5); } else { @@ -1486,10 +1488,11 @@ * because I thought it'd be funny :-) */ g_snprintf(tmp, sizeof(tmp), "%s has %s", name, - (b->options & OPT_POUNCE_SIGNON) ? "signed on" : - (b->options & OPT_POUNCE_UNIDLE) ? "returned from being idle" : - "returned from being away"); - + (b->options & OPT_POUNCE_TYPING) ? "started typing to you" : + (b->options & OPT_POUNCE_SIGNON) ? "signed on" : + (b->options & OPT_POUNCE_UNIDLE) ? "returned from being idle" : + "returned from being away"); + do_error_dialog(tmp, _("Buddy Pounce")); } if (b->options & OPT_POUNCE_SEND_IM) { diff -r d16a0504f1c8 -r 7239a392486c src/conversation.c --- a/src/conversation.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/conversation.c Sat Mar 02 04:52:21 2002 +0000 @@ -259,6 +259,8 @@ if (c->save_icon) gtk_widget_destroy(c->save_icon); #endif + if (c->typing_timeout) + gtk_timeout_remove(c->typing_timeout); g_string_free(c->history, TRUE); g_free(c); } @@ -629,7 +631,7 @@ cnv = g_list_nth(chats, currpage - convlen); while (cnv) { d = cnv->data; - if (d->unseen) + if (d->unseen > 0) break; cnv = cnv->next; d = NULL; @@ -643,7 +645,7 @@ cnv = g_list_nth(conversations, currpage); while (cnv) { d = cnv->data; - if (d->unseen) + if (d->unseen > 0) break; cnv = cnv->next; d = NULL; @@ -660,7 +662,7 @@ cnv = conversations; while (cnv) { d = cnv->data; - if (d->unseen) + if (d->unseen > 0) break; cnv = cnv->next; d = NULL; @@ -674,7 +676,7 @@ cnv = chats; while (cnv) { d = cnv->data; - if (d->unseen) + if (d->unseen > 0) break; cnv = cnv->next; d = NULL; @@ -691,7 +693,7 @@ cnv = chats; while (cnv) { d = cnv->data; - if (d->unseen) + if (d->unseen > 0) break; cnv = cnv->next; d = NULL; @@ -705,7 +707,7 @@ cnv = conversations; while (cnv) { d = cnv->data; - if (d->unseen) + if (d->unseen > 0) break; cnv = cnv->next; d = NULL; @@ -730,6 +732,7 @@ if (convo_options & OPT_CONVO_ESC_CAN_CLOSE) { gtk_signal_emit_stop_by_name(GTK_OBJECT(entry), "key_press_event"); close_callback(c->close, c); + c = NULL; } } else if (event->keyval == GDK_Page_Up) { gtk_signal_emit_stop_by_name(GTK_OBJECT(entry), "key_press_event"); @@ -896,6 +899,27 @@ gtk_signal_emit_stop_by_name(GTK_OBJECT(entry), "key_press_event"); } + if (c && (!(misc_options & OPT_MISC_STEALTH_TYPING)) && !c->is_chat) { + char *txt = gtk_editable_get_chars(GTK_EDITABLE(c->entry), 0, -1); + if ((strlen(txt) == 0 && event->keyval < 256 && isprint(event->keyval)) || + (c->type_again != 0 && time(NULL) > c->type_again)) { + int timeout = serv_send_typing(c->gc, c->name); + if (timeout) + c->type_again = time(NULL) + timeout; + else + c->type_again = 0; + } + else if (strlen(txt) == 1) { + if ((GTK_OLD_EDITABLE(c->entry)->current_pos == 1 && event->keyval == GDK_BackSpace) || + (GTK_OLD_EDITABLE(c->entry)->current_pos == 0 && event->keyval == GDK_Delete)) + serv_send_typing_stopped(c->gc, c->name); + } else if (GTK_OLD_EDITABLE(c->entry)->selection_start_pos == 0) { + if (GTK_OLD_EDITABLE(c->entry)->selection_end_pos == strlen(txt) && + (event->keyval == GDK_BackSpace || event->keyval == GDK_Delete)) + serv_send_typing_stopped(c->gc, c->name); + } + g_free(txt); + } return FALSE; } @@ -1464,7 +1488,8 @@ GString *logstr; char buf2[BUF_LONG]; char mdate[64]; - + int unhighlight = 0; + if (c->is_chat && (!c->gc || !g_slist_find(c->gc->buddy_chats, c))) return; @@ -1693,12 +1718,16 @@ offs = 0; if (gtk_notebook_get_current_page(GTK_NOTEBOOK(chat_notebook)) == g_list_index(chats, c) + offs) - return; + unhighlight = 1; } else { if (gtk_notebook_get_current_page(GTK_NOTEBOOK(convo_notebook)) == g_list_index(conversations, c)) - return; + unhighlight = 1; } + if ((c->unseen != -1) && unhighlight) /* If there's no typing message + and we're on the same tab, don't bother + changing the color. */ + return; { GtkNotebook *notebook = GTK_NOTEBOOK(c->is_chat ? chat_notebook : convo_notebook); @@ -1715,23 +1744,50 @@ gtk_widget_realize(label); gdk_font_unref(gtk_style_get_font(style)); gtk_style_set_font(style, gdk_font_ref(gtk_style_get_font(label->style))); - if (flags & WFLAG_NICK) { + if (!unhighlight && flags & WFLAG_NICK) { style->fg[0].red = 0x0000; style->fg[0].green = 0x0000; style->fg[0].blue = 0xcccc; c->unseen = 2; - } else { + } else if (!unhighlight) { style->fg[0].red = 0xcccc; style->fg[0].green = 0x0000; style->fg[0].blue = 0x0000; c->unseen = 1; + } else { + c->unseen = 0; } gtk_widget_set_style(label, style); gtk_style_unref(style); } + if (flags & WFLAG_RECV) + reset_typing(g_strdup(c->name)); } - +void update_progress(struct conversation *c, float percent) { + while (gtk_events_pending()) + gtk_main_iteration(); + + if (percent >= 1 && !(c->progress)) + return; + + if (percent >= 1) { + gtk_widget_destroy(c->progress); + c->progress = NULL; + return; + } + + if (!c->progress) { + GtkBox *box = GTK_BOX(c->text->parent->parent); + c->progress = gtk_progress_bar_new(); + gtk_box_pack_end(box, c->progress, FALSE, FALSE, 0); + gtk_widget_set_usize (c->progress, 1, 8); + gtk_widget_show (c->progress); + } + + if (percent < 1) + gtk_progress_set_percentage(GTK_PROGRESS(c->progress), percent); +} GtkWidget *build_conv_toolbar(struct conversation *c) { @@ -2231,6 +2287,7 @@ gtk_window_set_focus(GTK_WINDOW(c->window), c->entry); if (!GTK_WIDGET_REALIZED(label)) return; + if (c->unseen == -1) return; style = gtk_style_new(); gdk_font_unref(gtk_style_get_font(style)); gtk_style_set_font(style, gdk_font_ref(gtk_style_get_font(label->style))); @@ -2240,6 +2297,95 @@ c->unseen = 0; } +void show_typing(struct conversation *c) { + + GtkStyle *style; + GtkNotebook *notebook = GTK_NOTEBOOK(c->is_chat ? chat_notebook : convo_notebook); + int offs = ((convo_options & OPT_CONVO_COMBINE) && + (im_options & OPT_IM_ONE_WINDOW) && c->is_chat) ? + g_list_length(conversations) : 0; + GList *ws = (c->is_chat ? chats : conversations); + GtkWidget *label = gtk_notebook_get_tab_label(notebook, + gtk_notebook_get_nth_page(notebook, + offs + g_list_index(ws, c))); + if (c->is_chat) /* We shouldn't be getting typing notifications from chats. */ + return; + if (im_options & OPT_IM_ONE_WINDOW) { /* We'll make the tab green */ + + style = gtk_style_new(); + if (!GTK_WIDGET_REALIZED(label)) + gtk_widget_realize(label); + gdk_font_unref(gtk_style_get_font(style)); + gtk_style_set_font(style, gdk_font_ref(gtk_style_get_font(label->style))); + style->fg[0].red = 0x0000; + style->fg[0].green = 0x9999; + style->fg[0].blue = 0x0000; + gtk_widget_set_style(label, style); + debug_printf("setting style\n"); + gtk_style_unref(style); + c->unseen = -1; + } else { + GtkWindow *win = (GtkWindow *)c->window; + char *buf; + if (strstr(win->title, " [TYPING]")) + return; + buf = g_malloc(strlen(win->title) + strlen(" [TYPING]") + 1); + g_snprintf(buf, + strlen(win->title) + strlen(" [TYPING]") + 1, "%s [TYPING]", + win->title); + gtk_window_set_title(win, buf); + g_free(buf); + } + +} + +/* This returns a boolean, so that it can timeout */ +gboolean reset_typing(char *name) { + struct conversation *c = find_conversation(name); + if (!c) { + g_free(name); + return FALSE; + } + /* Reset the title (if necessary) */ + debug_printf("resetting style\n"); + if (c->is_chat) { + g_free(name); + c->typing_timeout = 0; + return FALSE; + } + if (!(im_options & OPT_IM_ONE_WINDOW)) { + GtkWindow *win = (GtkWindow*)c->window; + char *new_title; + if (strstr(win->title, " [TYPING]")) { + new_title = g_malloc(strlen(win->title) - strlen("[TYPING]")); + g_snprintf(new_title, strlen(win->title) - strlen("[TYPING]"), win->title); + gtk_window_set_title(win, new_title); + g_free(new_title); + + } + } else if (c->unseen == -1) { + GtkNotebook *notebook = GTK_NOTEBOOK(convo_notebook); + int offs = ((convo_options & OPT_CONVO_COMBINE) && + (im_options & OPT_IM_ONE_WINDOW) && c->is_chat) ? + g_list_length(conversations) : 0; + GList *ws = (conversations); + GtkWidget *label = gtk_notebook_get_tab_label(notebook, + gtk_notebook_get_nth_page(notebook, + offs + g_list_index(ws, c))); + GtkStyle *style; + style = gtk_style_new(); + if (!GTK_WIDGET_REALIZED(label)) + gtk_widget_realize(label); + gdk_font_unref(gtk_style_get_font(style)); + gtk_style_set_font(style, gdk_font_ref(gtk_style_get_font(label->style))); + c->unseen = 0; + gtk_widget_set_style(label, style); + gtk_style_unref(style); + } + g_free(name); + c->typing_timeout = 0; + return FALSE; +} void show_conv(struct conversation *c) { diff -r d16a0504f1c8 -r 7239a392486c src/core.h --- a/src/core.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/core.h Sat Mar 02 04:52:21 2002 +0000 @@ -84,6 +84,7 @@ event_im_displayed_sent, event_im_displayed_rcvd, event_chat_send_invite, + event_got_typing, /* any others? it's easy to add... */ }; @@ -192,6 +193,8 @@ /* Functions in server.c */ extern void serv_got_update(struct gaim_connection *, char *, int, int, time_t, time_t, int, guint); extern void serv_got_im(struct gaim_connection *, char *, char *, guint32, time_t, gint); +extern void serv_got_typing(struct gaim_connection *, char *, int); +extern void serv_got_typing_stopped(struct gaim_connection *, char *); extern void serv_got_eviled(struct gaim_connection *, char *, int); extern void serv_got_chat_invite(struct gaim_connection *, char *, char *, char *, GList *); extern struct conversation *serv_got_joined_chat(struct gaim_connection *, int, char *); diff -r d16a0504f1c8 -r 7239a392486c src/dialogs.c --- a/src/dialogs.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/dialogs.c Sat Mar 02 04:52:21 2002 +0000 @@ -148,6 +148,7 @@ GtkWidget *p_signon; GtkWidget *p_unaway; GtkWidget *p_unidle; + GtkWidget *p_typing; GtkWidget *save; GtkWidget *menu; GtkWidget *sound; @@ -641,9 +642,10 @@ label = gtk_label_new("You should be me. I'm so cute!"); else if (ee == 3) label = gtk_label_new("Now that's what I like!"); - else + else if (ee == 4) label = gtk_label_new("Ahh, and excellent choice!"); - + else + label = gtk_label_new("Everytime you click my name, an angel gets its wings."); gtk_widget_show(label); gtk_widget_show(ok); @@ -1124,6 +1126,9 @@ if (GTK_TOGGLE_BUTTON(b->p_unidle)->active) bp->options |= OPT_POUNCE_UNIDLE; + + if (GTK_TOGGLE_BUTTON(b->p_typing)->active) + bp->options |= OPT_POUNCE_TYPING; if (GTK_TOGGLE_BUTTON(b->save)->active) bp->options |= OPT_POUNCE_SAVE; @@ -1294,10 +1299,11 @@ (edit_bp->options & OPT_POUNCE_UNIDLE) ? TRUE : FALSE); gtk_table_attach(GTK_TABLE(table), b->p_unidle, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); gtk_widget_show(b->p_unidle); - - label = gtk_label_new(NULL); - gtk_table_attach(GTK_TABLE(table), label, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0); - gtk_widget_show(label); + + b->p_typing = gtk_check_button_new_with_label(_("Pounce when buddy is typing to you")); + gtk_table_attach(GTK_TABLE(table), b->p_typing,1,2,1,2, GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_widget_show(b->p_typing); + /* */ /* */ diff -r d16a0504f1c8 -r 7239a392486c src/gaim.h --- a/src/gaim.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/gaim.h Sat Mar 02 04:52:21 2002 +0000 @@ -154,7 +154,7 @@ #define OPT_POUNCE_SIGNON 0x010 #define OPT_POUNCE_UNAWAY 0x020 #define OPT_POUNCE_UNIDLE 0x040 - +#define OPT_POUNCE_TYPING 0x080 #define OPT_POUNCE_SAVE 0x100 #define OPT_POUNCE_NOTIFY 0x200 @@ -184,6 +184,7 @@ #define OPT_MISC_BROWSER_POPUP 0x00000002 #define OPT_MISC_BUDDY_TICKER 0x00000004 #define OPT_MISC_COOL_LOOK 0x00000008 +#define OPT_MISC_STEALTH_TYPING 0x00000010 extern guint logging_options; #define OPT_LOG_ALL 0x00000001 @@ -359,6 +360,8 @@ extern void serv_set_info(struct gaim_connection *, char *); extern void serv_set_away(struct gaim_connection *, char *, char *); extern void serv_set_away_all(char *); +extern int serv_send_typing(struct gaim_connection *, char *); +extern void serv_send_typing_stopped(struct gaim_connection *, char *); extern void serv_change_passwd(struct gaim_connection *, char *, char *); extern void serv_add_buddy(struct gaim_connection *, char *); extern void serv_add_buddies(struct gaim_connection *, GList *); diff -r d16a0504f1c8 -r 7239a392486c src/gtkimhtml.c --- a/src/gtkimhtml.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/gtkimhtml.c Sat Mar 02 04:52:21 2002 +0000 @@ -24,6 +24,7 @@ #endif #include "gtkimhtml.h" #include +#include #include #include #include @@ -35,6 +36,11 @@ #include #endif +#if USE_PIXBUF +#include +#include +#endif + #if GTK_CHECK_VERSION(1,3,0) # define GTK_IMHTML_GET_STYLE_FONT(style) gtk_style_get_font (style) #else @@ -248,10 +254,28 @@ imhtml->smiley_data = gtk_smiley_tree_new (); } +#if USE_PIXBUF +struct im_image { + gchar *filename; + + gint len; + gpointer data; + GdkPixbuf *pb; + + gint x,y; + gint width,height; + GtkIMHtml *imhtml; + GtkIMHtmlBit *bit; +}; +#endif + struct _GtkIMHtmlBit { gint type; gchar *text; +#if USE_PIXBUF + struct im_image *img; +#endif GdkPixmap *pm; GdkBitmap *bm; @@ -337,7 +361,11 @@ gdk_color_free (imhtml->default_fg_color); if (imhtml->default_bg_color) gdk_color_free (imhtml->default_bg_color); - + if (imhtml->default_hl_color) + gdk_color_free (imhtml->default_hl_color); + if (imhtml->default_hlfg_color) + gdk_color_free (imhtml->default_hlfg_color); + gdk_cursor_destroy (imhtml->hand_cursor); gdk_cursor_destroy (imhtml->arrow_cursor); @@ -409,6 +437,8 @@ imhtml->default_fg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->fg [GTK_STATE_NORMAL]); imhtml->default_bg_color = gdk_color_copy (>K_WIDGET (imhtml)->style->base [GTK_STATE_NORMAL]); + imhtml->default_hl_color = gdk_color_copy (>K_WIDGET (imhtml)->style->bg [GTK_STATE_SELECTED]); + imhtml->default_hlfg_color=gdk_color_copy (>K_WIDGET (imhtml)->style->fg [GTK_STATE_SELECTED]); gdk_window_show (GTK_LAYOUT (imhtml)->bin_window); } @@ -441,6 +471,7 @@ GdkWindow *window = GTK_LAYOUT (imhtml)->bin_window; gfloat xoff, yoff; GdkColor *bg, *fg; + gchar *start = NULL, *end = NULL; if (GTK_LAYOUT (imhtml)->freeze_count) return; @@ -481,7 +512,6 @@ if (line->selected) { gint width, x; - gchar *start, *end; GdkColor col; if ((line->sel_start > line->sel_end) && (line->sel_end != NULL)) { @@ -498,18 +528,17 @@ x = gdk_text_width (bit->font, line->text, start - line->text); if (end == NULL) - width = gdk_string_width (bit->font, line->text) - x; - else - width = gdk_text_width (bit->font, line->text, end - line->text) - x; - - col.red = col.green = col.blue = 0xc000; - gdk_color_alloc (cmap, &col); - gdk_gc_set_foreground (gc, &col); - + end = strchr(line->text, '\0'); + + width = gdk_text_width (bit->font, line->text, end - line->text) - x; + + gdk_gc_set_foreground (gc, imhtml->default_hl_color); + gdk_draw_rectangle (window, gc, TRUE, x + line->x - xoff, line->y - yoff, width, line->height); + gdk_gc_set_foreground (gc, imhtml->default_hlfg_color); + fg = gdk_color_copy(imhtml->default_hlfg_color); } - if (bit->url) { GdkColor *tc = gtk_imhtml_get_color ("#0000a0"); gdk_color_alloc (cmap, tc); @@ -530,19 +559,60 @@ gdk_color_alloc (cmap, fg); gdk_gc_set_foreground (gc, fg); } + + if (start) { + int offset = 0; + gdk_draw_text (window, bit->font, gc, line->x - xoff, + line->y - yoff + line->ascent, line->text, start - line->text); + offset = gdk_text_width(bit->font, line->text, start - line->text); + if (bit->underline || bit->url) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, + line->y - yoff + line->ascent + 1, + offset, 1); + if (bit->strike) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, + line->y - yoff + line->ascent - (bit->font->ascent / 2), + offset, 1); + gdk_gc_set_foreground (gc, imhtml->default_hlfg_color); + gdk_draw_text (window, bit->font, gc, line->x - xoff + offset, + line->y - yoff + line->ascent, start, end - start); + if (bit->underline || bit->url) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, + line->y - yoff + line->ascent + 1, + gdk_text_width(bit->font, line->text, end - start), 1); + if (bit->strike) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, + line->y - yoff + line->ascent - (bit->font->ascent / 2), + gdk_text_width(bit->font, line->text, end - start), 1); + offset = gdk_text_width(bit->font, line->text, end - line->text); + gdk_gc_set_foreground (gc, fg); + gdk_draw_string (window, bit->font, gc, line->x - xoff + offset, + line->y - yoff + line->ascent, end); + if (bit->underline || bit->url) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, + line->y - yoff + line->ascent + 1, + gdk_string_width(bit->font, end), 1); + if (bit->strike) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff + offset, + line->y - yoff + line->ascent - (bit->font->ascent / 2), + gdk_string_width(bit->font, end), 1); + } else { + gdk_draw_string (window, bit->font, gc, line->x - xoff, + line->y - yoff + line->ascent, line->text); + + if (bit->underline || bit->url) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff + line->ascent + 1, + gdk_string_width (bit->font, line->text), 1); + if (bit->strike) + gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, + line->y - yoff + line->ascent - (bit->font->ascent / 2), + gdk_string_width (bit->font, line->text), 1); + } + gdk_color_free (bg); gdk_color_free (fg); - gdk_draw_string (window, bit->font, gc, line->x - xoff, - line->y - yoff + line->ascent, line->text); - - if (bit->underline || bit->url) - gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, line->y - yoff + line->ascent + 1, - gdk_string_width (bit->font, line->text), 1); - if (bit->strike) - gdk_draw_rectangle (window, gc, TRUE, line->x - xoff, - line->y - yoff + line->ascent - (bit->font->ascent / 2), - gdk_string_width (bit->font, line->text), 1); + gdk_gc_unref (gc); } @@ -586,6 +656,10 @@ width, line->height); } + if (bit->bm) { + gdk_gc_set_clip_mask(gc, bit->bm); + gdk_gc_set_clip_origin(gc, line->x - xoff, line->y - yoff + hoff); + } gdk_draw_pixmap (window, gc, bit->pm, 0, 0, line->x - xoff, line->y - yoff + hoff, -1, -1); gdk_gc_unref (gc); @@ -1323,8 +1397,12 @@ NULL, imhtml->tip_window, "tooltip", 0, 0, -1, -1); y = font->ascent + 4; - gtk_paint_string (style, imhtml->tip_window->window, GTK_STATE_NORMAL, NULL, - imhtml->tip_window, "tooltip", 4, y, imhtml->tip_bit->url); + if (imhtml->tip_bit->url) + gtk_paint_string (style, imhtml->tip_window->window, GTK_STATE_NORMAL, NULL, + imhtml->tip_window, "tooltip", 4, y, imhtml->tip_bit->url); + else if (imhtml->tip_bit->img) + gtk_paint_string (style, imhtml->tip_window->window, GTK_STATE_NORMAL, NULL, + imhtml->tip_window, "tooltip", 4, y, imhtml->tip_bit->img->filename); return FALSE; } @@ -1367,7 +1445,8 @@ gap = 2; baseline_skip = font->ascent + font->descent + gap; - w = 8 + gdk_string_width (font, imhtml->tip_bit->url); + w = 8 + gdk_string_width (font, imhtml->tip_bit->img ? imhtml->tip_bit->img->filename : + imhtml->tip_bit->url); h = 8 - gap + baseline_skip; gdk_window_get_pointer (NULL, &x, &y, NULL); @@ -1452,7 +1531,8 @@ while (click) { uw = (struct clickable *) click->data; if ((x > uw->x) && (x < uw->x + uw->width) && - (y > uw->y) && (y < uw->y + uw->height)) { + (y > uw->y) && (y < uw->y + uw->height) && + (uw->bit->url || uw->bit->img)) { if (imhtml->tip_bit != uw->bit) { imhtml->tip_bit = uw->bit; if (imhtml->tip_timer != 0) @@ -1465,8 +1545,9 @@ gtk_imhtml_tip, imhtml); } - gdk_window_set_cursor (GTK_LAYOUT (imhtml)->bin_window, - imhtml->hand_cursor); + if (uw->bit->url) + gdk_window_set_cursor (GTK_LAYOUT (imhtml)->bin_window, + imhtml->hand_cursor); return TRUE; } click = g_list_next (click); @@ -1503,9 +1584,61 @@ imhtml->tip_window = NULL; } imhtml->tip_bit = NULL; - - return TRUE; +return TRUE; } +struct imgsv { + GtkWidget *savedialog; + struct im_image *img; +}; + +#if USE_PIXBUF +static void +save_img (GtkObject *object, + gpointer data) +{ + struct imgsv *is = data; + struct im_image *img = is->img; + gchar *filename; + FILE *f; + filename = gtk_file_selection_get_filename(GTK_FILE_SELECTION(is->savedialog)); + g_print("Saving %s\n", filename); + if (! (f=fopen(filename, "w"))) { + /* There should be some sort of dialog */ + g_print("Could not open file for writing.\n"); + gtk_widget_destroy(is->savedialog); + g_free(is); + return; + } + + fwrite(img->data, 1, img->len, f); + fclose(f); + gtk_widget_destroy(is->savedialog); + g_free(is); +} + +static void +save_img_dialog (GtkObject *object, + gpointer data) +{ + struct imgsv *is = g_malloc(sizeof(struct imgsv)); + struct im_image *img = data; + GtkWidget *savedialog = gtk_file_selection_new ("Gaim - Save Image"); + gtk_file_selection_set_filename (GTK_FILE_SELECTION(savedialog), img->filename); + gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION(savedialog)->cancel_button), + "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), + (gpointer) savedialog); + + is->img = img; + is->savedialog = savedialog; + + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(savedialog)->ok_button), + "clicked", GTK_SIGNAL_FUNC (save_img), is); + gtk_widget_show (savedialog); + + +} +#endif + static void menu_open_url (GtkObject *object, @@ -1587,6 +1720,14 @@ gtk_widget_show (button); } + if (uw->bit->img) { + button = gtk_menu_item_new_with_label ("Save Image"); + gtk_signal_connect (GTK_OBJECT (button), "activate", + GTK_SIGNAL_FUNC (save_img_dialog), uw->bit->img); + gtk_menu_append (GTK_MENU (menu), button); + gtk_widget_show (button); + } + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->time); if (imhtml->tip_timer) { @@ -1876,42 +2017,6 @@ #endif } -#define TRY_FONT tmp = g_strjoinv ("-", newvals); \ - if (default_font->type == GDK_FONT_FONT) \ - ret_font = gdk_font_load (tmp); \ - else \ - ret_font = gdk_fontset_load (tmp); \ - g_free (tmp); \ - if (ret_font) { \ - g_free (newvals); \ - g_strfreev (xnames); \ - g_strfreev (xflds); \ - g_strfreev (names); \ - return ret_font; \ - } else if (newvals [RGSTRY][0] != '*') { \ - /* if the registry isn't "*" then try it as "*". this is evil. */ \ - gchar *reg = newvals [RGSTRY]; \ - gchar *enc = newvals [ENCDNG]; \ - newvals [RGSTRY] = "*"; \ - newvals [ENCDNG] = "*"; \ - tmp = g_strjoinv ("-", newvals); \ - if (default_font->type == GDK_FONT_FONT) \ - ret_font = gdk_font_load (tmp); \ - else \ - ret_font = gdk_fontset_load (tmp); \ - g_free (tmp); \ - if (ret_font) { \ - g_free (newvals); \ - g_strfreev (xnames); \ - g_strfreev (xflds); \ - g_strfreev (names); \ - return ret_font; \ - } \ - newvals [RGSTRY] = reg; \ - newvals [ENCDNG] = enc; \ - } - - static GdkFont* gtk_imhtml_font_load (GtkIMHtml *imhtml, gchar *name, @@ -1923,142 +2028,162 @@ gchar *default_name; gchar **xnames; gchar **pos; - + gchar *tmp = NULL; + GdkFont *ret_font; + gchar *xname; + gchar **xflds; + gchar **newvals; + gchar **names = NULL; + + char *italicstrings[] = {"i","o","*"}; + int italicsind = 0, nameind = 0; + gboolean usebold = TRUE, usesize = TRUE, useregenc = TRUE; + /* if we're not changing anything, use the default. this is the common case */ if (!name && !bold && !italics && !fontsize) return gdk_font_ref (default_font); - + /* base things off of the default font name */ default_name = gtk_imhtml_get_font_name (default_font); - - /* the default font name can actually be several names separated by ','. */ - xnames = g_strsplit (default_name, ",", -1); - for (pos = xnames; pos && *pos; pos++) { - gchar *xname; - gchar **xflds; - - gchar **newvals; - gint i, j; - gchar **names; - gchar fs[10]; - - gchar *tmp; - GdkFont *ret_font; - - xname = *pos; - xname = g_strchomp (xname); - xname = g_strchug (xname); - - xflds = g_strsplit (xname, "-", -1); - - /* figure out if we have a valid name. i wish there were an - * easier way for determining how many values g_strplit gave */ - for (i = 0; xflds [i]; i++); - if (i != 15) { - int tmp; - newvals = g_malloc0 (16 * sizeof (gchar *)); - newvals [0] = ""; - for (tmp = 1; tmp < 15; tmp++) - newvals [tmp] = "*"; - } else - newvals = g_memdup (xflds, 16 * sizeof (xflds)); - - /* we force foundry as "*" because i hate them. i should give a better reason. */ - newvals [FNDRY] = "*"; - - /* if it's "*" then it defaults to (nil) anyway. some fonts don't want (nil) */ - if ((i > ADSTYL) && !xflds [ADSTYL][0]) - newvals [ADSTYL] = "*"; - - /* right. */ - if (bold) - newvals [WGHT] = "bold"; - if (italics) - newvals [SLANT] = "i"; - if (fontsize) { - g_snprintf (fs, sizeof (fs), "%d", POINT_SIZE (fontsize)); - newvals [PXLSZ] = "*"; - newvals [PTSZ] = fs; + /* the default font name can actually be several names separated by ','. + * This is a fontset... used in foreign encodings. */ + do { + xnames = g_strsplit (default_name, ",", -1); + for (pos = xnames; pos && *pos; pos++) { + gint i, j; + gchar fs[10]; + gchar *garbage; + xname = *pos; + xname = g_strchomp (xname); + xname = g_strchug (xname); + + xflds = g_strsplit (xname, "-", -1); + + /* figure out if we have a valid name. i wish there were an + * easier way for determining how many values g_strplit gave */ + for (i = 0; xflds [i]; i++); + if (i != 15) { + int tmp; + newvals = g_malloc0 (16 * sizeof (gchar *)); + newvals [0] = ""; + for (tmp = 1; tmp < 15; tmp++) + newvals [tmp] = "*"; + } else + newvals = g_memdup (xflds, 16 * sizeof (xflds)); + + /* we force foundry as "*" because i hate them. i should give a better reason. */ + newvals [FNDRY] = "*"; + + /* if it's "*" then it defaults to (nil) anyway. some fonts don't want (nil) */ + if ((i > ADSTYL) && !xflds [ADSTYL][0]) + newvals [ADSTYL] = "*"; + + /* If the font doesn't work the first time, we try it with + * registry and encoding as "*" */ + if (!useregenc) { + newvals [RGSTRY] = "*"; + newvals [ENCDNG] = "*"; + } + /* right. */ + if (bold) + if (usebold) + newvals [WGHT] = "bold"; + else + newvals [WGHT] = "*"; + if (italics) + /* We'll try "i" "o" to get italics and then just use "*" */ + newvals [SLANT] = italicstrings[italicsind]; + if (fontsize) { + if (usesize) { + g_snprintf (fs, sizeof (fs), "%d", POINT_SIZE (fontsize)); + newvals [PTSZ] = fs; + } else + newvals [PTSZ] = "*"; + newvals [PXLSZ] = "*"; + } + + if (name) { + /* we got passed a name. it might be a list of names. */ + gchar **tmp_nms = g_strsplit (name, ",", -1); + for (j = 0; tmp_nms [j]; j++); + names = g_new0 (char *, j + 2); + for (j = 0; tmp_nms [j]; j++) + names [j] = tmp_nms [j]; + g_free (tmp_nms); + /* Put the default font on the array. */ + if (i > FMLY) { + names [j] = g_strdup (xflds [FMLY]); + } + newvals [FMLY] = names[nameind]; + } else if (i > FMLY) { + /* we didn't get a name. we come here if the gtk font name is valid */ + names = g_new0 (gchar *, 2); + names [0] = g_strdup (xflds [FMLY]); + } else { + /* we got fucked */ + names = g_new0 (gchar *, 2); + names [0] = g_strdup ("*"); + } + if (!tmp) + tmp = g_strjoinv("-", newvals); + else { + /* We have to concat the xlfds in the fontset */ + garbage = tmp; + tmp = g_strconcat(garbage, ",", + g_strjoinv ("-", newvals), NULL); + g_free(garbage); + } + g_free (newvals); + g_strfreev (xflds); } - - if (name) { - /* we got passed a name. it might be a list of names. */ - gchar **tmp_nms = g_strsplit (name, ",", -1); - /* we do a bunch of weird things to also use the gtk font name as an option. */ - for (j = 0; tmp_nms [j]; j++); - names = g_new0 (char *, j + 2); - for (j = 0; tmp_nms [j]; j++) - names [j] = tmp_nms [j]; - g_free (tmp_nms); - if (i > FMLY) - names [j] = g_strdup (xflds [FMLY]); - } else if (i > FMLY) { - /* we didn't get a name. we come here if the gtk font name is valid */ - names = g_new0 (gchar *, 2); - names [0] = g_strdup (xflds [FMLY]); - } else { - /* we got fucked */ - names = g_new0 (gchar *, 2); - names [0] = g_strdup ("*"); - } - - /* for each name, try it */ - for (j = 0; names [j]; j++) { - newvals [FMLY] = names [j]; - TRY_FONT; + g_strfreev (xnames); + + if (default_font->type == GDK_FONT_FONT) + ret_font = gdk_font_load (tmp); + else { + /* For some reason, fontsets must end with a single * as an xlfd */ + gchar *garbage = tmp; + tmp = g_strconcat(garbage, ",*", NULL); + ret_font = gdk_fontset_load (tmp); } - - /* if italics is set, try various italics options for each name, including no italics */ - for (j = 0; italics && names [j]; j++) { - newvals [FMLY] = names [j]; - - newvals [SLANT] = "o"; - TRY_FONT; - - if (i > SLANT) - newvals [SLANT] = xflds [SLANT]; - else - newvals [SLANT] = "*"; - TRY_FONT; + + /* If the font didn't load, we change some of the xlfds one by one + * to get the closest we can. */ + if (!ret_font) { + if (!useregenc && + (!italics || italicsind == 2) && + (!bold || !usebold) && + (!fontsize || !usesize)) { + useregenc = TRUE; + usebold = TRUE; + italicsind = 0; + usesize = TRUE; + if (names && !names[nameind++]) { + ret_font = gdk_font_ref(default_font); + break; + } + } + if (useregenc) + useregenc = FALSE; + else if (italics && italicsind != 2) { + useregenc = TRUE; + italicsind++; + } else if (bold && usebold) { + useregenc = TRUE; + usebold = FALSE; + } else if (fontsize && usesize) + useregenc = TRUE; + usesize = FALSE; } - - /* if font size was set, try ignoring font size. for each name. */ - for (j = 0; fontsize && names [j]; j++) { - newvals [FMLY] = names [j]; - - if (i > PTSZ) { - newvals [PXLSZ] = xflds [PXLSZ]; - newvals [PTSZ] = xflds [PTSZ]; - } else { - newvals [PXLSZ] = "*"; - newvals [PTSZ] = "*"; - } - TRY_FONT; - } - - /* abandon bold */ - for (j = 0; bold && names [j]; j++) { - newvals [FMLY] = names [j]; - - if (i > WGHT) - newvals [WGHT] = xflds [WGHT]; - else - newvals [WGHT] = "*"; - TRY_FONT; - } - - g_free (newvals); - g_strfreev (xflds); g_strfreev (names); - - } - - g_strfreev (xnames); - - /* we couldn't get anything. so, we quit. you get the default. */ - return gdk_font_ref (default_font); + names = NULL; + g_free(tmp); + tmp=NULL; + } while (!ret_font); /* Loop with the new options */ + + return ret_font; } - + static void gtk_imhtml_init (GtkIMHtml *imhtml) { @@ -2144,6 +2269,10 @@ gtk_imhtml_set_adjustments (imhtml, hadj, vadj); +#if USE_PIXBUF + imhtml->im_images = NULL; +#endif + imhtml->bits = NULL; imhtml->click = NULL; @@ -2276,7 +2405,8 @@ GList *ls = NULL; struct line_info *li; struct clickable *uw; - + struct im_image *img; + if (height > imhtml->llheight) { diff = height - imhtml->llheight; @@ -2299,6 +2429,16 @@ ls = g_list_next (ls); } +#if USE_PIXBUF + ls = imhtml->im_images; + while(ls) { + img = ls->data; + if (img->y + diff > imhtml->y) + img->y += diff; + ls = g_list_next(ls); + } +#endif + imhtml->llheight = height; if (ascent) imhtml->llascent = ascent; @@ -2366,7 +2506,11 @@ li->ascent = 0; li->bit = bit; +#if USE_PIXBUF + if (bit->url || bit->img) { +#else if (bit->url) { +#endif uw = g_new0 (struct clickable, 1); uw->x = imhtml->x; uw->y = imhtml->y; @@ -2466,6 +2610,36 @@ g_free (copy); } else if ((bit->type == TYPE_SMILEY) || (bit->type == TYPE_IMG)) { +#if USE_PIXBUF + if (bit->img) { + GdkPixbuf *imagepb = bit->img->pb; + GdkPixbuf *tmp = NULL; + if (gdk_pixbuf_get_width(imagepb) > imhtml->xsize - imhtml->x) + new_line (imhtml); + + if (gdk_pixbuf_get_width(imagepb) > imhtml->xsize) { + tmp = gdk_pixbuf_scale_simple(imagepb, imhtml->xsize, + gdk_pixbuf_get_height(imagepb) * + imhtml->xsize/ + gdk_pixbuf_get_width(imagepb), + GDK_INTERP_TILES); + if (bit->pm) + gdk_pixmap_unref (bit->pm); + if (bit->bm) + gdk_bitmap_unref (bit->bm); + gdk_pixbuf_render_pixmap_and_mask(tmp, &(bit->pm), &(bit->bm), 100); + gdk_pixbuf_unref(tmp); + } + else { + if (bit->pm) + gdk_pixmap_unref (bit->pm); + if (bit->bm) + gdk_bitmap_unref (bit->bm); + gdk_pixbuf_render_pixmap_and_mask(imagepb, &(bit->pm), &(bit->bm), 100); + } + } +#endif + gdk_window_get_size (bit->pm, &width, &height); if ((imhtml->x != 0) && ((imhtml->x + width) > imhtml->xsize)) @@ -2802,8 +2976,10 @@ VALID_TAG ("/BODY"); VALID_TAG ("FONT"); VALID_TAG ("HEAD"); - VALID_TAG ("HEAD"); - + VALID_TAG ("/HEAD"); + VALID_TAG ("BINARY"); + VALID_TAG ("/BINARY"); + VALID_OPT_TAG ("HR"); VALID_OPT_TAG ("FONT"); VALID_OPT_TAG ("BODY"); @@ -2851,17 +3027,14 @@ if ((*t == '\"') || (*t == '\'')) { e = a = ++t; while (*e && (*e != *(t - 1))) e++; - if (*e != '\0') { - *e = '\0'; - return g_strdup (a); - } else { + if (*e == '\0') { return NULL; - } + } else + return g_strndup (a, e - a); } else { e = a = t; while (*e && !isspace ((gint) *e)) e++; - *e = '\0'; - return g_strdup (a); + return g_strndup (a, e - a); } } @@ -3055,12 +3228,27 @@ case 38: /* HEAD */ case 39: /* /HEAD */ break; - - case 40: /* HR (opt) */ + case 40: /* BINARY */ + + NEW_BIT (NEW_TEXT_BIT); + while (pos < len) { + if (!g_strncasecmp("", c, strlen(""))) + break; + else { + c++; + pos++; + } + } + c = c - tlen; /* Because it will add this later */ + break; + case 41: /* /BINARY */ + break; + + case 42: /* HR (opt) */ NEW_BIT (NEW_TEXT_BIT); NEW_BIT (NEW_SEP_BIT); break; - case 41: /* FONT (opt) */ + case 43: /* FONT (opt) */ { gchar *color, *back, *face, *size; FontDetail *font; @@ -3114,7 +3302,7 @@ fonts = g_slist_prepend (fonts, font); } break; - case 42: /* BODY (opt) */ + case 44: /* BODY (opt) */ if (!(options & GTK_IMHTML_NO_COLOURS)) { gchar *bgcolor = gtk_imhtml_get_html_opt (tag, "BGCOLOR="); if (bgcolor) { @@ -3128,7 +3316,7 @@ } } break; - case 43: /* A (opt) */ + case 45: /* A (opt) */ { gchar *href = gtk_imhtml_get_html_opt (tag, "HREF="); if (href) { @@ -3138,16 +3326,94 @@ } } break; - case 44: /* IMG (opt) */ - { + case 46: /* IMG (opt) */ + { gchar *src = gtk_imhtml_get_html_opt (tag, "SRC="); + gchar *id = gtk_imhtml_get_html_opt (tag, "ID="); + gchar *datasize = gtk_imhtml_get_html_opt (tag, "DATASIZE="); gchar **xpm; GdkColor *clr; GtkIMHtmlBit *bit; if (!src) break; - + + if (!imhtml->img && id && datasize) { /* This is an embedded IM image */ +#if USE_PIXBUF + char *tmp, *imagedata, *e; + const gchar *alltext; + struct im_image *img; + GdkPixbufLoader *load; + GdkPixbuf *imagepb = NULL; + +#endif + NEW_BIT (NEW_TEXT_BIT); +#if USE_PIXBUF + if (!id || !datasize) + break; + tmp = g_malloc(strlen("") + + strlen(id) + strlen(datasize)); + g_snprintf(tmp, strlen("") + + strlen(id) + strlen(datasize) + 1, + "", id, datasize); + alltext = c; + while (g_strncasecmp(alltext, tmp, strlen(tmp)) && alltext < (c + len)) + alltext++; + alltext = alltext + strlen("") + strlen(id) + strlen(datasize); + g_free(tmp); + imagedata = g_malloc(atoi(datasize)); + memcpy(imagedata, alltext, atoi(datasize)); + + if (!GTK_WIDGET_REALIZED (imhtml)) + gtk_widget_realize (GTK_WIDGET (imhtml)); + + img = g_new0 (struct im_image, 1); + tmp = e = src; + while (*tmp){ + if (*tmp == '/' || *tmp == '\\') { + tmp++; + src = tmp; + } else + tmp++; + } + + *tmp = '\0'; + + img->filename = g_strdup(src); + img->len = atoi(datasize); + if (img->len) { + img->data = g_malloc(img->len); + memcpy(img->data, imagedata, img->len); + + load = gdk_pixbuf_loader_new(); + if (!gdk_pixbuf_loader_write(load, imagedata, img->len)) + g_print("IM Image corrupt or unreadable.\n"); + else + imagepb = gdk_pixbuf_loader_get_pixbuf(load); + img->pb = imagepb; + } + if (imagepb) { + bit = g_new0 (GtkIMHtmlBit, 1); + bit->type = TYPE_IMG; + bit->img = img; + if (url) + bit->url = g_strdup (url); + + NEW_BIT (bit); + } else { + g_free(img->filename); + g_free(img->data); + gdk_pixbuf_unref(img->pb); + } + g_free(imagedata); + g_free(e); + g_free(id); + g_free(datasize); + +#endif + break; + } + if (!imhtml->img || ((xpm = imhtml->img (src)) == NULL)) { g_free (src); break; @@ -3171,10 +3437,10 @@ g_free (src); } break; - case 45: /* P (opt) */ - case 46: /* H3 (opt) */ + case 47: /* P (opt) */ + case 48: /* H3 (opt) */ break; - case 47: /* comment */ + case 49: /* comment */ NEW_BIT (NEW_TEXT_BIT); wpos = g_snprintf (ws, len, "%s", tag); NEW_BIT (NEW_COMMENT_BIT); @@ -3335,6 +3601,15 @@ gdk_pixmap_unref (bit->pm); if (bit->bm) gdk_bitmap_unref (bit->bm); +#if USE_PIXBUF + if (bit->img) { + g_free(bit->img->filename); + g_free(bit->img->data); + gdk_pixbuf_unref(bit->img->pb); + g_free(bit->img); + } +#endif + while (bit->chunks) { struct line_info *li = bit->chunks->data; if (li->text) @@ -3349,7 +3624,13 @@ g_free (imhtml->click->data); imhtml->click = g_list_remove (imhtml->click, imhtml->click->data); } - + +#if USE_PIXBUF + while (imhtml->im_images) { + imhtml->im_images = g_list_remove(imhtml->im_images, imhtml->im_images->data); + } +#endif + if (imhtml->selected_text) { g_string_free (imhtml->selected_text, TRUE); imhtml->selected_text = g_string_new (""); @@ -3376,6 +3657,11 @@ imhtml->scroll_timer = 0; } +#if USE_PIXBUF + g_list_free(imhtml->im_images); + imhtml->im_images = NULL; +#endif + imhtml->x = 0; imhtml->y = TOP_BORDER; imhtml->xsize = 0; diff -r d16a0504f1c8 -r 7239a392486c src/gtkimhtml.h --- a/src/gtkimhtml.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/gtkimhtml.h Sat Mar 02 04:52:21 2002 +0000 @@ -48,6 +48,8 @@ GdkFont *default_font; GdkColor *default_fg_color; GdkColor *default_bg_color; + GdkColor *default_hl_color; + GdkColor *default_hlfg_color; GdkCursor *hand_cursor; GdkCursor *arrow_cursor; @@ -55,6 +57,7 @@ GList *bits; GList *click; struct _GtkIMHtmlBit *tip_bit; + GList *im_images; GtkWidget *tip_window; guint tip_timer; diff -r d16a0504f1c8 -r 7239a392486c src/module.c --- a/src/module.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/module.c Sat Mar 02 04:52:21 2002 +0000 @@ -360,6 +360,9 @@ case event_chat_send_invite: sprintf(buf, "event_chat_send_invite"); break; + case event_got_typing: + sprintf(buf, "event_got_typing"); + break; default: sprintf(buf, "event_unknown"); break; @@ -373,7 +376,7 @@ return; switch (event) { - case event_quit: + case event_quit: debug_printf("%s\n", event_name(event)); break; case event_signon: @@ -392,9 +395,10 @@ case event_buddy_away: case event_buddy_back: case event_buddy_idle: - case event_buddy_unidle: - case event_set_info: - debug_printf("%s: %s %s\n", event_name(event), + case event_buddy_unidle: + case event_set_info: + case event_got_typing: + debug_printf("%s: %s %s\n", event_name(event), ((struct gaim_connection *)arg1)->username, (char *)arg2); break; case event_chat_leave: @@ -512,6 +516,7 @@ case event_chat_leave: case event_set_info: case event_draw_menu: + case event_got_typing: two = g->function; two(arg1, arg2, g->data); break; diff -r d16a0504f1c8 -r 7239a392486c src/prefs.c --- a/src/prefs.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/prefs.c Sat Mar 02 04:52:21 2002 +0000 @@ -183,6 +183,7 @@ GtkWidget *sep; GtkWidget *idle; GtkWidget *opt; + GtkWidget *typingbutton; parent = prefdialog->parent; gtk_widget_destroy(prefdialog); @@ -226,7 +227,15 @@ misc_options ^= OPT_MISC_DEBUG; debugbutton = gaim_button(_("Show Debug Window"), &misc_options, OPT_MISC_DEBUG, mbox); gtk_signal_connect(GTK_OBJECT(debugbutton), "destroy", GTK_SIGNAL_FUNC(destdeb), 0); - + + /* Preferences should be positive */ + typingbutton = gaim_button(_("Notify buddies that you are typing to them"), &misc_options, + OPT_MISC_STEALTH_TYPING, mbox); + + /* So we have to toggle it */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(typingbutton), !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(typingbutton))); + misc_options ^= OPT_MISC_STEALTH_TYPING; + frame = gtk_frame_new(_("Report Idle Times")); gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 5); gtk_widget_show(frame); diff -r d16a0504f1c8 -r 7239a392486c src/protocols/msn/msn.c --- a/src/protocols/msn/msn.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/msn/msn.c Sat Mar 02 04:52:21 2002 +0000 @@ -35,6 +35,9 @@ #define USEROPT_HOTMAIL 0 +#define MSN_TYPING_RECV_TIMEOUT 6 +#define MSN_TYPING_SEND_TIMEOUT 4 + struct msn_data { int fd; int trId; @@ -511,8 +514,14 @@ content = strstr(msg, "Content-Type: "); if (!content) return; - if (!g_strncasecmp(content, "Content-Type: text/plain", - strlen("Content-Type: text/plain"))) { + if (!g_strncasecmp(content, "Content-Type: text/x-msmsgscontrol\r\n", + strlen( "Content-Type: text/x-msmsgscontrol\r\n"))) { + if (strstr(content,"TypingUser: ")) { + serv_got_typing(ms->gc, ms->msguser, MSN_TYPING_RECV_TIMEOUT); + return; + } + } else if (!g_strncasecmp(content, "Content-Type: text/plain", + strlen("Content-Type: text/plain"))) { char *skiphead; skiphead = strstr(msg, "\r\n\r\n"); if (!skiphead || !skiphead[4]) { @@ -521,7 +530,7 @@ skiphead += 4; utf = utf8_to_str(skiphead); strip_linefeed(utf); - + if (ms->chat) serv_got_chat_in(ms->gc, ms->chat->id, ms->msguser, flags, utf, time(NULL)); else @@ -1475,6 +1484,24 @@ g_free(md); } +static int msn_send_typing(struct gaim_connection *gc, char *who) { + struct msn_switchboard *ms = msn_find_switch(gc, who); + char header[MSN_BUF_LEN] = "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgscontrol\r\n" + "User-Agent: Gaim/" VERSION "\r\n" + "TypingUser: "; + char buf [MSN_BUF_LEN]; + if (!ms) + return; + g_snprintf(buf, sizeof(buf), "MSG %d N %d\r\n%s%s\r\n\r\n\r\n", + ++ms->trId, + strlen(header) + strlen("\r\n\r\n\r\n") + strlen(gc->username), + header, gc->username); + if (msn_write(ms->fd, buf, strlen(buf)) < 0) + msn_kill_switch(ms); + return MSN_TYPING_SEND_TIMEOUT; +} + static int msn_send_im(struct gaim_connection *gc, char *who, char *message, int flags) { struct msn_data *md = gc->proto_data; @@ -1988,6 +2015,7 @@ ret->login = msn_login; ret->close = msn_close; ret->send_im = msn_send_im; + ret->send_typing = msn_send_typing; ret->away_states = msn_away_states; ret->set_away = msn_set_away; ret->set_idle = msn_set_idle; diff -r d16a0504f1c8 -r 7239a392486c src/protocols/oscar/aim.h --- a/src/protocols/oscar/aim.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/oscar/aim.h Sat Mar 02 04:52:21 2002 +0000 @@ -390,15 +390,25 @@ fu32_t membersince; fu32_t onlinesince; fu32_t sessionlen; - int capspresent; fu32_t capabilities; struct { fu32_t status; fu32_t ipaddr; fu8_t crap[0x25]; /* until we figure it out... */ } icqinfo; + fu32_t present; } aim_userinfo_t; +#define AIM_USERINFO_PRESENT_FLAGS 0x00000001 +#define AIM_USERINFO_PRESENT_MEMBERSINCE 0x00000002 +#define AIM_USERINFO_PRESENT_ONLINESINCE 0x00000004 +#define AIM_USERINFO_PRESENT_IDLE 0x00000008 +#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010 +#define AIM_USERINFO_PRESENT_ICQIPADDR 0x00000020 +#define AIM_USERINFO_PRESENT_ICQDATA 0x00000040 +#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080 +#define AIM_USERINFO_PRESENT_SESSIONLEN 0x00000100 + faim_export const char *aim_userinfo_sn(aim_userinfo_t *ui); faim_export fu16_t aim_userinfo_flags(aim_userinfo_t *ui); faim_export fu16_t aim_userinfo_idle(aim_userinfo_t *ui); @@ -827,6 +837,7 @@ faim_export int aim_send_im(aim_session_t *, const char *destsn, unsigned short flags, const char *msg); faim_export int aim_send_icon(aim_session_t *sess, const char *sn, const fu8_t *icon, int iconlen, time_t stamp, fu16_t iconsum); faim_export fu16_t aim_iconsum(const fu8_t *buf, int buflen); +faim_export int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing); faim_export int aim_send_im_direct(aim_session_t *, aim_conn_t *, const char *msg); faim_export const char *aim_directim_getsn(aim_conn_t *conn); faim_export aim_conn_t *aim_directim_initiate(aim_session_t *, const char *destsn); diff -r d16a0504f1c8 -r 7239a392486c src/protocols/oscar/aim_cbtypes.h --- a/src/protocols/oscar/aim_cbtypes.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/oscar/aim_cbtypes.h Sat Mar 02 04:52:21 2002 +0000 @@ -254,6 +254,7 @@ #define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004 #define AIM_CB_SPECIAL_FLAPVER 0x0005 #define AIM_CB_SPECIAL_CONNINITDONE 0x0006 +#define AIM_CB_SPECIAL_DOWNLOADIMAGE 0x007 #define AIM_CB_SPECIAL_UNKNOWN 0xffff #define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN diff -r d16a0504f1c8 -r 7239a392486c src/protocols/oscar/ft.c --- a/src/protocols/oscar/ft.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/oscar/ft.c Sat Mar 02 04:52:21 2002 +0000 @@ -109,24 +109,24 @@ } /** - * aim_send_im_direct - send IM client-to-client over established connection + * aim_send_typing - send client-to-client typing notification over established connection * @sess: session to conn * @conn: directim connection - * @msg: null-terminated string to send; if this is NULL, it will send a "typing" notice. + * @typing: If true, notify user has started typing; if false, notify user has stopped. * - * Call this just like you would aim_send_im, to send a directim. You - * _must_ have previously established the directim connection. + * The connection must have been previously established. */ -faim_export int aim_send_im_direct(aim_session_t *sess, aim_conn_t *conn, const char *msg) +faim_export int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing) { - struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal; + +struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal; aim_frame_t *fr; aim_bstream_t hdrbs; /* XXX this should be within aim_frame_t */ if (!sess || !conn || (conn->type != AIM_CONN_TYPE_RENDEZVOUS)) return -EINVAL; - if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x01, strlen(msg)))) + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x01, 0))) return -ENOMEM; memcpy(fr->hdr.oft.magic, "ODC2", 4); @@ -147,18 +147,18 @@ aimbs_put16(&hdrbs, 0x0000); aimbs_put16(&hdrbs, 0x0000); aimbs_put16(&hdrbs, 0x0000); - aimbs_put32(&hdrbs, strlen(msg)); + aimbs_put32(&hdrbs, 0x00000000); aimbs_put16(&hdrbs, 0x0000); aimbs_put16(&hdrbs, 0x0000); aimbs_put16(&hdrbs, 0x0000); - /* flags -- 0x000e for "typing", 0x0000 for message */ - aimbs_put16(&hdrbs, msg ? 0x0000 : 0x000e); + /* flags -- 0x000e for "started typing", 0x0002 for "stopped typing */ + aimbs_put16(&hdrbs, ( typing ? 0x000e : 0x0002)); aimbs_put16(&hdrbs, 0x0000); aimbs_put16(&hdrbs, 0x0000); aimbs_putraw(&hdrbs, sess->sn, strlen(sess->sn)); - + aim_bstream_setpos(&hdrbs, 52); /* bleeehh */ aimbs_put8(&hdrbs, 0x00); @@ -172,25 +172,92 @@ /* end of hdr2 */ - if (msg) { -#if 0 /* XXX this is how you send buddy icon info... */ - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0008); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x000c); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0000); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x1466); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0001); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x2e0f); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x393e); - i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0xcac8); -#endif - aimbs_putraw(&fr->data, msg, strlen(msg)); - } - aim_tx_enqueue(sess, fr); return 0; } +/** + * aim_send_im_direct - send IM client-to-client over established connection + * @sess: session to conn + * @conn: directim connection + * @msg: null-terminated string to send. + * + * Call this just like you would aim_send_im, to send a directim. You + * _must_ have previously established the directim connection. + */ +faim_export int aim_send_im_direct(aim_session_t *sess, aim_conn_t *conn, const char *msg) +{ + struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal; + aim_frame_t *fr; + aim_bstream_t hdrbs; /* XXX this should be within aim_frame_t */ + + if (!sess || !conn || !msg || (conn->type != AIM_CONN_TYPE_RENDEZVOUS)) + return -EINVAL; + + if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x01, strlen(msg)))) + return -ENOMEM; + + memcpy(fr->hdr.oft.magic, "ODC2", 4); + + fr->hdr.oft.hdr2len = 0x44; + + if (!(fr->hdr.oft.hdr2 = calloc(1, fr->hdr.oft.hdr2len))) { + aim_frame_destroy(fr); + return -ENOMEM; + } + + aim_bstream_init(&hdrbs, fr->hdr.oft.hdr2, fr->hdr.oft.hdr2len); + + aimbs_put16(&hdrbs, 0x0006); + aimbs_put16(&hdrbs, 0x0000); + aimbs_putraw(&hdrbs, intdata->cookie, 8); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put32(&hdrbs, strlen(msg)); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + + /* flags -- 0x000e for "started typing", 0x0002 for "stopped typing, 0x0000 for message */ + aimbs_put16(&hdrbs, 0x0000); + + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_putraw(&hdrbs, sess->sn, strlen(sess->sn)); + + aim_bstream_setpos(&hdrbs, 52); /* bleeehh */ + + aimbs_put8(&hdrbs, 0x00); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + aimbs_put16(&hdrbs, 0x0000); + + /* end of hdr2 */ + +#if 0 /* XXX this is how you send buddy icon info... */ + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0008); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x000c); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0000); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x1466); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0001); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x2e0f); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x393e); + i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0xcac8); +#endif + aimbs_putraw(&fr->data, msg, strlen(msg)); + + aim_tx_enqueue(sess, fr); + + return 0; +} + static int getlocalip(fu8_t *ip) { struct hostent *hptr; @@ -1021,26 +1088,45 @@ int ret = 0; if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING))) - ret = userfunc(sess, &fr, snptr); + ret = userfunc(sess, &fr, snptr, 1); + + return ret; + + } else if (flags == 0x0002) { + int ret = 0; + + if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING))) + ret = userfunc(sess, &fr, snptr, 0); return ret; } else if ((flags == 0x0000) && payloadlength) { - char *msg; + char *msg, *msg2; int ret = 0; + int recvd = 0; + int i; if (!(msg = calloc(1, payloadlength+1))) return -1; - - if (aim_recv(conn->fd, msg, payloadlength) < payloadlength) { - free(msg); - return -1; + msg2 = msg; + + while (payloadlength - recvd) { + if (payloadlength - recvd >= 1024) + i = aim_recv(conn->fd, msg2, 1024); + else + i = aim_recv(conn->fd, msg2, payloadlength - recvd); + if (i == 0) { + free(msg); + return -1; + } + recvd = recvd + i; + msg2 = msg2 + i; + if ((userfunc=aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_DOWNLOADIMAGE))) + userfunc(sess, &fr, snptr, (double)recvd / payloadlength); } - - msg[payloadlength] = '\0'; - + if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING)) ) - ret = userfunc(sess, &fr, snptr, msg); + ret = userfunc(sess, &fr, snptr, msg, payloadlength); free(msg); diff -r d16a0504f1c8 -r 7239a392486c src/protocols/oscar/info.c --- a/src/protocols/oscar/info.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/oscar/info.c Sat Mar 02 04:52:21 2002 +0000 @@ -106,7 +106,7 @@ faim_export int aim_userinfo_hascap(aim_userinfo_t *ui, fu32_t cap) { - if (!ui || !ui->capspresent) + if (!ui || !(ui->present & AIM_USERINFO_PRESENT_CAPABILITIES)) return -1; return !!(ui->capabilities & cap); @@ -338,6 +338,7 @@ * */ outinfo->flags = aimbs_get16(bs); + outinfo->present |= AIM_USERINFO_PRESENT_FLAGS; } else if (type == 0x0002) { /* @@ -347,6 +348,7 @@ * the service, stored in time_t format. */ outinfo->membersince = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE; } else if (type == 0x0003) { /* @@ -356,6 +358,7 @@ * session, stored in time_t format. */ outinfo->onlinesince = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE; } else if (type == 0x0004) { /* @@ -369,6 +372,7 @@ * related to reality. */ outinfo->idletime = aimbs_get16(bs); + outinfo->present |= AIM_USERINFO_PRESENT_IDLE; } else if (type == 0x0006) { /* @@ -379,6 +383,7 @@ */ aimbs_get16(bs); outinfo->icqinfo.status = aimbs_get16(bs); + outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS; } else if (type == 0x000a) { /* @@ -388,6 +393,7 @@ * Ahh, the joy of ICQ security. */ outinfo->icqinfo.ipaddr = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR; } else if (type == 0x000c) { /* @@ -398,6 +404,7 @@ * */ aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25); + outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA; } else if (type == 0x000d) { /* @@ -407,7 +414,7 @@ * */ outinfo->capabilities = aim_getcap(sess, bs, length); - outinfo->capspresent = 1; + outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES; } else if (type == 0x000e) { /* @@ -433,6 +440,7 @@ * */ outinfo->sessionlen = aimbs_get32(bs); + outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN; } else { @@ -644,7 +652,7 @@ aim_bstream_init(&cbs, ct->value, ct->length); userinfo.capabilities = aim_getcap(sess, &cbs, ct->length); - userinfo.capspresent = 1; + userinfo.present = AIM_USERINFO_PRESENT_CAPABILITIES; } } diff -r d16a0504f1c8 -r 7239a392486c src/protocols/oscar/oscar.c --- a/src/protocols/oscar/oscar.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/oscar/oscar.c Sat Mar 02 04:52:21 2002 +0000 @@ -101,6 +101,15 @@ gboolean killme; gboolean icq; GSList *evilhack; + + struct { + guint maxbuddies; /* max users you can watch */ + guint maxwatchers; /* max users who can watch you */ + guint maxpermits; /* max users on permit list */ + guint maxdenies; /* max users on deny list */ + guint maxsiglen; /* max size (bytes) of profile */ + guint maxawaymsglen; /* max size (bytes) of posted away message */ + } rights; }; struct create_room { @@ -244,6 +253,7 @@ static int conninitdone_chat (aim_session_t *, aim_frame_t *, ...); static int conninitdone_chatnav (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_msgerr (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_locaterights(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...); static int gaim_icbm_param_info (aim_session_t *, aim_frame_t *, ...); @@ -260,6 +270,7 @@ static int gaim_directim_initiate(aim_session_t *, aim_frame_t *, ...); static int gaim_directim_incoming(aim_session_t *, aim_frame_t *, ...); static int gaim_directim_typing (aim_session_t *, aim_frame_t *, ...); +static int gaim_update_ui (aim_session_t *, aim_frame_t *, ...); static char *msgerrreason[] = { "Invalid error", @@ -309,6 +320,7 @@ g_snprintf(buf, sizeof buf, _("Direct IM with %s closed"), sn); if ((cnv = find_conversation(sn))) write_to_conv(cnv, buf, WFLAG_SYSTEM, NULL, time(NULL), -1); + update_progress(cnv, 100); g_free(dim); /* I guess? I don't see it anywhere else... -- mid */ g_free(sn); @@ -660,6 +672,7 @@ aim_conn_addhandler(sess, bosconn, 0x0009, 0x0003, gaim_bosrights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_REDIRECT, gaim_handle_redirect, 0); + aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_RIGHTSINFO, gaim_parse_locaterights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_RIGHTSINFO, gaim_parse_buddyrights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ONCOMING, gaim_parse_oncoming, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_OFFGOING, gaim_parse_offgoing, 0); @@ -1108,22 +1121,23 @@ } static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) { - aim_userinfo_t *info; - time_t time_idle; - int type = 0; struct gaim_connection *gc = sess->aux_data; struct oscar_data *od = gc->proto_data; + aim_userinfo_t *info; + time_t time_idle = 0, signon = 0; + int type = 0; + int caps = 0; char *tmp; - int caps; va_list ap; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); va_end(ap); - caps = info->capabilities; - - if (!od->icq) { + if (info->present & AIM_USERINFO_PRESENT_CAPABILITIES) + caps = info->capabilities; + + if (!od->icq && (info->present & AIM_USERINFO_PRESENT_FLAGS)) { if (info->flags & AIM_FLAG_ACTIVEBUDDY) type |= UC_AB; if (info->flags & AIM_FLAG_UNCONFIRMED) @@ -1136,29 +1150,31 @@ type |= UC_NORMAL; if (info->flags & AIM_FLAG_AWAY) type |= UC_UNAVAILABLE; - } else { - if (info->icqinfo.status) { - type = (info->icqinfo.status << 6); - if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT)) - type |= UC_UNAVAILABLE; - } - if (caps & AIM_CAPS_ICQ) - caps ^= AIM_CAPS_ICQ; - debug_printf("icq status: %d\n", info->icqinfo.status); } - if (info->idletime) { + if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) { + type = (info->icqinfo.status << 6); + if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT)) + type |= UC_UNAVAILABLE; + } + + if (caps & AIM_CAPS_ICQ) + caps ^= AIM_CAPS_ICQ; + + if (info->present & AIM_USERINFO_PRESENT_IDLE) { time(&time_idle); time_idle -= info->idletime*60; - } else - time_idle = 0; + } + + if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN) + signon = time(NULL) - info->sessionlen; tmp = g_strdup(normalize(gc->username)); if (!strcmp(tmp, normalize(info->sn))) g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", info->sn); g_free(tmp); - serv_got_update(gc, info->sn, 1, info->warnlevel/10, time(NULL) - info->sessionlen, + serv_got_update(gc, info->sn, 1, info->warnlevel/10, signon, time_idle, type, caps); return 1; @@ -1246,7 +1262,8 @@ gaim_directim_incoming, 0); aim_conn_addhandler(od->sess, dim->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING, gaim_directim_typing, 0); - + aim_conn_addhandler(od->sess, dim->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_DOWNLOADIMAGE, + gaim_update_ui, 0); for (i = 0; i < (int)strlen(d->ip); i++) { if (d->ip[i] == ':') { port = atoi(&(d->ip[i+1])); @@ -1668,7 +1685,7 @@ GSList *l = od->evilhack; gboolean evilhack = FALSE; va_list ap; - char *asc; + gchar *membersince = NULL, *onlinesince = NULL, *idle = NULL; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); @@ -1685,23 +1702,38 @@ " : Administrator
" " : ActiveBuddy Interactive Agent
")); - if (info->membersince) - asc = g_strdup_printf("Member Since : %s
\n", - asctime(localtime(&info->membersince))); - else - asc = g_strdup(""); + if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE) { + onlinesince = g_strdup_printf("Online Since : %s
\n", + asctime(localtime(&info->onlinesince))); + } + + if (info->present & AIM_USERINFO_PRESENT_MEMBERSINCE) { + membersince = g_strdup_printf("Member Since : %s
\n", + asctime(localtime(&info->membersince))); + } + + if (info->present & AIM_USERINFO_PRESENT_IDLE) { + idle = g_strdup_printf("Idle : %d minutes", + info->idletime); + } else + idle = g_strdup("Idle: Active"); + g_snprintf(header, sizeof header, _("Username : %s %s
\n" + "Warning Level : %d %%
\n" "%s" - "Warning Level : %d %%
\n" - "Online Since : %s
\n" - "Idle Minutes : %d\n
\n

\n"), + "%s" + "%s
\n" + "

\n"), info->sn, images(info->flags), - asc, info->warnlevel/10, - asctime(localtime(&info->onlinesince)), - info->idletime); - g_free(asc); + onlinesince ? onlinesince : "", + membersince ? membersince : "", + idle ? idle : ""); + + g_free(onlinesince); + g_free(membersince); + g_free(idle); while (l) { char *x = l->data; @@ -2040,21 +2072,11 @@ } static int conninitdone_bos(aim_session_t *sess, aim_frame_t *fr, ...) { - struct gaim_connection *gc = sess->aux_data; aim_reqpersonalinfo(sess, fr->conn); aim_bos_reqlocaterights(sess, fr->conn); - aim_bos_setprofile(sess, fr->conn, gc->user->user_info, NULL, gaim_caps); aim_bos_reqbuddyrights(sess, fr->conn); - account_online(gc); - serv_finish_login(gc); - - if (bud_list_cache_exists(gc)) - do_import(gc, NULL); - - debug_printf("buddy list loaded\n"); - aim_reqicbmparams(sess); aim_bos_reqrights(sess, fr->conn); @@ -2133,10 +2155,31 @@ return 1; } -/* XXX this is frivelous... do you really want to know this info? */ +static int gaim_parse_locaterights(aim_session_t *sess, aim_frame_t *fr, ...) +{ + va_list ap; + fu16_t maxsiglen; + struct gaim_connection *gc = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)gc->proto_data; + + va_start(ap, fr); + maxsiglen = va_arg(ap, int); + va_end(ap); + + debug_printf("locate rights: max sig len = %d\n", maxsiglen); + + odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen; + + aim_bos_setprofile(sess, fr->conn, gc->user->user_info, NULL, gaim_caps); + + return 1; +} + static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; fu16_t maxbuddies, maxwatchers; + struct gaim_connection *gc = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)gc->proto_data; va_start(ap, fr); maxbuddies = (fu16_t)va_arg(ap, unsigned int); @@ -2145,12 +2188,17 @@ debug_printf("buddy list rights: Max buddies = %d / Max watchers = %d\n", maxbuddies, maxwatchers); + odata->rights.maxbuddies = (guint)maxbuddies; + odata->rights.maxwatchers = (guint)maxwatchers; + return 1; } static int gaim_bosrights(aim_session_t *sess, aim_frame_t *fr, ...) { fu16_t maxpermits, maxdenies; va_list ap; + struct gaim_connection *gc = sess->aux_data; + struct oscar_data *odata = (struct oscar_data *)gc->proto_data; va_start(ap, fr); maxpermits = (fu16_t)va_arg(ap, unsigned int); @@ -2159,6 +2207,17 @@ debug_printf("BOS rights: Max permit = %d / Max deny = %d\n", maxpermits, maxdenies); + odata->rights.maxpermits = (guint)maxpermits; + odata->rights.maxdenies = (guint)maxdenies; + + account_online(gc); + serv_finish_login(gc); + + if (bud_list_cache_exists(gc)) + do_import(gc, NULL); + + debug_printf("buddy list loaded\n"); + aim_clientready(sess, fr->conn); aim_icq_reqofflinemsgs(sess); @@ -2344,6 +2403,23 @@ return "Oscar"; } +static void oscar_send_typing_stopped(struct gaim_connection *gc, char *name) { + struct oscar_data *odata = (struct oscar_data *)gc->proto_data; + struct direct_im *dim = find_direct_im(odata, name); + if (!dim) + return; + aim_send_typing(odata->sess, dim->conn, FALSE); +} + +static int oscar_send_typing(struct gaim_connection *gc, char *name) { + struct oscar_data *odata = (struct oscar_data *)gc->proto_data; + struct direct_im *dim = find_direct_im(odata, name); + if (!dim) + return; + aim_send_typing(odata->sess, dim->conn, TRUE); + return 0; +} + static int oscar_send_im(struct gaim_connection *gc, char *name, char *message, int imflags) { struct oscar_data *odata = (struct oscar_data *)gc->proto_data; struct direct_im *dim = find_direct_im(odata, name); @@ -2447,34 +2523,64 @@ static void oscar_set_info(struct gaim_connection *g, char *info) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; - char inforeal[1025], away[1025]; - g_snprintf(inforeal, sizeof(inforeal), "%s", info); - if (g->away) - g_snprintf(away, sizeof(away), "%s", g->away); - if (strlen(info) > 1024) - do_error_dialog("Maximum info length (1024) exceeded, truncating", "Info Too Long"); - aim_bos_setprofile(odata->sess, odata->conn, inforeal, g->away ? NULL : "", gaim_caps); + gchar *inforeal; + + if (odata->rights.maxsiglen == 0) + do_error_dialog("oscar_set_info called before locate rights received", "Protocol Error"); + + if (strlen(info) > odata->rights.maxsiglen) { + gchar *errstr; + + errstr = g_strdup_printf("Maximum info length of %d bytes exceeded, truncating", odata->rights.maxsiglen); + + do_error_dialog(errstr, "Info Too Long"); + + g_free(errstr); + } + + inforeal = g_strndup(info, odata->rights.maxsiglen); + + aim_bos_setprofile(odata->sess, odata->conn, inforeal, NULL, gaim_caps); + + g_free(inforeal); + + return; } -static void oscar_set_away(struct gaim_connection *gc, char *state, char *message) { - struct oscar_data *od = (struct oscar_data *)gc->proto_data; - char away[1025]; - if (!od->icq) { - if (message) - g_snprintf(away, sizeof(away), "%s", message); - aim_bos_setprofile(od->sess, od->conn, NULL, message ? away : "", gaim_caps); - if (gc->away) - g_free (gc->away); - gc->away = NULL; - if (message) { - if (strlen(message) > sizeof(away)-1) - do_error_dialog("Maximum away length exceeded (1024), truncating", - "Info Too Long"); - gc->away = g_strdup (message); - } +static void oscar_set_away_aim(struct gaim_connection *gc, struct oscar_data *od, const char *message) +{ + + if (od->rights.maxawaymsglen == 0) + do_error_dialog("oscar_set_away_aim called before locate rights received", "Protocol Error"); + + if (gc->away) + g_free(gc->away); + gc->away = NULL; + + if (!message) { + aim_bos_setprofile(od->sess, od->conn, NULL, "", gaim_caps); return; } + if (strlen(message) > od->rights.maxawaymsglen) { + gchar *errstr; + + errstr = g_strdup_printf("Maximum away message length of %d bytes exceeded, truncating", od->rights.maxawaymsglen); + + do_error_dialog(errstr, "Away Message Too Long"); + + g_free(errstr); + } + + gc->away = g_strndup(message, od->rights.maxawaymsglen); + aim_bos_setprofile(od->sess, od->conn, NULL, gc->away, gaim_caps); + + return; +} + +static void oscar_set_away_icq(struct gaim_connection *gc, struct oscar_data *od, const char *state, const char *message) +{ + if (gc->away) gc->away = NULL; @@ -2506,6 +2612,20 @@ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); } } + + return; +} + +static void oscar_set_away(struct gaim_connection *gc, char *state, char *message) +{ + struct oscar_data *od = (struct oscar_data *)gc->proto_data; + + if (od->icq) + oscar_set_away_icq(gc, od, state, message); + else + oscar_set_away_aim(gc, od, message); + + return; } static void oscar_warn(struct gaim_connection *g, char *name, int anon) { @@ -3002,6 +3122,31 @@ gaim_directim_incoming, 0); aim_conn_addhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING, gaim_directim_typing, 0); + aim_conn_addhandler(od->sess, dim->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_DOWNLOADIMAGE, + gaim_update_ui, 0); + return 1; +} + +static int gaim_update_ui(aim_session_t *sess, aim_frame_t *fr, ...) { + va_list ap; + char *sn; + double percent; + struct gaim_connection *gc = sess->aux_data; + struct oscar_data *od = (struct oscar_data *)gc->proto_data; + struct conversation *c; + struct direct_im *dim; + + va_start(ap, fr); + sn = va_arg(ap, char *); + percent = va_arg(ap, double); + va_end(ap); + + dim = find_direct_im(od, sn); + gaim_input_remove(dim->watcher); /* Otherwise, the callback will callback */ + if ((c = find_conversation(sn))) + update_progress(c, percent); + dim->watcher = gaim_input_add(dim->conn->fd, GAIM_INPUT_READ, + oscar_callback, dim->conn); return 1; } @@ -3009,16 +3154,18 @@ static int gaim_directim_incoming(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; char *msg, *sn; + int len; struct gaim_connection *gc = sess->aux_data; va_start(ap, fr); sn = va_arg(ap, char *); msg = va_arg(ap, char *); + len = va_arg(ap, int); va_end(ap); debug_printf("Got DirectIM message from %s\n", sn); - serv_got_im(gc, sn, msg, 0, time(NULL), -1); + serv_got_im(gc, sn, msg, 0, time(NULL), len); return 1; } @@ -3026,14 +3173,20 @@ static int gaim_directim_typing(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; char *sn; + int typing; + struct gaim_connection *gc = sess->aux_data; va_start(ap, fr); sn = va_arg(ap, char *); + typing = va_arg(ap, int); va_end(ap); - /* I had to leave this. It's just too funny. It reminds me of my sister. */ - debug_printf("ohmigod! %s has started typing (DirectIM). He's going to send you a message! *squeal*\n", sn); - + if (typing) { + /* I had to leave this. It's just too funny. It reminds me of my sister. */ + debug_printf("ohmigod! %s has started typing (DirectIM). He's going to send you a message! *squeal*\n", sn); + serv_got_typing(gc,sn,0); + } else + serv_got_typing_stopped(gc,sn); return 1; } @@ -3364,6 +3517,8 @@ ret->login = oscar_login; ret->close = oscar_close; ret->send_im = oscar_send_im; + ret->send_typing = oscar_send_typing; + ret->send_typing_stopped = oscar_send_typing_stopped; ret->set_info = oscar_set_info; ret->get_info = oscar_get_info; ret->set_away = oscar_set_away; diff -r d16a0504f1c8 -r 7239a392486c src/protocols/yahoo/yahoo.c --- a/src/protocols/yahoo/yahoo.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/protocols/yahoo/yahoo.c Sat Mar 02 04:52:21 2002 +0000 @@ -48,7 +48,7 @@ #include "pixmaps/status-here.xpm" #include "pixmaps/status-idle.xpm" -#undef YAHOO_DEBUG +#define YAHOO_DEBUG #define USEROPT_MAIL 0 @@ -57,6 +57,11 @@ #define USEROPT_PAGERPORT 4 #define YAHOO_PAGER_PORT 5050 +/* I just made these values up. Anyone know what they should be? + * -SeanEgan */ +#define YAHOO_TYPING_RECV_TIMEOUT 10 +#define YAHOO_TYPING_SEND_TIMEOUT 8 + enum yahoo_service { /* these are easier to see in hex */ YAHOO_SERVICE_LOGON = 1, YAHOO_SERVICE_LOGOFF, @@ -92,6 +97,7 @@ YAHOO_SERVICE_GAMELOGOFF, YAHOO_SERVICE_GAMEMSG = 0x2a, YAHOO_SERVICE_FILETRANSFER = 0x46, + YAHOO_SERVICE_TYPING = 0x4B, /* may have other uses too */ YAHOO_SERVICE_LIST = 0x55, YAHOO_SERVICE_ADDBUDDY = 0x83, YAHOO_SERVICE_REMBUDDY = 0x84 @@ -111,7 +117,8 @@ YAHOO_STATUS_INVISIBLE = 12, YAHOO_STATUS_CUSTOM = 99, YAHOO_STATUS_IDLE = 999, - YAHOO_STATUS_OFFLINE = 0x5a55aa56 /* don't ask */ + YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */ + YAHOO_STATUS_TYPING = 0x16 }; struct yahoo_data { @@ -459,6 +466,24 @@ do_export(gc); } +static void yahoo_process_typing(struct gaim_connection *gc, struct yahoo_packet *pkt) +{ + char *msg = NULL; + char *from = NULL; + time_t tm = time(0); + GSList *l = pkt->hash; + + while (l) { + struct yahoo_pair *pair = l->data; + if (pair->key == 4) + from = pair->value; + if (pair->key == 49) + msg = pair->value; + l = l->next; + } + serv_got_typing(gc, from, YAHOO_TYPING_RECV_TIMEOUT); +} + static void yahoo_process_message(struct gaim_connection *gc, struct yahoo_packet *pkt) { char *msg = NULL; @@ -590,6 +615,9 @@ case YAHOO_SERVICE_ISBACK: yahoo_process_status(gc, pkt); break; + case YAHOO_SERVICE_TYPING: + yahoo_process_typing(gc, pkt); + break; case YAHOO_SERVICE_MESSAGE: case YAHOO_SERVICE_GAMEMSG: yahoo_process_message(gc, pkt); @@ -886,6 +914,25 @@ return 1; } +int yahoo_send_typing(struct gaim_connection *gc, char *who) +{ + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_TYPING, YAHOO_STATUS_TYPING, 0);//x6431de4f); + + yahoo_packet_hash(pkt, 49, "TYPING"); + yahoo_packet_hash(pkt, 1, gc->displayname); + yahoo_packet_hash(pkt, 14, " "); + yahoo_packet_hash(pkt, 13, "1"); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 1002, "1"); + + yahoo_send_packet(yd, pkt); + + yahoo_packet_free(pkt); + + return YAHOO_TYPING_SEND_TIMEOUT; +} + static void yahoo_set_away(struct gaim_connection *gc, char *state, char *msg) { struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; @@ -1057,6 +1104,7 @@ ret->keepalive = yahoo_keepalive; ret->add_buddy = yahoo_add_buddy; ret->remove_buddy = yahoo_remove_buddy; + ret->send_typing = yahoo_send_typing; my_protocol = ret; } diff -r d16a0504f1c8 -r 7239a392486c src/prpl.h --- a/src/prpl.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/prpl.h Sat Mar 02 04:52:21 2002 +0000 @@ -104,6 +104,8 @@ void (* close) (struct gaim_connection *); int (* send_im) (struct gaim_connection *, char *who, char *message, int away); void (* set_info) (struct gaim_connection *, char *info); + int (* send_typing) (struct gaim_connection *, char *name); + void (* send_typing_stopped) (struct gaim_connection *, char *name); void (* get_info) (struct gaim_connection *, char *who); void (* set_away) (struct gaim_connection *, char *state, char *message); void (* get_away) (struct gaim_connection *, char *who); diff -r d16a0504f1c8 -r 7239a392486c src/server.c --- a/src/server.c Sat Mar 02 01:37:01 2002 +0000 +++ b/src/server.c Sat Mar 02 04:52:21 2002 +0000 @@ -142,7 +142,19 @@ update_keepalive(gc, TRUE); } +/* This should return the elapsed time in seconds in which Gaim will not send + * typing notifications. + * if it returns zero, it will not send any more typing notifications */ +int serv_send_typing(struct gaim_connection *g, char *name) { + if (g && g->prpl && g->prpl->send_typing) + return g->prpl->send_typing(g, name); + else return 0; +} +void serv_send_typing_stopped(struct gaim_connection *g, char *name) { + if (g && g->prpl && g->prpl->send_typing_stopped) + g->prpl->send_typing_stopped(g, name); +} int serv_send_im(struct gaim_connection *gc, char *name, char *message, int flags) { @@ -153,6 +165,7 @@ if (!(flags & IM_FLAG_AWAY)) serv_touch_idle(gc); + serv_send_typing_stopped(gc, name); return val; } @@ -657,7 +670,7 @@ { struct buddy *b = find_buddy(gc, name); - if (gc->prpl->options & OPT_PROTO_CORRECT_TIME) { + if (signon && (gc->prpl->options & OPT_PROTO_CORRECT_TIME)) { char *tmp = g_strdup(normalize(name)); if (!g_strcasecmp(tmp, normalize(gc->username))) { gc->evil = evil; @@ -748,7 +761,29 @@ do_error_dialog(buf2, _("Warned")); } +void serv_got_typing(struct gaim_connection *gc, char *name, int timeout) { + struct conversation *cnv = find_conversation(name); + if (cnv) { + set_convo_gc(cnv, gc); + show_typing(cnv); + } else return; + plugin_event(event_got_typing, gc, name, 0, 0); + do_pounce(gc, name, OPT_POUNCE_TYPING); + if (timeout > 0) { + if (cnv->typing_timeout) + gtk_timeout_remove (cnv->typing_timeout); + cnv->typing_timeout = gtk_timeout_add(timeout * 1000,(GtkFunction)reset_typing, + g_strdup(name)); + } +} +void serv_got_typing_stopped(struct gaim_connection *gc, char *name) { + struct conversation *c = find_conversation(name); + if (c->typing_timeout) { + gtk_timeout_remove (c->typing_timeout); + } + reset_typing(g_strdup(name)); +} static void close_invite(GtkWidget *w, GtkWidget *w2) { diff -r d16a0504f1c8 -r 7239a392486c src/ui.h --- a/src/ui.h Sat Mar 02 01:37:01 2002 +0000 +++ b/src/ui.h Sat Mar 02 04:52:21 2002 +0000 @@ -153,7 +153,10 @@ GtkWidget *sep2; GtkWidget *menu; GtkWidget *check; + GtkWidget *progress; gint unseen; + guint typing_timeout; + time_t type_again; /* stuff used just for chat */ GList *in_room; @@ -399,6 +402,9 @@ extern void update_convo_font(); extern void set_hide_icons(); extern void set_convo_titles(); +extern void update_progress(struct conversation *, float); +extern void show_typing(struct conversation *); +extern gboolean reset_typing(char *); /* Functions in dialogs.c */ extern void alias_dialog_bud(struct buddy *);