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) {
457 ca_proplist *proplist;
458 ca_proplist_create(&proplist);
459 ca_proplist_sets(proplist,CA_PROP_EVENT_ID ,"message-new-email");
460 canberra_new_email_is_playing = TRUE;
461 ca_context_play_full(ca_gtk_context_get(), 0, proplist, canberra_finished_cb, NULL);
462 ca_proplist_destroy(proplist);
467 /* If folders is not NULL, then consider only those folder items
468 * If max_msgs is not 0, stop after collecting msg_msgs messages
470 GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
473 GList *folder_list, *walk;
475 TraverseCollect collect_data;
477 collect_data.unread_also = unread_also;
478 collect_data.collected_msgs = NULL;
479 collect_data.folder_items = folder_items;
480 collect_data.max_msgs = max_msgs;
481 collect_data.num_msgs = 0;
483 folder_list = folder_get_list();
484 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
487 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
488 notification_traverse_collect, &collect_data);
490 return collect_data.collected_msgs;
493 void notification_collected_msgs_free(GSList *collected_msgs)
497 for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
498 CollectedMsg *msg = walk->data;
502 g_free(msg->subject);
503 if(msg->folderitem_name)
504 g_free(msg->folderitem_name);
508 g_slist_free(collected_msgs);
512 static gboolean notification_traverse_collect(GNode *node, gpointer data)
514 TraverseCollect *cdata = data;
515 FolderItem *item = node->data;
516 gchar *folder_id_cur;
518 /* Obey global folder type limitations */
519 if(!notify_include_folder_type(item->folder->klass->type,
520 item->folder->klass->uistr))
523 /* If a folder_items list was given, check it first */
524 if((cdata->folder_items) && (item->path != NULL) &&
525 ((folder_id_cur = folder_item_get_identifier(item)) != NULL)) {
526 FolderItem *list_item;
528 gchar *folder_id_list;
530 gboolean folder_in_list = FALSE;
532 for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
533 list_item = walk->data;
534 folder_id_list = folder_item_get_identifier(list_item);
535 eq = !strcmp2(folder_id_list,folder_id_cur);
536 g_free(folder_id_list);
538 folder_in_list = TRUE;
542 g_free(folder_id_cur);
547 if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
548 GSList *msg_list = folder_item_get_msg_list(item);
550 for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
551 MsgInfo *msg_info = walk->data;
554 if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
557 if(MSG_IS_NEW(msg_info->flags) ||
558 (MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
560 cmsg = g_new(CollectedMsg, 1);
561 cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
562 cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
563 if(msg_info->folder && msg_info->folder->name)
564 cmsg->folderitem_name = g_strdup(msg_info->folder->path);
566 cmsg->folderitem_name = g_strdup("");
568 cmsg->msginfo = msg_info;
570 cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
574 procmsg_msg_list_free(msg_list);
580 gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
590 if(notify_config.include_mail)
594 if(notify_config.include_news)
600 else if(!strcmp(uistr, "vCalendar")) {
601 if(notify_config.include_calendar)
604 else if(!strcmp(uistr, "RSSyl")) {
605 if(notify_config.include_rss)
609 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
612 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
618 static void fix_folderview_scroll(MainWindow *mainwin)
620 static gboolean fix_done = FALSE;
625 gtk_widget_queue_resize(mainwin->folderview->ctree);
630 void notification_show_mainwindow(MainWindow *mainwin)
632 gtk_window_deiconify(GTK_WINDOW(mainwin->window));
633 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
634 main_window_show(mainwin);
635 gtk_window_present(GTK_WINDOW(mainwin->window));
636 fix_folderview_scroll(mainwin);
639 #ifdef HAVE_LIBNOTIFY
640 #define STR_MAX_LEN 511
641 /* Returns a newly allocated string which needs to be freed */
642 gchar* notification_libnotify_sanitize_str(gchar *in)
645 gchar tmp_str[STR_MAX_LEN+1];
647 if(in == NULL) return NULL;
652 if(i_out+3 >= STR_MAX_LEN) break;
653 memcpy(&(tmp_str[i_out]),"<",4);
656 else if(*in == '>') {
657 if(i_out+3 >= STR_MAX_LEN) break;
658 memcpy(&(tmp_str[i_out]),">",4);
661 else if(*in == '&') {
662 if(i_out+4 >= STR_MAX_LEN) break;
663 memcpy(&(tmp_str[i_out]),"&",5);
667 if(i_out >= STR_MAX_LEN) break;
668 tmp_str[i_out++] = *in++;
671 tmp_str[i_out] = '\0';
672 return strdup(tmp_str);
675 gchar* notification_validate_utf8_str(gchar *text)
677 gchar *utf8_str = NULL;
679 if(!g_utf8_validate(text, -1, NULL)) {
680 debug_print("Notification plugin: String is not valid utf8, "
681 "trying to fix it...\n");
683 utf8_str = conv_codeset_strdup(text,
684 conv_get_locale_charset_str_no_utf8(),
686 /* check if the fix worked */
687 if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
688 debug_print("Notification plugin: String is still not valid utf8, "
690 utf8_str = g_malloc(strlen(text)*2+1);
691 conv_localetodisp(utf8_str, strlen(text)*2+1, text);
695 debug_print("Notification plugin: String is valid utf8\n");
696 utf8_str = g_strdup(text);