changeset 29663:af5c36fe8d6f

propagate from branch 'im.pidgin.pidgin' (head e602a35f60e3d423d43bacc72fcb7239c8b43cf0) to branch 'im.pidgin.cpw.attention_ui' (head d6e2d83ffb82d0028d802e5959d57de1d61b4faf)
author Marcus Lundblad <ml@update.uu.se>
date Tue, 13 Jan 2009 18:39:52 +0000
parents 9ea6842536ba (diff) bacb1e93e7c4 (current diff)
children e841bad196e0
files libpurple/protocols/jabber/message.c libpurple/protocols/msn/msn.c libpurple/protocols/yahoo/yahoo.c
diffstat 20 files changed, 260 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/connection.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/connection.h	Tue Jan 13 18:39:52 2009 +0000
@@ -45,7 +45,7 @@
 	PURPLE_CONNECTION_NO_URLDESC = 0x0040,  /**< Connection does not support descriptions with links */ 
 	PURPLE_CONNECTION_NO_IMAGES = 0x0080,  /**< Connection does not support sending of images */
 	PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY = 0x0100 /**< Connection supports sending and receiving custom smileys */
-
+	
 } PurpleConnectionFlags;
 
 typedef enum
--- a/libpurple/conversation.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/conversation.c	Tue Jan 13 18:39:52 2009 +0000
@@ -960,6 +960,16 @@
 	g_free(displayed);
 }
 
+void
+purple_conversation_attention(PurpleConversation *conv, const char *who,
+	guint type, PurpleMessageFlags flags, time_t mtime)
+{
+	PurpleAccount *account = purple_conversation_get_account(conv);
+	purple_signal_emit(purple_conversations_get_handle(),
+		flags == PURPLE_MESSAGE_SEND ? "sent-attention" : "got-attention",
+		account, who, conv, type);
+}
+
 gboolean
 purple_conversation_has_focus(PurpleConversation *conv)
 {
@@ -2180,7 +2190,27 @@
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_CONVERSATION),
 						 purple_value_new(PURPLE_TYPE_UINT));
-
+	
+	purple_signal_register(handle, "sent-attention",
+						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT,
+						 NULL, 4,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_CONVERSATION),
+						 purple_value_new(PURPLE_TYPE_UINT));
+	
+	purple_signal_register(handle, "got-attention",
+						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT,
+						 NULL, 4,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_CONVERSATION),
+						 purple_value_new(PURPLE_TYPE_UINT));
+	
 	purple_signal_register(handle, "sending-im-msg",
 						 purple_marshal_VOID__POINTER_POINTER_POINTER,
 						 NULL, 3,
--- a/libpurple/conversation.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/conversation.h	Tue Jan 13 18:39:52 2009 +0000
@@ -645,7 +645,21 @@
 		const char *message, PurpleMessageFlags flags,
 		time_t mtime);
 
