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)
86 if((mainwin
= mainwindow_get_mainwindow()) == NULL
)
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
);
96 main_window_hide(mainwin
);
100 notification_show_mainwindow(mainwin
);
104 void notification_update_msg_counts(FolderItem
*removed_item
)
107 msg_count_hash
= g_hash_table_new_full(g_str_hash
,g_str_equal
,
110 folder_func_to_all_folders(msg_count_hash_update_func
, msg_count_hash
);
114 identifier
= folder_item_get_identifier(removed_item
);
116 g_hash_table_remove(msg_count_hash
, 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();
125 #ifdef NOTIFICATION_TRAYICON
126 notification_update_trayicon();
128 #ifdef NOTIFICATION_INDICATOR
129 notification_update_indicator();
131 notification_update_urgency_hint();
134 static void msg_count_clear(NotificationMsgCount
*count
)
137 count
->unread_msgs
= 0;
138 count
->unreadmarked_msgs
= 0;
139 count
->marked_msgs
= 0;
140 count
->total_msgs
= 0;
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
;
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
)
166 GSList
**list
= data
;
167 *list
= g_slist_prepend(*list
, node
->data
);
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
);
180 void notification_core_get_msg_count_of_foldername(gchar
*foldername
, NotificationMsgCount
*count
)
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
;
196 debug_print("Notification plugin: Error: Could not find folder %s\n", foldername
);
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
)
212 msg_count_copy(count
,&msg_count
);
214 msg_count_clear(count
);
215 for(walk
= folder_list
; walk
; walk
= walk
->next
) {
217 NotificationMsgCount
*item_count
;
218 FolderItem
*item
= (FolderItem
*) walk
->data
;
219 identifier
= folder_item_get_identifier(item
);
221 item_count
= g_hash_table_lookup(msg_count_hash
,identifier
);
224 msg_count_add(count
, item_count
);
230 static void msg_count_hash_update_func(FolderItem
*item
, gpointer data
)
233 NotificationMsgCount
*count
;
234 GHashTable
*hash
= data
;
236 if(!notify_include_folder_type(item
->folder
->klass
->type
,
237 item
->folder
->klass
->uistr
))
240 identifier
= folder_item_get_identifier(item
);
244 count
= g_hash_table_lookup(hash
, identifier
);
247 count
= g_new0(NotificationMsgCount
,1);
248 g_hash_table_insert(hash
, identifier
, count
);
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
,
263 NotificationMsgCount
*count
= value
;
264 msg_count_add(&msg_count
,count
);
268 /* Replacement for the post-filtering hook:
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
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
)) {
305 msg
= msg_update
->msginfo
;
309 debug_print("Notification Plugin: Message has no message ID!\n");
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 "
319 g_hash_table_remove(notified_hash
, msgid
);
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
;
333 notified_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
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
)) {
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
)
351 FolderItem
*item
= (FolderItem
*) node
->data
;
354 if(!(item
->new_msgs
))
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
)) {
368 debug_print("Notification Plugin: Message has no message ID!\n");
372 /* If the message id is not yet in the hash, add it */
373 g_hash_table_insert(notified_hash
, g_strdup(msgid
),
375 debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
377 /* Decrement left count and check if we're already done */
379 if(new_msgs_left
== 0)
383 procmsg_msg_list_free(msg_list
);
387 void notification_core_free(void)
390 g_hash_table_destroy(notified_hash
);
391 notified_hash
= NULL
;
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
)) {
410 msg
= (MsgInfo
*) walk
->data
;
412 if(MSG_IS_NEW(msg
->flags
)) {
418 debug_print("Notification Plugin: Message has not message ID!\n");
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");
428 /* Add to hashtable */
429 g_hash_table_insert(notified_hash
, g_strdup(msgid
),
431 debug_print("no, added to table.\n");
433 /* Do the notification */
434 notification_new_unnotified_do_msg(msg
);
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
;
449 static void notification_new_unnotified_do_msg(MsgInfo
*msg
)
451 #ifdef NOTIFICATION_POPUP
452 notification_popup_msg(msg
);
454 #ifdef NOTIFICATION_COMMAND
455 notification_command_msg(msg
);
457 #ifdef NOTIFICATION_TRAYICON
458 notification_trayicon_msg(msg
);
461 #ifdef HAVE_LIBCANBERRA_GTK
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
);
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
,
480 GList
*folder_list
, *walk
;
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
)) {
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
)
504 for(walk
= collected_msgs
; walk
!= NULL
; walk
= g_slist_next(walk
)) {
505 CollectedMsg
*msg
= walk
->data
;
509 g_free(msg
->subject
);
510 if(msg
->folderitem_name
)
511 g_free(msg
->folderitem_name
);
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
))
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
;
535 gchar
*folder_id_list
;
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
);
545 folder_in_list
= TRUE
;
549 g_free(folder_id_cur
);
554 if(item
->new_msgs
|| (cdata
->unread_also
&& item
->unread_msgs
)) {
555 GSList
*msg_list
= folder_item_get_msg_list(item
);
557 for(walk
= msg_list
; walk
!= NULL
; walk
= g_slist_next(walk
)) {
558 MsgInfo
*msg_info
= walk
->data
;
561 if((cdata
->max_msgs
!= 0) && (cdata
->num_msgs
>= cdata
->max_msgs
))
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
);
573 cmsg
->folderitem_name
= g_strdup("");
575 cmsg
->msginfo
= msg_info
;
577 cdata
->collected_msgs
= g_slist_prepend(cdata
->collected_msgs
, cmsg
);
581 procmsg_msg_list_free(msg_list
);
587 gboolean
notify_include_folder_type(FolderType ftype
, gchar
*uistr
)
597 if(notify_config
.include_mail
)
601 if(notify_config
.include_news
)
607 else if(!strcmp(uistr
, "vCalendar")) {
608 if(notify_config
.include_calendar
)
611 else if(!strcmp(uistr
, "RSSyl")) {
612 if(notify_config
.include_rss
)
616 debug_print("Notification Plugin: Unknown folder type %d\n",ftype
);
619 debug_print("Notification Plugin: Unknown folder type %d\n",ftype
);
625 static void fix_folderview_scroll(MainWindow
*mainwin
)
627 static gboolean fix_done
= FALSE
;
632 gtk_widget_queue_resize(mainwin
->folderview
->ctree
);
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
)
652 gchar tmp_str
[STR_MAX_LEN
+1];
654 if(in
== NULL
) return NULL
;
659 if(out
+4 > STR_MAX_LEN
) break;
660 memcpy(&(tmp_str
[out
]),"<",4);
663 else if(*in
== '>') {
664 if(out
+4 > STR_MAX_LEN
) break;
665 memcpy(&(tmp_str
[out
]),">",4);
668 else if(*in
== '&') {
669 if(out
+5 > STR_MAX_LEN
) break;
670 memcpy(&(tmp_str
[out
]),"&",5);
674 if(out
+1 > STR_MAX_LEN
) break;
675 tmp_str
[out
++] = *in
++;
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");
690 utf8_str
= conv_codeset_strdup(text
,
691 conv_get_locale_charset_str_no_utf8(),
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, "
697 utf8_str
= g_malloc(strlen(text
)*2+1);
698 conv_localetodisp(utf8_str
, strlen(text
)*2+1, text
);
702 debug_print("Notification plugin: String is valid utf8\n");
703 utf8_str
= g_strdup(text
);