baa0826428bab3a273375ce299096d6ae84889e3
[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_ALT_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 #if HAVE_LIBNOTIFY
96   FolderType ftype;
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(!g_strcmp0(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
140   G_LOCK(popup);
141 #ifdef HAVE_LIBNOTIFY
142   /* Check out which type to notify about */
143   ftype = msginfo->folder->folder->klass->type;
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
231   G_LOCK(popup);
232
233   ppopup = &popup;
234   if(ppopup->window) {
235     gtk_widget_destroy(ppopup->window);
236     ppopup->window = NULL;
237   }
238   ppopup->timeout_id = 0;
239
240   if(ppopup->msg_path) {
241     g_free(ppopup->msg_path);
242     ppopup->msg_path = NULL;
243   }
244   ppopup->count = 0;
245   G_UNLOCK(popup);
246   debug_print("Notification Plugin: Popup closed due to timeout.\n");
247   return FALSE;
248 }
249 #endif
250
251 #ifdef HAVE_LIBNOTIFY
252 static void default_action_cb(NotifyNotification *notification,
253                               const char *action,
254                               void *user_data)
255 {
256   if(strcmp("default", action))
257     return;
258
259   MainWindow *mainwin;
260   mainwin = mainwindow_get_mainwindow();
261   if(mainwin) {
262     NotificationFolderType nftype;
263
264     /* Let mainwindow pop up */
265     notification_show_mainwindow(mainwin);
266     /* If there is only one new mail message, jump to this message */
267     nftype = (NotificationFolderType)GPOINTER_TO_INT(user_data);
268     if(nftype == F_TYPE_MAIL) {
269       if(popup[F_TYPE_MAIL].count == 1) {
270         gchar *select_str;
271         G_LOCK(popup);
272         select_str = g_strdup(popup[F_TYPE_MAIL].msg_path);
273         G_UNLOCK(popup);
274         debug_print("Select message %s\n", select_str);
275         mainwindow_jump_to(select_str, FALSE);
276         g_free(select_str);
277       }
278     }
279   }
280 }
281
282 static gboolean notification_libnotify_create(MsgInfo *msginfo,
283                                               NotificationFolderType nftype)
284 {
285   GdkPixbuf *pixbuf;
286   NotificationPopup *ppopup;
287   gchar *summary = NULL;
288   gchar *text = NULL;
289   gchar *utf8_str = NULL;
290   gchar *subj = NULL;
291   gchar *from = NULL;
292   gchar *foldname = NULL;
293   GList *caps = NULL;
294   gboolean support_actions = FALSE;
295
296   g_return_val_if_fail(msginfo, FALSE);
297
298   ppopup = &(popup[nftype]);
299
300   /* init libnotify if necessary */
301   if(!notify_is_initted()) {
302     if(!notify_init("claws-mail")) {
303       debug_print("Notification Plugin: Failed to initialize libnotify. "
304                   "No popup will be shown.\n");
305       return FALSE;
306     }
307   }
308
309   switch(nftype) {
310   case F_TYPE_MAIL:
311     summary = _("New Mail message");
312     from    = notification_libnotify_sanitize_str(msginfo->from ?
313                                                   msginfo->from : _("(No From)"));
314     subj    = notification_libnotify_sanitize_str(msginfo->subject ?
315                                                   msginfo->subject : _("(No Subject)"));
316         if (notify_config.popup_display_folder_name) {
317                 foldname = notification_libnotify_sanitize_str(msginfo->folder->path);
318         text = g_strconcat(from,"\n\n", subj, "\n\n", foldname, NULL);
319         }
320         else
321                 text = g_strconcat(from, "\n\n",subj, NULL);
322
323     /* Make sure text is valid UTF8 */
324     utf8_str = notification_validate_utf8_str(text);
325     g_free(text);
326
327     if(from) g_free(from);
328     if(subj) g_free(subj);
329     if(foldname) g_free(foldname);
330     break;
331   case F_TYPE_NEWS:
332     summary = _("New News post");
333     utf8_str    = g_strdup(_("A new message arrived"));
334     break;
335   case F_TYPE_CALENDAR:
336     summary = _("New Calendar message");
337     utf8_str    = g_strdup(_("A new calendar message arrived"));
338     break;
339   case F_TYPE_RSS:
340     summary = _("New RSS feed article");
341     utf8_str = g_strdup(_("A new article in a RSS feed arrived"));
342     break;
343   default:
344     summary = _("New unknown message");
345     utf8_str = g_strdup(_("Unknown message type arrived"));
346     break;
347   }
348
349   ppopup->notification = notify_notification_new(summary, utf8_str, NULL
350 #if !NOTIFY_CHECK_VERSION(0, 7, 0)
351       , NULL
352 #endif
353       );
354   g_free(utf8_str);
355   if(ppopup->notification == NULL) {
356     debug_print("Notification Plugin: Failed to create a new "
357                 "notification.\n");
358     return FALSE;
359   }
360
361   caps = notify_get_server_caps();
362     if(caps != NULL) {
363       GList *c;
364       for(c = caps; c != NULL; c = c->next) {
365         if(strcmp((char*)c->data, "actions") == 0 ) {
366           support_actions = TRUE;
367           break;
368         }
369       }
370
371     g_list_foreach(caps, (GFunc)g_free, NULL);
372     g_list_free(caps);
373   }
374
375   /* Default action */
376   if (support_actions)
377     notify_notification_add_action(ppopup->notification,
378                                    "default", _("Present main window"),
379                                    (NotifyActionCallback)default_action_cb,
380                                    GINT_TO_POINTER(nftype),
381                                    notification_libnotify_free_func);
382
383   /* Icon */
384   pixbuf = NULL;
385 #ifndef USE_ALT_ADDRBOOK
386   if(msginfo && msginfo->from) {
387     gchar *icon_path;
388     icon_path = addrindex_get_picture_file(msginfo->from);
389     if(is_file_exist(icon_path)) {
390       GError *error = NULL;
391       gint w, h;
392
393       gdk_pixbuf_get_file_info(icon_path, &w, &h);
394       if((w > 64) || (h > 64))
395         pixbuf = gdk_pixbuf_new_from_file_at_scale(icon_path,
396                                                    64, 64, TRUE, &error);
397       else
398         pixbuf = gdk_pixbuf_new_from_file(icon_path, &error);
399
400       if(!pixbuf) {
401         debug_print("Could not load picture file: %s\n",
402                     error ? error->message : "no details");
403         g_error_free(error);
404       }
405     }
406     else
407       debug_print("Picture path does not exist: %s\n",icon_path);
408     g_free(icon_path);
409   }
410 #endif
411   if(!pixbuf)
412    pixbuf = g_object_ref(notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64));
413
414   if(pixbuf) {
415     notify_notification_set_icon_from_pixbuf(ppopup->notification, pixbuf);
416     g_object_unref(pixbuf);
417   }
418   else /* This is not fatal */
419     debug_print("Notification plugin: Icon could not be loaded.\n");
420
421   /* timeout */
422   notify_notification_set_timeout(ppopup->notification, notify_config.popup_timeout);
423
424   /* Category */
425   notify_notification_set_category(ppopup->notification, "email.arrived");
426
427   /* get notified on bubble close */
428   g_signal_connect(G_OBJECT(popup->notification), "closed", G_CALLBACK(popup_timeout_fun), NULL);
429
430   /* Show the popup */
431   notify_notification_set_hint_string(ppopup->notification, "desktop-entry", "claws-mail");
432   if(!notify_notification_show(ppopup->notification, &(ppopup->error))) {
433     debug_print("Notification Plugin: Failed to send notification: %s\n",
434                 ppopup->error->message);
435     g_clear_error(&(ppopup->error));
436     g_object_unref(G_OBJECT(ppopup->notification));
437     ppopup->notification = NULL;
438     return FALSE;
439   }
440
441   debug_print("Notification Plugin: Popup created with libnotify.\n");
442   ppopup->count = 1;
443
444   /* Store path to message */
445   if(nftype == F_TYPE_MAIL) {
446     if(msginfo && msginfo->folder) {
447       gchar *ident;
448       ident = folder_item_get_identifier(msginfo->folder);
449       ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
450                                          msginfo->msgnum);
451       g_free(ident);
452     }
453     else
454       ppopup->msg_path = NULL;
455   }
456
457   return TRUE;
458 }
459
460 static gboolean notification_libnotify_add_msg(MsgInfo *msginfo,
461                                                NotificationFolderType nftype)
462 {
463   gchar *summary;
464   gchar *text;
465   gboolean retval;
466   NotificationPopup *ppopup;
467   GdkPixbuf *pixbuf;
468
469   ppopup = &(popup[nftype]);
470
471   if(!ppopup->notification)
472     return notification_libnotify_create(msginfo,nftype);
473
474   ppopup->count++;
475
476   if(ppopup->msg_path) {
477     g_free(ppopup->msg_path);
478     ppopup->msg_path = NULL;
479   }
480
481   /* make sure we show a logo on many msg arrival */
482   pixbuf = notification_pixbuf_get(NOTIFICATION_CM_LOGO_64x64);
483   if(pixbuf)
484     notify_notification_set_icon_from_pixbuf(ppopup->notification, pixbuf);
485
486   switch(nftype) {
487   case F_TYPE_MAIL:
488     summary = _("Mail message");
489     text = g_strdup_printf(ngettext("%d new message arrived",
490                                     "%d new messages arrived",
491                                     ppopup->count), ppopup->count);
492     break;
493   case F_TYPE_NEWS:
494     summary = _("News message");
495     text = g_strdup_printf(ngettext("%d new message arrived",
496                                      "%d new messages arrived",
497                                      ppopup->count), ppopup->count);
498     break;
499   case F_TYPE_CALENDAR:
500     summary = _("Calendar message");
501     text = g_strdup_printf(ngettext("%d new calendar message arrived",
502                                      "%d new calendar messages arrived",
503                                      ppopup->count), ppopup->count);
504     break;
505   case F_TYPE_RSS:
506     summary = _("RSS news feed");
507     text = g_strdup_printf(ngettext("%d new article in a RSS feed arrived",
508                                      "%d new articles in a RSS feed arrived",
509                                      ppopup->count), ppopup->count);
510     break;
511   default:
512     /* Should not happen */
513     debug_print("Notification Plugin: Unknown folder type ignored\n");
514     return FALSE;
515   }
516
517   retval = notify_notification_update(ppopup->notification, summary,
518                                       text, NULL);
519   g_free(text);
520   if(!retval) {
521     debug_print("Notification Plugin: Failed to update notification.\n");
522     return FALSE;
523   }
524
525   /* Show the popup */
526   notify_notification_set_hint_string(ppopup->notification, "desktop-entry", "claws-mail");
527   if(!notify_notification_show(ppopup->notification, &(ppopup->error))) {
528     debug_print("Notification Plugin: Failed to send updated notification: "
529                 "%s\n", ppopup->error->message);
530     g_clear_error(&(ppopup->error));
531     return FALSE;
532   }
533
534   debug_print("Notification Plugin: Popup successfully modified "
535               "with libnotify.\n");
536   return TRUE;
537 }
538
539 void notification_libnotify_free_func(gpointer data)
540 {
541   if(popup[F_TYPE_MAIL].msg_path) {
542     g_free(popup[F_TYPE_MAIL].msg_path);
543     popup[F_TYPE_MAIL].msg_path = NULL;
544   }
545   debug_print("Freed notification data\n");
546 }
547
548 #else /* !HAVE_LIBNOTIFY */
549 static gboolean notification_popup_add_msg(MsgInfo *msginfo)
550 {
551   gchar *message;
552   NotificationPopup *ppopup;
553
554   ppopup = &popup;
555
556   if(!ppopup->window)
557     return notification_popup_create(msginfo);
558
559   ppopup->count++;
560
561   if(ppopup->msg_path) {
562     g_free(ppopup->msg_path);
563     ppopup->msg_path = NULL;
564   }
565
566   if(ppopup->label2) {
567     gtk_widget_destroy(ppopup->label2);
568                 ppopup->label2 = NULL;
569         }
570
571   message = g_strdup_printf(ngettext("%d new message",
572                                      "%d new messages",
573                                      ppopup->count), ppopup->count);
574   gtk_label_set_text(GTK_LABEL(ppopup->label1), message);
575   g_free(message);
576   return TRUE;
577 }
578
579 static gboolean notification_popup_create(MsgInfo *msginfo)
580 {
581   GdkColor bg;
582   GdkColor fg;
583   NotificationPopup *ppopup;
584
585   g_return_val_if_fail(msginfo, FALSE);
586
587   ppopup = &popup;
588
589   /* Window */
590   ppopup->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "notification_popup");
591   gtk_window_set_decorated(GTK_WINDOW(ppopup->window), FALSE);
592   gtk_window_set_keep_above(GTK_WINDOW(ppopup->window), TRUE);
593   gtk_window_set_accept_focus(GTK_WINDOW(ppopup->window), FALSE);
594   gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ppopup->window), TRUE);
595   gtk_window_set_skip_pager_hint(GTK_WINDOW(ppopup->window), TRUE);
596   gtk_window_move(GTK_WINDOW(ppopup->window), notify_config.popup_root_x,
597                   notify_config.popup_root_y);
598   gtk_window_resize(GTK_WINDOW(ppopup->window), notify_config.popup_width, 1);
599   if(notify_config.popup_sticky)
600     gtk_window_stick(GTK_WINDOW(ppopup->window));
601   /* Signals */
602   gtk_widget_set_events(ppopup->window,
603                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
604   g_signal_connect(ppopup->window, "button_press_event",
605                    G_CALLBACK(notification_popup_button), NULL);
606
607   /* Event box */
608   ppopup->event_box = gtk_event_box_new();
609   gtk_container_add(GTK_CONTAINER(ppopup->window), ppopup->event_box);
610
611   /* Frame */
612   ppopup->frame = gtk_frame_new(NULL);
613   gtk_frame_set_shadow_type(GTK_FRAME(ppopup->frame), GTK_SHADOW_ETCHED_OUT);
614   gtk_container_add(GTK_CONTAINER(ppopup->event_box), ppopup->frame);
615
616   /* Vbox with labels */
617   ppopup->vbox = gtk_vbox_new(FALSE, 2);
618   gtk_container_set_border_width(GTK_CONTAINER(ppopup->vbox), 5);
619   ppopup->label1 = gtk_label_new(msginfo->from ?
620                                  msginfo->from : _("(No From)"));
621   gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label1, FALSE, FALSE, 0);
622
623   ppopup->label2 = gtk_label_new(msginfo->subject ?
624                                  msginfo->subject : _("(No Subject)"));
625   gtk_box_pack_start(GTK_BOX(ppopup->vbox), ppopup->label2, FALSE, FALSE, 0);
626
627   gtk_container_add(GTK_CONTAINER(ppopup->frame), ppopup->vbox);
628   gtk_widget_set_size_request(ppopup->vbox, notify_config.popup_width, -1);
629
630   /* Color */
631   if(notify_config.popup_enable_colors) {
632     gtkut_convert_int_to_gdk_color(notify_config.popup_color_bg,&bg);
633     gtkut_convert_int_to_gdk_color(notify_config.popup_color_fg,&fg);
634     gtk_widget_modify_bg(ppopup->event_box,GTK_STATE_NORMAL,&bg);
635     gtk_widget_modify_fg(ppopup->label1,GTK_STATE_NORMAL,&fg);
636     gtk_widget_modify_fg(ppopup->label2,GTK_STATE_NORMAL,&fg);
637   }
638
639   gtk_widget_show_all(ppopup->window);
640
641   ppopup->count = 1;
642
643   if(msginfo->folder && msginfo->folder->name) {
644       gchar *ident;
645       ident = folder_item_get_identifier(msginfo->folder);
646       ppopup->msg_path = g_strdup_printf("%s%s%u", ident,G_DIR_SEPARATOR_S,
647                                          msginfo->msgnum);
648       g_free(ident);
649   }
650
651   return TRUE;
652 }
653
654 static gboolean notification_popup_button(GtkWidget *widget,
655                                           GdkEventButton *event,
656                                           gpointer data)
657 {
658   if(event->type == GDK_BUTTON_PRESS) {
659     if(event->button == 1) {
660       MainWindow *mainwin;
661       /* Let mainwindow pop up */
662       mainwin = mainwindow_get_mainwindow();
663       if(!mainwin)
664         return TRUE;
665       notification_show_mainwindow(mainwin);
666       /* If there is only one new mail message, jump to this message */
667       if(popup.count == 1) {
668         gchar *select_str;
669         G_LOCK(popup);
670         select_str = g_strdup(popup.msg_path);
671         G_UNLOCK(popup);
672         debug_print("Select message %s\n", select_str);
673         mainwindow_jump_to(select_str, FALSE);
674         g_free(select_str);
675       }
676     }
677   }
678   return TRUE;
679 }
680 #endif /* !HAVE_LIBNOTIFY */
681
682 #endif /* NOTIFICATION_POPUP */