-
+/**
+ * Sends an attention to a conversation window.
+ *
+ * This is to be called by prpls to tell UIs to set off the action for
+ * an attention message
+ *
+ * @param conv		The conversation
+ * @param who		The user who sent the attention
+ * @param type		The attention type (will be 0 for protocols that only have 1 type)
+ * @param flags		The message flags (send, receive)
+ * @param mtime		The time the attentsion was sent
+ */
+void purple_conversation_attention(PurpleConversation *conv, const char *who,
+	guint attention_type, PurpleMessageFlags flags, time_t mtime);
+	
 /**
 	Set the features as supported for the given conversation.
 	@param conv      The conversation
--- a/libpurple/protocols/jabber/jabber.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Jan 13 18:39:52 2009 +0000
@@ -1786,9 +1786,12 @@
 	PurpleStatusType *type;
 	GList *types = NULL;
 	PurpleValue *priority_value;
+	PurpleValue *buzz_enabled;
 
 	priority_value = purple_value_new(PURPLE_TYPE_INT);
 	purple_value_set_int(priority_value, 1);
+	buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
+	purple_value_set_boolean(buzz_enabled, TRUE);
 	type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
 			jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_ONLINE),
 			NULL, TRUE, TRUE, FALSE,
@@ -1797,7 +1800,7 @@
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
-			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
+			"buzz", _("Allow Buzz"), buzz_enabled,
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1811,7 +1814,7 @@
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
-			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
+			"buzz", _("Allow Buzz"), buzz_enabled,
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1825,7 +1828,7 @@
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
-			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
+			"buzz", _("Allow Buzz"), buzz_enabled,								 
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1839,7 +1842,7 @@
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
-			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
+			"buzz", _("Allow Buzz"), buzz_enabled,								 
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1853,7 +1856,6 @@
 			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
 			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
 			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
-			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -2436,8 +2438,13 @@
 
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
-	GList *iter;
-
+	PurpleConnection *gc = js->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	PurpleConversation *conv = 
+		purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, username,
+			account);
+	gchar *str;
+	
 	if(!username)
 		return FALSE;
 
@@ -2446,53 +2453,62 @@
 		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username);
 		return FALSE;
 	}
-
+	
 	jbr = jabber_buddy_find_resource(jb, NULL);
-	if(!jbr) {
-		*error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), username);
-		return FALSE;
-	}
-
-	if(!jbr->caps) {
-		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username);
+	if (!jbr) {
+		*error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), 
+			username);
 		return FALSE;
 	}
-
-	for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
-		if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) {
-			xmlnode *buzz, *msg = xmlnode_new("message");
-			gchar *to;
-
-			to = g_strdup_printf("%s/%s", username, jbr->name);
-			xmlnode_set_attrib(msg, "to", to);
-			g_free(to);
-
-			/* avoid offline storage */
-			xmlnode_set_attrib(msg, "type", "headline");
-
-			buzz = xmlnode_new_child(msg, "attention");
-			xmlnode_set_namespace(buzz, "http://www.xmpp.org/extensions/xep-0224.html#ns");
-
-			jabber_send(js, msg);
-			xmlnode_free(msg);
-
-			return TRUE;
-		}
+	
+	if (jabber_resource_has_capability(jbr, XEP_0224_NAMESPACE)) {
+		xmlnode *buzz, *msg = xmlnode_new("message");
+		gchar *to;
+
+		to = g_strdup_printf("%s/%s", username, jbr->name);
+		xmlnode_set_attrib(msg, "to", to);
+		g_free(to);
+
+		/* avoid offline storage */
+		xmlnode_set_attrib(msg, "type", "headline");
+
+		buzz = xmlnode_new_child(msg, "attention");
+		xmlnode_set_namespace(buzz, XEP_0224_NAMESPACE);
+
+		jabber_send(js, msg);
+		xmlnode_free(msg);
+
+		str = g_strdup_printf(_("Buzzing %s..."), username);
+		purple_conversation_write(conv, NULL, str, 
+			PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
+		g_free(str);
+		
+		return TRUE;
+	} else {
+		*error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), username);
+		return FALSE;
 	}
-
-	*error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), username);
-	return FALSE;
 }
 
 static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
 		const char *cmd, char **args, char **error, void *data)
 {
 	JabberStream *js = conv->account->gc->proto_data;
-
-	if(!args || !args[0])
-		return PURPLE_CMD_RET_FAILED;
-
-	return _jabber_send_buzz(js, args[0], error)  ? PURPLE_CMD_RET_OK : PURPLE_CMD_RET_FAILED;
+	const gchar *who;
+	
+	if (!args || !args[0]) {
+		/* use the buddy from conversation, if it's a one-to-one conversation */
+		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
+			who = purple_conversation_get_name(conv);
+		} else {
+			return PURPLE_CMD_RET_FAILED;
+		}
+	} else {
+		who = args[0];
+	}
+	
+	purple_conversation_attention(conv, who, 0, PURPLE_MESSAGE_SEND, time(NULL));
+	return _jabber_send_buzz(js, who, error)  ? PURPLE_CMD_RET_OK : PURPLE_CMD_RET_FAILED;
 }
 
 GList *jabber_attention_types(PurpleAccount *account)
