=== modified file 'configure.ac'
--- old/configure.ac	2009-01-31 14:57:15 +0000
+++ new/configure.ac	2009-09-11 20:19:47 +0000
@@ -74,6 +74,15 @@
 AC_SUBST(LIBNOTIFY_LIBS)
 
 #
+# Check for libnotify
+#
+
+PKG_CHECK_MODULES(LIBINDICATE, indicate >= 0.2.0 indicate-gtk >= 0.2.0)
+
+AC_SUBST(LIBINDICATE_CFLAGS)
+AC_SUBST(LIBINDICATE_LIBS)
+
+#
 # Check for GTK+
 #
 PKG_CHECK_MODULES(GTK, gtk+-2.0)

=== modified file 'src/Makefile.am'
--- old/src/Makefile.am	2008-10-14 11:11:34 +0000
+++ new/src/Makefile.am	2009-02-18 22:25:48 +0000
@@ -10,7 +10,7 @@
 	pidgin-libnotify.c \
 	gln_intl.h
 
-pidgin_libnotify_la_LIBADD = $(LIBNOTIFY_LIBS) $(DBUS_LIBS) $(GTK_LIBS)
+pidgin_libnotify_la_LIBADD = $(LIBINDICATE_LIBS) $(LIBNOTIFY_LIBS) $(DBUS_LIBS) $(GTK_LIBS)
 
 endif
 
@@ -24,6 +24,7 @@
 	$(PIDGIN_CFLAGS) \
 	$(LIBPURPLE_CFLAGS) \
 	$(LIBNOTIFY_CFLAGS) \
+	$(LIBINDICATE_CFLAGS) \
 	$(DBUS_CFLAGS) \
 	$(GTK_CFLAGS)
 

=== modified file 'src/pidgin-libnotify.c'
--- old/src/pidgin-libnotify.c	2009-01-31 14:57:15 +0000
+++ new/src/pidgin-libnotify.c	2010-03-08 16:50:46 +0000
@@ -35,15 +35,53 @@
 
 /* for pidgin_create_prpl_icon */
 #include <gtkutils.h>
+#include <gtkblist.h>
 
 #include <libnotify/notify.h>
+#include <libindicate/indicator.h>
+#include <libindicate-gtk/indicator.h>
+#include <libindicate/indicator-messages.h>
+#include <libindicate/server.h>
 
 #include <string.h>
 
 #define PLUGIN_ID "pidgin-libnotify"
 
+#ifdef G_LOG_DOMAIN
+#undef G_LOG_DOMAIN
+#endif
+#define G_LOG_DOMAIN "pidgin-libnotify-plugin"
+
+#define PIDGIN_DESKTOP_FILE  "/usr/share/applications/pidgin.desktop"
+#define BLACKLIST_FILENAME   "pidgin-libnotify"
+#define BLACKLIST_DIR        "indicators/messages/applications-blacklist"
+
+/* Prototypes */
+static void indicate_chat_nick (PurpleAccount *account, const gchar *sender, const gchar *message, PurpleConversation *conv, gpointer data);
+static void notify_new_message_cb (PurpleAccount *account, const gchar *sender, const gchar *message, int flags, gpointer data);
+
+/* Structs */
+typedef struct _indicate_chat_nick_idle_t indicate_chat_nick_idle_t;
+struct _indicate_chat_nick_idle_t {
+	PurpleAccount * account;
+	gchar * sender;
+	gchar * message;
+};
+
+/* Globals */
 static GHashTable *buddy_hash;
 
+static gboolean  notify_supports_actions = FALSE;
+static gboolean  notify_supports_append = FALSE;
+static gboolean  notify_supports_truncation = FALSE;
+
+static gboolean  visibility_managed = FALSE;
+
+static IndicateServer * indicate_server = NULL;
+static guint never_loaded = 0;
+
+static void conv_delete_cb (PurpleConversation * conv, void * data);
+
 static PurplePluginPrefFrame *
 get_plugin_pref_frame (PurplePlugin *plugin)
 {
@@ -82,6 +120,11 @@
                             _("Only when available"));
 	purple_plugin_pref_frame_add (frame, ppref);
 
+	ppref = purple_plugin_pref_new_with_name_and_label (
+                            "/plugins/gtk/libnotify/blocked_nicks",
+                            _("Names to remove notifications for"));
+	purple_plugin_pref_frame_add (frame, ppref);
+
 	return frame;
 }
 
@@ -127,7 +170,9 @@
 		return;
 
 	just_signed_on_accounts = g_list_prepend (just_signed_on_accounts, account);
-	g_timeout_add (5000, event_connection_throttle_cb, (gpointer)account);
+	g_timeout_add_seconds (15, event_connection_throttle_cb, (gpointer)account);
+
+	return;
 }
 
 /* do NOT g_free() the string returned by this function */
@@ -154,7 +199,6 @@
 	data = purple_buddy_icon_get_data (buddy_icon, &len);
 
 	loader = gdk_pixbuf_loader_new ();
-	gdk_pixbuf_loader_set_size (loader, 48, 48);
 	gdk_pixbuf_loader_write (loader, data, len, NULL);
 	gdk_pixbuf_loader_close (loader, NULL);
 
@@ -169,6 +213,97 @@
 	return icon;
 }
 
