Fix error creation and warning
[claws.git] / src / plugins / notification / notification_core.c
blob3751fd36620abc6a06ff0a3ccd38ce3de7090d1d
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 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 # include "claws-features.h"
21 #endif
23 #include "folder.h"
24 #include "folderview.h"
25 #include "codeconv.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>
40 #endif
42 typedef struct {
43 GSList *collected_msgs;
44 GSList *folder_items;
45 gboolean unread_also;
46 gint max_msgs;
47 gint num_msgs;
48 } TraverseCollect;
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;
59 #endif
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();
71 #endif
73 if(msg_count_hash) {
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)
83 MainWindow *mainwin;
84 GdkWindow *gdkwin;
86 if((mainwin = mainwindow_get_mainwindow()) == NULL)
87 return;
89 gdkwin = gtk_widget_get_window(GTK_WIDGET(mainwin->window));
90 if(gtk_widget_get_visible(GTK_WIDGET(mainwin->window))) {
91 if((gdk_window_get_state(gdkwin) & GDK_WINDOW_STATE_ICONIFIED)
92 || mainwindow_is_obscured()) {
93 notification_show_mainwindow(mainwin);
95 else {
96 main_window_hide(mainwin);
99 else {
100 notification_show_mainwindow(mainwin);
104 void notification_update_msg_counts(FolderItem *removed_item)
106 if(!msg_count_hash)
107 msg_count_hash = g_hash_table_new_full(g_str_hash,g_str_equal,
108 g_free,g_free);
110 folder_func_to_all_folders(msg_count_hash_update_func, msg_count_hash);
112 if(removed_item) {
113 gchar *identifier;
114 identifier = folder_item_get_identifier(removed_item);
115 if(identifier) {
116 g_hash_table_remove(msg_count_hash, identifier);
117 g_free(identifier);
120 msg_count_clear(&msg_count);
121 g_hash_table_foreach(msg_count_hash, msg_count_update_from_hash, NULL);
122 #ifdef NOTIFICATION_LCDPROC
123 notification_update_lcdproc();
124 #endif
125 #ifdef NOTIFICATION_TRAYICON
126 notification_update_trayicon();
127 #endif
128 #ifdef NOTIFICATION_INDICATOR
129 notification_update_indicator();
130 #endif
131 notification_update_urgency_hint();
134 static void msg_count_clear(NotificationMsgCount *count)
136 count->new_msgs = 0;
137 count->unread_msgs = 0;
138 count->unreadmarked_msgs = 0;
139 count->marked_msgs = 0;
140 count->total_msgs = 0;
143 /* c1 += c2 */
144 static void msg_count_add(NotificationMsgCount *c1,NotificationMsgCount *c2)
146 c1->new_msgs += c2->new_msgs;
147 c1->unread_msgs += c2->unread_msgs;
148 c1->unreadmarked_msgs += c2->unreadmarked_msgs;
149 c1->marked_msgs += c2->marked_msgs;
150 c1->total_msgs += c2->total_msgs;
153 /* c1 = c2 */
154 static void msg_count_copy(NotificationMsgCount *c1,NotificationMsgCount *c2)
156 c1->new_msgs = c2->new_msgs;
157 c1->unread_msgs = c2->unread_msgs;
158 c1->unreadmarked_msgs = c2->unreadmarked_msgs;
159 c1->marked_msgs = c2->marked_msgs;
160 c1->total_msgs = c2->total_msgs;
163 gboolean get_flat_gslist_from_nodes_traverse_func(GNode *node, gpointer data)
165 if(node->data) {
166 GSList **list = data;
167 *list = g_slist_prepend(*list, node->data);
169 return FALSE;
172 GSList* get_flat_gslist_from_nodes(GNode *node)
174 GSList *retval = NULL;
176 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, get_flat_gslist_from_nodes_traverse_func, &retval);
177 return retval;
180 void notification_core_get_msg_count_of_foldername(gchar *foldername, NotificationMsgCount *count)
182 GList *list;
183 GSList *f_list;
185 Folder *walk_folder;
186 Folder *folder = NULL;
188 for(list = folder_get_list(); list != NULL; list = list->next) {
189 walk_folder = list->data;
190 if(g_strcmp0(foldername, walk_folder->name) == 0) {
191 folder = walk_folder;
192 break;
195 if(!folder) {
196 debug_print("Notification plugin: Error: Could not find folder %s\n", foldername);
197 return;
200 msg_count_clear(count);
201 f_list = get_flat_gslist_from_nodes(folder->node);
202 notification_core_get_msg_count(f_list, count);
203 g_slist_free(f_list);
206 void notification_core_get_msg_count(GSList *folder_list,
207 NotificationMsgCount *count)
209 GSList *walk;
211 if(!folder_list)
212 msg_count_copy(count,&msg_count);
213 else {
214 msg_count_clear(count);
215 for(walk = folder_list; walk; walk = walk->next) {
216 gchar *identifier;
217 NotificationMsgCount *item_count;
218 FolderItem *item = (FolderItem*) walk->data;
219 identifier = folder_item_get_identifier(item);
220 if(identifier) {
221 item_count = g_hash_table_lookup(msg_count_hash,identifier);
222 g_free(identifier);
223 if(item_count)
224 msg_count_add(count, item_count);
230 static void msg_count_hash_update_func(FolderItem *item, gpointer data)
232 gchar *identifier;
233 NotificationMsgCount *count;
234 GHashTable *hash = data;
236 if(!notify_include_folder_type(item->folder->klass->type,
237 item->folder->klass->uistr))
238 return;
240 identifier = folder_item_get_identifier(item);
241 if(!identifier)
242 return;
244 count = g_hash_table_lookup(hash, identifier);
246 if(!count) {
247 count = g_new0(NotificationMsgCount,1);
248 g_hash_table_insert(hash, identifier, count);
250 else
251 g_free(identifier);
253 count->new_msgs = item->new_msgs;
254 count->unread_msgs = item->unread_msgs;
255 count->unreadmarked_msgs = item->unreadmarked_msgs;
256 count->marked_msgs = item->marked_msgs;
257 count->total_msgs = item->total_msgs;
260 static void msg_count_update_from_hash(gpointer key, gpointer value,
261 gpointer data)
263 NotificationMsgCount *count = value;
264 msg_count_add(&msg_count,count);
268 /* Replacement for the post-filtering hook:
269 Pseudocode by Colin:
270 hook on FOLDER_ITEM_UPDATE_HOOKLIST
271 if hook flags & F_ITEM_UPDATE_MSGCOUNT
272 scan mails (folder_item_get_msg_list)
273 if MSG_IS_NEW(msginfo->flags) and not in hashtable
274 notify()
275 add to hashtable
276 procmsg_msg_list_free
278 hook on MSGINFO_UPDATE_HOOKLIST
279 if hook flags & MSGINFO_UPDATE_FLAGS
280 if !MSG_IS_NEW(msginfo->flags)
281 remove from hashtable, it's now useless
284 /* This hash table holds all mails that we already notified about,
285 and that still are marked as "new". The keys are the msgid's,
286 the values are just 1's stored in a pointer. */
287 static GHashTable *notified_hash = NULL;
290 /* Remove message from the notified_hash if
291 * - the message flags changed
292 * - the message is not new
293 * - the message is in the hash
295 gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate *msg_update)
297 g_return_val_if_fail(msg_update != NULL, FALSE);
299 if((msg_update->flags & MSGINFO_UPDATE_FLAGS) &&
300 !MSG_IS_NEW(msg_update->msginfo->flags)) {
302 MsgInfo *msg;
303 gchar *msgid;
305 msg = msg_update->msginfo;
306 if(msg->msgid)
307 msgid = msg->msgid;
308 else {
309 debug_print("Notification Plugin: Message has no message ID!\n");
310 msgid = "";
313 g_return_val_if_fail(msg != NULL, FALSE);
315 if(g_hash_table_lookup(notified_hash, msgid) != NULL) {
317 debug_print("Notification Plugin: Removing message id %s from hash "
318 "table\n", msgid);
319 g_hash_table_remove(notified_hash, msgid);
322 return FALSE;
325 /* On startup, mark all new mails as already notified
326 * (by including them in the hash) */
327 void notification_notified_hash_startup_init(void)
329 GList *folder_list, *walk;
330 Folder *folder;
332 if(!notified_hash) {
333 notified_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
334 g_free, NULL);
335 debug_print("Notification Plugin: Hash table created\n");
338 folder_list = folder_get_list();
339 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
340 folder = walk->data;
342 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
343 notification_traverse_hash_startup, NULL);
347 static gboolean notification_traverse_hash_startup(GNode *node, gpointer data)
349 GSList *walk;
350 GSList *msg_list;
351 FolderItem *item = (FolderItem*) node->data;
352 gint new_msgs_left;
354 if(!(item->new_msgs))
355 return FALSE;
357 new_msgs_left = item->new_msgs;
358 msg_list = folder_item_get_msg_list(item);
360 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
361 MsgInfo *msg = (MsgInfo*) walk->data;
362 if(MSG_IS_NEW(msg->flags)) {
363 gchar *msgid;
365 if(msg->msgid)
366 msgid = msg->msgid;
367 else {
368 debug_print("Notification Plugin: Message has no message ID!\n");
369 msgid = "";
372 /* If the message id is not yet in the hash, add it */
373 g_hash_table_insert(notified_hash, g_strdup(msgid),
374 GINT_TO_POINTER(1));
375 debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
376 msgid);
377 /* Decrement left count and check if we're already done */
378 new_msgs_left--;
379 if(new_msgs_left == 0)
380 break;
383 procmsg_msg_list_free(msg_list);
384 return FALSE;
387 void notification_core_free(void)
389 if(notified_hash) {
390 g_hash_table_destroy(notified_hash);
391 notified_hash = NULL;
393 if(msg_count_hash) {
394 g_hash_table_destroy(msg_count_hash);
395 msg_count_hash = NULL;
397 debug_print("Notification Plugin: Freed internal data\n");
400 void notification_new_unnotified_msgs(FolderItemUpdateData *update_data)
402 GSList *msg_list, *walk;
404 g_return_if_fail(notified_hash != NULL);
406 msg_list = folder_item_get_msg_list(update_data->item);
408 for(walk = msg_list; walk; walk = g_slist_next(walk)) {
409 MsgInfo *msg;
410 msg = (MsgInfo*) walk->data;
412 if(MSG_IS_NEW(msg->flags)) {
413 gchar *msgid;
415 if(msg->msgid)
416 msgid = msg->msgid;
417 else {
418 debug_print("Notification Plugin: Message has not message ID!\n");
419 msgid = "";
422 debug_print("Notification Plugin: Found msg %s, "
423 "checking if it is in hash...\n", msgid);
424 /* Check if message is in hash table */
425 if(g_hash_table_lookup(notified_hash, msgid) != NULL)
426 debug_print("yes.\n");
427 else {
428 /* Add to hashtable */
429 g_hash_table_insert(notified_hash, g_strdup(msgid),
430 GINT_TO_POINTER(1));
431 debug_print("no, added to table.\n");
433 /* Do the notification */
434 notification_new_unnotified_do_msg(msg);
437 } /* msg is 'new' */
438 } /* for all messages */
439 procmsg_msg_list_free(msg_list);
442 #ifdef HAVE_LIBCANBERRA_GTK
443 static void canberra_finished_cb(ca_context *c, uint32_t id, int error, void *data)
445 canberra_new_email_is_playing = FALSE;
447 #endif
449 static void notification_new_unnotified_do_msg(MsgInfo *msg)
451 #ifdef NOTIFICATION_POPUP
452 notification_popup_msg(msg);
453 #endif
454 #ifdef NOTIFICATION_COMMAND
455 notification_command_msg(msg);
456 #endif
457 #ifdef NOTIFICATION_TRAYICON
458 notification_trayicon_msg(msg);
459 #endif
461 #ifdef HAVE_LIBCANBERRA_GTK
462 /* canberra */
463 if(notify_config.canberra_play_sounds && !canberra_new_email_is_playing) {
464 ca_proplist *proplist;
465 ca_proplist_create(&proplist);
466 ca_proplist_sets(proplist,CA_PROP_EVENT_ID ,"message-new-email");
467 canberra_new_email_is_playing = TRUE;
468 ca_context_play_full(ca_gtk_context_get(), 0, proplist, canberra_finished_cb, NULL);
469 ca_proplist_destroy(proplist);
471 #endif
474 /* If folders is not NULL, then consider only those folder items
475 * If max_msgs is not 0, stop after collecting msg_msgs messages
477 GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
478 gint max_msgs)
480 GList *folder_list, *walk;
481 Folder *folder;
482 TraverseCollect collect_data;
484 collect_data.unread_also = unread_also;
485 collect_data.collected_msgs = NULL;
486 collect_data.folder_items = folder_items;
487 collect_data.max_msgs = max_msgs;
488 collect_data.num_msgs = 0;
490 folder_list = folder_get_list();
491 for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
492 folder = walk->data;
494 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
495 notification_traverse_collect, &collect_data);
497 return collect_data.collected_msgs;
500 void notification_collected_msgs_free(GSList *collected_msgs)
502 if(collected_msgs) {
503 GSList *walk;
504 for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
505 CollectedMsg *msg = walk->data;
506 if(msg->from)
507 g_free(msg->from);
508 if(msg->subject)
509 g_free(msg->subject);
510 if(msg->folderitem_name)
511 g_free(msg->folderitem_name);
512 msg->msginfo = NULL;
513 g_free(msg);
515 g_slist_free(collected_msgs);
519 static gboolean notification_traverse_collect(GNode *node, gpointer data)
521 TraverseCollect *cdata = data;
522 FolderItem *item = node->data;
523 gchar *folder_id_cur;
525 /* Obey global folder type limitations */
526 if(!notify_include_folder_type(item->folder->klass->type,
527 item->folder->klass->uistr))
528 return FALSE;
530 /* If a folder_items list was given, check it first */
531 if((cdata->folder_items) && (item->path != NULL) &&
532 ((folder_id_cur = folder_item_get_identifier(item)) != NULL)) {
533 FolderItem *list_item;
534 GSList *walk;
535 gchar *folder_id_list;
536 gboolean eq;
537 gboolean folder_in_list = FALSE;
539 for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
540 list_item = walk->data;
541 folder_id_list = folder_item_get_identifier(list_item);
542 eq = !g_strcmp0(folder_id_list,folder_id_cur);
543 g_free(folder_id_list);
544 if(eq) {
545 folder_in_list = TRUE;
546 break;
549 g_free(folder_id_cur);
550 if(!folder_in_list)
551 return FALSE;
554 if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
555 GSList *msg_list = folder_item_get_msg_list(item);
556 GSList *walk;
557 for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
558 MsgInfo *msg_info = walk->data;
559 CollectedMsg *cmsg;
561 if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
562 return FALSE;
564 if(MSG_IS_NEW(msg_info->flags) ||
565 (MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
567 cmsg = g_new(CollectedMsg, 1);
568 cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
569 cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
570 if(msg_info->folder && msg_info->folder->name)
571 cmsg->folderitem_name = g_strdup(msg_info->folder->path);
572 else
573 cmsg->folderitem_name = g_strdup("");
575 cmsg->msginfo = msg_info;
577 cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
578 cdata->num_msgs++;
581 procmsg_msg_list_free(msg_list);
584 return FALSE;
587 gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
589 gboolean retval;
591 retval = FALSE;
592 switch(ftype) {
593 case F_MH:
594 case F_MBOX:
595 case F_MAILDIR:
596 case F_IMAP:
597 if(notify_config.include_mail)
598 retval = TRUE;
599 break;
600 case F_NEWS:
601 if(notify_config.include_news)
602 retval = TRUE;
603 break;
604 case F_UNKNOWN:
605 if(uistr == NULL)
606 retval = FALSE;
607 else if(!strcmp(uistr, "vCalendar")) {
608 if(notify_config.include_calendar)
609 retval = TRUE;
611 else if(!strcmp(uistr, "RSSyl")) {
612 if(notify_config.include_rss)
613 retval = TRUE;
615 else
616 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
617 break;
618 default:
619 debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
622 return retval;
625 static void fix_folderview_scroll(MainWindow *mainwin)
627 static gboolean fix_done = FALSE;
629 if (fix_done)
630 return;
632 gtk_widget_queue_resize(mainwin->folderview->ctree);
634 fix_done = TRUE;
637 void notification_show_mainwindow(MainWindow *mainwin)
639 gtk_window_deiconify(GTK_WINDOW(mainwin->window));
640 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin->window), FALSE);
641 main_window_show(mainwin);
642 gtk_window_present(GTK_WINDOW(mainwin->window));
643 fix_folderview_scroll(mainwin);
646 #ifdef HAVE_LIBNOTIFY
647 #define STR_MAX_LEN 511
648 /* Returns a newly allocated string which needs to be freed */
649 gchar* notification_libnotify_sanitize_str(gchar *in)
651 gint out;
652 gchar tmp_str[STR_MAX_LEN+1];
654 if(in == NULL) return NULL;
656 out = 0;
657 while(*in) {
658 if(*in == '<') {
659 if(out+4 > STR_MAX_LEN) break;
660 memcpy(&(tmp_str[out]),"&lt;",4);
661 in++; out += 4;
663 else if(*in == '>') {
664 if(out+4 > STR_MAX_LEN) break;
665 memcpy(&(tmp_str[out]),"&gt;",4);
666 in++; out += 4;
668 else if(*in == '&') {
669 if(out+5 > STR_MAX_LEN) break;
670 memcpy(&(tmp_str[out]),"&amp;",5);
671 in++; out += 5;
673 else {
674 if(out+1 > STR_MAX_LEN) break;
675 tmp_str[out++] = *in++;
678 tmp_str[out] = '\0';
679 return strdup(tmp_str);
682 gchar* notification_validate_utf8_str(gchar *text)
684 gchar *utf8_str = NULL;
686 if(!g_utf8_validate(text, -1, NULL)) {
687 debug_print("Notification plugin: String is not valid utf8, "
688 "trying to fix it...\n");
689 /* fix it */
690 utf8_str = conv_codeset_strdup(text,
691 conv_get_locale_charset_str_no_utf8(),
692 CS_INTERNAL);
693 /* check if the fix worked */
694 if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
695 debug_print("Notification plugin: String is still not valid utf8, "
696 "sanitizing...\n");
697 utf8_str = g_malloc(strlen(text)*2+1);
698 conv_localetodisp(utf8_str, strlen(text)*2+1, text);
701 else {
702 debug_print("Notification plugin: String is valid utf8\n");
703 utf8_str = g_strdup(text);
705 return utf8_str;
707 #endif