@@ -2513,7 +2529,13 @@
 	gchar *error = NULL;
 
 	if (!_jabber_send_buzz(js, username, &error)) {
+		PurpleAccount *account = purple_connection_get_account(gc);
+		PurpleConversation *conv = 
+			purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
+				username, account);
 		purple_debug_error("jabber", "jabber_send_attention: jabber_cmd_buzz failed with error: %s\n", error ? error : "(NULL)");
+		purple_conversation_write(conv, username, error,
+			PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
 		g_free(error);
 		return FALSE;
 	}
@@ -2605,8 +2627,9 @@
 					  "prpl-jabber", jabber_cmd_ping,
 					  _("ping &lt;jid&gt;:	Ping a user/component/server."),
 					  NULL);
-	purple_cmd_register("buzz", "s", PURPLE_CMD_P_PRPL,
-					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,
+	purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL,
+					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY | 
+					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
 					  "prpl-jabber", jabber_cmd_buzz,
 					  _("buzz: Buzz a user to get their attention"), NULL);
 }
--- a/libpurple/protocols/jabber/libxmpp.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Tue Jan 13 18:39:52 2009 +0000
@@ -283,7 +283,7 @@
 	
 	jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb);
 	jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb);
-	jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns",
+	jabber_add_feature("buzz", XEP_0224_NAMESPACE,
 					   jabber_buzz_isenabled);
 	jabber_add_feature("bob", XEP_0231_NAMESPACE,
 					   jabber_custom_smileys_isenabled);
--- a/libpurple/protocols/jabber/message.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Tue Jan 13 18:39:52 2009 +0000
@@ -731,7 +731,7 @@
 			jm->type = JABBER_MESSAGE_EVENT;
 			for(items = xmlnode_get_child(child,"items"); items; items = items->next)
 				jm->eventitems = g_list_append(jm->eventitems, items);
-		} else if(!strcmp(child->name, "attention") && !strcmp(xmlns,"http://www.xmpp.org/extensions/xep-0224.html#ns")) {
+		} else if(!strcmp(child->name, "attention") && !strcmp(xmlns, XEP_0224_NAMESPACE)) {
 			jm->hasBuzz = TRUE;
 		} else if(!strcmp(child->name, "delay") && !strcmp(xmlns,"urn:xmpp:delay")) {
 			const char *timestamp = xmlnode_get_attrib(child, "stamp");
--- a/libpurple/protocols/jabber/message.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/jabber/message.h	Tue Jan 13 18:39:52 2009 +0000
@@ -26,6 +26,8 @@
 #include "jabber.h"
 #include "xmlnode.h"
 
+#define XEP_0224_NAMESPACE "urn:xmpp:attention:0"
+
 typedef struct _JabberMessage {
 	JabberStream *js;
 	enum {
--- a/libpurple/protocols/msn/msn.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/msn/msn.c	Tue Jan 13 18:39:52 2009 +0000
@@ -162,7 +162,9 @@
 	username = purple_conversation_get_name(conv);
 
 	purple_prpl_send_attention(gc, username, MSN_NUDGE);
-
+	purple_conversation_attention(conv, username, 0, PURPLE_MESSAGE_SEND,
+		time(NULL));
+	
 	return PURPLE_CMD_RET_OK;
 }
 
--- a/libpurple/protocols/msn/switchboard.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Tue Jan 13 18:39:52 2009 +0000
@@ -1038,6 +1038,9 @@
 
 		else
 			purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
+		
+		purple_conversation_attention(swboard->conv, user, 0, 
+			PURPLE_MESSAGE_RECV, time(NULL));
 
 	} else if (!strcmp(id, "2")) {
 		/* Wink */
--- a/libpurple/protocols/msnp9/msn.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Tue Jan 13 18:39:52 2009 +0000
@@ -775,7 +775,8 @@
 
 	gc->proto_data = session;
 	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
-		PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
+		PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY |
+		PURPLE_CONNECTION_ALLOW_ATTENTION;
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_START);
 
--- a/libpurple/protocols/yahoo/yahoo.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Tue Jan 13 18:39:52 2009 +0000
@@ -878,6 +878,8 @@
 
 			username = g_markup_escape_text(im->from, -1);
 			purple_prpl_got_attention(gc, username, YAHOO_BUZZ);
+			purple_conversation_attention(c, username, 0, PURPLE_MESSAGE_RECV,
+				time(NULL));
 			g_free(username);
 			g_free(m);
 			g_free(im);
@@ -4142,6 +4144,7 @@
 		return PURPLE_CMD_RET_FAILED;
 
 	purple_prpl_send_attention(account->gc, c->name, YAHOO_BUZZ);
+	purple_conversation_attention(c, c->name, 0, PURPLE_MESSAGE_SEND, time(NULL));
 
 	return PURPLE_CMD_RET_OK;
 }