+/*
+ * This takes a pixbuf that we want to send to the notify server, and
+ * transforms it to the desired dimensions suitable for a notification.
+ * We scale the pixbuf down to size * size (square), but preserve the
+ * original aspect ratio and fill in the edges with transparent pixels
+ * if the original pixbuf was not square. 
+ */
+static GdkPixbuf *
+normalize_icon (GdkPixbuf *icon, gint size)
+{
+  gint w, h;
+  int dest_x, dest_y;
+  gdouble max_edge;
+  gint new_width, new_height;
+  GdkPixbuf *scaled_icon;
+  GdkPixbuf *new_icon;
+
+  w = gdk_pixbuf_get_width (icon);
+  h = gdk_pixbuf_get_height (icon);
+
+  dest_x = dest_y = 0;
+
+  max_edge = MAX (w, h);
+
+  new_width = size * (w / max_edge);
+  new_height = size * (h / max_edge);
+
+  /* Scale the image down, preserving the aspect ratio */
+  scaled_icon = gdk_pixbuf_scale_simple (icon,
+					 new_width,
+					 new_height,
+					 GDK_INTERP_HYPER);
+
+  g_object_unref (icon);
+
+  /* Create a square pixbuf with an alpha channel, dimensions size * size */
+  new_icon = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (scaled_icon),
+			     TRUE,
+			     gdk_pixbuf_get_bits_per_sample (scaled_icon),
+			     size, size);
+
+  /* Clear the pixbuf so it is transparent */
+  gdk_pixbuf_fill (new_icon, 0x00000000);
+
+  /* Center the aspect ratio preseved pixbuf in the square pixbuf */
+  if (new_width > new_height) {
+    dest_y = (new_width - new_height) / 2;
+  } else if (new_height > new_width) {
+    dest_x = (new_height - new_width) / 2;
+  }
+
+  /* Copy from the aspect ratio-preserved scaled pixbuf into the
+   * new pixbuf, at a centered position. */
+  gdk_pixbuf_copy_area (scaled_icon,
+			0, 0,
+			gdk_pixbuf_get_width (scaled_icon),
+			gdk_pixbuf_get_height (scaled_icon),
+			new_icon,
+			dest_x, dest_y);
+
+  g_object_unref (scaled_icon);
+
+  return new_icon;
+}
+
+/* Check the name against the static list of black listed
+   names that we're looking out for.  These shouldn't result
+   in either notifications or indicators. */
+static gboolean
+name_blacklisted (PurpleAccount * account, const gchar * name)
+{
+	if (account != NULL) {
+		const gchar * username = purple_account_get_username(account);
+		gchar ** userparts = g_strsplit(username, "@", 2);
+		if (g_strcmp0(name, userparts[1]) == 0) {
+			g_strfreev(userparts);
+			return TRUE;
+		}
+		g_strfreev(userparts);
+	}
+
+	GList * blacklist = purple_prefs_get_string_list("/plugins/gtk/libnotify/blocked_nicks");
+	GList * pnt;
+	for (pnt = blacklist; pnt != NULL; pnt = g_list_next(pnt)) {
+		if (g_strcmp0(name, (gchar *)pnt->data) == 0) {
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
 static void
 action_cb (NotifyNotification *notification,
 		   gchar *action, gpointer user_data)
@@ -177,9 +312,9 @@
 	PurpleConversation *conv = NULL;
 
 	purple_debug_info (PLUGIN_ID, "action_cb(), "
-					"notification: 0x%x, action: '%s'", notification, action);
+					"notification: 0x%x, action: '%s'", GPOINTER_TO_UINT(notification), action);
 
-	buddy = (PurpleBuddy *)g_object_get_data (G_OBJECT(notification), "buddy");
+	buddy = (PurpleBuddy *)user_data;
 
 	if (!buddy) {
 		purple_debug_warning (PLUGIN_ID, "Got no buddy!");
@@ -199,13 +334,25 @@
 }
 
 static gboolean
-closed_cb (NotifyNotification *notification)
-{
-	PurpleContact *contact;
-
-	purple_debug_info (PLUGIN_ID, "closed_cb(), notification: 0x%x\n", notification);
-
-	contact = (PurpleContact *)g_object_get_data (G_OBJECT(notification), "contact");
+notification_list_closed_cb (NotifyNotification *notification, PurpleConversation * conv)
+{
+	purple_debug_info (PLUGIN_ID, "closed_cb(), notification: 0x%x\n", GPOINTER_TO_UINT(notification));
+
+	if (conv != NULL) {
+		GList * notifylist = purple_conversation_get_data(conv, "notification-list");
+		notifylist = g_list_remove(notifylist, notification);
+		purple_conversation_set_data(conv, "notification-list", notifylist);
+	}
+	g_object_unref(notification);
+
+	return FALSE;
+}
+
+static gboolean
+closed_cb (NotifyNotification *notification, PurpleContact * contact)
+{
+	purple_debug_info (PLUGIN_ID, "closed_cb(), notification: 0x%x\n", GPOINTER_TO_UINT(notification));
+
 	if (contact)
 		g_hash_table_remove (buddy_hash, contact);
 
@@ -218,11 +365,12 @@
  * num_chars is utf-8 characters */
 static gchar *
 truncate_escape_string (const gchar *str,
-						int num_chars)
+			int num_chars,
+			gboolean escape)
 {
 	gchar *escaped_str;
 
-	if (g_utf8_strlen (str, num_chars*2+1) > num_chars) {
+	if (!notify_supports_truncation && g_utf8_strlen (str, num_chars*2+1) > num_chars) {
 		gchar *truncated_str;
 		gchar *str2;
 
@@ -231,11 +379,11 @@
 
 		g_utf8_strncpy (str2, str, num_chars-2);
 		truncated_str = g_strdup_printf ("%s..", str2);
-		escaped_str = g_markup_escape_text (truncated_str, strlen (truncated_str));
+		escaped_str = escape ? g_markup_escape_text (truncated_str, strlen (truncated_str)) : g_strdup (truncated_str);
 		g_free (str2);
 		g_free (truncated_str);
 	} else {
-		escaped_str = g_markup_escape_text (str, strlen (str));
+		escaped_str = escape ? g_markup_escape_text (str, strlen (str)) : g_strdup (str);
 	}
 
 	return escaped_str;
@@ -257,67 +405,105 @@
 static void
 notify (const gchar *title,
 		const gchar *body,
-		PurpleBuddy *buddy)
+		PurpleBuddy *buddy,
+		PurpleConversation *conv)
 {
 	NotifyNotification *notification = NULL;
-	GdkPixbuf *icon;
-	PurpleBuddyIcon *buddy_icon;
-	gchar *tr_body;
-	PurpleContact *contact;
+	GdkPixbuf *icon = NULL;
+	PurpleBuddyIcon *buddy_icon = NULL;
+	gchar *tr_body = NULL;
+	PurpleContact *contact = NULL;
 
-	contact = purple_buddy_get_contact (buddy);
+	if (buddy != NULL) {
+		contact = purple_buddy_get_contact (buddy);
+	}
 
 	if (body)
-		tr_body = truncate_escape_string (body, 60);
+		tr_body = truncate_escape_string (body, 60, TRUE);
 	else
 		tr_body = NULL;
 
-	notification = g_hash_table_lookup (buddy_hash, contact);
+	/* If we're appending we shouldn't update an already
+	   existing notification */
+	if (conv == NULL && contact != NULL) {
+		notification = g_hash_table_lookup (buddy_hash, contact);
+	}
 
+	/* This will only happen if we're a login message */
 	if (notification != NULL) {
 		notify_notification_update (notification, title, tr_body, NULL);
+
 		/* this shouldn't be necessary, file a bug */
 		notify_notification_show (notification, NULL);
 
 		purple_debug_info (PLUGIN_ID, "notify(), update: "
 						 "title: '%s', body: '%s', buddy: '%s'\n",
-						 title, tr_body, best_name (buddy));
+						 title, tr_body, buddy != NULL ? best_name (buddy) : "(null)");
 
 		g_free (tr_body);
 		return;
 	}
-	notification = notify_notification_new (title, tr_body, NULL, NULL);
+
+	notification = notify_notification_new (title, tr_body, "notification-message-im", NULL);
 	purple_debug_info (PLUGIN_ID, "notify(), new: "
 					 "title: '%s', body: '%s', buddy: '%s'\n",
-					 title, tr_body, best_name (buddy));
+					 title, tr_body, buddy != NULL ? best_name (buddy) : "(null)");
 
 	g_free (tr_body);
 
-	buddy_icon = purple_buddy_get_icon (buddy);
-	if (buddy_icon) {
+	if (notify_supports_append) {
+		if (conv != NULL) {
+			notify_notification_set_hint_string(notification, "x-canonical-append", "allow");
+		}
+	}
+
+	if (buddy != NULL) {
+		buddy_icon = purple_buddy_get_icon (buddy);
+	}
+
+	if (buddy_icon != NULL) {
 		icon = pixbuf_from_buddy_icon (buddy_icon);
 		purple_debug_info (PLUGIN_ID, "notify(), has a buddy icon.\n");
 	} else {
-		icon = pidgin_create_prpl_icon (buddy->account, 1);
-		purple_debug_info (PLUGIN_ID, "notify(), has a prpl icon.\n");
+		if (buddy != NULL) {
+			icon = pidgin_create_prpl_icon (buddy->account, PIDGIN_PRPL_ICON_LARGE);
+			purple_debug_info (PLUGIN_ID, "notify(), has a prpl icon.\n");
+		}
 	}
 
-	if (icon) {
+	icon = normalize_icon (icon, 48);
+
+	if (icon != NULL) {
 		notify_notification_set_icon_from_pixbuf (notification, icon);
 		g_object_unref (icon);
-	} else {
-		purple_debug_warning (PLUGIN_ID, "notify(), couldn't find any icon!\n");
-	}
-
-	g_hash_table_insert (buddy_hash, contact, notification);
-
-	g_object_set_data (G_OBJECT(notification), "contact", contact);
-
-	g_signal_connect (notification, "closed", G_CALLBACK(closed_cb), NULL);
+
+		GValue iconname = {0};
+		g_value_init(&iconname, G_TYPE_STRING);
+		g_value_set_static_string(&iconname, "");
+		g_object_set_property(G_OBJECT(notification), "icon-name", &iconname);
+	}
+
+	if (contact != NULL && conv == NULL) {
+		g_hash_table_insert (buddy_hash, contact, notification);
+
+		g_signal_connect (notification, "closed", G_CALLBACK(closed_cb), contact);
+	}
+	if (conv != NULL) {
+		GList * notifylist = purple_conversation_get_data(conv, "notification-list");
+		notifylist = g_list_append(notifylist, notification);
+		purple_conversation_set_data(conv, "notification-list", notifylist);
+		g_signal_connect(notification, "closed", G_CALLBACK(notification_list_closed_cb), conv);
+	}
+	if (contact == NULL && conv == NULL) {
+		/* Should never happen, but just in case, let's not have a memory leak */
+		g_signal_connect(notification, "closed", G_CALLBACK(g_object_unref), NULL);
+	}
 
 	notify_notification_set_urgency (notification, NOTIFY_URGENCY_NORMAL);
 
-	notify_notification_add_action (notification, "show", _("Show"), action_cb, NULL, NULL);
+	if (notify_supports_actions) {
+		notify_notification_add_action (notification, "show", _("Show"), action_cb, buddy, NULL);
+	}
 
 	if (!notify_notification_show (notification, NULL)) {
 		purple_debug_error (PLUGIN_ID, "notify(), failed to send notification\n");
@@ -329,7 +515,7 @@
 notify_buddy_signon_cb (PurpleBuddy *buddy,
 						gpointer data)
 {
-	gchar *tr_name, *title;
+	gchar *tr_name;
 	gboolean blocked;
 
 	g_return_if_fail (buddy);
@@ -347,21 +533,18 @@
 	if (!should_notify_unavailable (purple_buddy_get_account (buddy)))
 		return;
 
-	tr_name = truncate_escape_string (best_name (buddy), 25);
-
-	title = g_strdup_printf (_("%s signed on"), tr_name);
-
-	notify (title, NULL, buddy);
+	tr_name = truncate_escape_string (best_name (buddy), 25, FALSE);
+
+	notify (tr_name, _("is online"), buddy, NULL);
 
 	g_free (tr_name);
-	g_free (title);
 }
 
 static void
 notify_buddy_signoff_cb (PurpleBuddy *buddy,
 						 gpointer data)
 {
-	gchar *tr_name, *title;
+	gchar *tr_name;
 	gboolean blocked;
 
 	g_return_if_fail (buddy);
@@ -379,45 +562,78 @@
 	if (!should_notify_unavailable (purple_buddy_get_account (buddy)))
 		return;
 
-	tr_name = truncate_escape_string (best_name (buddy), 25);
-
-	title = g_strdup_printf (_("%s signed off"), tr_name);
-
-	notify (title, NULL, buddy);
+	tr_name = truncate_escape_string (best_name (buddy), 25, FALSE);
+
+	notify (tr_name, _("is offline"), buddy, NULL);
 
 	g_free (tr_name);
-	g_free (title);
 }
 
 static void
 notify_msg_sent (PurpleAccount *account,
 				 const gchar *sender,
-				 const gchar *message)
+				 const gchar *message,
+				 PurpleConversation * conv)
 {
-	PurpleBuddy *buddy;
-	gchar *title, *body, *tr_name;
+	PurpleBuddy *buddy = NULL;
+	gchar *body = NULL, *tr_name = NULL;
 	gboolean blocked;
 
+	blocked = purple_prefs_get_bool ("/plugins/gtk/libnotify/blocked");
+	if (!purple_privacy_check(account, sender) && blocked)
+		return;
+
+	if (g_list_find (just_signed_on_accounts, account))
+		return;
+
 	buddy = purple_find_buddy (account, sender);
-	if (!buddy)
-		return;
-
-	blocked = purple_prefs_get_bool ("/plugins/gtk/libnotify/blocked");
-	if (!purple_privacy_check(account, sender) && blocked)
-		return;
-
-	tr_name = truncate_escape_string (best_name (buddy), 25);
-
-	title = g_strdup_printf (_("%s says:"), tr_name);
+
+	if (buddy != NULL) {
+		tr_name = truncate_escape_string (best_name (buddy), 25, FALSE);
+	} else {
+		if (conv != NULL) {
+			const gchar * temp = purple_conversation_get_title(conv);
+			if (temp != NULL) {
+				if (sender == NULL || !g_strcmp0(sender, temp)) {
+					tr_name = g_strdup(temp);
+				} else {
+					tr_name = g_strdup_printf("%s (%s)", sender, temp);
+				}
+			} else {
+				if (sender != NULL) {
+					tr_name = g_strdup(sender);
+				}
+			}
+		}
+	}
+
+	if (tr_name == NULL) {
+		purple_debug_warning(PLUGIN_ID, "Unable to find a title for the notification");
+		return;
+	}
+
 	body = purple_markup_strip_html (message);
 
-	notify (title, body, buddy);
+	notify (tr_name, body, buddy, conv);
 
 	g_free (tr_name);
-	g_free (title);
 	g_free (body);
 }
 
+static gboolean
+notify_new_message_idle (gpointer data)
+{
+	indicate_chat_nick_idle_t * idle_data = (indicate_chat_nick_idle_t *)data;
+
+	notify_new_message_cb(idle_data->account, idle_data->sender, idle_data->message, 0, NULL);
+
+	g_free(idle_data->sender);
+	g_free(idle_data->message);
+	g_free(idle_data);
+
+	return FALSE;
+}
+
 static void
 notify_new_message_cb (PurpleAccount *account,
 					   const gchar *sender,
@@ -430,24 +646,44 @@
 	if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/newmsg"))
 		return;
 
+	if (name_blacklisted(account, sender)) return;
+
 	conv = purple_find_conversation_with_account (PURPLE_CONV_TYPE_IM, sender, account);
 
 #ifndef DEBUG /* in debug mode, always show notifications */
 	if (conv && purple_conversation_has_focus (conv)) {
-		purple_debug_info (PLUGIN_ID, "Conversation has focus 0x%x\n", conv);
+		purple_debug_info (PLUGIN_ID, "Conversation has focus 0x%x\n", GPOINTER_TO_UINT(conv));
 		return;
 	}
 #endif
 
 	if (conv && purple_prefs_get_bool ("/plugins/gtk/libnotify/newconvonly")) {
-		purple_debug_info (PLUGIN_ID, "Conversation is not new 0x%x\n", conv);
+		purple_debug_info (PLUGIN_ID, "Conversation is not new 0x%x\n", GPOINTER_TO_UINT(conv));
+		return;
+	}
+
+	if (conv == NULL) {
+		purple_debug_warning(PLUGIN_ID, "Notify Message send has NULL Conversation, going idle");
+		indicate_chat_nick_idle_t * idle_data = g_new0(indicate_chat_nick_idle_t, 1);
+		idle_data->account = account;
+		idle_data->sender = g_strdup(sender);
+		idle_data->message = g_strdup(message);
+		g_idle_add(notify_new_message_idle, idle_data);
 		return;
 	}
 
 	if (!should_notify_unavailable (account))
 		return;
 
-	notify_msg_sent (account, sender, message);
+	PidginConversation * pconv = PIDGIN_CONVERSATION(conv);
+	if (pconv != NULL) {
+	if (pconv->entry != NULL && pconv->imhtml != NULL) {
+	if (GTK_WIDGET_HAS_FOCUS(pconv->entry) || GTK_WIDGET_HAS_FOCUS(pconv->imhtml)) {
+		purple_debug_warning(PLUGIN_ID, "Pidgin conversation's widgets are in focus");
+		return;
+	}}}
+
+	notify_msg_sent (account, sender, message, conv);
 }
 
 static void
@@ -463,10 +699,490 @@
 	if (nick && !strcmp (sender, nick))
 		return;
 
-	if (!g_strstr_len (message, strlen(message), nick))
-		return;
-
-	notify_msg_sent (account, sender, message);
+	if (!purple_utf8_has_word (message, nick))
+		return;
+
+	PidginConversation * pconv = PIDGIN_CONVERSATION(conv);
+	if (pconv != NULL) {
+	if (pconv->entry != NULL && pconv->imhtml != NULL) {
+	if (GTK_WIDGET_HAS_FOCUS(pconv->entry) || GTK_WIDGET_HAS_FOCUS(pconv->imhtml)) {
+		purple_debug_warning(PLUGIN_ID, "Pidgin conversation's widgets are in focus");
+		return;
+	}}}
+
+	if (name_blacklisted(account, sender)) return;
+
+	notify_msg_sent (account, sender, message, conv);
+}
+
+static gboolean
+indicate_focus_cb (GtkWidget * widget, GdkEventFocus * event, gpointer data)
+{
+	conv_delete_cb((PurpleConversation *)data, NULL);
+
+	/* Don't swallow event */
+	return FALSE;
+}
+
+static void
+indicate_show_cb (IndicateIndicator * indicator, guint timestamp, PurpleConversation * conv)
+{
+	/* g_debug ("indicate_show_cb()"); */
+
+	if (conv == NULL) {
+		purple_debug_warning(PLUGIN_ID, "\tNULL conversation");
+	}
+
+	PidginConversation * gtkconv = PIDGIN_CONVERSATION(conv);
+	if (gtkconv == NULL) {
+		purple_debug_warning(PLUGIN_ID, "\tNULL Pidgin Conversation");
+	}
+
+	pidgin_conv_switch_active_conversation(conv);
+	pidgin_conv_window_switch_gtkconv(gtkconv->win, gtkconv);
+	gtk_window_present_with_time(GTK_WINDOW(gtkconv->win->window), timestamp);
+
+	return;
+}
+
+static void
+conv_delete_cb (PurpleConversation * conv, void * data)
+{
+	/* g_debug("Pidgin conv delete: %s", purple_conversation_get_name(conv)); */
+	IndicateIndicator * indicator = INDICATE_INDICATOR(purple_conversation_get_data(conv, "indicate-indicator"));
+	if (indicator != NULL) {
+		indicate_indicator_hide(indicator);
+		g_object_unref(G_OBJECT(indicator));
+		purple_conversation_set_data(conv, "indicate-indicator", NULL);
+	}
+
+	GList * notifylist = purple_conversation_get_data(conv, "notification-list");
+	if (notifylist != NULL) {
+		GList * i;
+		for (i = notifylist; i != NULL; i = i->next) {
+			NotifyNotification * notification = NOTIFY_NOTIFICATION(i->data);
+			if (notification == NULL) break;
+
+			g_signal_handlers_disconnect_by_func(G_OBJECT(notification), notification_list_closed_cb, conv);
+			notify_notification_close(notification, NULL); /* Don't care if it fails, it's going to die. */
+			g_object_unref(G_OBJECT(notification));
+		}
+		g_list_free(notifylist);
+
+		purple_conversation_set_data(conv, "notification-list", NULL);
+	}
+
+	PidginConversation * pconv = PIDGIN_CONVERSATION(conv);
+	if (pconv != NULL) {
+		g_signal_handlers_disconnect_by_func(G_OBJECT(pconv->entry),  G_CALLBACK(indicate_focus_cb), conv);
+		g_signal_handlers_disconnect_by_func(G_OBJECT(pconv->imhtml), G_CALLBACK(indicate_focus_cb), conv);
+	}
+
+	return;
+}
+
+static gboolean
+indicate_chat_nick_idle (gpointer data)
+{
+	indicate_chat_nick_idle_t * idle_data = (indicate_chat_nick_idle_t *)data;
+
+	PurpleConversation * conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, idle_data->sender, idle_data->account);
+	if (conv != NULL) {
+		indicate_chat_nick(idle_data->account, idle_data->sender, idle_data->message, conv, NULL);
+	}
+
+	g_free(idle_data->sender);
+	g_free(idle_data->message);
+	g_free(idle_data);
+
+	return FALSE;
+}
+
+static void
+indicate_chat_nick (PurpleAccount *account,
+				    const gchar *sender,
+				    const gchar *message,
+				    PurpleConversation *conv,
+				    gpointer data)
+{
+	PurpleBuddy *buddy = NULL;
+	GdkPixbuf *icon = NULL;
+	gchar *tr_name = NULL;
+	PurpleBuddyIcon * buddy_icon = NULL;
+
+	/* g_debug("Entering indicate_chat_nick"); */
+	if (name_blacklisted(account, sender)) return;
+
+	if (conv == NULL) {
+		purple_debug_warning(PLUGIN_ID, "Conversation is NULL, setting up to check in idle");
+		indicate_chat_nick_idle_t * idle_data = g_new0(indicate_chat_nick_idle_t, 1);
+		idle_data->account = account;
+		idle_data->sender = g_strdup(sender);
+		idle_data->message = g_strdup(message);
+		g_idle_add(indicate_chat_nick_idle, idle_data);
+		return;
+	}
+
+	if (g_list_find (just_signed_on_accounts, account))
+		return;
+
+	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
+		gchar * nick = (gchar *)purple_conv_chat_get_nick (PURPLE_CONV_CHAT(conv));
+		if (nick && !strcmp (sender, nick))
+			return;
+
+		if (!purple_utf8_has_word (message, nick))
+			return;
+	}
+
+	IndicateIndicator * indicator = purple_conversation_get_data(conv, "indicate-indicator");
+	if (indicator != NULL) {
+		/* We've already indicated this one, let's set the time */
+		/* g_debug("Updating indicator time"); */
+		GTimeVal time; g_get_current_time(&time);
+		indicate_indicator_set_property_time(INDICATE_INDICATOR(indicator), "time", &time);
+		return;
+	}
+
+	PidginConversation * pconv = PIDGIN_CONVERSATION(conv);
+	if (pconv != NULL) {
+	if (pconv->entry != NULL && pconv->imhtml != NULL) {
+	if (GTK_WIDGET_HAS_FOCUS(pconv->entry) || GTK_WIDGET_HAS_FOCUS(pconv->imhtml)) {
+		purple_debug_warning(PLUGIN_ID, "Pidgin conversation's widgets are in focus");
+		return;
+	}}}
+
+	if (account != NULL && sender != NULL) {
+		buddy = purple_find_buddy (account, sender);
+	} else {
+		purple_debug_warning(PLUGIN_ID, "We can't create an indicator for an account or a sender that don't exist!");
+	}
+
+	if (buddy == NULL) {
+		purple_debug_warning(PLUGIN_ID, "Unable to find buddy.");
+	} else {
+		buddy_icon = purple_buddy_get_icon(buddy);
+	}
+
+	if (buddy_icon != NULL) {
+		icon = pixbuf_from_buddy_icon(buddy_icon);
+	} else {
+		if (buddy != NULL) {
+			icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_LARGE);
+		}
+	}
+
+	if (buddy != NULL) {
+		tr_name = truncate_escape_string (best_name (buddy), 25, FALSE);
+	} else {
+		const gchar * temp = purple_conversation_get_title(conv);
+		if (temp != NULL) {
+			if (sender == NULL || !g_strcmp0(sender, temp)) {
+				tr_name = g_strdup(temp);
+			} else {
+				tr_name = g_strdup_printf("%s (%s)", sender, temp);
+			}
+		} else {
+			if (sender != NULL) {
+				tr_name = g_strdup(sender);
+			}
+		}
+	}
+
+	if (tr_name == NULL) {
+		purple_debug_warning(PLUGIN_ID, "Unable to determine a sender");
+		return;
+	}
+
+	indicator = indicate_indicator_new();
+
+	indicate_indicator_set_property(INDICATE_INDICATOR(indicator), INDICATE_INDICATOR_MESSAGES_PROP_NAME, tr_name);
+	indicate_indicator_set_property(INDICATE_INDICATOR(indicator), INDICATE_INDICATOR_MESSAGES_PROP_ATTENTION, "true");
+	if (icon) {
+		indicate_indicator_set_property_icon(INDICATE_INDICATOR(indicator), INDICATE_INDICATOR_MESSAGES_PROP_ICON, icon);
+		g_object_unref(G_OBJECT(icon));
+	}
+	GTimeVal time; g_get_current_time(&time);
+	indicate_indicator_set_property_time(INDICATE_INDICATOR(indicator), INDICATE_INDICATOR_MESSAGES_PROP_TIME, &time);
+	indicate_indicator_show(INDICATE_INDICATOR(indicator));
+
+	purple_conversation_set_data(conv, "indicate-indicator", indicator);
+
+	g_signal_connect(G_OBJECT(pconv->entry),  "focus-in-event", G_CALLBACK(indicate_focus_cb), conv);
+	g_signal_connect(G_OBJECT(pconv->imhtml), "focus-in-event", G_CALLBACK(indicate_focus_cb), conv);
+
+	g_signal_connect(G_OBJECT(indicator), INDICATE_INDICATOR_SIGNAL_DISPLAY, G_CALLBACK(indicate_show_cb), conv);
+
+	return;
+}
+
+static void
+indicate_new_message_cb (PurpleAccount *account,
+					   const gchar *sender,
+					   const gchar *message,
+					   int flags,
+					   gpointer data)
+{
+	PurpleConversation * conv = purple_find_conversation_with_account (PURPLE_CONV_TYPE_IM, sender, account);
+
+	return indicate_chat_nick(account, sender, message, conv, data);
+}
+
+static void
+indicate_server_show_interest (IndicateServer * server, IndicateInterests interest, gpointer data)
+{
+	if (interest == INDICATE_INTEREST_SERVER_SIGNAL) {
+		if (visibility_managed == FALSE) {
+			pidgin_blist_visibility_manager_add();
+			visibility_managed = TRUE;
+		}
+	}
+
+	return;
+}
+
+static void
+indicate_server_remove_interest (IndicateServer * server, IndicateInterests interest, gpointer data)
+{
+	if (interest == INDICATE_INTEREST_SERVER_SIGNAL) {
+		if (visibility_managed == TRUE) {
+			pidgin_blist_visibility_manager_remove();
+			visibility_managed = FALSE;
+		}
+	}
+
+	return;
+}
+
+static void
+indicate_server_display (IndicateServer * server, guint timestamp, gpointer data)
+{
+	if (visibility_managed == FALSE) {
+		/* If we haven't been told that someone is interested in
+		   sending signals, but we get one.  How rude is that! */
+		g_warning("Got a signal on our server, but no one has told us that they were interested in doing that.  Hmph.");
+		return;
+	}
+
+	purple_blist_set_visible(TRUE);
+
+	PidginBuddyList * gtkblist = pidgin_blist_get_default_gtk_blist();
+	if (gtkblist == NULL) return;
+
+	if (gtkblist->window == NULL) return;
+	gtk_window_present_with_time(GTK_WINDOW(gtkblist->window), timestamp);
+
+	return;
+}
+
+static gboolean
+indicate_login_timeout (gpointer data)
+{
+	IndicateIndicator * indicator = INDICATE_INDICATOR(data);
+
+	indicate_indicator_hide(indicator);
+	g_object_unref(G_OBJECT(indicator));
+
+	return FALSE;
+}
+
+static void
+indicate_login_cb (IndicateIndicator * indicator, guint timestamp, gpointer data)
+{
+	PurpleBuddy * buddy = (PurpleBuddy *)data;
+
+	if (buddy == NULL)
+		return;
+
+	PurpleAccount * account = purple_buddy_get_account(buddy);
+	const char * name = purple_buddy_get_name(buddy);
+
+	PurpleConversation * conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, account);
+	if (conv == NULL) {
+		conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name);
+	}
+
+	indicate_show_cb(NULL, timestamp, conv);
+
+	return;
+}
+
+static void
+indicate_buddy_signon_cb (PurpleBuddy *buddy,
+						gpointer data)
+{
+	GdkPixbuf *icon = NULL;
+	PurpleBuddyIcon * buddy_icon = NULL;
+	gchar *tr_name = NULL;
+	IndicateIndicator * indicator = NULL;
+	gboolean blocked;
+
+	g_return_if_fail (buddy);
+
+	if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/signon"))
+		return;
+
+	if (g_list_find (just_signed_on_accounts, buddy->account))
+		return;
+
+	blocked = purple_prefs_get_bool ("/plugins/gtk/libnotify/blocked");
+	if (!purple_privacy_check (buddy->account, buddy->name) && blocked)
+		return;
+
+	if (!should_notify_unavailable (purple_buddy_get_account (buddy)))
+		return;
+
+	tr_name = truncate_escape_string (best_name (buddy), 25, FALSE);
+
+	buddy_icon = purple_buddy_get_icon(buddy);
+
+	if (buddy_icon != NULL) {
+		icon = pixbuf_from_buddy_icon(buddy_icon);
+	} else {
+		if (buddy != NULL) {
+			icon = pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_LARGE);
+		}
+	}
+
+	indicator = INDICATE_INDICATOR(indicate_indicator_new());
+
+	indicate_indicator_set_property(INDICATE_INDICATOR(indicator), INDICATE_INDICATOR_MESSAGES_PROP_NAME, tr_name);
+	if (icon) {
+		indicate_indicator_set_property_icon(INDICATE_INDICATOR(indicator), INDICATE_INDICATOR_MESSAGES_PROP_ICON, icon);
+		g_object_unref(G_OBJECT(icon));
+	}
+	indicate_indicator_show(INDICATE_INDICATOR(indicator));
+
+	g_timeout_add_seconds(60, indicate_login_timeout, indicator);
+	g_signal_connect(G_OBJECT(indicator), INDICATE_INDICATOR_SIGNAL_DISPLAY, G_CALLBACK(indicate_login_cb), buddy);
+
+	g_free (tr_name);
+}
+
+static void
+remove_from_blacklist (void)
+{
+	gchar *bpath;
+
+	bpath = g_build_filename (g_get_user_config_dir(),
+	                          BLACKLIST_DIR,
+	                          BLACKLIST_FILENAME,
+	                          NULL);
+
+	if (g_file_test (bpath, G_FILE_TEST_EXISTS)) {
+		GFile *bfile;
+		bfile = g_file_new_for_path (bpath);
+
+		if (bfile) {
+			GError *error = NULL;
+			g_file_delete (bfile, NULL, &error);
+
+			if (error) {
+				g_warning ("Unable to remove blacklist file: %s", error->message);
+				g_error_free (error);
+			}
+
+			g_object_unref (bfile);
+		}
+	}
+
+	g_free (bpath);
+
+	return;
+}
+
+static gboolean
+plugin_never_loaded (gpointer data)
+{
+	gchar  *bdir;
+	gchar  *bpath;
+	GError *error = NULL;
+
+	bdir = g_build_filename (g_get_user_config_dir (),
+	                         BLACKLIST_DIR,
+	                         NULL);
+	if (!g_file_test (bdir, G_FILE_TEST_IS_DIR)) {
+		GFile *dirfile;
+
+		dirfile = g_file_new_for_path (bdir);
+		if (dirfile) {
+			g_file_make_directory_with_parents (dirfile,
+			                                    NULL, 
+			                                    &error);
+			if (error) {
+				g_warning ("Unable to create blacklist directory: %s",
+				           error->message);
+				g_error_free (error);
+				g_object_unref (dirfile);
+				g_free (bdir);
+				return FALSE;
+			}
+		} else {
+			g_warning ("Unable to create blacklist directory: Unable to create "
+			           "GFile for path %s", bdir);
+			g_free (bdir);
+			return FALSE;
+		}
+
+		g_object_unref (dirfile);
+	}
+	g_free (bdir);
+
+	bpath = g_build_filename (g_get_user_config_dir (),
+	                          BLACKLIST_DIR,
+	                          BLACKLIST_FILENAME,
+	                          NULL);
+
+	if (g_file_set_contents (bpath,
+	                         PIDGIN_DESKTOP_FILE,
+	                         -1,
+	                         &error)) {
+		g_debug ("Successfully wrote blacklist file to %s", bpath);
+	} else {
+		g_debug ("Unable to write blacklist file to %s: %s",
+		         bpath,
+		         error ? error->message : "Unknown");
+		if (error)
+			g_error_free (error);
+	}
+
+	g_free (bpath);
+
+	return FALSE;
+}
+
+static void
+notify_check_caps_helper (gpointer data, gpointer user_data)
+{
+	gchar * cap = (gchar *)data;
+
+	if (cap == NULL) return;
+
+	if (!strcmp(cap, "actions")) {
+		notify_supports_actions = TRUE;
+	} else if (!strcmp(cap, "append")) {
+		notify_supports_append = TRUE;
+	} else if (!strcmp(cap, "x-canonical-append")) {
+		notify_supports_append = TRUE;
+	} else if (!strcmp(cap, "truncation")) {
+		notify_supports_truncation = TRUE;
+	} else if (!strcmp(cap, "x-canonical-truncation")) {
+		notify_supports_truncation = TRUE;
+	}
+
+	return;
+}
+
+static void
+notify_check_caps(void)
+{
+	GList * caps = notify_get_server_caps();
+
+	g_list_foreach(caps, notify_check_caps_helper, NULL);
+	g_list_foreach(caps, (GFunc)g_free, NULL);
+	g_list_free(caps);
+
+	return;
 }
 
 static gboolean
