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>
41 #ifdef NOTIFICATION_AYATANA_INDICATOR
42 # include "notification_ayatana_indicator.h"
46 GSList
*collected_msgs
;
53 static gboolean
notification_traverse_collect(GNode
*, gpointer
);
54 static void notification_new_unnotified_do_msg(MsgInfo
*);
55 static gboolean
notification_traverse_hash_startup(GNode
*, gpointer
);
57 static GHashTable
*msg_count_hash
;
58 static NotificationMsgCount msg_count
;
60 #ifdef HAVE_LIBCANBERRA_GTK
61 static gboolean canberra_new_email_is_playing
= FALSE
;
64 static void msg_count_hash_update_func(FolderItem
*, gpointer
);
65 static void msg_count_update_from_hash(gpointer
, gpointer
, gpointer
);
66 static void msg_count_clear(NotificationMsgCount
*);
67 static void msg_count_add(NotificationMsgCount
*,NotificationMsgCount
*);
68 static void msg_count_copy(NotificationMsgCount
*,NotificationMsgCount
*);
70 void notification_core_global_includes_changed(void)
72 #ifdef NOTIFICATION_BANNER
73 notification_update_banner();
77 g_hash_table_destroy(msg_count_hash
);
78 msg_count_hash
= NULL
;
80 notification_update_msg_counts(NULL
);
83 /* Hide/show main window */
84 void notification_toggle_hide_show_window(void)
89 if((mainwin
= mainwindow_get_mainwindow()) == NULL
)
92 gdkwin
= gtk_widget_get_window(GTK_WIDGET(mainwin
->window
));
93 if(gtk_widget_get_visible(GTK_WIDGET(mainwin
->window
))) {
94 if((gdk_window_get_state(gdkwin
) & GDK_WINDOW_STATE_ICONIFIED
)
95 || mainwindow_is_obscured()) {
96 notification_show_mainwindow(mainwin
);
99 main_window_hide(mainwin
);
103 notification_show_mainwindow(mainwin
);
107 void notification_update_msg_counts(FolderItem
*removed_item
)
110 msg_count_hash
= g_hash_table_new_full(g_str_hash
,g_str_equal
,
113 folder_func_to_all_folders(msg_count_hash_update_func
, msg_count_hash
);
117 identifier
= folder_item_get_identifier(removed_item
);
119 g_hash_table_remove(msg_count_hash
, identifier
);
123 msg_count_clear(&msg_count
);
124 g_hash_table_foreach(msg_count_hash
, msg_count_update_from_hash
, NULL
);
125 #ifdef NOTIFICATION_LCDPROC
126 notification_update_lcdproc();
128 #ifdef NOTIFICATION_TRAYICON
129 notification_update_trayicon();
131 #ifdef NOTIFICATION_AYATANA_INDICATOR
132 notification_update_ayatana_indicator();
134 #ifdef NOTIFICATION_INDICATOR
135 notification_update_indicator();
137 notification_update_urgency_hint();
140 static void msg_count_clear(NotificationMsgCount
*count
)
143 count
->unread_msgs
= 0;
144 count
->unreadmarked_msgs
= 0;
145 count
->marked_msgs
= 0;
146 count
->total_msgs
= 0;
150 static void msg_count_add(NotificationMsgCount
*c1
,NotificationMsgCount
*c2
)
152 c1
->new_msgs
+= c2
->new_msgs
;
153 c1
->unread_msgs
+= c2
->unread_msgs
;
154 c1
->unreadmarked_msgs
+= c2
->unreadmarked_msgs
;
155 c1
->marked_msgs
+= c2
->marked_msgs
;
156 c1
->total_msgs
+= c2
->total_msgs
;
160 static void msg_count_copy(NotificationMsgCount
*c1
,NotificationMsgCount
*c2
)
162 c1
->new_msgs
= c2
->new_msgs
;
163 c1
->unread_msgs
= c2
->unread_msgs
;
164 c1
->unreadmarked_msgs
= c2
->unreadmarked_msgs
;
165 c1
->marked_msgs
= c2
->marked_msgs
;
166 c1
->total_msgs
= c2
->total_msgs
;
169 gboolean
get_flat_gslist_from_nodes_traverse_func(GNode
*node
, gpointer data
)
172 GSList
**list
= data
;
173 *list
= g_slist_prepend(*list
, node
->data
);
178 GSList
* get_flat_gslist_from_nodes(GNode
*node
)
180 GSList
*retval
= NULL
;
182 g_node_traverse(node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1, get_flat_gslist_from_nodes_traverse_func
, &retval
);
186 void notification_core_get_msg_count_of_foldername(gchar
*foldername
, NotificationMsgCount
*count
)
192 Folder
*folder
= NULL
;
194 for(list
= folder_get_list(); list
!= NULL
; list
= list
->next
) {
195 walk_folder
= list
->data
;
196 if(g_strcmp0(foldername
, walk_folder
->name
) == 0) {
197 folder
= walk_folder
;
202 debug_print("Notification plugin: Error: Could not find folder %s\n", foldername
);
206 msg_count_clear(count
);
207 f_list
= get_flat_gslist_from_nodes(folder
->node
);
208 notification_core_get_msg_count(f_list
, count
);
209 g_slist_free(f_list
);
212 void notification_core_get_msg_count(GSList
*folder_list
,
213 NotificationMsgCount
*count
)
218 msg_count_copy(count
,&msg_count
);
220 msg_count_clear(count
);
221 for(walk
= folder_list
; walk
; walk
= walk
->next
) {
223 NotificationMsgCount
*item_count
;
224 FolderItem
*item
= (FolderItem
*) walk
->data
;
225 identifier
= folder_item_get_identifier(item
);
227 item_count
= g_hash_table_lookup(msg_count_hash
,identifier
);
230 msg_count_add(count
, item_count
);
236 static void msg_count_hash_update_func(FolderItem
*item
, gpointer data
)
239 NotificationMsgCount
*count
;
240 GHashTable
*hash
= data
;
242 if(!notify_include_folder_type(item
->folder
->klass
->type
,
243 item
->folder
->klass
->uistr
))
246 identifier
= folder_item_get_identifier(item
);
250 count
= g_hash_table_lookup(hash
, identifier
);
253 count
= g_new0(NotificationMsgCount
,1);
254 g_hash_table_insert(hash
, identifier
, count
);
259 count
->new_msgs
= item
->new_msgs
;
260 count
->unread_msgs
= item
->unread_msgs
;
261 count
->unreadmarked_msgs
= item
->unreadmarked_msgs
;
262 count
->marked_msgs
= item
->marked_msgs
;
263 count
->total_msgs
= item
->total_msgs
;
266 static void msg_count_update_from_hash(gpointer key
, gpointer value
,
269 NotificationMsgCount
*count
= value
;
270 msg_count_add(&msg_count
,count
);
274 /* Replacement for the post-filtering hook:
276 hook on FOLDER_ITEM_UPDATE_HOOKLIST
277 if hook flags & F_ITEM_UPDATE_MSGCOUNT
278 scan mails (folder_item_get_msg_list)
279 if MSG_IS_NEW(msginfo->flags) and not in hashtable
282 procmsg_msg_list_free
284 hook on MSGINFO_UPDATE_HOOKLIST
285 if hook flags & MSGINFO_UPDATE_FLAGS
286 if !MSG_IS_NEW(msginfo->flags)
287 remove from hashtable, it's now useless
290 /* This hash table holds all mails that we already notified about,
291 and that still are marked as "new". The keys are the msgid's,
292 the values are just 1's stored in a pointer. */
293 static GHashTable
*notified_hash
= NULL
;
296 /* Remove message from the notified_hash if
297 * - the message flags changed
298 * - the message is not new
299 * - the message is in the hash
301 gboolean
notification_notified_hash_msginfo_update(MsgInfoUpdate
*msg_update
)
303 g_return_val_if_fail(msg_update
!= NULL
, FALSE
);
305 if((msg_update
->flags
& MSGINFO_UPDATE_FLAGS
) &&
306 !MSG_IS_NEW(msg_update
->msginfo
->flags
)) {
311 msg
= msg_update
->msginfo
;
315 debug_print("Notification Plugin: Message has no message ID!\n");
319 g_return_val_if_fail(msg
!= NULL
, FALSE
);
321 if(g_hash_table_lookup(notified_hash
, msgid
) != NULL
) {
323 debug_print("Notification Plugin: Removing message id %s from hash "
325 g_hash_table_remove(notified_hash
, msgid
);
331 /* On startup, mark all new mails as already notified
332 * (by including them in the hash) */
333 void notification_notified_hash_startup_init(void)
335 GList
*folder_list
, *walk
;
339 notified_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
341 debug_print("Notification Plugin: Hash table created\n");
344 folder_list
= folder_get_list();
345 for(walk
= folder_list
; walk
!= NULL
; walk
= g_list_next(walk
)) {
348 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
349 notification_traverse_hash_startup
, NULL
);
353 static gboolean
notification_traverse_hash_startup(GNode
*node
, gpointer data
)
357 FolderItem
*item
= (FolderItem
*) node
->data
;
360 if(!(item
->new_msgs
))
363 new_msgs_left
= item
->new_msgs
;
364 msg_list
= folder_item_get_msg_list(item
);
366 for(walk
= msg_list
; walk
; walk
= g_slist_next(walk
)) {
367 MsgInfo
*msg
= (MsgInfo
*) walk
->data
;
368 if(MSG_IS_NEW(msg
->flags
)) {
374 debug_print("Notification Plugin: Message has no message ID!\n");
378 /* If the message id is not yet in the hash, add it */
379 g_hash_table_insert(notified_hash
, g_strdup(msgid
),
381 debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
383 /* Decrement left count and check if we're already done */
385 if(new_msgs_left
== 0)
389 procmsg_msg_list_free(msg_list
);
393 void notification_core_free(void)
396 g_hash_table_destroy(notified_hash
);
397 notified_hash
= NULL
;
400 g_hash_table_destroy(msg_count_hash
);
401 msg_count_hash
= NULL
;
403 debug_print("Notification Plugin: Freed internal data\n");
406 void notification_new_unnotified_msgs(FolderItemUpdateData
*update_data
)
408 GSList
*msg_list
, *walk
;
410 g_return_if_fail(notified_hash
!= NULL
);
412 msg_list
= folder_item_get_msg_list(update_data
->item
);
414 for(walk
= msg_list
; walk
; walk
= g_slist_next(walk
)) {
416 msg
= (MsgInfo
*) walk
->data
;
418 if(MSG_IS_NEW(msg
->flags
)) {
424 debug_print("Notification Plugin: Message has not message ID!\n");
428 debug_print("Notification Plugin: Found msg %s, "
429 "checking if it is in hash...\n", msgid
);
430 /* Check if message is in hash table */
431 if(g_hash_table_lookup(notified_hash
, msgid
) != NULL
)
432 debug_print("yes.\n");
434 /* Add to hashtable */
435 g_hash_table_insert(notified_hash
, g_strdup(msgid
),
437 debug_print("no, added to table.\n");
439 /* Do the notification */
440 notification_new_unnotified_do_msg(msg
);
444 } /* for all messages */
445 procmsg_msg_list_free(msg_list
);
448 #ifdef HAVE_LIBCANBERRA_GTK
449 static void canberra_finished_cb(ca_context
*c
, uint32_t id
, int error
, void *data
)
451 canberra_new_email_is_playing
= FALSE
;
455 static void notification_new_unnotified_do_msg(MsgInfo
*msg
)
457 #ifdef NOTIFICATION_POPUP
458 notification_popup_msg(msg
);
460 #ifdef NOTIFICATION_COMMAND
461 notification_command_msg(msg
);
463 #ifdef NOTIFICATION_TRAYICON
464 notification_trayicon_msg(msg
);
467 #ifdef HAVE_LIBCANBERRA_GTK
469 if(notify_config
.canberra_play_sounds
&& !canberra_new_email_is_playing
) {
470 ca_proplist
*proplist
;
471 ca_proplist_create(&proplist
);
472 ca_proplist_sets(proplist
,CA_PROP_EVENT_ID
,"message-new-email");
473 canberra_new_email_is_playing
= TRUE
;
474 ca_context_play_full(ca_gtk_context_get(), 0, proplist
, canberra_finished_cb
, NULL
);
475 ca_proplist_destroy(proplist
);
480 /* If folders is not NULL, then consider only those folder items
481 * If max_msgs is not 0, stop after collecting msg_msgs messages
483 GSList
* notification_collect_msgs(gboolean unread_also
, GSList
*folder_items
,
486 GList
*folder_list
, *walk
;
488 TraverseCollect collect_data
;
490 collect_data
.unread_also
= unread_also
;
491 collect_data
.collected_msgs
= NULL
;
492 collect_data
.folder_items
= folder_items
;
493 collect_data
.max_msgs
= max_msgs
;
494 collect_data
.num_msgs
= 0;
496 folder_list
= folder_get_list();
497 for(walk
= folder_list
; walk
!= NULL
; walk
= g_list_next(walk
)) {
500 g_node_traverse(folder
->node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
501 notification_traverse_collect
, &collect_data
);
503 return collect_data
.collected_msgs
;
506 void notification_collected_msgs_free(GSList
*collected_msgs
)
510 for(walk
= collected_msgs
; walk
!= NULL
; walk
= g_slist_next(walk
)) {
511 CollectedMsg
*msg
= walk
->data
;
515 g_free(msg
->subject
);
516 if(msg
->folderitem_name
)
517 g_free(msg
->folderitem_name
);
521 g_slist_free(collected_msgs
);
525 static gboolean
notification_traverse_collect(GNode
*node
, gpointer data
)
527 TraverseCollect
*cdata
= data
;
528 FolderItem
*item
= node
->data
;
529 gchar
*folder_id_cur
;
531 /* Obey global folder type limitations */
532 if(!notify_include_folder_type(item
->folder
->klass
->type
,
533 item
->folder
->klass
->uistr
))
536 /* If a folder_items list was given, check it first */
537 if((cdata
->folder_items
) && (item
->path
!= NULL
) &&
538 ((folder_id_cur
= folder_item_get_identifier(item
)) != NULL
)) {
539 FolderItem
*list_item
;
541 gchar
*folder_id_list
;
543 gboolean folder_in_list
= FALSE
;
545 for(walk
= cdata
->folder_items
; walk
!= NULL
; walk
= g_slist_next(walk
)) {
546 list_item
= walk
->data
;
547 folder_id_list
= folder_item_get_identifier(list_item
);
548 eq
= !g_strcmp0(folder_id_list
,folder_id_cur
);
549 g_free(folder_id_list
);
551 folder_in_list
= TRUE
;
555 g_free(folder_id_cur
);
560 if(item
->new_msgs
|| (cdata
->unread_also
&& item
->unread_msgs
)) {
561 GSList
*msg_list
= folder_item_get_msg_list(item
);
563 for(walk
= msg_list
; walk
!= NULL
; walk
= g_slist_next(walk
)) {
564 MsgInfo
*msg_info
= walk
->data
;
567 if((cdata
->max_msgs
!= 0) && (cdata
->num_msgs
>= cdata
->max_msgs
))
570 if(MSG_IS_NEW(msg_info
->flags
) ||
571 (MSG_IS_UNREAD(msg_info
->flags
) && cdata
->unread_also
)) {
573 cmsg
= g_new(CollectedMsg
, 1);
574 cmsg
->from
= g_strdup(msg_info
->from
? msg_info
->from
: "");
575 cmsg
->subject
= g_strdup(msg_info
->subject
? msg_info
->subject
: "");
576 if(msg_info
->folder
&& msg_info
->folder
->name
)
577 cmsg
->folderitem_name
= g_strdup(msg_info
->folder
->path
);
579 cmsg
->folderitem_name
= g_strdup("");
581 cmsg
->msginfo
= msg_info
;
583 cdata
->collected_msgs
= g_slist_prepend(cdata
->collected_msgs
, cmsg
);
587 procmsg_msg_list_free(msg_list
);
593 gboolean
notify_include_folder_type(FolderType ftype
, gchar
*uistr
)
603 if(notify_config
.include_mail
)
607 if(notify_config
.include_news
)
613 else if(!strcmp(uistr
, "vCalendar")) {
614 if(notify_config
.include_calendar
)
617 else if(!strcmp(uistr
, "RSSyl")) {
618 if(notify_config
.include_rss
)
622 debug_print("Notification Plugin: Unknown folder type %d\n",ftype
);
625 debug_print("Notification Plugin: Unknown folder type %d\n",ftype
);
631 static void fix_folderview_scroll(MainWindow
*mainwin
)
633 static gboolean fix_done
= FALSE
;
638 gtk_widget_queue_resize(mainwin
->folderview
->ctree
);
643 void notification_show_mainwindow(MainWindow
*mainwin
)
645 gtk_window_deiconify(GTK_WINDOW(mainwin
->window
));
646 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mainwin
->window
), FALSE
);
647 main_window_show(mainwin
);
648 gtk_window_present(GTK_WINDOW(mainwin
->window
));
649 fix_folderview_scroll(mainwin
);
652 #ifdef HAVE_LIBNOTIFY
653 #define STR_MAX_LEN 511
654 /* Returns a newly allocated string which needs to be freed */
655 gchar
* notification_libnotify_sanitize_str(gchar
*in
)
658 gchar tmp_str
[STR_MAX_LEN
+1];
660 if(in
== NULL
) return NULL
;
665 if(out
+4 > STR_MAX_LEN
) break;
666 memcpy(&(tmp_str
[out
]),"<",4);
669 else if(*in
== '>') {
670 if(out
+4 > STR_MAX_LEN
) break;
671 memcpy(&(tmp_str
[out
]),">",4);
674 else if(*in
== '&') {
675 if(out
+5 > STR_MAX_LEN
) break;
676 memcpy(&(tmp_str
[out
]),"&",5);
680 if(out
+1 > STR_MAX_LEN
) break;
681 tmp_str
[out
++] = *in
++;
685 return strdup(tmp_str
);
688 gchar
* notification_validate_utf8_str(gchar
*text
)
690 gchar
*utf8_str
= NULL
;
692 if(!g_utf8_validate(text
, -1, NULL
)) {
693 debug_print("Notification plugin: String is not valid utf8, "
694 "trying to fix it...\n");
696 utf8_str
= conv_codeset_strdup(text
,
697 conv_get_locale_charset_str_no_utf8(),
699 /* check if the fix worked */
700 if(utf8_str
== NULL
|| !g_utf8_validate(utf8_str
, -1, NULL
)) {
701 debug_print("Notification plugin: String is still not valid utf8, "
703 utf8_str
= g_malloc(strlen(text
)*2+1);
704 conv_localetodisp(utf8_str
, strlen(text
)*2+1, text
);
708 debug_print("Notification plugin: String is valid utf8\n");
709 utf8_str
= g_strdup(text
);