1 /* Notification plugin for Claws-Mail
2 * Copyright (C) 2005-2007 Holger Berndt
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.
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.
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/>.
18 #include "pluginconfig.h"
21 #include "folderview.h"
23 #include "gtk/gtkutils.h"
25 #include "notification_core.h"
26 #include "notification_plugin.h"
27 #include "notification_prefs.h"
28 #include "notification_banner.h"
29 #include "notification_popup.h"
30 #include "notification_command.h"
31 #include "notification_lcdproc.h"
32 #include "notification_trayicon.h"
33 #include "notification_indicator.h"
35 #ifdef HAVE_LIBCANBERRA_GTK
36 # include <canberra-gtk.h>
40 GSList *collected_msgs;
47 static gboolean notification_traverse_collect(GNode*, gpointer);
48 static void notification_new_unnotified_do_msg(MsgInfo*);
49 static gboolean notification_traverse_hash_startup(GNode*, gpointer);
51 static GHashTable *msg_count_hash;
52 static NotificationMsgCount msg_count;
54 #ifdef HAVE_LIBCANBERRA_GTK
55 static gboolean canberra_new_email_is_playing = FALSE;
58 static void msg_count_hash_update_func(FolderItem*, gpointer);
59 static void msg_count_update_from_hash(gpointer, gpointer, gpointer);
60 static void msg_count_clear(NotificationMsgCount*);
61 static void msg_count_add(NotificationMsgCount*,NotificationMsgCount*);
62 static void msg_count_copy(NotificationMsgCount*,NotificationMsgCount*);
64 void notification_core_global_includes_changed(void)
66 #ifdef NOTIFICATION_BANNER
67 notification_update_banner();
71 g_hash_table_destroy(msg_count_hash);
72 msg_count_hash = NULL;
74 notification_update_msg_counts(NULL);
77 /* Hide/show main window */
78 void notification_toggle_hide_show_window(void)
81 if((mainwin = mainwindow_get_mainwindow()) == NULL)
84 if(gtk_widget_get_visible(GTK_WIDGET(mainwin->window))) {
85 if((gdk_window_get_state(GTK_WIDGET(mainwin->window)->window)&GDK_WINDOW_STATE_ICONIFIED)
86 || mainwindow_is_obscured()) {
87 notification_show_mainwindow(mainwin);
90 main_window_hide(mainwin);
94 notification_show_mainwindow(mainwin);
98 void notification_update_msg_counts(FolderItem *removed_item)
101 msg_count_hash = g_hash_table_new_full(g_str_hash,g_str_equal,
104 folder_func_to_all_folders(msg_count_hash_update_func, msg_count_hash);
108 identifier = folder_item_get_identifier(removed_item);
110 g_hash_table_remove(msg_count_hash, identifier);
114 msg_count_clear(&msg_count);
115 g_hash_table_foreach(msg_count_hash, msg_count_update_from_hash, NULL);
116 #ifdef NOTIFICATION_LCDPROC
117 notification_update_lcdproc();
119 #ifdef NOTIFICATION_TRAYICON
120 notification_update_trayicon();
122 #ifdef NOTIFICATION_INDICATOR
123 notification_update_indicator();
125 notification_update_urgency_hint();
128 static void msg_count_clear(NotificationMsgCount *count)
131 count->unread_msgs = 0;
132 count->unreadmarked_msgs = 0;
133 count->marked_msgs = 0;
134 count->total_msgs = 0;
138 static void msg_count_add(NotificationMsgCount *c1,NotificationMsgCount *c2)
140 c1->new_msgs += c2->new_msgs;
141 c1->unread_msgs += c2->unread_msgs;
142 c1->unreadmarked_msgs += c2->unreadmarked_msgs;
143 c1->marked_msgs += c2->marked_msgs;
144 c1->total_msgs += c2->total_msgs;
148 static void msg_count_copy(NotificationMsgCount *c1,NotificationMsgCount *c2)
150 c1->new_msgs = c2->new_msgs;
151 c1->unread_msgs = c2->unread_msgs;
152 c1->unreadmarked_msgs = c2->unreadmarked_msgs;
153 c1->marked_msgs = c2->marked_msgs;
154 c1->total_msgs = c2->total_msgs;
157 gboolean get_flat_gslist_from_nodes_traverse_func(GNode *node, gpointer data)
160 GSList **list = data;
161 *list = g_slist_prepend(*list, node->data);
166 GSList* get_flat_gslist_from_nodes(GNode *node)
168 GSList *retval = NULL;
170 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, get_flat_gslist_from_nodes_traverse_func, &retval);
174 void notification_core_get_msg_count_of_foldername(gchar *foldername, NotificationMsgCount *count)
178 Folder *folder = NULL;
180 for(list = folder_get_list(); list != NULL; list = list->next) {
181 walk_folder = list->data;
182 if(strcmp2(foldername, walk_folder->name) == 0) {
183 folder = walk_folder;
188 debug_print("Notification plugin: Error: Could not find folder %s\n", foldername);
192 msg_count_clear(count);
193 notification_core_get_msg_count(get_flat_gslist_from_nodes(folder->node), count);
196 void notification_core_get_msg_count(GSList *folder_list,
197 NotificationMsgCount *count)
202 msg_count_copy(count,&msg_count);
204 msg_count_clear(count);
205 for(walk = folder_list; walk; walk = walk->next) {
207 NotificationMsgCount *item_count;
208 FolderItem *item = (FolderItem*) walk->data;
209 identifier = folder_item_get_identifier(item);
211 item_count = g_hash_table_lookup(msg_count_hash,identifier);
214 msg_count_add(count, item_count);
220 static void msg_count_hash_update_func(FolderItem *item, gpointer data)
223 NotificationMsgCount *count;
224 GHashTable *hash = data;
226 if(!notify_include_folder_type(item->folder->klass->type,
227 item->folder->klass->uistr))
230 identifier = folder_item_get_identifier(item);
234 count = g_hash_table_lookup(hash, identifier);
237 count = g_new0(NotificationMsgCount,1);
238 g_hash_table_insert(hash, identifier, count);
243 count->new_msgs = item->new_msgs;
244 count->unread_msgs = item->unread_msgs;
245 count->unreadmarked_msgs = item->unreadmarked_msgs;
246 count->marked_msgs = item->marked_msgs;
247 count->total_msgs = item->total_msgs;
250 static void msg_count_update_from_hash(gpointer key, gpointer value,
253 NotificationMsgCount *count = value;
254 msg_count_add(&msg_count,count);
258 /* Replacement for the post-filtering hook:
260 hook on FOLDER_ITEM_UPDATE_HOOKLIST
261 if hook flags & F_ITEM_UPDATE_MSGCOUNT
262 scan mails (folder_item_get_msg_list)
263 if MSG_IS_NEW(msginfo->flags) and not in hashtable
266 procmsg_msg_list_free
268 hook on MSGINFO_UPDATE_HOOKLIST
269 if hook flags & MSGINFO_UPDATE_FLAGS
270 if !MSG_IS_NEW(msginfo->flags)
271 remove from hashtable, it's now useless
274 /* This hash table holds all mails that we already notified about,
275 and that still are marked as "new". The keys are the msgid's,
276 the values are just 1's stored in a pointer. */
277 static GHashTable *notified_hash = NULL;
280 /* Remove message from the notified_hash if
281 * - the message flags changed
282 * - the message is not new
283 * - the message is in the hash
285 gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate *msg_update)
287 g_return_val_if_fail(msg_update != NULL, FALSE);
289 if((msg_update->flags & MSGINFO_UPDATE_FLAGS) &&
290 !MSG_IS_NEW(msg_update->msginfo->flags)) {
295 msg = msg_update->msginfo;
299 debug_print("Notification Plugin: Message has no message ID!\n");
303 g_return_val_if_fail(msg != NULL, FALSE);
305 if(g_hash_table_lookup(notified_hash, msgid) != NULL) {
307 debug_print("Notification Plugin: Removing message id %s from hash "
309 g_hash_table_remove(notified_hash, msgid);
315 /* On startup, mark all new mails as already notified
316 * (by including them in the hash) */
317 void notification_notified_hash_startup_init(void)
319 GList *folder_list, *walk;
323 notified_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
325 debug_print("Notification Plugin: Hash table created\n");
328 folder_list = folder_get_list();
329 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
332 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
333 notification_traverse_hash_startup, NULL);
337 static gboolean notification_traverse_hash_startup(GNode *node, gpointer data)
341 FolderItem *item = (FolderItem*) node->data;
344 if(!(item->new_msgs))
347 new_msgs_left = item->new_msgs;
348 msg_list = folder_item_get_msg_list(item);
350 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
351 MsgInfo *msg = (MsgInfo*) walk->data;
352 if(MSG_IS_NEW(msg->flags)) {
358 debug_print("Notification Plugin: Message has no message ID!\n");
362 /* If the message id is not yet in the hash, add it */
363 g_hash_table_insert(notified_hash, g_strdup(msgid),
365 debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
367 /* Decrement left count and check if we're already done */
369 if(new_msgs_left == 0)
373 procmsg_msg_list_free(msg_list);
377 void notification_core_free(void)
380 g_hash_table_destroy(notified_hash);
381 notified_hash = NULL;
384 g_hash_table_destroy(msg_count_hash);
385 msg_count_hash = NULL;
387 debug_print("Notification Plugin: Freed internal data\n");
390 void notification_new_unnotified_msgs(FolderItemUpdateData *update_data)
392 GSList *msg_list, *walk;
394 g_return_if_fail(notified_hash != NULL);
396 msg_list = folder_item_get_msg_list(update_data->item);
398 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
400 msg = (MsgInfo*) walk->data;
402 if(MSG_IS_NEW(msg->flags)) {
408 debug_print("Notification Plugin: Message has not message ID!\n");
412 debug_print("Notification Plugin: Found msg %s, "
413 "checking if it is in hash...\n", msgid);
414 /* Check if message is in hash table */
415 if(g_hash_table_lookup(notified_hash, msgid) != NULL)
416 debug_print("yes.\n");
418 /* Add to hashtable */
419 g_hash_table_insert(notified_hash, g_strdup(msgid),
421 debug_print("no, added to table.\n");
423 /* Do the notification */
424 notification_new_unnotified_do_msg(msg);
428 } /* for all messages */
429 procmsg_msg_list_free(msg_list);
432 #ifdef HAVE_LIBCANBERRA_GTK
433 static void canberra_finished_cb(ca_context *c, uint32_t id, int error, void *data)
435 canberra_new_email_is_playing = FALSE;
439 static void notification_new_unnotified_do_msg(MsgInfo *msg)
441 #ifdef NOTIFICATION_POPUP
442 notification_popup_msg(msg);
444 #ifdef NOTIFICATION_COMMAND
445 notification_command_msg(msg);
447 #ifdef NOTIFICATION_TRAYICON
448 notification_trayicon_msg(msg);
451 #ifdef HAVE_LIBCANBERRA_GTK
453 if(notify_config.canberra_play_sounds && !canberra_new_email_is_playing) {
455 ca_proplist *proplist;
456 mainwin = mainwindow_get_mainwindow();
457 ca_proplist_create(&proplist);
458 ca_proplist_sets(proplist,CA_PROP_EVENT_ID ,"message-new-email");
459 canberra_new_email_is_playing = TRUE;
460 ca_context_play_full(ca_gtk_context_get(), 0, proplist, canberra_finished_cb, NULL);
461 ca_proplist_destroy(proplist);
466 /* If folders is not NULL, then consider only those folder items
467 * If max_msgs is not 0, stop after collecting msg_msgs messages
469 GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
472 GList *folder_list, *walk;
474 TraverseCollect collect_data;
476 collect_data.unread_also = unread_also;
477 collect_data.collected_msgs = NULL;
478 collect_data.folder_items = folder_items;
479 collect_data.max_msgs = max_msgs;
480 collect_data.num_msgs = 0;
482 folder_list = folder_get_list();
483 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
486 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
487 notification_traverse_collect, &collect_data);
489 return collect_data.collected_msgs;
492 void notification_collected_msgs_free(GSList *collected_msgs)
496 for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
497 CollectedMsg *msg = walk->data;
501 g_free(msg->subject);
502 if(msg->folderitem_name)
503 g_free(msg->folderitem_name);
507 g_slist_free(collected_msgs);
511 static gboolean notification_traverse_collect(GNode *node, gpointer data)
513 TraverseCollect *cdata = data;
514 FolderItem *item = node->data;
515 gchar *folder_id_cur;
517 /* Obey global folder type limitations */
518 if(!notify_include_folder_type(item->folder->klass->type,
519 item->folder->klass->uistr))
522 /* If a folder_items list was given, check it first */
523 if((cdata->folder_items) && (item->path != NULL) &&
524 ((folder_id_cur = folder_item_get_identifier(item)) != NULL)) {
525 FolderItem *list_item;
527 gchar *folder_id_list;
529 gboolean folder_in_list = FALSE;
531 for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
532 list_item = walk->data;
533 folder_id_list = folder_item_get_identifier(list_item);
534 eq = !strcmp2(folder_id_list,folder_id_cur);
535 g_free(folder_id_list);
537 folder_in_list = TRUE;
541 g_free(folder_id_cur);
546 if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
547 GSList *msg_list = folder_item_get_msg_list(item);
549 for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
550 MsgInfo *msg_info = walk->data;
553 if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
556 if(MSG_IS_NEW(msg_info->flags) ||
557 (MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
559 cmsg = g_new(CollectedMsg, 1);
560 cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
561 cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
562 if(msg_info->folder && msg_info->folder->name)
563 cmsg->folderitem_name = g_strdup(msg_info->folder->path);
565 cmsg->folderitem_name = g_strdup("");
567 cmsg->msginfo = msg_info;
569 cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
573 procmsg_msg_list_free(msg_list);
579 gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
589 if(notify_config.include_mail)
593 if(notify_config.include_news)
599 else if(!strcmp(uistr, "vCalendar")) {
600 if(notify_config.include_calendar)
603 else if(!strcmp(uistr, "RSSyl")) {
604 if(notify_config.include_rss)
608 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
611 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
617 static void fix_folderview_scroll(MainWindow *mainwin)
619 static gboolean fix_done = FALSE;
624 gtk_widget_queue_resize(mainwin->folderview->ctree);
629 void notification_show_mainwindow(MainWindow *mainwin)
631 gtk_window_deiconify(GTK_WINDOW(mainwin->window));
632 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
633 main_window_show(mainwin);
634 gtk_window_present(GTK_WINDOW(mainwin->window));
635 fix_folderview_scroll(mainwin);
638 #ifdef HAVE_LIBNOTIFY
639 #define STR_MAX_LEN 511
640 /* Returns a newly allocated string which needs to be freed */
641 gchar* notification_libnotify_sanitize_str(gchar *in)
644 gchar tmp_str[STR_MAX_LEN+1];
646 if(in == NULL) return NULL;
651 if(i_out+3 >= STR_MAX_LEN) break;
652 memcpy(&(tmp_str[i_out]),"<",4);
655 else if(*in == '>') {
656 if(i_out+3 >= STR_MAX_LEN) break;
657 memcpy(&(tmp_str[i_out]),">",4);
660 else if(*in == '&') {
661 if(i_out+4 >= STR_MAX_LEN) break;
662 memcpy(&(tmp_str[i_out]),"&",5);
666 if(i_out >= STR_MAX_LEN) break;
667 tmp_str[i_out++] = *in++;
670 tmp_str[i_out] = '\0';
671 return strdup(tmp_str);
674 gchar* notification_validate_utf8_str(gchar *text)
676 gchar *utf8_str = NULL;
678 if(!g_utf8_validate(text, -1, NULL)) {
679 debug_print("Notification plugin: String is not valid utf8, "
680 "trying to fix it...\n");
682 utf8_str = conv_codeset_strdup(text,
683 conv_get_locale_charset_str_no_utf8(),
685 /* check if the fix worked */
686 if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
687 debug_print("Notification plugin: String is still not valid utf8, "
689 utf8_str = g_malloc(strlen(text)*2+1);
690 conv_localetodisp(utf8_str, strlen(text)*2+1, text);
694 debug_print("Notification plugin: String is valid utf8\n");
695 utf8_str = g_strdup(text);