--- a/libpurple/sound.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/libpurple/sound.h	Tue Jan 13 18:39:52 2009 +0000
@@ -51,6 +51,8 @@
 	PURPLE_SOUND_CHAT_SAY,         /**< Someone else says somthing in a chat. */
 	PURPLE_SOUND_POUNCE_DEFAULT,   /**< Default sound for a buddy pounce.     */
 	PURPLE_SOUND_CHAT_NICK,        /**< Someone says your name in a chat.     */
+	PURPLE_SOUND_SEND_ATTENTION,   /**< Send an attention					  */
+	PURPLE_SOUND_GOT_ATTENTION,	   /**< Got an attention					  */
 	PURPLE_NUM_SOUNDS              /**< Total number of sounds.               */
 
 } PurpleSoundEventID;
--- a/pidgin/gtkconv.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/gtkconv.c	Tue Jan 13 18:39:52 2009 +0000
@@ -1212,6 +1212,20 @@
 }
 
 static void
+menu_get_attention_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = data;
+	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+
+	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
+		purple_prpl_send_attention(purple_conversation_get_gc(conv), 
+			purple_conversation_get_name(conv), 0);
+		purple_conversation_attention(conv, purple_conversation_get_name(conv),
+			0, PURPLE_MESSAGE_SEND, time(NULL));
+	}
+}
+
+static void
 menu_add_pounce_cb(gpointer data, guint action, GtkWidget *widget)
 {
 	PidginWindow *win = data;
@@ -2274,6 +2288,11 @@
 	gtkconv = PIDGIN_CONVERSATION(conv);
 	old_conv = gtkconv->active_conv;
 
+	purple_debug_info("gtkconv", "setting active conversation on toolbar %p\n",
+		conv);
+	gtk_imhtmltoolbar_switch_active_conversation(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), 
+		conv);
+	
 	if (old_conv == conv)
 		return;
 
@@ -3110,6 +3129,7 @@
 	{ "/Conversation/sep1", NULL, NULL, 0, "<Separator>", NULL },
 
 	{ N_("/Conversation/Se_nd File..."), NULL, menu_send_file_cb, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_FILE },