@@ -479,6 +1195,22 @@
 		return FALSE;
 	}
 
+	/* They really do love me! */
+	if (never_loaded != 0) {
+		g_source_remove(never_loaded);
+	}
+	remove_from_blacklist();
+
+	notify_check_caps();
+
+	indicate_server = indicate_server_ref_default();
+	indicate_server_set_type(indicate_server, "message.instant");
+	indicate_server_set_desktop_file(indicate_server, PIDGIN_DESKTOP_FILE);
+	g_signal_connect(G_OBJECT(indicate_server), INDICATE_SERVER_SIGNAL_SERVER_DISPLAY, G_CALLBACK(indicate_server_display), NULL);
+	g_signal_connect(G_OBJECT(indicate_server), INDICATE_SERVER_SIGNAL_INTEREST_ADDED, G_CALLBACK(indicate_server_show_interest), NULL);
+	g_signal_connect(G_OBJECT(indicate_server), INDICATE_SERVER_SIGNAL_INTEREST_REMOVED, G_CALLBACK(indicate_server_remove_interest), NULL);
+	indicate_server_show(indicate_server);
+
 	conv_handle = purple_conversations_get_handle ();
 	blist_handle = purple_blist_get_handle ();
 	conn_handle = purple_connections_get_handle();
@@ -497,6 +1229,18 @@
 	purple_signal_connect (conv_handle, "received-chat-msg", plugin,
 						PURPLE_CALLBACK(notify_chat_nick), NULL);
 
