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/>.
20 # include "claws-features.h"
24 #include "folderview.h"
26 #include "gtk/gtkutils.h"
28 #include "notification_core.h"
29 #include "notification_plugin.h"
30 #include "notification_prefs.h"
31 #include "notification_banner.h"
32 #include "notification_popup.h"
33 #include "notification_command.h"
34 #include "notification_lcdproc.h"
35 #include "notification_trayicon.h"
36 #include "notification_indicator.h"
38 #ifdef HAVE_LIBCANBERRA_GTK
39 # include <canberra-gtk.h>
43 GSList *collected_msgs;
50 static gboolean notification_traverse_collect(GNode*, gpointer);
51 static void notification_new_unnotified_do_msg(MsgInfo*);
52 static gboolean notification_traverse_hash_startup(GNode*, gpointer);
54 static GHashTable *msg_count_hash;
55 static NotificationMsgCount msg_count;
57 #ifdef HAVE_LIBCANBERRA_GTK
58 static gboolean canberra_new_email_is_playing = FALSE;
61 static void msg_count_hash_update_func(FolderItem*, gpointer);
62 static void msg_count_update_from_hash(gpointer, gpointer, gpointer);
63 static void msg_count_clear(NotificationMsgCount*);
64 static void msg_count_add(NotificationMsgCount*,NotificationMsgCount*);
65 static void msg_count_copy(NotificationMsgCount*,NotificationMsgCount*);
67 void notification_core_global_includes_changed(void)
69 #ifdef NOTIFICATION_BANNER
70 notification_update_banner();
74 g_hash_table_destroy(msg_count_hash);
75 msg_count_hash = NULL;
77 notification_update_msg_counts(NULL);
80 /* Hide/show main window */
81 void notification_toggle_hide_show_window(void)
84 if((mainwin = mainwindow_get_mainwindow()) == NULL)
87 if(gtk_widget_get_visible(GTK_WIDGET(mainwin->window))) {
88 if((gdk_window_get_state(GTK_WIDGET(mainwin->window)->window)&GDK_WINDOW_STATE_ICONIFIED)
89 || mainwindow_is_obscured()) {
90 notification_show_mainwindow(mainwin);
93 main_window_hide(mainwin);
97 notification_show_mainwindow(mainwin);
101 void notification_update_msg_counts(FolderItem *removed_item)
104 msg_count_hash = g_hash_table_new_full(g_str_hash,g_str_equal,
107 folder_func_to_all_folders(msg_count_hash_update_func, msg_count_hash);
111 identifier = folder_item_get_identifier(removed_item);
113 g_hash_table_remove(msg_count_hash, identifier);
117 msg_count_clear(&msg_count);
118 g_hash_table_foreach(msg_count_hash, msg_count_update_from_hash, NULL);
119 #ifdef NOTIFICATION_LCDPROC
120 notification_update_lcdproc();
122 #ifdef NOTIFICATION_TRAYICON
123 notification_update_trayicon();
125 #ifdef NOTIFICATION_INDICATOR
126 notification_update_indicator();
128 notification_update_urgency_hint();
131 static void msg_count_clear(NotificationMsgCount *count)
134 count->unread_msgs = 0;
135 count->unreadmarked_msgs = 0;
136 count->marked_msgs = 0;
137 count->total_msgs = 0;
141 static void msg_count_add(NotificationMsgCount *c1,NotificationMsgCount *c2)
143 c1->new_msgs += c2->new_msgs;
144 c1->unread_msgs += c2->unread_msgs;
145 c1->unreadmarked_msgs += c2->unreadmarked_msgs;
146 c1->marked_msgs += c2->marked_msgs;
147 c1->total_msgs += c2->total_msgs;
151 static void msg_count_copy(NotificationMsgCount *c1,NotificationMsgCount *c2)
153 c1->new_msgs = c2->new_msgs;
154 c1->unread_msgs = c2->unread_msgs;
155 c1->unreadmarked_msgs = c2->unreadmarked_msgs;
156 c1->marked_msgs = c2->marked_msgs;
157 c1->total_msgs = c2->total_msgs;
160 gboolean get_flat_gslist_from_nodes_traverse_func(GNode *node, gpointer data)
163 GSList **list = data;
164 *list = g_slist_prepend(*list, node->data);
169 GSList* get_flat_gslist_from_nodes(GNode *node)
171 GSList *retval = NULL;
173 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, get_flat_gslist_from_nodes_traverse_func, &retval);
177 void notification_core_get_msg_count_of_foldername(gchar *foldername, NotificationMsgCount *count)
181 Folder *folder = NULL;
183 for(list = folder_get_list(); list != NULL; list = list->next) {
184 walk_folder = list->data;
185 if(strcmp2(foldername, walk_folder->name) == 0) {
186 folder = walk_folder;
191 debug_print("Notification plugin: Error: Could not find folder %s\n", foldername);
195 msg_count_clear(count);
196 notification_core_get_msg_count(get_flat_gslist_from_nodes(folder->node), count);
199 void notification_core_get_msg_count(GSList *folder_list,
200 NotificationMsgCount *count)
205 msg_count_copy(count,&msg_count);
207 msg_count_clear(count);
208 for(walk = folder_list; walk; walk = walk->next) {
210 NotificationMsgCount *item_count;
211 FolderItem *item = (FolderItem*) walk->data;
212 identifier = folder_item_get_identifier(item);
214 item_count = g_hash_table_lookup(msg_count_hash,identifier);
217 msg_count_add(count, item_count);
223 static void msg_count_hash_update_func(FolderItem *item, gpointer data)
226 NotificationMsgCount *count;
227 GHashTable *hash = data;
229 if(!notify_include_folder_type(item->folder->klass->type,
230 item->folder->klass->uistr))
233 identifier = folder_item_get_identifier(item);
237 count = g_hash_table_lookup(hash, identifier);
240 count = g_new0(NotificationMsgCount,1);
241 g_hash_table_insert(hash, identifier, count);
246 count->new_msgs = item->new_msgs;
247 count->unread_msgs = item->unread_msgs;
248 count->unreadmarked_msgs = item->unreadmarked_msgs;
249 count->marked_msgs = item->marked_msgs;
250 count->total_msgs = item->total_msgs;
253 static void msg_count_update_from_hash(gpointer key, gpointer value,
256 NotificationMsgCount *count = value;
257 msg_count_add(&msg_count,count);
261 /* Replacement for the post-filtering hook:
263 hook on FOLDER_ITEM_UPDATE_HOOKLIST
264 if hook flags & F_ITEM_UPDATE_MSGCOUNT
265 scan mails (folder_item_get_msg_list)
266 if MSG_IS_NEW(msginfo->flags) and not in hashtable
269 procmsg_msg_list_free
271 hook on MSGINFO_UPDATE_HOOKLIST
272 if hook flags & MSGINFO_UPDATE_FLAGS
273 if !MSG_IS_NEW(msginfo->flags)
274 remove from hashtable, it's now useless
277 /* This hash table holds all mails that we already notified about,
278 and that still are marked as "new". The keys are the msgid's,
279 the values are just 1's stored in a pointer. */
280 static GHashTable *notified_hash = NULL;
283 /* Remove message from the notified_hash if
284 * - the message flags changed
285 * - the message is not new
286 * - the message is in the hash
288 gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate *msg_update)
290 g_return_val_if_fail(msg_update != NULL, FALSE);
292 if((msg_update->flags & MSGINFO_UPDATE_FLAGS) &&
293 !MSG_IS_NEW(msg_update->msginfo->flags)) {
298 msg = msg_update->msginfo;
302 debug_print("Notification Plugin: Message has no message ID!\n");
306 g_return_val_if_fail(msg != NULL, FALSE);
308 if(g_hash_table_lookup(notified_hash, msgid) != NULL) {
310 debug_print("Notification Plugin: Removing message id %s from hash "
312 g_hash_table_remove(notified_hash, msgid);
318 /* On startup, mark all new mails as already notified
319 * (by including them in the hash) */
320 void notification_notified_hash_startup_init(void)
322 GList *folder_list, *walk;
326 notified_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
328 debug_print("Notification Plugin: Hash table created\n");
331 folder_list = folder_get_list();
332 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
335 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
336 notification_traverse_hash_startup, NULL);
340 static gboolean notification_traverse_hash_startup(GNode *node, gpointer data)
344 FolderItem *item = (FolderItem*) node->data;
347 if(!(item->new_msgs))
350 new_msgs_left = item->new_msgs;
351 msg_list = folder_item_get_msg_list(item);
353 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
354 MsgInfo *msg = (MsgInfo*) walk->data;
355 if(MSG_IS_NEW(msg->flags)) {
361 debug_print("Notification Plugin: Message has no message ID!\n");
365 /* If the message id is not yet in the hash, add it */
366 g_hash_table_insert(notified_hash, g_strdup(msgid),
368 debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
370 /* Decrement left count and check if we're already done */
372 if(new_msgs_left == 0)
376 procmsg_msg_list_free(msg_list);
380 void notification_core_free(void)
383 g_hash_table_destroy(notified_hash);
384 notified_hash = NULL;
387 g_hash_table_destroy(msg_count_hash);
388 msg_count_hash = NULL;
390 debug_print("Notification Plugin: Freed internal data\n");
393 void notification_new_unnotified_msgs(FolderItemUpdateData *update_data)
395 GSList *msg_list, *walk;
397 g_return_if_fail(notified_hash != NULL);
399 msg_list = folder_item_get_msg_list(update_data->item);
401 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
403 msg = (MsgInfo*) walk->data;
405 if(MSG_IS_NEW(msg->flags)) {
411 debug_print("Notification Plugin: Message has not message ID!\n");
415 debug_print("Notification Plugin: Found msg %s, "
416 "checking if it is in hash...\n", msgid);
417 /* Check if message is in hash table */
418 if(g_hash_table_lookup(notified_hash, msgid) != NULL)
419 debug_print("yes.\n");
421 /* Add to hashtable */
422 g_hash_table_insert(notified_hash, g_strdup(msgid),
424 debug_print("no, added to table.\n");
426 /* Do the notification */
427 notification_new_unnotified_do_msg(msg);
431 } /* for all messages */
432 procmsg_msg_list_free(msg_list);
435 #ifdef HAVE_LIBCANBERRA_GTK
436 static void canberra_finished_cb(ca_context *c, uint32_t id, int error, void *data)
438 canberra_new_email_is_playing = FALSE;
442 static void notification_new_unnotified_do_msg(MsgInfo *msg)
444 #ifdef NOTIFICATION_POPUP
445 notification_popup_msg(msg);
447 #ifdef NOTIFICATION_COMMAND
448 notification_command_msg(msg);
450 #ifdef NOTIFICATION_TRAYICON
451 notification_trayicon_msg(msg);
454 #ifdef HAVE_LIBCANBERRA_GTK
456 if(notify_config.canberra_play_sounds && !canberra_new_email_is_playing) {
458 ca_proplist *proplist;
459 mainwin = mainwindow_get_mainwindow();
460 ca_proplist_create(&proplist);
461 ca_proplist_sets(proplist,CA_PROP_EVENT_ID ,"message-new-email");
462 canberra_new_email_is_playing = TRUE;
463 ca_context_play_full(ca_gtk_context_get(), 0, proplist, canberra_finished_cb, NULL);
464 ca_proplist_destroy(proplist);
469 /* If folders is not NULL, then consider only those folder items
470 * If max_msgs is not 0, stop after collecting msg_msgs messages
472 GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
475 GList *folder_list, *walk;
477 TraverseCollect collect_data;
479 collect_data.unread_also = unread_also;
480 collect_data.collected_msgs = NULL;
481 collect_data.folder_items = folder_items;
482 collect_data.max_msgs = max_msgs;
483 collect_data.num_msgs = 0;
485 folder_list = folder_get_list();
486 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
489 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
490 notification_traverse_collect, &collect_data);
492 return collect_data.collected_msgs;
495 void notification_collected_msgs_free(GSList *collected_msgs)
499 for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
500 CollectedMsg *msg = walk->data;
504 g_free(msg->subject);
505 if(msg->folderitem_name)
506 g_free(msg->folderitem_name);
510 g_slist_free(collected_msgs);
514 static gboolean notification_traverse_collect(GNode *node, gpointer data)
516 TraverseCollect *cdata = data;
517 FolderItem *item = node->data;
518 gchar *folder_id_cur;
520 /* Obey global folder type limitations */
521 if(!notify_include_folder_type(item->folder->klass->type,
522 item->folder->klass->uistr))
525 /* If a folder_items list was given, check it first */
526 if((cdata->folder_items) && (item->path != NULL) &&
527 ((folder_id_cur = folder_item_get_identifier(item)) != NULL)) {
528 FolderItem *list_item;
530 gchar *folder_id_list;
532 gboolean folder_in_list = FALSE;
534 for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
535 list_item = walk->data;
536 folder_id_list = folder_item_get_identifier(list_item);
537 eq = !strcmp2(folder_id_list,folder_id_cur);
538 g_free(folder_id_list);
540 folder_in_list = TRUE;
544 g_free(folder_id_cur);
549 if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
550 GSList *msg_list = folder_item_get_msg_list(item);
552 for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
553 MsgInfo *msg_info = walk->data;
556 if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
559 if(MSG_IS_NEW(msg_info->flags) ||
560 (MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
562 cmsg = g_new(CollectedMsg, 1);
563 cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
564 cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
565 if(msg_info->folder && msg_info->folder->name)
566 cmsg->folderitem_name = g_strdup(msg_info->folder->path);
568 cmsg->folderitem_name = g_strdup("");
570 cmsg->msginfo = msg_info;
572 cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
576 procmsg_msg_list_free(msg_list);
582 gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
592 if(notify_config.include_mail)
596 if(notify_config.include_news)
602 else if(!strcmp(uistr, "vCalendar")) {
603 if(notify_config.include_calendar)
606 else if(!strcmp(uistr, "RSSyl")) {
607 if(notify_config.include_rss)
611 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
614 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
620 static void fix_folderview_scroll(MainWindow *mainwin)
622 static gboolean fix_done = FALSE;
627 gtk_widget_queue_resize(mainwin->folderview->ctree);
632 void notification_show_mainwindow(MainWindow *mainwin)
634 gtk_window_deiconify(GTK_WINDOW(mainwin->window));
635 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
636 main_window_show(mainwin);
637 gtk_window_present(GTK_WINDOW(mainwin->window));
638 fix_folderview_scroll(mainwin);
641 #ifdef HAVE_LIBNOTIFY
642 #define STR_MAX_LEN 511
643 /* Returns a newly allocated string which needs to be freed */
644 gchar* notification_libnotify_sanitize_str(gchar *in)
647 gchar tmp_str[STR_MAX_LEN+1];
649 if(in == NULL) return NULL;
654 if(i_out+3 >= STR_MAX_LEN) break;
655 memcpy(&(tmp_str[i_out]),"<",4);
658 else if(*in == '>') {
659 if(i_out+3 >= STR_MAX_LEN) break;
660 memcpy(&(tmp_str[i_out]),">",4);
663 else if(*in == '&') {
664 if(i_out+4 >= STR_MAX_LEN) break;
665 memcpy(&(tmp_str[i_out]),"&",5);
669 if(i_out >= STR_MAX_LEN) break;
670 tmp_str[i_out++] = *in++;
673 tmp_str[i_out] = '\0';
674 return strdup(tmp_str);
677 gchar* notification_validate_utf8_str(gchar *text)
679 gchar *utf8_str = NULL;
681 if(!g_utf8_validate(text, -1, NULL)) {
682 debug_print("Notification plugin: String is not valid utf8, "
683 "trying to fix it...\n");
685 utf8_str = conv_codeset_strdup(text,
686 conv_get_locale_charset_str_no_utf8(),
688 /* check if the fix worked */
689 if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
690 debug_print("Notification plugin: String is still not valid utf8, "
692 utf8_str = g_malloc(strlen(text)*2+1);
693 conv_localetodisp(utf8_str, strlen(text)*2+1, text);
697 debug_print("Notification plugin: String is valid utf8\n");
698 utf8_str = g_strdup(text);