+	{ N_("/Conversation/Get _Attention"), NULL, menu_get_attention_cb, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION },
 	{ N_("/Conversation/Add Buddy _Pounce..."), NULL, menu_add_pounce_cb,
 			0, "<Item>", NULL },
 	{ N_("/Conversation/_Get Info"), "<CTL>O", menu_get_info_cb, 0,
@@ -3425,6 +3445,9 @@
 		gtk_item_factory_get_widget(win->menu.item_factory,
 		                            N_("/Conversation/Send File..."));
 
+	win->menu.get_attention =
+		gtk_item_factory_get_widget(win->menu.item_factory,
+			                    N_("/Conversation/Get Attention"));
 	win->menu.add_pounce =
 		gtk_item_factory_get_widget(win->menu.item_factory,
 		                            N_("/Conversation/Add Buddy Pounce..."));
@@ -6315,6 +6338,7 @@
 		/* Deal with menu items */
 		gtk_widget_show(win->menu.view_log);
 		gtk_widget_show(win->menu.send_file);
+		gtk_widget_show(win->menu.get_attention);
 		gtk_widget_show(win->menu.add_pounce);
 		gtk_widget_show(win->menu.get_info);
 		gtk_widget_hide(win->menu.invite);
@@ -6343,6 +6367,7 @@
 		/* Deal with menu items */
 		gtk_widget_show(win->menu.view_log);
 		gtk_widget_hide(win->menu.send_file);
+		gtk_widget_hide(win->menu.get_attention);
 		gtk_widget_hide(win->menu.add_pounce);
 		gtk_widget_hide(win->menu.get_info);
 		gtk_widget_show(win->menu.invite);
@@ -6420,6 +6445,7 @@
 			gtk_widget_set_sensitive(win->menu.send_file,
 									 (prpl_info->send_file != NULL && (!prpl_info->can_receive_file ||
 									  prpl_info->can_receive_file(gc, purple_conversation_get_name(conv)))));
+			gtk_widget_set_sensitive(win->menu.get_attention, (prpl_info->send_attention != NULL));
 			gtk_widget_set_sensitive(win->menu.alias,
 									 (account != NULL) &&
 									 (purple_find_buddy(account, purple_conversation_get_name(conv)) != NULL));
@@ -6440,6 +6466,7 @@
 		/* Then deal with menu items */
 		gtk_widget_set_sensitive(win->menu.view_log, TRUE);
 		gtk_widget_set_sensitive(win->menu.send_file, FALSE);
+		gtk_widget_set_sensitive(win->menu.get_attention, FALSE);
 		gtk_widget_set_sensitive(win->menu.add_pounce, TRUE);
 		gtk_widget_set_sensitive(win->menu.get_info, FALSE);
 		gtk_widget_set_sensitive(win->menu.invite, FALSE);
@@ -6744,6 +6771,7 @@
 	}
 }
 
+
 static PurpleConversationUiOps conversation_ui_ops =
 {
 	pidgin_conv_new,
--- a/pidgin/gtkconvwin.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/gtkconvwin.h	Tue Jan 13 18:39:52 2009 +0000
@@ -51,6 +51,7 @@
 		GtkWidget *view_log;
 
 		GtkWidget *send_file;
+		GtkWidget *get_attention;
 		GtkWidget *add_pounce;
 		GtkWidget *get_info;
 		GtkWidget *invite;
--- a/pidgin/gtkimhtmltoolbar.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Tue Jan 13 18:39:52 2009 +0000
@@ -33,6 +33,7 @@
 #include "request.h"
 #include "pidginstock.h"
 #include "util.h"
+#include "debug.h"
 
 #include "gtkdialogs.h"
 #include "gtkimhtmltoolbar.h"
@@ -913,6 +914,18 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
+static void send_attention_cb(GtkWidget *attention, GtkIMHtmlToolbar *toolbar)
+{
+	PurpleConversation *conv = toolbar->active_conv;
+	const gchar *who = purple_conversation_get_name(conv);
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+	
+	toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->attention), FALSE, 
+		toolbar);
+	purple_conversation_attention(conv, who, 0, PURPLE_MESSAGE_SEND, time(NULL));
+	purple_prpl_send_attention(gc, who, 0);
+}
+
 static void update_buttons_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, GtkIMHtmlToolbar *toolbar)
 {
 	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->bold), buttons & GTK_IMHTML_BOLD);
@@ -1232,6 +1245,8 @@
 		{PIDGIN_STOCK_TOOLBAR_INSERT_LINK, insert_link_cb, &toolbar->link, _("Insert Link")},
 		{PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, insert_image_cb, &toolbar->image, _("Insert IM Image")},
 		{PIDGIN_STOCK_TOOLBAR_SMILEY, insert_smiley_cb, &toolbar->smiley, _("Insert Smiley")},
+		{PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION, send_attention_cb, &toolbar->attention,
+			_("Send Attention")},
 		{NULL, NULL, NULL, NULL}
 	};
 	int iter;
@@ -1297,6 +1312,7 @@
 	GtkWidget *insert_button;
 	GtkWidget *font_button;
 	GtkWidget *smiley_button;
+	GtkWidget *attention_button;
 	GtkWidget *font_menu;
 	GtkWidget *insert_menu;
 	GtkWidget *menuitem;
@@ -1438,6 +1454,32 @@
 	g_signal_connect_swapped(G_OBJECT(smiley_button), "clicked", G_CALLBACK(gtk_button_clicked), toolbar->smiley);
 	gtk_widget_show_all(smiley_button);
 
