=== 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 2009-09-16 19:08:18 +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,529 @@ 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; +} + +/* + * Taken from libtomboy, (C) 2008 Novell, LGPL v2 or later + */ + +static void +tomboy_window_override_user_time (GtkWindow *window) +{ + guint32 ev_time = gtk_get_current_event_time(); + + if (ev_time == 0) { + /* + * FIXME: Global keypresses use an event filter on the root + * window, which processes events before GDK sees them. + */ + //ev_time = tomboy_keybinder_get_current_event_time (); + ev_time = GDK_CURRENT_TIME; + } + if (ev_time == 0) { + gint ev_mask = gtk_widget_get_events (GTK_WIDGET(window)); + if (!(ev_mask & GDK_PROPERTY_CHANGE_MASK)) { + gtk_widget_add_events (GTK_WIDGET (window), + GDK_PROPERTY_CHANGE_MASK); + } + + /* + * NOTE: Last resort for D-BUS or other non-interactive + * openings. Causes roundtrip to server. Lame. + */ + ev_time = gdk_x11_get_server_time (GTK_WIDGET(window)->window); + } + + gdk_x11_window_set_user_time (GTK_WIDGET(window)->window, ev_time); +} + +static void +really_present_window (GtkWindow *window) +{ + if (!GTK_WIDGET_REALIZED (window)) + gtk_widget_realize (GTK_WIDGET (window)); + + tomboy_window_override_user_time (window); + + gtk_window_present (window); +} + +static void +indicate_show_cb (IndicateIndicator * indicator, 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); + really_present_window(GTK_WINDOW(gtkconv->win->window)); + + 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, 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); +} + +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, 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, 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)); + } + 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)); + + 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 +1234,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 +1268,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 +1308,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 +1327,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 +1386,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);