changeset 2993:7239a392486c

[gaim-migrate @ 3006] 0.53 :) committer: Tailor Script <tailor@pidgin.im>
author Rob Flynn <gaim@robflynn.com>
date Sat, 02 Mar 2002 04:52:21 +0000
parents d16a0504f1c8
children 60018f862a51
files AUTHORS ChangeLog NEWS doc/CREDITS gaim.spec.in plugins/SIGNALS plugins/chatlist.c src/about.c src/aim.c src/buddy.c src/conversation.c src/core.h src/dialogs.c src/gaim.h src/gtkimhtml.c src/gtkimhtml.h src/module.c src/prefs.c src/protocols/msn/msn.c src/protocols/oscar/aim.h src/protocols/oscar/aim_cbtypes.h src/protocols/oscar/ft.c src/protocols/oscar/info.c src/protocols/oscar/oscar.c src/protocols/yahoo/yahoo.c src/prpl.h src/server.c src/ui.h
diffstat 28 files changed, 1257 insertions(+), 367 deletions(-) [+]
line wrap: on
line diff
--- 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 <bj91704@binghamton.edu> - Developer
 	AIM: SeanEgn
+	ICQ: 96642211
+	MSN: SeanEgn@hotmail.com
+	Yahoo:	SeanEgn
+	Jabber: SeanEgn@jabber.org
 
 Syd Logan - Hacker and Designated Driver (lazy bum)
 
--- 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)
--- 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. 
--- 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        <IM: zilding>			jimduchek@ou.edu
 Eric Warmenhoven                    		warmenhoven@yahoo.com
 Mark Spencer      <IM: markster97>		markster@marko.net
+Sean Egan	  <IM: SeanEgn>			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
--- 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 <rob@marko.net> (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 <rob@marko.net> (0.52 release)
 - Better buddy icon transparency (thanks SeanEgan)
 - Updated Polish Translation (thanks Przemyslaw Sulek)
--- 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.
--- 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 <stdlib.h>
 #include <string.h>
 
-#define AOL_SRCHSTR "/community/aimcheck.adp/url="
+#define AOL_SRCHSTR "aim:GoChat?RoomName="
 
 struct chat_page {
 	GtkWidget *list1;
--- 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);
--- 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();
--- 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) {
--- 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)
 {
--- 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 *);
--- 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);
+
 	/* </pounce type="when"> */
 	
 	/* <pounce type="action"> */
--- 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 *);
--- 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 <X11/Xlib.h>
+#include <stdlib.h>
 #include <gdk/gdkx.h>
 #include <gtk/gtk.h>
 #include <string.h>
@@ -35,6 +36,11 @@
 #include <locale.h>
 #endif
 
+#if USE_PIXBUF
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixbuf-loader.h>
+#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 (&GTK_WIDGET (imhtml)->style->fg [GTK_STATE_NORMAL]);
 	imhtml->default_bg_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->base [GTK_STATE_NORMAL]);
+	imhtml->default_hl_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->bg [GTK_STATE_SELECTED]);
+	imhtml->default_hlfg_color=gdk_color_copy (&GTK_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("</BINARY>", c, strlen("</BINARY>"))) 
+						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("<DATA ID=\"\" SIZE=\"\">") + 
+						       strlen(id) + strlen(datasize));
+					g_snprintf(tmp, strlen("<DATA ID=\"\" SIZE=\"\">") + 
+						   strlen(id) + strlen(datasize) + 1, 
+						   "<DATA ID=\"%s\" SIZE=\"%s\">", id, datasize);
+					alltext = c;
+					while (g_strncasecmp(alltext, tmp, strlen(tmp)) && alltext < (c + len))
+						alltext++;
+					alltext = alltext + strlen("<DATA ID=\"\" SIZE=\"\">") + 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;
--- 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;
 
--- 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;
--- 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);
--- 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;
--- 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);
--- 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
 
--- 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);
 
--- 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;
 		}
 	}
 
--- 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 @@
 			"<IMG SRC=\"admin_icon.gif\"> : Administrator <br>"
 			"<IMG SRC=\"ab_icon.gif\"> : ActiveBuddy Interactive Agent<br>"));
 
-	if (info->membersince)
-		asc = g_strdup_printf("Member Since : <B>%s</B><BR>\n",
-				asctime(localtime(&info->membersince)));
-	else
-		asc = g_strdup("");
+	if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE) {
+		onlinesince = g_strdup_printf("Online Since : <B>%s</B><BR>\n",
+					asctime(localtime(&info->onlinesince)));
+	}
+
+	if (info->present & AIM_USERINFO_PRESENT_MEMBERSINCE) {
+		membersince = g_strdup_printf("Member Since : <B>%s</B><BR>\n",
+					asctime(localtime(&info->membersince)));
+	}
+
+	if (info->present & AIM_USERINFO_PRESENT_IDLE) {
+		idle = g_strdup_printf("Idle : <B>%d minutes</B>",
+					info->idletime);
+	} else
+		idle = g_strdup("Idle: <B>Active</B>");
+
 	g_snprintf(header, sizeof header,
 			_("Username : <B>%s</B>  %s <BR>\n"
+			"Warning Level : <B>%d %%</B><BR>\n"
 			"%s"
-			"Warning Level : <B>%d %%</B><BR>\n"
-			"Online Since : <B>%s</B><BR>\n"
-			"Idle Minutes : <B>%d</B>\n<BR>\n<HR><BR>\n"),
+			"%s"
+			"%s<BR>\n"
+			"<HR><BR>\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;
--- 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;
 }
--- 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);
--- 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)
 {
--- 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 *);