+	/* Sep */
+	sep = gtk_vseparator_new();
+	gtk_box_pack_start(GTK_BOX(box), sep, FALSE, FALSE, 0);
+	gtk_widget_show_all(sep);
+
+	/* Attention */
+	attention_button = gtk_button_new();
+	gtk_button_set_relief(GTK_BUTTON(attention_button), GTK_RELIEF_NONE);
+	bbox = gtk_hbox_new(FALSE, 3);
+	gtk_container_add(GTK_CONTAINER(attention_button), bbox);
+	image = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION, 
+		gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
+	gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
+	label = gtk_label_new_with_mnemonic(_("_Attention!"));
+	gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(box), attention_button, FALSE, FALSE, 0);
+	g_signal_connect_swapped(G_OBJECT(attention_button), "clicked", 
+		G_CALLBACK(gtk_button_clicked), toolbar->attention);
+	gtk_widget_show_all(attention_button);
+	
+	g_signal_connect(G_OBJECT(toolbar->attention), "notify::sensitive",
+			G_CALLBACK(button_sensitiveness_changed), attention_button);
+
+	/* set attention button to be greyed out until we get a conversation */
+	gtk_widget_set_sensitive(toolbar->attention, FALSE);
+	
 	gtk_box_pack_start(GTK_BOX(hbox), box, FALSE, FALSE, 0);
 	g_object_set_data(G_OBJECT(hbox), "lean-view", box);
 	gtk_widget_show(box);
@@ -1519,3 +1561,21 @@
 	g_free(toolbar->sml);
 	toolbar->sml = g_strdup(proto_id);
 }
+
+void gtk_imhtmltoolbar_switch_active_conversation(GtkIMHtmlToolbar *toolbar,
+	PurpleConversation *conv)
+{
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+	PurplePlugin *prpl = purple_connection_get_prpl(gc);
+	
+	purple_debug_info("gtkimhtmltoolbar", "switch active conversation to %p\n",
+		conv);
+	toolbar->active_conv = conv;
+	
+	/* gray out attention button on protocols that don't support it
+	 for the time being it is always disabled for chats */
+	gtk_widget_set_sensitive(toolbar->attention,
+		conv && prpl && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM && 
+		PURPLE_PLUGIN_PROTOCOL_INFO(prpl)->send_attention != NULL);
+}
+
--- a/pidgin/gtkimhtmltoolbar.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/gtkimhtmltoolbar.h	Tue Jan 13 18:39:52 2009 +0000
@@ -65,7 +65,8 @@
 	GtkWidget *image;
 	GtkWidget *link;
 	GtkWidget *smiley;
-
+	GtkWidget *attention;
+	
 	GtkWidget *font_dialog;
 	GtkWidget *fgcolor_dialog;
 	GtkWidget *bgcolor_dialog;
@@ -76,6 +77,8 @@
 	char *sml;
 	GtkWidget *strikethrough;
 	GtkWidget *insert_hr;
+	
+	PurpleConversation *active_conv;
 };
 
 struct _GtkIMHtmlToolbarClass {
@@ -89,6 +92,8 @@
 void gtk_imhtmltoolbar_attach    (GtkIMHtmlToolbar *toolbar, GtkWidget *imhtml);
 void gtk_imhtmltoolbar_associate_smileys (GtkIMHtmlToolbar *toolbar, const char *proto_id);
 
+void gtk_imhtmltoolbar_switch_active_conversation(GtkIMHtmlToolbar *toolbar,
+	PurpleConversation *conv);
 
 #ifdef __cplusplus
 }
--- a/pidgin/gtksound.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/gtksound.c	Tue Jan 13 18:39:52 2009 +0000
@@ -70,7 +70,9 @@
 	{N_("Others talk in chat"), "chat_msg_recv", "receive.wav"},
 	/* this isn't a terminator, it's the buddy pounce default sound event ;-) */
 	{NULL, "pounce_default", "alert.wav"},