+	purple_signal_connect (conv_handle, "received-im-msg", plugin,
+						PURPLE_CALLBACK(indicate_new_message_cb), NULL);
+
+	purple_signal_connect (conv_handle, "received-chat-msg", plugin,
+						PURPLE_CALLBACK(indicate_chat_nick), NULL);
+
+	purple_signal_connect (blist_handle, "buddy-signed-on", plugin,
+						PURPLE_CALLBACK(indicate_buddy_signon_cb), NULL);
+
+	purple_signal_connect (conv_handle, "deleting-conversation", plugin,
+	                    PURPLE_CALLBACK(conv_delete_cb), NULL);
+
 	/* used just to not display the flood of guifications we'd get */
 	purple_signal_connect (conn_handle, "signed-on", plugin,
 						PURPLE_CALLBACK(event_connection_throttle), NULL);
@@ -525,6 +1269,18 @@
 	purple_signal_disconnect (conv_handle, "received-chat-msg", plugin,
 							PURPLE_CALLBACK(notify_chat_nick));
 
+	purple_signal_disconnect (conv_handle, "received-im-msg", plugin,
+							PURPLE_CALLBACK(indicate_new_message_cb));
+
+	purple_signal_disconnect (conv_handle, "received-chat-msg", plugin,
+							PURPLE_CALLBACK(indicate_chat_nick));
+
+	purple_signal_disconnect (blist_handle, "buddy-signed-on", plugin,
+							PURPLE_CALLBACK(indicate_buddy_signon_cb));
+
+	purple_signal_disconnect (conv_handle, "deleting-conversation", plugin,
+							PURPLE_CALLBACK(conv_delete_cb));
+
 	purple_signal_disconnect (conn_handle, "signed-on", plugin,
 							PURPLE_CALLBACK(event_connection_throttle));
 
