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)
183 Folder *folder = NULL;
185 for(list = folder_get_list(); list != NULL; list = list->next) {
186 walk_folder = list->data;
187 if(strcmp2(foldername, walk_folder->name) == 0) {
188 folder = walk_folder;
193 debug_print("Notification plugin: Error: Could not find folder %s\n", foldername);
197 msg_count_clear(count);
198 f_list = get_flat_gslist_from_nodes(folder->node);
199 notification_core_get_msg_count(f_list, count);
200 g_slist_free(f_list);
203 void notification_core_get_msg_count(GSList *folder_list,
204 NotificationMsgCount *count)
209 msg_count_copy(count,&msg_count);
211 msg_count_clear(count);
212 for(walk = folder_list; walk; walk = walk->next) {
214 NotificationMsgCount *item_count;
215 FolderItem *item = (FolderItem*) walk->data;
216 identifier = folder_item_get_identifier(item);
218 item_count = g_hash_table_lookup(msg_count_hash,identifier);
221 msg_count_add(count, item_count);
227 static void msg_count_hash_update_func(FolderItem *item, gpointer data)
230 NotificationMsgCount *count;
231 GHashTable *hash = data;
233 if(!notify_include_folder_type(item->folder->klass->type,
234 item->folder->klass->uistr))
237 identifier = folder_item_get_identifier(item);
241 count = g_hash_table_lookup(hash, identifier);
244 count = g_new0(NotificationMsgCount,1);
245 g_hash_table_insert(hash, identifier, count);
250 count->new_msgs = item->new_msgs;
251 count->unread_msgs = item->unread_msgs;
252 count->unreadmarked_msgs = item->unreadmarked_msgs;
253 count->marked_msgs = item->marked_msgs;
254 count->total_msgs = item->total_msgs;
257 static void msg_count_update_from_hash(gpointer key, gpointer value,
260 NotificationMsgCount *count = value;
261 msg_count_add(&msg_count,count);
265 /* Replacement for the post-filtering hook:
267 hook on FOLDER_ITEM_UPDATE_HOOKLIST
268 if hook flags & F_ITEM_UPDATE_MSGCOUNT
269 scan mails (folder_item_get_msg_list)
270 if MSG_IS_NEW(msginfo->flags) and not in hashtable
273 procmsg_msg_list_free
275 hook on MSGINFO_UPDATE_HOOKLIST
276 if hook flags & MSGINFO_UPDATE_FLAGS
277 if !MSG_IS_NEW(msginfo->flags)
278 remove from hashtable, it's now useless
281 /* This hash table holds all mails that we already notified about,
282 and that still are marked as "new". The keys are the msgid's,
283 the values are just 1's stored in a pointer. */
284 static GHashTable *notified_hash = NULL;
287 /* Remove message from the notified_hash if
288 * - the message flags changed
289 * - the message is not new
290 * - the message is in the hash
292 gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate *msg_update)
294 g_return_val_if_fail(msg_update != NULL, FALSE);
296 if((msg_update->flags & MSGINFO_UPDATE_FLAGS) &&
297 !MSG_IS_NEW(msg_update->msginfo->flags)) {
302 msg = msg_update->msginfo;
306 debug_print("Notification Plugin: Message has no message ID!\n");
310 g_return_val_if_fail(msg != NULL, FALSE);
312 if(g_hash_table_lookup(notified_hash, msgid) != NULL) {
314 debug_print("Notification Plugin: Removing message id %s from hash "
316 g_hash_table_remove(notified_hash, msgid);
322 /* On startup, mark all new mails as already notified
323 * (by including them in the hash) */
324 void notification_notified_hash_startup_init(void)
326 GList *folder_list, *walk;
330 notified_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
332 debug_print("Notification Plugin: Hash table created\n");
335 folder_list = folder_get_list();
336 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
339 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
340 notification_traverse_hash_startup, NULL);
344 static gboolean notification_traverse_hash_startup(GNode *node, gpointer data)
348 FolderItem *item = (FolderItem*) node->data;
351 if(!(item->new_msgs))
354 new_msgs_left = item->new_msgs;
355 msg_list = folder_item_get_msg_list(item);
357 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
358 MsgInfo *msg = (MsgInfo*) walk->data;
359 if(MSG_IS_NEW(msg->flags)) {
365 debug_print("Notification Plugin: Message has no message ID!\n");
369 /* If the message id is not yet in the hash, add it */
370 g_hash_table_insert(notified_hash, g_strdup(msgid),
372 debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
374 /* Decrement left count and check if we're already done */
376 if(new_msgs_left == 0)
380 procmsg_msg_list_free(msg_list);
384 void notification_core_free(void)
387 g_hash_table_destroy(notified_hash);
388 notified_hash = NULL;
391 g_hash_table_destroy(msg_count_hash);
392 msg_count_hash = NULL;
394 debug_print("Notification Plugin: Freed internal data\n");
397 void notification_new_unnotified_msgs(FolderItemUpdateData *update_data)
399 GSList *msg_list, *walk;
401 g_return_if_fail(notified_hash != NULL);
403 msg_list = folder_item_get_msg_list(update_data->item);
405 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
407 msg = (MsgInfo*) walk->data;
409 if(MSG_IS_NEW(msg->flags)) {
415 debug_print("Notification Plugin: Message has not message ID!\n");
419 debug_print("Notification Plugin: Found msg %s, "
420 "checking if it is in hash...\n", msgid);
421 /* Check if message is in hash table */
422 if(g_hash_table_lookup(notified_hash, msgid) != NULL)
423 debug_print("yes.\n");
425 /* Add to hashtable */
426 g_hash_table_insert(notified_hash, g_strdup(msgid),
428 debug_print("no, added to table.\n");
430 /* Do the notification */
431 notification_new_unnotified_do_msg(msg);
435 } /* for all messages */
436 procmsg_msg_list_free(msg_list);
439 #ifdef HAVE_LIBCANBERRA_GTK
440 static void canberra_finished_cb(ca_context *c, uint32_t id, int error, void *data)
442 canberra_new_email_is_playing = FALSE;
446 static void notification_new_unnotified_do_msg(MsgInfo *msg)
448 #ifdef NOTIFICATION_POPUP
449 notification_popup_msg(msg);
451 #ifdef NOTIFICATION_COMMAND
452 notification_command_msg(msg);
454 #ifdef NOTIFICATION_TRAYICON
455 notification_trayicon_msg(msg);
458 #ifdef HAVE_LIBCANBERRA_GTK
460 if(notify_config.canberra_play_sounds && !canberra_new_email_is_playing) {
461 ca_proplist *proplist;
462 ca_proplist_create(&proplist);
463 ca_proplist_sets(proplist,CA_PROP_EVENT_ID ,"message-new-email");
464 canberra_new_email_is_playing = TRUE;
465 ca_context_play_full(ca_gtk_context_get(), 0, proplist, canberra_finished_cb, NULL);
466 ca_proplist_destroy(proplist);
471 /* If folders is not NULL, then consider only those folder items
472 * If max_msgs is not 0, stop after collecting msg_msgs messages
474 GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
477 GList *folder_list, *walk;
479 TraverseCollect collect_data;
481 collect_data.unread_also = unread_also;
482 collect_data.collected_msgs = NULL;
483 collect_data.folder_items = folder_items;
484 collect_data.max_msgs = max_msgs;
485 collect_data.num_msgs = 0;
487 folder_list = folder_get_list();
488 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
491 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
492 notification_traverse_collect, &collect_data);
494 return collect_data.collected_msgs;
497 void notification_collected_msgs_free(GSList *collected_msgs)
501 for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
502 CollectedMsg *msg = walk->data;
506 g_free(msg->subject);
507 if(msg->folderitem_name)
508 g_free(msg->folderitem_name);
512 g_slist_free(collected_msgs);
516 static gboolean notification_traverse_collect(GNode *node, gpointer data)
518 TraverseCollect *cdata = data;
519 FolderItem *item = node->data;
520 gchar *folder_id_cur;
522 /* Obey global folder type limitations */
523 if(!notify_include_folder_type(item->folder->klass->type,
524 item->folder->klass->uistr))
527 /* If a folder_items list was given, check it first */
528 if((cdata->folder_items) && (item->path != NULL) &&
529 ((folder_id_cur = folder_item_get_identifier(item)) != NULL)) {
530 FolderItem *list_item;
532 gchar *folder_id_list;
534 gboolean folder_in_list = FALSE;
536 for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
537 list_item = walk->data;
538 folder_id_list = folder_item_get_identifier(list_item);
539 eq = !strcmp2(folder_id_list,folder_id_cur);
540 g_free(folder_id_list);
542 folder_in_list = TRUE;
546 g_free(folder_id_cur);
551 if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
552 GSList *msg_list = folder_item_get_msg_list(item);
554 for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
555 MsgInfo *msg_info = walk->data;
558 if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
561 if(MSG_IS_NEW(msg_info->flags) ||
562 (MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
564 cmsg = g_new(CollectedMsg, 1);
565 cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
566 cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
567 if(msg_info->folder && msg_info->folder->name)
568 cmsg->folderitem_name = g_strdup(msg_info->folder->path);
570 cmsg->folderitem_name = g_strdup("");
572 cmsg->msginfo = msg_info;
574 cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
578 procmsg_msg_list_free(msg_list);
584 gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
594 if(notify_config.include_mail)
598 if(notify_config.include_news)
604 else if(!strcmp(uistr, "vCalendar")) {
605 if(notify_config.include_calendar)
608 else if(!strcmp(uistr, "RSSyl")) {
609 if(notify_config.include_rss)
613 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
616 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
622 static void fix_folderview_scroll(MainWindow *mainwin)
624 static gboolean fix_done = FALSE;
629 gtk_widget_queue_resize(mainwin->folderview->ctree);
634 void notification_show_mainwindow(MainWindow *mainwin)
636 gtk_window_deiconify(GTK_WINDOW(mainwin->window));
637 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
638 main_window_show(mainwin);
639 gtk_window_present(GTK_WINDOW(mainwin->window));
640 fix_folderview_scroll(mainwin);
643 #ifdef HAVE_LIBNOTIFY
644 #define STR_MAX_LEN 511
645 /* Returns a newly allocated string which needs to be freed */
646 gchar* notification_libnotify_sanitize_str(gchar *in)
649 gchar tmp_str[STR_MAX_LEN+1];
651 if(in == NULL) return NULL;
656 if(i_out+3 >= STR_MAX_LEN) break;
657 memcpy(&(tmp_str[i_out]),"<",4);
660 else if(*in == '>') {
661 if(i_out+3 >= STR_MAX_LEN) break;
662 memcpy(&(tmp_str[i_out]),">",4);
665 else if(*in == '&') {
666 if(i_out+4 >= STR_MAX_LEN) break;
667 memcpy(&(tmp_str[i_out]),"&",5);
671 if(i_out >= STR_MAX_LEN) break;
672 tmp_str[i_out++] = *in++;
675 tmp_str[i_out] = '\0';
676 return strdup(tmp_str);
679 gchar* notification_validate_utf8_str(gchar *text)
681 gchar *utf8_str = NULL;
683 if(!g_utf8_validate(text, -1, NULL)) {
684 debug_print("Notification plugin: String is not valid utf8, "
685 "trying to fix it...\n");
687 utf8_str = conv_codeset_strdup(text,
688 conv_get_locale_charset_str_no_utf8(),
690 /* check if the fix worked */
691 if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
692 debug_print("Notification plugin: String is still not valid utf8, "
694 utf8_str = g_malloc(strlen(text)*2+1);
695 conv_localetodisp(utf8_str, strlen(text)*2+1, text);
699 debug_print("Notification plugin: String is valid utf8\n");
700 utf8_str = g_strdup(text);