-	{N_("Someone says your username in chat"), "nick_said", "alert.wav"}
+	{N_("Someone says your username in chat"), "nick_said", "alert.wav"},
+	{N_("Attention received"), "got_attention", "alert.wav"},
+	{N_("Attention sent"), "sent_attention", "alert.wav"}
 };
 
 static gboolean
@@ -215,6 +217,20 @@
 		play_conv_event(conv, event);
 }
 
+static void
+sent_attention_cb(PurpleAccount *account, const char *who, 
+	PurpleConversation *conv, guint type, PurpleSoundEventID event)
+{
+	play_conv_event(conv, event);
+}
+
+static void
+got_attention_cb(PurpleAccount *account, const char *who, 
+	PurpleConversation *conv, guint type, PurpleSoundEventID event)
+{
+	play_conv_event(conv, event);
+}
+
 /*
  * We mute sounds for the 10 seconds after you log in so that
  * you don't get flooded with sounds when the blist shows all
@@ -294,6 +310,10 @@
 	purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/nick_said", "");
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/pounce_default", TRUE);
 	purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/pounce_default", "");
+	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/sent_attention", TRUE);
+	purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/sent_attention", "");
+	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/enabled/got_attention", TRUE);
+	purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/file/got_attention", "");
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/conv_focus", TRUE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/sound/mute", FALSE);
 	purple_prefs_add_path(PIDGIN_PREFS_ROOT "/sound/command", "");
@@ -340,6 +360,12 @@
 	purple_signal_connect(conv_handle, "received-chat-msg",
 						gtk_sound_handle, PURPLE_CALLBACK(chat_msg_received_cb),
 						GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY));
+	purple_signal_connect(conv_handle, "sent-attention", gtk_sound_handle,
+						PURPLE_CALLBACK(sent_attention_cb),
+						  GINT_TO_POINTER(PURPLE_SOUND_SEND_ATTENTION));
+	purple_signal_connect(conv_handle, "got-attention", gtk_sound_handle,
+						PURPLE_CALLBACK(got_attention_cb),
+						  GINT_TO_POINTER(PURPLE_SOUND_GOT_ATTENTION));
 }
 
 static void
--- a/pidgin/pidginstock.c	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/pidginstock.c	Tue Jan 13 18:39:52 2009 +0000
@@ -192,7 +192,8 @@
 	{ PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR, "toolbar", "select-avatar.png", FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_SEND_FILE, "toolbar", "send-file.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TOOLBAR_TRANSFER, "toolbar", "transfer.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
-
+	{ PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION, "toolbar", "get-attention.png", FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL  },
+	
 	{ PIDGIN_STOCK_TRAY_AVAILABLE, "tray", "tray-online.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TRAY_INVISIBLE, "tray", "tray-invisible.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
 	{ PIDGIN_STOCK_TRAY_AWAY, "tray", "tray-away.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL  },
--- a/pidgin/pidginstock.h	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/pidginstock.h	Tue Jan 13 18:39:52 2009 +0000
@@ -153,6 +153,7 @@
 #define PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR "pidgin-select-avatar"
 #define PIDGIN_STOCK_TOOLBAR_SEND_FILE    "pidgin-send-file"
 #define PIDGIN_STOCK_TOOLBAR_TRANSFER     "pidgin-transfer"
+#define PIDGIN_STOCK_TOOLBAR_SEND_ATTENTION	"pidgin-send-attention"
 
 /* Tray icons */
 #define PIDGIN_STOCK_TRAY_AVAILABLE       "pidgin-tray-available"
--- a/pidgin/pixmaps/Makefile.am	Tue Jan 13 02:02:14 2009 +0000
+++ b/pidgin/pixmaps/Makefile.am	Tue Jan 13 18:39:52 2009 +0000
@@ -442,7 +442,8 @@
 		toolbar/16/plugins.png \
 		toolbar/16/send-file.png \
 		toolbar/16/transfer.png \
-		toolbar/16/unblock.png
+		toolbar/16/unblock.png \
+		toolbar/16/get-attention.png
 
 TOOLBAR_22_SCALABLE = \
 		toolbar/22/scalable/select-avatar.svg