@@ -532,6 +1288,19 @@
 
 	notify_uninit ();
 
+	indicate_server_hide(indicate_server);
+
+	if (visibility_managed == TRUE) {
+		pidgin_blist_visibility_manager_remove();
+	}
+
+	g_object_unref(G_OBJECT(indicate_server));
+
+	/* If this goes off, we were unloaded by the user
+	   and not by shutdown.  Same thing as us never
+	   getting loaded at all. */
+	never_loaded = g_timeout_add_seconds(30, plugin_never_loaded, NULL);
+
 	return TRUE;
 }
 
@@ -578,7 +1347,24 @@
 	info.summary = _("Displays popups via libnotify.");
 	info.description = _("Pidgin-libnotify:\nDisplays popups via libnotify.");
 
+	/* If we get init'd and we never get loaded
+	   chances are the user hasn't enabled this
+	   plugin. */
+	never_loaded = g_timeout_add_seconds(30, plugin_never_loaded, NULL);
+
 	purple_prefs_add_none ("/plugins/gtk/libnotify");
+
+	/* Create a list of nicks that are commonly used by
+	   IRC servers but don't represent real people. */
+	GList * nicklist = NULL;
+	nicklist = g_list_append(nicklist, "NickServ");
+	nicklist = g_list_append(nicklist, "ChanServ");
+	nicklist = g_list_append(nicklist, "MsgServ");
+	nicklist = g_list_append(nicklist, "freenode-connect");
+
+	purple_prefs_add_string_list ("/plugins/gtk/libnotify/blocked_nicks", nicklist);
+	g_list_free(nicklist);
+
 	purple_prefs_add_bool ("/plugins/gtk/libnotify/newmsg", TRUE);
 	purple_prefs_add_bool ("/plugins/gtk/libnotify/blocked", TRUE);
 	purple_prefs_add_bool ("/plugins/gtk/libnotify/newconvonly", FALSE);

