1 /*
2 * Pidgin-libnotify - Provides a libnotify interface for Pidgin
3 * Copyright (C) 2005-2007 Duarte Henriques
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include "gln_intl.h"
25
26 #ifndef PURPLE_PLUGINS
27 #define PURPLE_PLUGINS
28 #endif
29
30 #include <pidgin.h>
31 #include <version.h>
32 #include <debug.h>
33 #include <util.h>
34 #include <privacy.h>
35
36 /* for pidgin_create_prpl_icon */
37 #include <gtkutils.h>
38
39 #include <libnotify/notify.h>
40
41 #include <string.h>
42
43 #define PLUGIN_ID "pidgin-libnotify"
44
45 static GHashTable *buddy_hash;
46
47 static PurplePluginPrefFrame *
get_plugin_pref_frame(PurplePlugin * plugin)48 get_plugin_pref_frame (PurplePlugin *plugin)
49 {
50 PurplePluginPrefFrame *frame;
51 PurplePluginPref *ppref;
52
53 frame = purple_plugin_pref_frame_new ();
54
55 ppref = purple_plugin_pref_new_with_name_and_label (
56 "/plugins/gtk/libnotify/newmsg",
57 _("New messages"));
58 purple_plugin_pref_frame_add (frame, ppref);
59
60 ppref = purple_plugin_pref_new_with_name_and_label (
61 "/plugins/gtk/libnotify/newconvonly",
62 _("Only new conversations"));
63 purple_plugin_pref_frame_add (frame, ppref);
64
65 ppref = purple_plugin_pref_new_with_name_and_label (
66 "/plugins/gtk/libnotify/blocked",
67 _("Ignore events from blocked users"));
68 purple_plugin_pref_frame_add (frame, ppref);
69
70 ppref = purple_plugin_pref_new_with_name_and_label (
71 "/plugins/gtk/libnotify/signon",
72 _("Buddy signs on"));
73 purple_plugin_pref_frame_add (frame, ppref);
74
75 ppref = purple_plugin_pref_new_with_name_and_label (
76 "/plugins/gtk/libnotify/signoff",
77 _("Buddy signs off"));
78 purple_plugin_pref_frame_add (frame, ppref);
79
80 ppref = purple_plugin_pref_new_with_name_and_label (
81 "/plugins/gtk/libnotify/only_available",
82 _("Only when available"));
83 purple_plugin_pref_frame_add (frame, ppref);
84
85 return frame;
86 }
87
88 /* Signon flood be gone! - thanks to the guifications devs */
89 static GList *just_signed_on_accounts = NULL;
90
91 static gboolean
event_connection_throttle_cb(gpointer data)92 event_connection_throttle_cb (gpointer data)
93 {
94 PurpleAccount *account;
95
96 account = (PurpleAccount *)data;
97
98 if (!account)
99 return FALSE;
100
101 if (!purple_account_get_connection (account)) {
102 just_signed_on_accounts = g_list_remove (just_signed_on_accounts, account);
103 return FALSE;
104 }
105
106 if (!purple_account_is_connected (account))
107 return TRUE;
108
109 just_signed_on_accounts = g_list_remove (just_signed_on_accounts, account);
110 return FALSE;
111 }
112
113 static void
event_connection_throttle(PurpleConnection * conn,gpointer data)114 event_connection_throttle (PurpleConnection *conn, gpointer data)
115 {
116 PurpleAccount *account;
117
118 /* TODO: this function gets called after buddy signs on for GTalk
119 users who have themselves as a buddy */
120 purple_debug_info (PLUGIN_ID, "event_connection_throttle() called\n");
121
122 if (!conn)
123 return;
124
125 account = purple_connection_get_account(conn);
126 if (!account)
127 return;
128
129 just_signed_on_accounts = g_list_prepend (just_signed_on_accounts, account);
130 g_timeout_add (5000, event_connection_throttle_cb, (gpointer)account);
131 }
132
133 /* do NOT g_free() the string returned by this function */
134 static gchar *
best_name(PurpleBuddy * buddy)135 best_name (PurpleBuddy *buddy)
136 {
137 if (buddy->alias) {
138 return buddy->alias;
139 } else if (buddy->server_alias) {
140 return buddy->server_alias;
141 } else {
142 return buddy->name;
143 }
144 }
145
146 static GdkPixbuf *
pixbuf_from_buddy_icon(PurpleBuddyIcon * buddy_icon)147 pixbuf_from_buddy_icon (PurpleBuddyIcon *buddy_icon)
148 {
149 GdkPixbuf *icon;
150 const guchar *data;
151 size_t len;
152 GdkPixbufLoader *loader;
153
154 data = purple_buddy_icon_get_data (buddy_icon, &len);
155
156 loader = gdk_pixbuf_loader_new ();
157 gdk_pixbuf_loader_set_size (loader, 48, 48);
158 gdk_pixbuf_loader_write (loader, data, len, NULL);
159 gdk_pixbuf_loader_close (loader, NULL);
160
161 icon = gdk_pixbuf_loader_get_pixbuf (loader);
162
163 if (icon) {
164 g_object_ref (icon);
165 }
166
167 g_object_unref (loader);
168
169 return icon;
170 }
171
172 static void
action_cb(NotifyNotification * notification,gchar * action,gpointer user_data)173 action_cb (NotifyNotification *notification,
174 gchar *action, gpointer user_data)
175 {
176 PurpleBuddy *buddy = NULL;
177 PurpleConversation *conv = NULL;
178
179 purple_debug_info (PLUGIN_ID, "action_cb(), "
180 "notification: 0x%x, action: '%s'", notification, action);
181
182 buddy = (PurpleBuddy *)g_object_get_data (G_OBJECT(notification), "buddy");
183
184 if (!buddy) {
185 purple_debug_warning (PLUGIN_ID, "Got no buddy!");
186 return;
187 }
188
189 conv = purple_find_conversation_with_account (PURPLE_CONV_TYPE_ANY, buddy->name, buddy->account);
190
191 if (!conv) {
192 conv = purple_conversation_new (PURPLE_CONV_TYPE_IM,
193 buddy->account,
194 buddy->name);
195 }
196 conv->ui_ops->present (conv);
197
198 notify_notification_close (notification, NULL);
199 }
200
201 static gboolean
closed_cb(NotifyNotification * notification)202 closed_cb (NotifyNotification *notification)
203 {
204 PurpleContact *contact;
205
206 purple_debug_info (PLUGIN_ID, "closed_cb(), notification: 0x%x\n", notification);
207
208 contact = (PurpleContact *)g_object_get_data (G_OBJECT(notification), "contact");
209 if (contact)
210 g_hash_table_remove (buddy_hash, contact);
211
212 g_object_unref (G_OBJECT(notification));
213
214 return FALSE;
215 }
216
217 /* you must g_free the returned string
218 * num_chars is utf-8 characters */
219 static gchar *
truncate_escape_string(const gchar * str,int num_chars)220 truncate_escape_string (const gchar *str,
221 int num_chars)
222 {
223 gchar *escaped_str;
224
225 if (g_utf8_strlen (str, num_chars*2+1) > num_chars) {
226 gchar *truncated_str;
227 gchar *str2;
228
229 /* allocate number of bytes and not number of utf-8 chars */
230 str2 = g_malloc ((num_chars-1) * 2 * sizeof(gchar));
231
232 g_utf8_strncpy (str2, str, num_chars-2);
233 truncated_str = g_strdup_printf ("%s..", str2);
234 escaped_str = g_markup_escape_text (truncated_str, strlen (truncated_str));
235 g_free (str2);
236 g_free (truncated_str);
237 } else {
238 escaped_str = g_markup_escape_text (str, strlen (str));
239 }
240
241 return escaped_str;
242 }
243
244 static gboolean
should_notify_unavailable(PurpleAccount * account)245 should_notify_unavailable (PurpleAccount *account)
246 {
247 PurpleStatus *status;
248
249 if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/only_available"))
250 return TRUE;
251
252 status = purple_account_get_active_status (account);
253
254 return purple_status_is_online (status) && purple_status_is_available (status);
255 }
256
257 static void
notify(const gchar * title,const gchar * body,PurpleBuddy * buddy)258 notify (const gchar *title,
259 const gchar *body,
260 PurpleBuddy *buddy)
261 {
262 NotifyNotification *notification = NULL;
263 GdkPixbuf *icon;
264 PurpleBuddyIcon *buddy_icon;
265 gchar *tr_body;
266 PurpleContact *contact;
267
268 contact = purple_buddy_get_contact (buddy);
269
270 if (body)
271 tr_body = truncate_escape_string (body, 60);
272 else
273 tr_body = NULL;
274
275 notification = g_hash_table_lookup (buddy_hash, contact);
276
277 if (notification != NULL) {
278 notify_notification_update (notification, title, tr_body, NULL);
279 /* this shouldn't be necessary, file a bug */
280 notify_notification_show (notification, NULL);
281
282 purple_debug_info (PLUGIN_ID, "notify(), update: "
283 "title: '%s', body: '%s', buddy: '%s'\n",
284 title, tr_body, best_name (buddy));
285
286 g_free (tr_body);
287 return;
288 }
289 notification = notify_notification_new (title, tr_body, NULL);
290 purple_debug_info (PLUGIN_ID, "notify(), new: "
291 "title: '%s', body: '%s', buddy: '%s'\n",
292 title, tr_body, best_name (buddy));
293
294 g_free (tr_body);
295
296 buddy_icon = purple_buddy_get_icon (buddy);
297 if (buddy_icon) {
298 icon = pixbuf_from_buddy_icon (buddy_icon);
299 purple_debug_info (PLUGIN_ID, "notify(), has a buddy icon.\n");
300 } else {
301 icon = pidgin_create_prpl_icon (buddy->account, 1);
302 purple_debug_info (PLUGIN_ID, "notify(), has a prpl icon.\n");
303 }
304
305 if (icon) {
306 notify_notification_set_icon_from_pixbuf (notification, icon);
307 g_object_unref (icon);
308 } else {
309 purple_debug_warning (PLUGIN_ID, "notify(), couldn't find any icon!\n");
310 }
311
312 g_hash_table_insert (buddy_hash, contact, notification);
313
314 g_object_set_data (G_OBJECT(notification), "contact", contact);
315
316 g_signal_connect (notification, "closed", G_CALLBACK(closed_cb), NULL);
317
318 notify_notification_set_urgency (notification, NOTIFY_URGENCY_NORMAL);
319
320 notify_notification_add_action (notification, "show", _("Show"), action_cb, NULL, NULL);
321
322 if (!notify_notification_show (notification, NULL)) {
323 purple_debug_error (PLUGIN_ID, "notify(), failed to send notification\n");
324 }
325
326 }
327
328 static void
notify_buddy_signon_cb(PurpleBuddy * buddy,gpointer data)329 notify_buddy_signon_cb (PurpleBuddy *buddy,
330 gpointer data)
331 {
332 gchar *tr_name, *title;
333 gboolean blocked;
334
335 g_return_if_fail (buddy);
336
337 if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/signon"))
338 return;
339
340 if (g_list_find (just_signed_on_accounts, buddy->account))
341 return;
342
343 blocked = purple_prefs_get_bool ("/plugins/gtk/libnotify/blocked");
344 if (!purple_privacy_check (buddy->account, buddy->name) && blocked)
345 return;
346
347 if (!should_notify_unavailable (purple_buddy_get_account (buddy)))
348 return;
349
350 tr_name = truncate_escape_string (best_name (buddy), 25);
351
352 title = g_strdup_printf (_("%s signed on"), tr_name);
353
354 notify (title, NULL, buddy);
355
356 g_free (tr_name);
357 g_free (title);
358 }
359
360 static void
notify_buddy_signoff_cb(PurpleBuddy * buddy,gpointer data)361 notify_buddy_signoff_cb (PurpleBuddy *buddy,
362 gpointer data)
363 {
364 gchar *tr_name, *title;
365 gboolean blocked;
366
367 g_return_if_fail (buddy);
368
369 if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/signoff"))
370 return;
371
372 if (g_list_find (just_signed_on_accounts, buddy->account))
373 return;
374
375 blocked = purple_prefs_get_bool ("/plugins/gtk/libnotify/blocked");
376 if (!purple_privacy_check (buddy->account, buddy->name) && blocked)
377 return;
378
379 if (!should_notify_unavailable (purple_buddy_get_account (buddy)))
380 return;
381
382 tr_name = truncate_escape_string (best_name (buddy), 25);
383
384 title = g_strdup_printf (_("%s signed off"), tr_name);
385
386 notify (title, NULL, buddy);
387
388 g_free (tr_name);
389 g_free (title);
390 }
391
392 static void
notify_msg_sent(PurpleAccount * account,const gchar * sender,const gchar * message)393 notify_msg_sent (PurpleAccount *account,
394 const gchar *sender,
395 const gchar *message)
396 {
397 PurpleBuddy *buddy;
398 gchar *title, *body, *tr_name;
399 gboolean blocked;
400
401 buddy = purple_find_buddy (account, sender);
402 if (!buddy)
403 return;
404
405 blocked = purple_prefs_get_bool ("/plugins/gtk/libnotify/blocked");
406 if (!purple_privacy_check(account, sender) && blocked)
407 return;
408
409 tr_name = truncate_escape_string (best_name (buddy), 25);
410
411 title = g_strdup_printf (_("%s says:"), tr_name);
412 body = purple_markup_strip_html (message);
413
414 notify (title, body, buddy);
415
416 g_free (tr_name);
417 g_free (title);
418 g_free (body);
419 }
420
421 static void
notify_new_message_cb(PurpleAccount * account,const gchar * sender,const gchar * message,int flags,gpointer data)422 notify_new_message_cb (PurpleAccount *account,
423 const gchar *sender,
424 const gchar *message,
425 int flags,
426 gpointer data)
427 {
428 PurpleConversation *conv;
429
430 if (!purple_prefs_get_bool ("/plugins/gtk/libnotify/newmsg"))
431 return;
432
433 conv = purple_find_conversation_with_account (PURPLE_CONV_TYPE_IM, sender, account);
434
435 #ifndef DEBUG /* in debug mode, always show notifications */
436 if (conv && purple_conversation_has_focus (conv)) {
437 purple_debug_info (PLUGIN_ID, "Conversation has focus 0x%x\n", conv);
438 return;
439 }
440 #endif
441
442 if (conv && purple_prefs_get_bool ("/plugins/gtk/libnotify/newconvonly")) {
443 purple_debug_info (PLUGIN_ID, "Conversation is not new 0x%x\n", conv);
444 return;
445 }
446
447 if (!should_notify_unavailable (account))
448 return;
449
450 notify_msg_sent (account, sender, message);
451 }
452
453 static void
notify_chat_nick(PurpleAccount * account,const gchar * sender,const gchar * message,PurpleConversation * conv,gpointer data)454 notify_chat_nick (PurpleAccount *account,
455 const gchar *sender,
456 const gchar *message,
457 PurpleConversation *conv,
458 gpointer data)
459 {
460 gchar *nick;
461
462 nick = (gchar *)purple_conv_chat_get_nick (PURPLE_CONV_CHAT(conv));
463 if (nick && !strcmp (sender, nick))
464 return;
465
466 if (!g_strstr_len (message, strlen(message), nick))
467 return;
468
469 notify_msg_sent (account, sender, message);
470 }
471
472 static gboolean
plugin_load(PurplePlugin * plugin)473 plugin_load (PurplePlugin *plugin)
474 {
475 void *conv_handle, *blist_handle, *conn_handle;
476
477 if (!notify_is_initted () && !notify_init ("Pidgin")) {
478 purple_debug_error (PLUGIN_ID, "libnotify not running!\n");
479 return FALSE;
480 }
481
482 conv_handle = purple_conversations_get_handle ();
483 blist_handle = purple_blist_get_handle ();
484 conn_handle = purple_connections_get_handle();
485
486 buddy_hash = g_hash_table_new (NULL, NULL);
487
488 purple_signal_connect (blist_handle, "buddy-signed-on", plugin,
489 PURPLE_CALLBACK(notify_buddy_signon_cb), NULL);
490
491 purple_signal_connect (blist_handle, "buddy-signed-off", plugin,
492 PURPLE_CALLBACK(notify_buddy_signoff_cb), NULL);
493
494 purple_signal_connect (conv_handle, "received-im-msg", plugin,
495 PURPLE_CALLBACK(notify_new_message_cb), NULL);
496
497 purple_signal_connect (conv_handle, "received-chat-msg", plugin,
498 PURPLE_CALLBACK(notify_chat_nick), NULL);
499
500 /* used just to not display the flood of guifications we'd get */
501 purple_signal_connect (conn_handle, "signed-on", plugin,
502 PURPLE_CALLBACK(event_connection_throttle), NULL);
503
504 return TRUE;
505 }
506
507 static gboolean
plugin_unload(PurplePlugin * plugin)508 plugin_unload (PurplePlugin *plugin)
509 {
510 void *conv_handle, *blist_handle, *conn_handle;
511
512 conv_handle = purple_conversations_get_handle ();
513 blist_handle = purple_blist_get_handle ();
514 conn_handle = purple_connections_get_handle();
515
516 purple_signal_disconnect (blist_handle, "buddy-signed-on", plugin,
517 PURPLE_CALLBACK(notify_buddy_signon_cb));
518
519 purple_signal_disconnect (blist_handle, "buddy-signed-off", plugin,
520 PURPLE_CALLBACK(notify_buddy_signoff_cb));
521
522 purple_signal_disconnect (conv_handle, "received-im-msg", plugin,
523 PURPLE_CALLBACK(notify_new_message_cb));
524
525 purple_signal_disconnect (conv_handle, "received-chat-msg", plugin,
526 PURPLE_CALLBACK(notify_chat_nick));
527
528 purple_signal_disconnect (conn_handle, "signed-on", plugin,
529 PURPLE_CALLBACK(event_connection_throttle));
530
531 g_hash_table_destroy (buddy_hash);
532
533 notify_uninit ();
534
535 return TRUE;
536 }
537
538 static PurplePluginUiInfo prefs_info = {
539 get_plugin_pref_frame,
540 0, /* page num (Reserved) */
541 NULL /* frame (Reserved) */
542 };
543
544 static PurplePluginInfo info = {
545 PURPLE_PLUGIN_MAGIC, /* api version */
546 PURPLE_MAJOR_VERSION,
547 PURPLE_MINOR_VERSION,
548 PURPLE_PLUGIN_STANDARD, /* type */
549 0, /* ui requirement */
550 0, /* flags */
551 NULL, /* dependencies */
552 PURPLE_PRIORITY_DEFAULT, /* priority */
553
554 PLUGIN_ID, /* id */
555 NULL, /* name */
556 VERSION, /* version */
557 NULL, /* summary */
558 NULL, /* description */
559
560 "Duarte Henriques <duarte.henriques@gmail.com>", /* author */
561 "http://sourceforge.net/projects/gaim-libnotify/", /* homepage */
562
563 plugin_load, /* load */
564 plugin_unload, /* unload */
565 NULL, /* destroy */
566 NULL, /* ui info */
567 NULL, /* extra info */
568 &prefs_info /* prefs info */
569 };
570
571 static void
init_plugin(PurplePlugin * plugin)572 init_plugin (PurplePlugin *plugin)
573 {
574 bindtextdomain (PACKAGE, LOCALEDIR);
575 bind_textdomain_codeset (PACKAGE, "UTF-8");
576
577 info.name = _("Libnotify Popups");
578 info.summary = _("Displays popups via libnotify.");
579 info.description = _("Pidgin-libnotify:\nDisplays popups via libnotify.");
580
581 purple_prefs_add_none ("/plugins/gtk/libnotify");
582 purple_prefs_add_bool ("/plugins/gtk/libnotify/newmsg", TRUE);
583 purple_prefs_add_bool ("/plugins/gtk/libnotify/blocked", TRUE);
584 purple_prefs_add_bool ("/plugins/gtk/libnotify/newconvonly", FALSE);
585 purple_prefs_add_bool ("/plugins/gtk/libnotify/signon", TRUE);
586 purple_prefs_add_bool ("/plugins/gtk/libnotify/signoff", FALSE);
587 purple_prefs_add_bool ("/plugins/gtk/libnotify/only_available", FALSE);
588 }
589
590 PURPLE_INIT_PLUGIN(notify, init_plugin, info)
591
592