Fix another double define, and add subdir-objects where needed
[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   gboolean retval;
96   FolderType ftype;
97   NotificationPopup *ppopup;
98 #if HAVE_LIBNOTIFY
99   gchar *uistr;
100 #endif
101   NotificationFolderType nftype;
102
103   nftype = F_TYPE_MAIL;
104
105   if(!msginfo || !notify_config.popup_show)
106     return;
107
108   if(notify_config.popup_folder_specific) {
109     guint id;
110     GSList *list;
111     gchar *identifier;
112     gboolean found = FALSE;
113
114     if(!(msginfo->folder))
115       return;
116
117     identifier = folder_item_get_identifier(msginfo->folder);
118
119     id =
120       notification_register_folder_specific_list(POPUP_SPECIFIC_FOLDER_ID_STR);
121     list = notification_foldercheck_get_list(id);
122     for(; (list != NULL) && !found; list = g_slist_next(list)) {
123       gchar *list_identifier;
124       FolderItem *list_item = (FolderItem*) list->data;
125
126       list_identifier = folder_item_get_identifier(list_item);
127       if(!strcmp2(list_identifier, identifier))
128         found = TRUE;
129
130       g_free(list_identifier);
131     }
132     g_free(identifier);
133
134     if(!found)
135       return;
136   }
137
138   ftype = msginfo->folder->folder->klass->type;
139
140   G_LOCK(popup);
141 #ifdef HAVE_LIBNOTIFY
142   /* Check out which type to notify about */
143   switch(ftype) {
144   case F_MH:
145   case F_MBOX:
146   case F_MAILDIR:
147   case F_IMAP:
148     nftype = F_TYPE_MAIL;
149     break;
150   case F_NEWS:
151     nftype = F_TYPE_NEWS;
152     break;
153   case F_UNKNOWN:
154     if((uistr = msginfo->folder->folder->klass->uistr) == NULL) {
155       G_UNLOCK(popup);
156       return;
157     }
158     else if(!strcmp(uistr, "vCalendar"))
159       nftype = F_TYPE_CALENDAR;
160     else if(!strcmp(uistr, "RSSyl"))
161       nftype = F_TYPE_RSS;
162     else {
163       debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
164       G_UNLOCK(popup);
165       return;
166     }
167     break;
168   default:
169     debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
170     G_UNLOCK(popup);
171     return;
172   }
173
174   ppopup = &(popup[nftype]);
175   retval = 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
572   message = g_strdup_printf(ngettext("%d new message",
573                                      "%d new messages",
574                                      ppopup->count), ppopup->count);
575   gtk_label_set_text(GTK_LABEL(ppopup->label1), message);
576   g_free(message);
577   return TRUE;
578 }
579
580 static gboolean notification_popup_create(MsgInfo *msginfo)
581 {
582   GdkColor bg;
583   GdkColor fg;
584   NotificationPopup *ppopup;
585
586   g_return_val_if_fail(msginfo, FALSE);
587
588   ppopup = &popup;
589
590   /* Window */
591   ppopup->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_popup");
592   gtk_window_set_decorated(GTK_WINDOW(ppopup->window), FALSE);
593   gtk_window_set_keep_above(GTK_WINDOW(ppopup->window), TRUE);
594   gtk_window_set_accept_focus(GTK_WINDOW(ppopup->window), FALSE);
595   gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ppopup->window), TRUE);
596   gtk_window_set_skip_pager_hint(GTK_WINDOW(ppopup->window), TRUE);
597   gtk_window_move(GTK_WINDOW(ppopup->window), notify_config.popup_root_x,
598                   notify_config.popup_root_y);
599   gtk_window_resize(GTK_WINDOW(ppopup->window), notify_config.popup_width, 1);
600   if(notify_config.popup_sticky)
601     gtk_window_stick(GTK_WINDOW(ppopup->window));
602   /* Signals */
603   gtk_widget_set_events(ppopup->window,
604                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
605   g_signal_connect(ppopup->window, "button_press_event",
606                    G_CALLBACK(notification_popup_button), NULL);
607
608   /* Event box */
609   ppopup->event_box = gtk_event_box_new();
610   gtk_container_add(GTK_CONTAINER(ppopup->window), ppopup->event_box);
611
612   /* Frame */
613   ppopup->frame = gtk_frame_new(NULL);
614   gtk_frame_set_shadow_type(GTK_FRAME(ppopup->frame), GTK_SHADOW_ETCHED_OUT);
615   gtk_container_add(GTK_CONTAINER(ppopup->event_box), ppopup->frame);
616
617   /* Vbox with labels */
618   ppopup->vbox = gtk_vbox_new(FALSE, 2);
619   gtk_container_set_border_width(GTK_CONTAINER(ppopup->vbox), 5);
620   ppopup->label1 = gtk_label_new(msginfo->from ?
621                                  msginfo->from : _("(No From)"));
622   gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label1, FALSE, FALSE, 0);
623
624   ppopup->label2 = gtk_label_new(msginfo->subject ?
625                                  msginfo->subject : _("(No Subject)"));
626   gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label2, FALSE, FALSE, 0);
627
628   gtk_container_add(GTK_CONTAINER(ppopup->frame), ppopup->vbox);
629   gtk_widget_set_size_request(ppopup->vbox, notify_config.popup_width, -1);
630
631   /* Color */
632   if(notify_config.popup_enable_colors) {
633     gtkut_convert_int_to_gdk_color(notify_config.popup_color_bg,&bg);
634     gtkut_convert_int_to_gdk_color(notify_config.popup_color_fg,&fg);
635     gtk_widget_modify_bg(ppopup->event_box,GTK_STATE_NORMAL,&bg);
636     gtk_widget_modify_fg(ppopup->label1,GTK_STATE_NORMAL,&fg);
637     gtk_widget_modify_fg(ppopup->label2,GTK_STATE_NORMAL,&fg);
638   }
639
640   gtk_widget_show_all(ppopup->window);
641
642   ppopup->count = 1;
643
644   if(msginfo->folder && msginfo->folder->name) {
645       gchar *ident;
646       ident = folder_item_get_identifier(msginfo->folder);
647       ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
648                                          msginfo->msgnum);
649       g_free(ident);
650   }
651
652   return TRUE;
653 }
654
655 static gboolean notification_popup_button(GtkWidget *widget,
656                                           GdkEventButton *event,
657                                           gpointer data)
658 {
659   if(event->type == GDK_BUTTON_PRESS) {
660     if(event->button == 1) {
661       MainWindow *mainwin;
662       /* Let mainwindow pop up */
663       mainwin = mainwindow_get_mainwindow();
664       if(!mainwin)
665         return TRUE;
666       notification_show_mainwindow(mainwin);
667       /* If there is only one new mail message, jump to this message */
668       if(popup.count == 1) {
669         gchar *select_str;
670         G_LOCK(popup);
671         select_str = g_strdup(popup.msg_path);
672         G_UNLOCK(popup);
673         debug_print("Select message %s\n", select_str);
674         mainwindow_jump_to(select_str, FALSE);
675         g_free(select_str);
676       }
677     }
678   }
679   return TRUE;
680 }
681 #endif /* !HAVE_LIBNOTIFY */
682
683 #endif /* NOTIFICATION_POPUP */