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