Fix crash on double notification popup.
[claws.git] / src / plugins / notification / notification_popup.c
1 /* Notification plugin for Claws-Mail
2  * Copyright (C) 2005-2007 Holger Berndt
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #  include "claws-features.h"
21 #endif
22
23 #include <glib.h>
24 #include <glib/gi18n.h>
25
26 #ifdef NOTIFICATION_POPUP
27
28 #include <gtk/gtk.h>
29
30 #include "mainwindow.h"
31 #include "procmsg.h"
32 #include "folder.h"
33 #ifndef USE_NEW_ADDRBOOK
34     #include "addrindex.h"
35 #endif
36 #include "common/utils.h"
37 #include "gtk/gtkutils.h"
38
39 #include "notification_popup.h"
40 #include "notification_prefs.h"
41 #include "notification_foldercheck.h"
42 #include "notification_pixbuf.h"
43 #include "notification_core.h"
44
45 #ifdef HAVE_LIBNOTIFY
46 #  include <libnotify/notify.h>
47 #endif
48
49 #ifndef NOTIFY_CHECK_VERSION
50 # define NOTIFY_CHECK_VERSION(a,b,c) 0
51 #endif
52
53 typedef struct {
54   gint count;
55   gchar *msg_path;
56 #ifdef HAVE_LIBNOTIFY
57   NotifyNotification *notification;
58   GError *error;
59 #else /* !HAVE_LIBNOTIFY */
60   guint timeout_id;
61   GtkWidget *window;
62   GtkWidget *frame;
63   GtkWidget *event_box;
64   GtkWidget *vbox;
65   GtkWidget *label1;
66   GtkWidget *label2;
67 #endif
68 } NotificationPopup;
69
70 G_LOCK_DEFINE_STATIC(popup);
71
72 #ifdef HAVE_LIBNOTIFY
73 static NotificationPopup popup[F_TYPE_LAST];
74
75 static void popup_timeout_fun(NotifyNotification*, gpointer data);
76
77 static gboolean notification_libnotify_create(MsgInfo*, NotificationFolderType);
78 static gboolean notification_libnotify_add_msg(MsgInfo*, NotificationFolderType);
79 static void default_action_cb(NotifyNotification*, const char*,void*);
80 static void notification_libnotify_free_func(gpointer);
81
82 #else
83 static NotificationPopup popup;
84
85 static gboolean popup_timeout_fun(gpointer data);
86
87 static gboolean notification_popup_add_msg(MsgInfo*);
88 static gboolean notification_popup_create(MsgInfo*);
89 static gboolean notification_popup_button(GtkWidget*, GdkEventButton*, gpointer);
90 #endif
91
92
93 void notification_popup_msg(MsgInfo *msginfo)
94 {
95   FolderType ftype;
96 #if HAVE_LIBNOTIFY
97   gchar *uistr;
98 #else
99   NotificationPopup *ppopup;
100   gboolean retval;
101 #endif
102   NotificationFolderType nftype;
103
104   nftype = F_TYPE_MAIL;
105
106   if(!msginfo || !notify_config.popup_show)
107     return;
108
109   if(notify_config.popup_folder_specific) {
110     guint id;
111     GSList *list;
112     gchar *identifier;
113     gboolean found = FALSE;
114
115     if(!(msginfo->folder))
116       return;
117
118     identifier = folder_item_get_identifier(msginfo->folder);
119
120     id =
121       notification_register_folder_specific_list(POPUP_SPECIFIC_FOLDER_ID_STR);
122     list = notification_foldercheck_get_list(id);
123     for(; (list != NULL) && !found; list = g_slist_next(list)) {
124       gchar *list_identifier;
125       FolderItem *list_item = (FolderItem*) list->data;
126
127       list_identifier = folder_item_get_identifier(list_item);
128       if(!strcmp2(list_identifier, identifier))
129         found = TRUE;
130
131       g_free(list_identifier);
132     }
133     g_free(identifier);
134
135     if(!found)
136       return;
137   }
138
139   ftype = msginfo->folder->folder->klass->type;
140
141   G_LOCK(popup);
142 #ifdef HAVE_LIBNOTIFY
143   /* Check out which type to notify about */
144   switch(ftype) {
145   case F_MH:
146   case F_MBOX:
147   case F_MAILDIR:
148   case F_IMAP:
149     nftype = F_TYPE_MAIL;
150     break;
151   case F_NEWS:
152     nftype = F_TYPE_NEWS;
153     break;
154   case F_UNKNOWN:
155     if((uistr = msginfo->folder->folder->klass->uistr) == NULL) {
156       G_UNLOCK(popup);
157       return;
158     }
159     else if(!strcmp(uistr, "vCalendar"))
160       nftype = F_TYPE_CALENDAR;
161     else if(!strcmp(uistr, "RSSyl"))
162       nftype = F_TYPE_RSS;
163     else {
164       debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
165       G_UNLOCK(popup);
166       return;
167     }
168     break;
169   default:
170     debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
171     G_UNLOCK(popup);
172     return;
173   }
174
175   notification_libnotify_add_msg(msginfo, nftype);
176 #else /* !HAVE_LIBNOTIFY */
177   ppopup = &popup;
178   retval = notification_popup_add_msg(msginfo);
179
180   /* Renew timeout only when the above call was successful */
181   if(retval) {
182     if(ppopup->timeout_id)
183       g_source_remove(ppopup->timeout_id);
184     ppopup->timeout_id = g_timeout_add(notify_config.popup_timeout,
185                        popup_timeout_fun,
186                        GINT_TO_POINTER(nftype));
187   }
188
189   #endif /* !HAVE_LIBNOTIFY */
190
191   G_UNLOCK(popup);
192
193 #ifndef HAVE_LIBNOTIFY
194   /* GUI update */
195   while(gtk_events_pending())
196     gtk_main_iteration();
197 #endif /* !HAVE_LIBNOTIFY */
198
199 }
200
201 #ifdef HAVE_LIBNOTIFY
202 static void popup_timeout_fun(NotifyNotification *nn, gpointer data)
203 {
204   NotificationPopup *ppopup;
205   NotificationFolderType nftype;
206
207   nftype = GPOINTER_TO_INT(data);
208
209   G_LOCK(popup);
210
211   ppopup = &(popup[nftype]);
212
213   g_object_unref(G_OBJECT(ppopup->notification));
214   ppopup->notification = NULL;
215   g_clear_error(&(ppopup->error));
216
217   if(ppopup->msg_path) {
218     g_free(ppopup->msg_path);
219     ppopup->msg_path = NULL;
220   }
221   ppopup->count = 0;
222   G_UNLOCK(popup);
223   debug_print("Notification Plugin: Popup closed due to timeout.\n");
224 }
225
226 #else
227 static gboolean popup_timeout_fun(gpointer data)
228 {
229   NotificationPopup *ppopup;
230   NotificationFolderType nftype;
231
232   nftype = GPOINTER_TO_INT(data);
233
234   G_LOCK(popup);
235
236   ppopup = &popup;
237   if(ppopup->window) {
238     gtk_widget_destroy(ppopup->window);
239     ppopup->window = NULL;
240   }
241   ppopup->timeout_id = 0;
242
243   if(ppopup->msg_path) {
244     g_free(ppopup->msg_path);
245     ppopup->msg_path = NULL;
246   }
247   ppopup->count = 0;
248   G_UNLOCK(popup);
249   debug_print("Notification Plugin: Popup closed due to timeout.\n");
250   return FALSE;
251 }
252 #endif
253
254 #ifdef HAVE_LIBNOTIFY
255 static void default_action_cb(NotifyNotification *notification,
256                               const char *action,
257                               void *user_data)
258 {
259   if(strcmp("default", action))
260     return;
261
262   MainWindow *mainwin;
263   mainwin = mainwindow_get_mainwindow();
264   if(mainwin) {
265     NotificationFolderType nftype;
266
267     /* Let mainwindow pop up */
268     notification_show_mainwindow(mainwin);
269     /* If there is only one new mail message, jump to this message */
270     nftype = (NotificationFolderType)GPOINTER_TO_INT(user_data);
271     if(nftype == F_TYPE_MAIL) {
272       if(popup[F_TYPE_MAIL].count == 1) {
273         gchar *select_str;
274         G_LOCK(popup);
275         select_str = g_strdup(popup[F_TYPE_MAIL].msg_path);
276         G_UNLOCK(popup);
277         debug_print("Select message %s\n", select_str);
278         mainwindow_jump_to(select_str, FALSE);
279         g_free(select_str);
280       }
281     }
282   }
283 }
284
285 static gboolean notification_libnotify_create(MsgInfo *msginfo,
286                                               NotificationFolderType nftype)
287 {
288   GdkPixbuf *pixbuf;
289   NotificationPopup *ppopup;
290   gchar *summary = NULL;
291   gchar *text = NULL;
292   gchar *utf8_str = NULL;
293   gchar *subj = NULL;
294   gchar *from = NULL;
295   gchar *foldname = NULL;
296   GList *caps = NULL;
297   gboolean support_actions = FALSE;
298
299   g_return_val_if_fail(msginfo, FALSE);
300
301   ppopup = &(popup[nftype]);
302
303   /* init libnotify if necessary */
304   if(!notify_is_initted()) {
305     if(!notify_init("claws-mail")) {
306       debug_print("Notification Plugin: Failed to initialize libnotify. "
307                   "No popup will be shown.\n");
308       return FALSE;
309     }
310   }
311
312   switch(nftype) {
313   case F_TYPE_MAIL:
314     summary = _("New Mail message");
315     from    = notification_libnotify_sanitize_str(msginfo->from ?
316                                                   msginfo->from : _("(No From)"));
317     subj    = notification_libnotify_sanitize_str(msginfo->subject ?
318                                                   msginfo->subject : _("(No Subject)"));
319         if (notify_config.popup_display_folder_name) {
320                 foldname = notification_libnotify_sanitize_str(msginfo->folder->path);
321         text = g_strconcat(from,"\n\n", subj, "\n\n", foldname, NULL);
322         }
323         else
324                 text = g_strconcat(from, "\n\n",subj, NULL);
325
326     /* Make sure text is valid UTF8 */
327     utf8_str = notification_validate_utf8_str(text);
328     g_free(text);
329
330     if(from) g_free(from);
331     if(subj) g_free(subj);
332     if(foldname) g_free(foldname);
333     break;
334   case F_TYPE_NEWS:
335     summary = _("New News post");
336     utf8_str    = g_strdup(_("A new message arrived"));
337     break;
338   case F_TYPE_CALENDAR:
339     summary = _("New Calendar message");
340     utf8_str    = g_strdup(_("A new calendar message arrived"));
341     break;
342   case F_TYPE_RSS:
343     summary = _("New RSS feed article");
344     utf8_str = g_strdup(_("A new article in a RSS feed arrived"));
345     break;
346   default:
347     summary = _("New unknown message");
348     utf8_str = g_strdup(_("Unknown message type arrived"));
349     break;
350   }
351
352   ppopup->notification = notify_notification_new(summary, utf8_str, NULL
353 #if !NOTIFY_CHECK_VERSION(0, 7, 0)
354       , NULL
355 #endif
356       );
357   g_free(utf8_str);
358   if(ppopup->notification == NULL) {
359     debug_print("Notification Plugin: Failed to create a new "
360                 "notification.\n");
361     return FALSE;
362   }
363
364   caps = notify_get_server_caps();
365     if(caps != NULL) {
366       GList *c;
367       for(c = caps; c != NULL; c = c->next) {
368         if(strcmp((char*)c->data, "actions") == 0 ) {
369           support_actions = TRUE;
370           break;
371         }
372       }
373
374     g_list_foreach(caps, (GFunc)g_free, NULL);
375     g_list_free(caps);
376   }
377
378   /* Default action */
379   if (support_actions)
380     notify_notification_add_action(ppopup->notification,
381                                    "default", _("Present main window"),
382                                    (NotifyActionCallback)default_action_cb,
383                                    GINT_TO_POINTER(nftype),
384                                    notification_libnotify_free_func);
385
386   /* Icon */
387   pixbuf = NULL;
388 #ifndef USE_NEW_ADDRBOOK
389   if(msginfo && msginfo->from) {
390     gchar *icon_path;
391     icon_path = addrindex_get_picture_file(msginfo->from);
392     if(is_file_exist(icon_path)) {
393       GError *error = NULL;
394       gint w, h;
395
396       gdk_pixbuf_get_file_info(icon_path, &w, &h);
397       if((w > 64) || (h > 64))
398         pixbuf = gdk_pixbuf_new_from_file_at_scale(icon_path,
399                                                    64, 64, TRUE, &error);
400       else
401         pixbuf = gdk_pixbuf_new_from_file(icon_path, &error);
402
403       if(!pixbuf) {
404         debug_print("Could not load picture file: %s\n",
405                     error ? error->message : "no details");
406         g_error_free(error);
407       }
408     }
409     else
410       debug_print("Picture path does not exist: %s\n",icon_path);
411     g_free(icon_path);
412   }
413 #endif
414   if(!pixbuf)
415    pixbuf = g_object_ref(notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64));
416
417   if(pixbuf) {
418     notify_notification_set_icon_from_pixbuf(ppopup->notification, pixbuf);
419     g_object_unref(pixbuf);
420   }
421   else /* This is not fatal */
422     debug_print("Notification plugin: Icon could not be loaded.\n");
423
424   /* timeout */
425   notify_notification_set_timeout(ppopup->notification, notify_config.popup_timeout);
426
427   /* Category */
428   notify_notification_set_category(ppopup->notification, "email.arrived");
429
430   /* get notified on bubble close */
431   g_signal_connect(G_OBJECT(popup->notification), "closed", G_CALLBACK(popup_timeout_fun), NULL);
432
433   /* Show the popup */
434   notify_notification_set_hint_string(ppopup->notification, "desktop-entry", "claws-mail");
435   if(!notify_notification_show(ppopup->notification, &(ppopup->error))) {
436     debug_print("Notification Plugin: Failed to send notification: %s\n",
437                 ppopup->error->message);
438     g_clear_error(&(ppopup->error));
439     g_object_unref(G_OBJECT(ppopup->notification));
440     ppopup->notification = NULL;
441     return FALSE;
442   }
443
444   debug_print("Notification Plugin: Popup created with libnotify.\n");
445   ppopup->count = 1;
446
447   /* Store path to message */
448   if(nftype == F_TYPE_MAIL) {
449     if(msginfo && msginfo->folder) {
450       gchar *ident;
451       ident = folder_item_get_identifier(msginfo->folder);
452       ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
453                                          msginfo->msgnum);
454       g_free(ident);
455     }
456     else
457       ppopup->msg_path = NULL;
458   }
459
460   return TRUE;
461 }
462
463 static gboolean notification_libnotify_add_msg(MsgInfo *msginfo,
464                                                NotificationFolderType nftype)
465 {
466   gchar *summary;
467   gchar *text;
468   gboolean retval;
469   NotificationPopup *ppopup;
470   GdkPixbuf *pixbuf;
471
472   ppopup = &(popup[nftype]);
473
474   if(!ppopup->notification)
475     return notification_libnotify_create(msginfo,nftype);
476
477   ppopup->count++;
478
479   if(ppopup->msg_path) {
480     g_free(ppopup->msg_path);
481     ppopup->msg_path = NULL;
482   }
483
484   /* make sure we show a logo on many msg arrival */
485   pixbuf = notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64);
486   if(pixbuf)
487     notify_notification_set_icon_from_pixbuf(ppopup->notification, pixbuf);
488
489   switch(nftype) {
490   case F_TYPE_MAIL:
491     summary = _("Mail message");
492     text = g_strdup_printf(ngettext("%d new message arrived",
493                                     "%d new messages arrived",
494                                     ppopup->count), ppopup->count);
495     break;
496   case F_TYPE_NEWS:
497     summary = _("News message");
498     text = g_strdup_printf(ngettext("%d new message arrived",
499                                      "%d new messages arrived",
500                                      ppopup->count), ppopup->count);
501     break;
502   case F_TYPE_CALENDAR:
503     summary = _("Calendar message");
504     text = g_strdup_printf(ngettext("%d new calendar message arrived",
505                                      "%d new calendar messages arrived",
506                                      ppopup->count), ppopup->count);
507     break;
508   case F_TYPE_RSS:
509     summary = _("RSS news feed");
510     text = g_strdup_printf(ngettext("%d new article in a RSS feed arrived",
511                                      "%d new articles in a RSS feed arrived",
512                                      ppopup->count), ppopup->count);
513     break;
514   default:
515     /* Should not happen */
516     debug_print("Notification Plugin: Unknown folder type ignored\n");
517     return FALSE;
518   }
519
520   retval = notify_notification_update(ppopup->notification, summary,
521                                       text, NULL);
522   g_free(text);
523   if(!retval) {
524     debug_print("Notification Plugin: Failed to update notification.\n");
525     return FALSE;
526   }
527
528   /* Show the popup */
529   notify_notification_set_hint_string(ppopup->notification, "desktop-entry", "claws-mail");
530   if(!notify_notification_show(ppopup->notification, &(ppopup->error))) {
531     debug_print("Notification Plugin: Failed to send updated notification: "
532                 "%s\n", ppopup->error->message);
533     g_clear_error(&(ppopup->error));
534     return FALSE;
535   }
536
537   debug_print("Notification Plugin: Popup successfully modified "
538               "with libnotify.\n");
539   return TRUE;
540 }
541
542 void notification_libnotify_free_func(gpointer data)
543 {
544   if(popup[F_TYPE_MAIL].msg_path) {
545     g_free(popup[F_TYPE_MAIL].msg_path);
546     popup[F_TYPE_MAIL].msg_path = NULL;
547   }
548   debug_print("Freed notification data\n");
549 }
550
551 #else /* !HAVE_LIBNOTIFY */
552 static gboolean notification_popup_add_msg(MsgInfo *msginfo)
553 {
554   gchar *message;
555   NotificationPopup *ppopup;
556
557   ppopup = &popup;
558
559   if(!ppopup->window)
560     return notification_popup_create(msginfo);
561
562   ppopup->count++;
563
564   if(ppopup->msg_path) {
565     g_free(ppopup->msg_path);
566     ppopup->msg_path = NULL;
567   }
568
569   if(ppopup->label2) {
570     gtk_widget_destroy(ppopup->label2);
571                 ppopup->label2 = NULL;
572         }
573
574   message = g_strdup_printf(ngettext("%d new message",
575                                      "%d new messages",
576                                      ppopup->count), ppopup->count);
577   gtk_label_set_text(GTK_LABEL(ppopup->label1), message);
578   g_free(message);
579   return TRUE;
580 }
581
582 static gboolean notification_popup_create(MsgInfo *msginfo)
583 {
584   GdkColor bg;
585   GdkColor fg;
586   NotificationPopup *ppopup;
587
588   g_return_val_if_fail(msginfo, FALSE);
589
590   ppopup = &popup;
591
592   /* Window */
593   ppopup->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_popup");
594   gtk_window_set_decorated(GTK_WINDOW(ppopup->window), FALSE);
595   gtk_window_set_keep_above(GTK_WINDOW(ppopup->window), TRUE);
596   gtk_window_set_accept_focus(GTK_WINDOW(ppopup->window), FALSE);
597   gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ppopup->window), TRUE);
598   gtk_window_set_skip_pager_hint(GTK_WINDOW(ppopup->window), TRUE);
599   gtk_window_move(GTK_WINDOW(ppopup->window), notify_config.popup_root_x,
600                   notify_config.popup_root_y);
601   gtk_window_resize(GTK_WINDOW(ppopup->window), notify_config.popup_width, 1);
602   if(notify_config.popup_sticky)
603     gtk_window_stick(GTK_WINDOW(ppopup->window));
604   /* Signals */
605   gtk_widget_set_events(ppopup->window,
606                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
607   g_signal_connect(ppopup->window, "button_press_event",
608                    G_CALLBACK(notification_popup_button), NULL);
609
610   /* Event box */
611   ppopup->event_box = gtk_event_box_new();
612   gtk_container_add(GTK_CONTAINER(ppopup->window), ppopup->event_box);
613
614   /* Frame */
615   ppopup->frame = gtk_frame_new(NULL);
616   gtk_frame_set_shadow_type(GTK_FRAME(ppopup->frame), GTK_SHADOW_ETCHED_OUT);
617   gtk_container_add(GTK_CONTAINER(ppopup->event_box), ppopup->frame);
618
619   /* Vbox with labels */
620   ppopup->vbox = gtk_vbox_new(FALSE, 2);
621   gtk_container_set_border_width(GTK_CONTAINER(ppopup->vbox), 5);
622   ppopup->label1 = gtk_label_new(msginfo->from ?
623                                  msginfo->from : _("(No From)"));
624   gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label1, FALSE, FALSE, 0);
625
626   ppopup->label2 = gtk_label_new(msginfo->subject ?
627                                  msginfo->subject : _("(No Subject)"));
628   gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label2, FALSE, FALSE, 0);
629
630   gtk_container_add(GTK_CONTAINER(ppopup->frame), ppopup->vbox);
631   gtk_widget_set_size_request(ppopup->vbox, notify_config.popup_width, -1);
632
633   /* Color */
634   if(notify_config.popup_enable_colors) {
635     gtkut_convert_int_to_gdk_color(notify_config.popup_color_bg,&bg);
636     gtkut_convert_int_to_gdk_color(notify_config.popup_color_fg,&fg);
637     gtk_widget_modify_bg(ppopup->event_box,GTK_STATE_NORMAL,&bg);
638     gtk_widget_modify_fg(ppopup->label1,GTK_STATE_NORMAL,&fg);
639     gtk_widget_modify_fg(ppopup->label2,GTK_STATE_NORMAL,&fg);
640   }
641
642   gtk_widget_show_all(ppopup->window);
643
644   ppopup->count = 1;
645
646   if(msginfo->folder && msginfo->folder->name) {
647       gchar *ident;
648       ident = folder_item_get_identifier(msginfo->folder);
649       ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
650                                          msginfo->msgnum);
651       g_free(ident);
652   }
653
654   return TRUE;
655 }
656
657 static gboolean notification_popup_button(GtkWidget *widget,
658                                           GdkEventButton *event,
659                                           gpointer data)
660 {
661   if(event->type == GDK_BUTTON_PRESS) {
662     if(event->button == 1) {
663       MainWindow *mainwin;
664       /* Let mainwindow pop up */
665       mainwin = mainwindow_get_mainwindow();
666       if(!mainwin)
667         return TRUE;
668       notification_show_mainwindow(mainwin);
669       /* If there is only one new mail message, jump to this message */
670       if(popup.count == 1) {
671         gchar *select_str;
672         G_LOCK(popup);
673         select_str = g_strdup(popup.msg_path);
674         G_UNLOCK(popup);
675         debug_print("Select message %s\n", select_str);
676         mainwindow_jump_to(select_str, FALSE);
677         g_free(select_str);
678       }
679     }
680   }
681   return TRUE;
682 }
683 #endif /* !HAVE_LIBNOTIFY */
684
685 #endif /* NOTIFICATION_POPUP */