1 /* Notification plugin for Claws Mail
2  * Copyright (C) 2005-2007 Holger Berndt
3  *
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.
8  *
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.
13  *
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/>.
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #  include "claws-features.h"
21 #endif
22 
23 #include <glib.h>
24 #include <glib/gi18n.h>
25 
26 #include "common/claws.h"
27 #include "common/version.h"
28 #include "common/utils.h"
29 #include "common/defs.h"
30 #include "folder.h"
31 #include "common/hooks.h"
32 #include "procmsg.h"
33 #include "mainwindow.h"
34 #include "main.h"
35 #include "gtk/gtkutils.h"
36 
37 #include "notification_plugin.h"
38 #include "notification_core.h"
39 #include "notification_prefs.h"
40 #include "notification_banner.h"
41 #include "notification_lcdproc.h"
42 #include "notification_trayicon.h"
43 #include "notification_indicator.h"
44 #include "notification_hotkeys.h"
45 #include "notification_foldercheck.h"
46 #include "notification_pixbuf.h"
47 #include "plugin.h"
48 
49 #if HAVE_LIBNOTIFY
50 #  include <libnotify/notify.h>
51 #endif
52 
53 /* delay in ms until the tray icon shall be embedded in the
54    system tray before it is assumed that the tray doesn't
55    work and the main window is shown again */
56 #define CM_NOTIFICATION_TRAYICON_SAFETY_NET_DELAY_MS 5000
57 
58 static gboolean my_folder_item_update_hook(gpointer, gpointer);
59 static gboolean my_folder_update_hook(gpointer, gpointer);
60 static gboolean my_msginfo_update_hook(gpointer, gpointer);
61 static gboolean my_offline_switch_hook(gpointer, gpointer);
62 static gboolean my_main_window_close_hook(gpointer, gpointer);
63 static gboolean my_main_window_got_iconified_hook(gpointer, gpointer);
64 static gboolean my_account_list_changed_hook(gpointer, gpointer);
65 static gboolean my_update_theme_hook(gpointer, gpointer);
66 
67 #ifdef NOTIFICATION_TRAYICON
68 static gboolean trayicon_startup_idle(gpointer);
69 #endif
70 
71 static gulong hook_f_item;
72 static gulong hook_f;
73 static gulong hook_m_info;
74 static gulong hook_offline;
75 static gulong hook_mw_close;
76 static gulong hook_got_iconified;
77 static gulong hook_account;
78 static gulong hook_theme_changed;
79 
80 #ifdef NOTIFICATION_BANNER
81 static GSList* banner_collected_msgs;
82 #endif
83 
notification_update_urgency_hint(void)84 void notification_update_urgency_hint(void)
85 {
86   MainWindow *mainwin;
87   mainwin = mainwindow_get_mainwindow();
88   if(mainwin) {
89     NotificationMsgCount count;
90     gboolean active;
91     active = FALSE;
92     if(notify_config.urgency_hint_new || notify_config.urgency_hint_unread) {
93       notification_core_get_msg_count(NULL, &count);
94       if(notify_config.urgency_hint_new)
95         active = (active || (count.new_msgs > 0));
96       if(notify_config.urgency_hint_unread)
97         active = (active || (count.unread_msgs > 0));
98     }
99     gtk_window_set_urgency_hint(GTK_WINDOW(mainwin->window), active);
100   }
101 }
102 
my_account_list_changed_hook(gpointer source,gpointer data)103 static gboolean my_account_list_changed_hook(gpointer source,
104 					     gpointer data)
105 {
106   gboolean retVal = FALSE;
107 
108 #ifdef NOTIFICATION_TRAYICON
109   notification_update_msg_counts(NULL);
110   retVal = notification_trayicon_account_list_changed(source, data);
111 
112 #endif
113   return retVal;
114 }
115 
my_update_theme_hook(gpointer source,gpointer data)116 static gboolean my_update_theme_hook(gpointer source, gpointer data)
117 {
118   notification_pixbuf_free_all();
119 #ifdef NOTIFICATION_TRAYICON
120   notification_update_trayicon();
121 #endif
122   return FALSE;
123 }
124 
my_main_window_got_iconified_hook(gpointer source,gpointer data)125 static gboolean my_main_window_got_iconified_hook(gpointer source,
126 						  gpointer data)
127 {
128   gboolean retVal = FALSE;
129 #ifdef NOTIFICATION_TRAYICON
130   notification_update_msg_counts(NULL);
131   retVal = notification_trayicon_main_window_got_iconified(source, data);
132 #endif
133   return retVal;
134 }
135 
my_main_window_close_hook(gpointer source,gpointer data)136 static gboolean my_main_window_close_hook(gpointer source, gpointer data)
137 {
138   gboolean retVal = FALSE;
139 #ifdef NOTIFICATION_TRAYICON
140   notification_update_msg_counts(NULL);
141   retVal = notification_trayicon_main_window_close(source, data);
142 #endif
143   return retVal;
144 }
145 
my_offline_switch_hook(gpointer source,gpointer data)146 static gboolean my_offline_switch_hook(gpointer source, gpointer data)
147 {
148 #ifdef NOTIFICATION_TRAYICON
149   notification_update_msg_counts(NULL);
150 #endif
151   return FALSE;
152 }
153 
my_folder_item_update_hook(gpointer source,gpointer data)154 static gboolean my_folder_item_update_hook(gpointer source, gpointer data)
155 {
156   FolderItemUpdateData *update_data = source;
157   FolderType ftype;
158   gchar *uistr;
159 
160   g_return_val_if_fail(source != NULL, FALSE);
161 
162   if (folder_has_parent_of_type(update_data->item, F_DRAFT))
163       return FALSE;
164 
165 #if defined(NOTIFICATION_LCDPROC) || defined(NOTIFICATION_TRAYICON) || defined(NOTIFICATION_INDICATOR)
166     notification_update_msg_counts(NULL);
167 #else
168     if(notify_config.urgency_hint_new || notify_config.urgency_hint_unread)
169     	notification_update_msg_counts(NULL);
170 #endif
171 
172   /* Check if the folder types is to be notified about */
173   ftype = update_data->item->folder->klass->type;
174   uistr = update_data->item->folder->klass->uistr;
175   if(!notify_include_folder_type(ftype, uistr))
176     return FALSE;
177 
178   if(update_data->update_flags & F_ITEM_UPDATE_MSGCNT) {
179 #ifdef NOTIFICATION_BANNER
180     notification_update_banner();
181 #endif
182 #if defined(NOTIFICATION_POPUP) || defined(NOTIFICATION_COMMAND)
183     notification_new_unnotified_msgs(update_data);
184 #endif
185   }
186   return FALSE;
187 }
188 
my_folder_update_hook(gpointer source,gpointer data)189 static gboolean my_folder_update_hook(gpointer source, gpointer data)
190 {
191   FolderUpdateData *hookdata;
192 
193   g_return_val_if_fail(source != NULL, FALSE);
194   hookdata = source;
195 
196 #if defined(NOTIFICATION_LCDPROC) || defined(NOTIFICATION_TRAYICON)
197   if(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
198     notification_update_msg_counts(hookdata->item);
199   else
200     notification_update_msg_counts(NULL);
201 #endif
202 
203   return FALSE;
204 
205 }
206 
207 
my_msginfo_update_hook(gpointer source,gpointer data)208 static gboolean my_msginfo_update_hook(gpointer source, gpointer data)
209 {
210   return notification_notified_hash_msginfo_update((MsgInfoUpdate*)source);
211 }
212 
plugin_init(gchar ** error)213 gint plugin_init(gchar **error)
214 {
215   gchar *rcpath;
216 
217   /* Version check */
218   /* No be able to test against new-contacts */
219   if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,8,1,46),
220 			   VERSION_NUMERIC, _("Notification"), error))
221     return -1;
222 
223   /* Check if threading is enabled */
224   if(!g_thread_supported()) {
225     *error = g_strdup(_("The Notification plugin needs threading support."));
226     return -1;
227   }
228 
229   hook_f_item = hooks_register_hook(FOLDER_ITEM_UPDATE_HOOKLIST,
230 				    my_folder_item_update_hook, NULL);
231   if(hook_f_item == 0) {
232     *error = g_strdup(_("Failed to register folder item update hook in the "
233 			"Notification plugin"));
234     return -1;
235   }
236 
237   hook_f = hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
238 			       my_folder_update_hook, NULL);
239   if(hook_f == 0) {
240     *error = g_strdup(_("Failed to register folder update hook in the "
241 			"Notification plugin"));
242     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
243     return -1;
244   }
245 
246 
247   hook_m_info = hooks_register_hook(MSGINFO_UPDATE_HOOKLIST,
248 				    my_msginfo_update_hook, NULL);
249   if(hook_m_info == 0) {
250     *error = g_strdup(_("Failed to register msginfo update hook in the "
251 			"Notification plugin"));
252     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
253     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
254     return -1;
255   }
256 
257   hook_offline = hooks_register_hook(OFFLINE_SWITCH_HOOKLIST,
258 				     my_offline_switch_hook, NULL);
259   if(hook_offline == 0) {
260     *error = g_strdup(_("Failed to register offline switch hook in the "
261 			"Notification plugin"));
262     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
263     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
264     hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
265     return -1;
266   }
267 
268   hook_mw_close = hooks_register_hook(MAIN_WINDOW_CLOSE,
269 				      my_main_window_close_hook, NULL);
270   if(hook_mw_close == 0) {
271     *error = g_strdup(_("Failed to register main window close hook in the "
272 			"Notification plugin"));
273     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
274     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
275     hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
276     hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
277     return -1;
278   }
279 
280   hook_got_iconified = hooks_register_hook(MAIN_WINDOW_GOT_ICONIFIED,
281 					   my_main_window_got_iconified_hook,
282 					   NULL);
283   if(hook_got_iconified == 0) {
284     *error = g_strdup(_("Failed to register got iconified hook in the "
285 			"Notification plugin"));
286     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
287     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
288     hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
289     hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
290     hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
291     return -1;
292   }
293 
294   hook_account = hooks_register_hook(ACCOUNT_LIST_CHANGED_HOOKLIST,
295 				     my_account_list_changed_hook, NULL);
296   if (hook_account == 0) {
297     *error = g_strdup(_("Failed to register account list changed hook in the "
298 			"Notification plugin"));
299     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
300     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
301     hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
302     hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
303     hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
304     hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, hook_got_iconified);
305     return -1;
306   }
307 
308   hook_theme_changed = hooks_register_hook(THEME_CHANGED_HOOKLIST, my_update_theme_hook, NULL);
309   if(hook_theme_changed == 0) {
310     *error = g_strdup(_("Failed to register theme change hook in the "
311       "Notification plugin"));
312     hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
313     hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
314     hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
315     hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
316     hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
317     hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, hook_got_iconified);
318     hooks_unregister_hook(ACCOUNT_LIST_CHANGED_HOOKLIST, hook_account);
319     return -1;
320   }
321 
322   /* Configuration */
323   prefs_set_default(notify_param);
324   rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
325   prefs_read_config(notify_param, "NotificationPlugin", rcpath, NULL);
326   g_free(rcpath);
327 
328   /* Folder specific stuff */
329   notification_foldercheck_read_array();
330 
331   notification_notified_hash_startup_init();
332 
333   notify_gtk_init();
334 
335 #ifdef NOTIFICATION_INDICATOR
336   notification_indicator_setup();
337 #endif
338 #ifdef NOTIFICATION_BANNER
339   notification_update_banner();
340 #endif
341 #ifdef NOTIFICATION_LCDPROC
342   notification_lcdproc_connect();
343 #endif
344 #ifdef NOTIFICATION_TRAYICON
345   if(notify_config.trayicon_enabled &&
346 		 notify_config.trayicon_hide_at_startup && claws_is_starting()) {
347     MainWindow *mainwin = mainwindow_get_mainwindow();
348 
349 		g_timeout_add(CM_NOTIFICATION_TRAYICON_SAFETY_NET_DELAY_MS, trayicon_startup_idle,NULL);
350     if(mainwin && gtk_widget_get_visible(GTK_WIDGET(mainwin->window)))
351       main_window_hide(mainwin);
352     main_set_show_at_startup(FALSE);
353   }
354 #endif
355   my_account_list_changed_hook(NULL,NULL);
356 
357   if(notify_config.urgency_hint_new || notify_config.urgency_hint_unread)
358 	notification_update_msg_counts(NULL);
359 
360 #ifdef NOTIFICATION_HOTKEYS
361   notification_hotkeys_update_bindings();
362 #endif
363 
364   debug_print("Notification plugin loaded\n");
365 
366   return 0;
367 }
368 
plugin_done(void)369 gboolean plugin_done(void)
370 {
371   hooks_unregister_hook(FOLDER_ITEM_UPDATE_HOOKLIST, hook_f_item);
372   hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, hook_f);
373   hooks_unregister_hook(MSGINFO_UPDATE_HOOKLIST, hook_m_info);
374   hooks_unregister_hook(OFFLINE_SWITCH_HOOKLIST, hook_offline);
375   hooks_unregister_hook(MAIN_WINDOW_CLOSE, hook_mw_close);
376   hooks_unregister_hook(MAIN_WINDOW_GOT_ICONIFIED, hook_got_iconified);
377   hooks_unregister_hook(ACCOUNT_LIST_CHANGED_HOOKLIST, hook_account);
378   hooks_unregister_hook(THEME_CHANGED_HOOKLIST, hook_theme_changed);
379 
380   notify_save_config();
381 
382   notify_gtk_done();
383 
384   /* foldercheck cleanup */
385   notification_foldercheck_write_array();
386   notification_free_folder_specific_array();
387 
388 #ifdef NOTIFICATION_BANNER
389   notification_collected_msgs_free(banner_collected_msgs);
390   banner_collected_msgs = NULL;
391   notification_banner_destroy();
392 #endif
393 #ifdef NOTIFICATION_LCDPROC
394   notification_lcdproc_disconnect();
395 #endif
396 #ifdef NOTIFICATION_TRAYICON
397   notification_trayicon_destroy();
398 #endif
399 #ifdef NOTIFICATION_INDICATOR
400   notification_indicator_destroy();
401 #endif
402   notification_core_free();
403 
404 #ifdef HAVE_LIBNOTIFY
405   if(notify_is_initted())
406     notify_uninit();
407 #endif
408 
409 #ifdef NOTIFICATION_HOTKEYS
410   notification_hotkeys_unbind_all();
411 #endif
412 
413   notification_pixbuf_free_all();
414 
415   debug_print("Notification plugin unloaded\n");
416 
417   /* Returning FALSE here means that g_module_close() will not be called on the plugin.
418    * This is necessary, as needed libraries are not designed to be unloaded. */
419   return FALSE;
420 }
421 
plugin_name(void)422 const gchar *plugin_name(void)
423 {
424   return _("Notification");
425 }
426 
plugin_desc(void)427 const gchar *plugin_desc(void)
428 {
429   return _("This plugin provides various ways "
430     "to notify the user of new and unread email.\n"
431     "The plugin is extensively configurable in the "
432     "plugins section of the preferences dialog.\n\n"
433     "Feedback to <berndth@gmx.de> is welcome.");
434 }
435 
plugin_type(void)436 const gchar *plugin_type(void)
437 {
438   return "GTK2";
439 }
440 
plugin_licence(void)441 const gchar *plugin_licence(void)
442 {
443   return "GPL3+";
444 }
445 
plugin_version(void)446 const gchar *plugin_version(void)
447 {
448   return VERSION;
449 }
450 
plugin_provides(void)451 struct PluginFeature *plugin_provides(void)
452 {
453   static struct PluginFeature features[] =
454     { {PLUGIN_NOTIFIER, N_("Various tools")},
455       {PLUGIN_NOTHING, NULL}};
456   return features;
457 }
458 
459 #ifdef NOTIFICATION_BANNER
notification_update_banner(void)460 void notification_update_banner(void)
461 {
462   notification_collected_msgs_free(banner_collected_msgs);
463   banner_collected_msgs = NULL;
464 
465   if(notify_config.banner_show != NOTIFY_BANNER_SHOW_NEVER) {
466     guint id;
467     GSList *folder_list = NULL;
468 
469     if(notify_config.banner_folder_specific) {
470       id = notification_register_folder_specific_list
471 				(BANNER_SPECIFIC_FOLDER_ID_STR);
472       folder_list = notification_foldercheck_get_list(id);
473     }
474 
475     if(!(notify_config.banner_folder_specific && (folder_list == NULL)))
476       banner_collected_msgs =
477 				notification_collect_msgs(notify_config.banner_include_unread,
478 																	notify_config.banner_folder_specific ?
479 																	folder_list : NULL, notify_config.banner_max_msgs);
480   }
481 
482   notification_banner_show(banner_collected_msgs);
483 }
484 #endif
485 
486 #ifdef NOTIFICATION_TRAYICON
trayicon_startup_idle(gpointer data)487 static gboolean trayicon_startup_idle(gpointer data)
488 {
489 	/* if the trayicon is not available,
490 		 simulate click on it to show mainwindow */
491 	if(!notification_trayicon_is_available())
492 		notification_trayicon_on_activate(NULL,data);
493 	return FALSE;
494 }
495 #endif
496