/* * Pidgin-libnotify - Provides a libnotify interface for Pidgin * Copyright (C) 2005-2007 Duarte Henriques * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "gln_intl.h" #ifndef PURPLE_PLUGINS #define PURPLE_PLUGINS #endif #include #include #include #include #include /* for pidgin_create_prpl_icon */ #include #include #include #define PLUGIN_ID "pidgin-libnotify" #define KEY "notification_key" static GHashTable *buddy_hash; static PurplePluginPrefFrame * get_plugin_pref_frame (PurplePlugin *plugin) { PurplePluginPrefFrame *frame; PurplePluginPref *ppref; frame = purple_plugin_pref_frame_new (); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/newmsg", _("New messages")); purple_plugin_pref_frame_add (frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/newconvonly", _("Only new conversations")); purple_plugin_pref_frame_add (frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/all_chat_msgs", _("All messages in chats")); purple_plugin_pref_frame_add (frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/blocked", _("Ignore events from blocked users")); purple_plugin_pref_frame_add (frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/signon", _("Buddy signs on")); purple_plugin_pref_frame_add (frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/signoff", _("Buddy signs off")); purple_plugin_pref_frame_add (frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label ( "/plugins/gtk/libnotify/only_available", _("Only when available")); purple_plugin_pref_frame_add (frame, ppref); return frame; } /* Signon flood be gone! - thanks to the guifications devs */ static GList *just_signed_on_accounts = NULL; static gboolean event_connection_throttle_cb (gpointer data) { PurpleAccount *account; account = (PurpleAccount *)data; if (!account) return FALSE; if (!purple_account_get_connection (account)) { just_signed_on_accounts = g_list_remove (just_signed_on_accounts, account); return FALSE; } if (!purple_account_is_connected (account)) return TRUE; just_signed_on_accounts = g_list_remove (just_signed_on_accounts, account); return FALSE; } static void event_connection_throttle (PurpleConnection *conn, gpointer data) { PurpleAccount *account; /* TODO: this function gets called after buddy signs on for GTalk users who have themselves as a buddy */ purple_debug_info (PLUGIN_ID, "event_connection_throttle() called\n"); if (!conn) return; account = purple_connection_get_account(conn); if (!account) return; just_signed_on_accounts = g_list_prepend (just_signed_on_accounts, account); g_timeout_add (5000, event_connection_throttle_cb, (gpointer)account); } /* do NOT g_free() the string returned by this function */ static gchar * best_name (PurpleBuddy *buddy) { if (buddy->alias) { return buddy->alias; } else if (buddy->server_alias) { return buddy->server_alias; } else { return buddy->name; } } static GdkPixbuf * pixbuf_from_buddy_icon (PurpleBuddyIcon *buddy_icon) { GdkPixbuf *icon; const guchar *data; size_t len; GdkPixbufLoader *loader; 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); icon = gdk_pixbuf_loader_get_pixbuf (loader); if (icon) { g_object_ref (icon); } g_object_unref (loader); return icon; } /** Return a pixbuf describing the buddy. It should be unref'd. */ static GdkPixbuf * pixbuf_from_buddy(const PurpleBuddy *buddy) { GdkPixbuf *icon; PurpleBuddyIcon *buddy_icon; buddy_icon = purple_buddy_get_icon (buddy); if (buddy_icon) { icon = pixbuf_from_buddy_icon (buddy_icon); purple_debug_info (PLUGIN_ID, "pixbuf_from_buddy(), icon from buddy\n"); } else { icon = pidgin_create_prpl_icon (buddy->account, 1); purple_debug_info (PLUGIN_ID, "pixbuf_from_buddy(), icon from account\n"); } return icon; } static void action_cb (NotifyNotification *notification, gchar *action, gpointer user_data) { PurpleBuddy *buddy = NULL; PurpleConversation *conv = NULL; purple_debug_info (PLUGIN_ID, "action_cb(), " "notification: 0x%x, action: '%s'", notification, action); buddy = (PurpleBuddy *)g_object_get_data (G_OBJECT(notification), "buddy"); if (!buddy) { purple_debug_warning (PLUGIN_ID, "Got no buddy!"); return; } conv = purple_find_conversation_with_account (PURPLE_CONV_TYPE_ANY, buddy->name, buddy->account); if (!conv) { conv = purple_conversation_new (PURPLE_CONV_TYPE_IM, buddy->account, buddy->name); } conv->ui_ops->present (conv); notify_notification_close (notification, NULL); } static gboolean closed_cb (NotifyNotification *notification) { gpointer notification_key; notification_key = (gpointer)g_object_get_data (G_OBJECT(notification), KEY); purple_debug_info (PLUGIN_ID, "closed_cb(), notification: 0x%x " "key: 0x%x\n", notification, notification_key ); if (notification_key) g_hash_table_remove (buddy_hash, notification_key); g_object_unref (G_OBJECT(notification)); return FALSE; } /* you must g_free the returned string * num_chars is utf-8 characters */ static gchar * truncate_escape_string (const gchar *str, int num_chars) { gchar *escaped_str; if (g_utf8_strlen (str, num_chars*2+1) > num_chars) { gchar *truncated_str; gchar *str2; /* allocate number of bytes and not number of utf-8 chars */ str2 = g_malloc ((num_chars-1) * 2 * sizeof(gchar)); 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)); g_free (str2); g_free (truncated_str); } else { escaped_str = g_markup_escape_text (str, strlen (str)); } return escaped_str; } static gboolean should_notify_unavailable (PurpleAccount *account) { PurpleStatus *status; if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/only_available")) return TRUE; status = purple_account_get_active_status (account); return purple_status_is_online (status) && purple_status_is_available (status); } static void notify (const GdkPixbuf *icon, const gchar *title, const gchar *body, PurpleBuddy *buddy, gpointer notification_key) { NotifyNotification *notification = NULL; gchar *tr_body, *buddy_name; PurpleContact *contact; if (body) tr_body = truncate_escape_string (body, 60); else tr_body = NULL; if (buddy) { contact = purple_buddy_get_contact (buddy); buddy_name = best_name (buddy); } else { contact = NULL; buddy_name = "(not-set)"; } notification = g_hash_table_lookup (buddy_hash, notification_key); if (notification != NULL) { purple_debug_info (PLUGIN_ID, "notify(), updating 0x%x: " "title: '%s', body: '%s', buddy: '%s'\n", notification_key, title, tr_body, buddy_name); 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, buddy_name); g_free (tr_body); return; } #ifdef NOTIFY_CHECK_VERSION #if NOTIFY_CHECK_VERSION (0, 7, 0) notification = notify_notification_new (title, tr_body, NULL); #else notification = notify_notification_new (title, tr_body, NULL, NULL); #endif #else notification = notify_notification_new (title, tr_body, NULL, NULL); #endif purple_debug_info (PLUGIN_ID, "notify(), new 0x%x: " "title: '%s', body: '%s', buddy: '%s'\n", notification_key, title, tr_body, buddy_name); g_free (tr_body); if (icon) { notify_notification_set_icon_from_pixbuf (notification, icon); } else { purple_debug_warning (PLUGIN_ID, "notify(), didn't get an icon!\n"); } if (body) notify_notification_set_category (notification, "im.received"); else notify_notification_set_category (notification, "im"); g_hash_table_insert (buddy_hash, notification_key, notification); g_object_set_data (G_OBJECT(notification), KEY, notification_key); g_signal_connect (notification, "closed", G_CALLBACK(closed_cb), NULL); notify_notification_set_urgency (notification, NOTIFY_URGENCY_NORMAL); // notify_notification_add_action (notification, "show", _("Show"), action_cb, NULL, NULL); if (!notify_notification_show (notification, NULL)) { purple_debug_error (PLUGIN_ID, "notify(), failed to send notification\n"); } } static void notify_buddy_signon_cb (PurpleBuddy *buddy, gpointer data) { GdkPixbuf *icon; gchar *tr_name, *title; 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); title = g_strdup_printf (_("%s signed on"), tr_name); icon = pixbuf_from_buddy (buddy); notify (icon, title, NULL, buddy, buddy); g_object_unref (icon); g_free (tr_name); g_free (title); } static void notify_buddy_signoff_cb (PurpleBuddy *buddy, gpointer data) { GdkPixbuf *icon; gchar *tr_name, *title; gboolean blocked; g_return_if_fail (buddy); if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/signoff")) 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); title = g_strdup_printf (_("%s signed off"), tr_name); icon = pixbuf_from_buddy (buddy); notify (icon, title, NULL, buddy, buddy); g_object_unref (icon); g_free (tr_name); g_free (title); } static void notify_msg_sent (PurpleAccount *account, const gchar *sender, const gchar *message) { PurpleBuddy *buddy; GdkPixbuf *icon; gchar *title, *body, *tr_name; gboolean blocked; 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); body = purple_markup_strip_html (message); icon = pixbuf_from_buddy (buddy); notify (icon, title, body, buddy, buddy); g_object_unref (icon); g_free (tr_name); g_free (title); g_free (body); } static void notify_new_message_cb (PurpleAccount *account, const gchar *sender, const gchar *message, int flags, gpointer data) { PurpleConversation *conv; if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/newmsg")) 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); 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); return; } if (!should_notify_unavailable (account)) return; notify_msg_sent (account, sender, message); } static void notify_chat_nick (PurpleAccount *account, const gchar *sender, const gchar *message, PurpleConversation *conv, gpointer data) { GdkPixbuf *icon; PurpleBuddy *buddy; gchar *nick, *tr_name, *title, *body; nick = (gchar *)purple_conv_chat_get_nick (PURPLE_CONV_CHAT(conv)); if (nick && !strcmp (sender, nick)) return; if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/all_chat_msgs") && !g_strstr_len (message, strlen(message), nick)) return; tr_name = truncate_escape_string (sender, 25); title = g_strdup_printf (_("%s says:"), tr_name); body = purple_markup_strip_html (message); buddy = purple_find_buddy (account, sender); if (buddy) icon = pixbuf_from_buddy (buddy); else icon = NULL; if (!icon) icon = pidgin_create_prpl_icon (account, PIDGIN_PRPL_ICON_MEDIUM); notify (icon, title, body, NULL, conv); g_object_unref (icon); g_free (tr_name); g_free (title); g_free (body); } static gboolean plugin_load (PurplePlugin *plugin) { void *conv_handle, *blist_handle, *conn_handle; if (!notify_is_initted () && !notify_init ("Pidgin")) { purple_debug_error (PLUGIN_ID, "libnotify not running!\n"); return FALSE; } conv_handle = purple_conversations_get_handle (); blist_handle = purple_blist_get_handle (); conn_handle = purple_connections_get_handle(); buddy_hash = g_hash_table_new (NULL, NULL); purple_signal_connect (blist_handle, "buddy-signed-on", plugin, PURPLE_CALLBACK(notify_buddy_signon_cb), NULL); purple_signal_connect (blist_handle, "buddy-signed-off", plugin, PURPLE_CALLBACK(notify_buddy_signoff_cb), NULL); purple_signal_connect (conv_handle, "received-im-msg", plugin, PURPLE_CALLBACK(notify_new_message_cb), NULL); purple_signal_connect (conv_handle, "received-chat-msg", plugin, PURPLE_CALLBACK(notify_chat_nick), 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); return TRUE; } static gboolean plugin_unload (PurplePlugin *plugin) { void *conv_handle, *blist_handle, *conn_handle; conv_handle = purple_conversations_get_handle (); blist_handle = purple_blist_get_handle (); conn_handle = purple_connections_get_handle(); purple_signal_disconnect (blist_handle, "buddy-signed-on", plugin, PURPLE_CALLBACK(notify_buddy_signon_cb)); purple_signal_disconnect (blist_handle, "buddy-signed-off", plugin, PURPLE_CALLBACK(notify_buddy_signoff_cb)); purple_signal_disconnect (conv_handle, "received-im-msg", plugin, PURPLE_CALLBACK(notify_new_message_cb)); purple_signal_disconnect (conv_handle, "received-chat-msg", plugin, PURPLE_CALLBACK(notify_chat_nick)); purple_signal_disconnect (conn_handle, "signed-on", plugin, PURPLE_CALLBACK(event_connection_throttle)); g_hash_table_destroy (buddy_hash); notify_uninit (); return TRUE; } static PurplePluginUiInfo prefs_info = { get_plugin_pref_frame, 0, /* page num (Reserved) */ NULL /* frame (Reserved) */ }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, /* api version */ PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_STANDARD, /* type */ 0, /* ui requirement */ 0, /* flags */ NULL, /* dependencies */ PURPLE_PRIORITY_DEFAULT, /* priority */ PLUGIN_ID, /* id */ NULL, /* name */ VERSION, /* version */ NULL, /* summary */ NULL, /* description */ "Cedric Girard ", /* author */ "https://github.com/X-dark/ArchLinux/tree/master/code/pidgin-libnotify", /* homepage */ plugin_load, /* load */ plugin_unload, /* unload */ NULL, /* destroy */ NULL, /* ui info */ NULL, /* extra info */ &prefs_info /* prefs info */ }; static void init_plugin (PurplePlugin *plugin) { bindtextdomain (PACKAGE, LOCALEDIR); bind_textdomain_codeset (PACKAGE, "UTF-8"); info.name = _("Libnotify Popups"); info.summary = _("Displays popups via libnotify."); info.description = _("Pidgin-libnotify:\nDisplays popups via libnotify."); purple_prefs_add_none ("/plugins/gtk/libnotify"); 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); purple_prefs_add_bool ("/plugins/gtk/libnotify/signon", TRUE); purple_prefs_add_bool ("/plugins/gtk/libnotify/signoff", FALSE); purple_prefs_add_bool ("/plugins/gtk/libnotify/only_available", FALSE); purple_prefs_add_bool ("/plugins/gtk/libnotify/all_chat_msgs", FALSE); } PURPLE_INIT_PLUGIN(notify, init_plugin, info)