[balsa/77-notification] notification improvements: play sound, basic system tray icon support




commit 2e797f48acec50b2d7c8f7852cdd510f4d206003
Author: Albrecht Dreß <albrecht dress netcologne de>
Date:   Tue May 10 20:50:05 2022 +0200

    notification improvements: play sound, basic system tray icon support
    
    See issue #77 for further details.
    
    This branch implements the following improvements/extensions:
    
    (1) New messages sound
    Balsa had an option for playing a sound when new messages arrive, which
    was a no-op.  Make this option functional, and unify playing sound
    (requires libcanberra).
    
    (2) Basic System Tray Icon support
    Add an option to display a System Tray icon which
    - changes the colour when new messages arrived,
    - shows/hides the Balsa main window on primary click,
    - shows a simple menu on secondary click.
    (requires libxapp, not supported by all desktop environments/wm's).
    
    Details:
    - configure.ac, meson.build, meson_options.txt: add option to enable
    systray support
    - images/Makefile.am, images/meson.build, images/balsa_attention.png:
    add/install red Balsa icon (i.e. new messages)
    - libbalsa/Makefile.am, libbalsa/meson.build, libbalsa/system-tray.[ch]:
    implement system tray icon support
    - libbalsa/libbalsa.[ch]: implement helper to play sound using canberra
    - libbalsa/filter.c: use new helper to play filter sound
    - src/balsa-app.[ch]: drop unused notify_new_mail_icon, add and
    initialise enable_systray_icon, new_mail_sound_file
    - src/save-restore.c: adjust save/restore of notify_new_mail_icon,
    enable_systray_icon, new_mail_sound_file
    - src/adjust prefs dialogue re. notify_new_mail_icon,
    enable_systray_icon, new_mail_sound_file
    - src/main-window.c: use system tray, add sound and system tray to new
    messages notification handler
    
    Signed-off-by: Albrecht Dreß <albrecht dress netcologne de>

 configure.ac               |  20 +++++-
 images/Makefile.am         |   1 +
 images/balsa_attention.png | Bin 0 -> 7951 bytes
 images/meson.build         |   1 +
 libbalsa/Makefile.am       |   2 +
 libbalsa/filter.c          |  15 ++---
 libbalsa/libbalsa.c        |  22 +++++++
 libbalsa/libbalsa.h        |   4 ++
 libbalsa/meson.build       |   2 +
 libbalsa/system-tray.c     | 142 ++++++++++++++++++++++++++++++++++++++++
 libbalsa/system-tray.h     |  55 ++++++++++++++++
 meson.build                |  12 ++++
 meson_options.txt          |   7 +-
 src/balsa-app.c            |  11 +++-
 src/balsa-app.h            |  12 ++--
 src/main-window.c          | 104 ++++++++++++++++++++++++++++-
 src/pref-manager.c         | 158 +++++++++++++++++++++++++++++++++++----------
 src/save-restore.c         |  22 +++++--
 18 files changed, 528 insertions(+), 62 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index ba4cf446f..59db71c9d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -65,9 +65,14 @@ AC_ARG_ENABLE([autocrypt],
                   [build with Autocrypt support (see https://autocrypt.org/), default=no, requires 
sqlite3)]),
                   [autocrypt=$enableval], [autocrypt=no])
 
+AC_ARG_ENABLE([systray],
+   AC_HELP_STRING([--enable-systray],
+                  [enable System Tray Icon support, default=no, requires libxapp]),
+                  [systray=$enableval], [systray=no])
+
 AC_ARG_WITH(canberra,
    AC_HELP_STRING([--with-canberra],
-                  [Use libcanberra-gtk3 for filter sounds (default=no)]),
+                  [Use libcanberra-gtk3 for new messages and filter sounds (default=no)]),
                   [with_canberra=$withval],[with_canberra=no])
 
 AC_ARG_WITH(compface,
@@ -321,6 +326,18 @@ else
        AC_MSG_RESULT([no])
 fi
 
+# System Tray icon support via libxapp
+AC_MSG_CHECKING(whether to build with System Tray Icon support)
+if test x"$systray" != xno ; then
+       AC_MSG_RESULT([yes])
+       PKG_CHECK_MODULES(SYSTRAY, [xapp])
+       AC_DEFINE(ENABLE_SYSTRAY,1,[If defined, enable System Tray Icon support])
+       BALSA_CFLAGS="$BALSA_CFLAGS $SYSTRAY_CFLAGS"
+       BALSA_LIBS="$BALSA_LIBS $SYSTRAY_LIBS"
+else
+       AC_MSG_RESULT([no])
+fi
+
 # OpenLDAP configuration.
 #
 AC_MSG_CHECKING(whether to use LDAP)
@@ -653,6 +670,7 @@ echo "               HTML widget: $use_html_widget"
 echo "                 Use GNOME: $with_gnome"
 echo "              Use Canberra: $with_canberra"
 echo "             Use Autocrypt: $autocrypt"
+echo "          System Tray Icon: $systray"
 echo "                  Use LDAP: $with_ldap"
 echo "                   Use GSS: $with_gss"
 echo "                Use SQLite: $with_sqlite"
diff --git a/images/Makefile.am b/images/Makefile.am
index 18bc73e15..e3b54917b 100644
--- a/images/Makefile.am
+++ b/images/Makefile.am
@@ -6,6 +6,7 @@ balsa_IMGS = \
        attachment.png          \
        balsa-top.png           \
        balsa_icon.png          \
+       balsa_attention.png     \
        balsa_logo.png
 
 EXTRA_DIST =   $(balsa_IMGS)   \
diff --git a/images/balsa_attention.png b/images/balsa_attention.png
new file mode 100644
index 000000000..4cc76832d
Binary files /dev/null and b/images/balsa_attention.png differ
diff --git a/images/meson.build b/images/meson.build
index cf81c39e9..34448d199 100644
--- a/images/meson.build
+++ b/images/meson.build
@@ -10,6 +10,7 @@ balsa_imgs = [
   'attachment.png',
   'balsa-top.png',
   'balsa_icon.png',
+  'balsa_attention.png',
   'balsa_logo.png'
   ]
 
diff --git a/libbalsa/Makefile.am b/libbalsa/Makefile.am
index 28ae1d2e3..3e72b6cd7 100644
--- a/libbalsa/Makefile.am
+++ b/libbalsa/Makefile.am
@@ -130,6 +130,8 @@ libbalsa_a_SOURCES =                \
        server-config.h         \
        smtp-server.c           \
        smtp-server.h           \
+       system-tray.c           \
+       system-tray.h           \
        source-viewer.c         \
        url.c                   \
        url.h                   \
diff --git a/libbalsa/filter.c b/libbalsa/filter.c
index 2bbc7868a..565f88365 100644
--- a/libbalsa/filter.c
+++ b/libbalsa/filter.c
@@ -35,10 +35,6 @@
 #include <ctype.h>
 #include <string.h>
 
-#if HAVE_CANBERRA
-#include <canberra-gtk.h>
-#endif                          /* HAVE_CANBERRA */
-
 #include "libbalsa.h"
 #include "libbalsa_private.h"
 
@@ -249,13 +245,10 @@ libbalsa_filter_mailbox_messages(LibBalsaFilter * filt,
 
 #if HAVE_CANBERRA
     if (filt->sound) {
-        GdkScreen *screen;
-        gint rc;
-
-        screen = gdk_screen_get_default();
-        rc = ca_context_play(ca_gtk_context_get_for_screen(screen), 0,
-                             CA_PROP_MEDIA_FILENAME, filt->sound, NULL);
-        g_debug("(%s) play %s, %s", __func__, filt->sound, ca_strerror(rc));
+        if (!libbalsa_play_sound(filt->sound, &err)) {
+            g_warning("%s: %s", __func__, (err != NULL) ? err->message : "unknown");
+            g_clear_error(&err);
+        }
     }
 #endif                          /* HAVE_CANBERRA */
     if (filt->popup_text)
diff --git a/libbalsa/libbalsa.c b/libbalsa/libbalsa.c
index 6da54f574..ec5fb116a 100644
--- a/libbalsa/libbalsa.c
+++ b/libbalsa/libbalsa.c
@@ -43,6 +43,10 @@
 #include <gtksourceview/gtksource.h>
 #endif
 
+#if HAVE_CANBERRA
+#include <canberra-gtk.h>
+#endif
+
 #include "misc.h"
 #include "missing.h"
 #include "x509-cert-widget.h"
@@ -695,6 +699,24 @@ libbalsa_source_view_new(gboolean highlight_phrases)
 }
 #endif  /* HAVE_GTKSOURCEVIEW */
 
+#if HAVE_CANBERRA
+gboolean
+libbalsa_play_sound(const gchar *soundfile, GError **error)
+{
+       GdkScreen *screen;
+       gint rc;
+
+       g_return_val_if_fail(soundfile != NULL, FALSE);
+
+       screen = gdk_screen_get_default();
+       rc = ca_context_play(ca_gtk_context_get_for_screen(screen), 0, CA_PROP_MEDIA_FILENAME, soundfile, 
NULL);
+       if (rc != 0) {
+               g_set_error(error, LIBBALSA_ERROR_QUARK, rc, _("Cannot play sound file “%s”: %s"), soundfile, 
ca_strerror(rc));
+       }
+       return rc == 0;
+}
+#endif /* HAVE_CANBERRA */
+
 /*
  * Error domains for GError:
  */
diff --git a/libbalsa/libbalsa.h b/libbalsa/libbalsa.h
index 205da2510..0d048e047 100644
--- a/libbalsa/libbalsa.h
+++ b/libbalsa/libbalsa.h
@@ -190,4 +190,8 @@ GtkDialogFlags libbalsa_dialog_flags(void);
 GtkWidget *libbalsa_source_view_new(gboolean highlight_phrases);
 #endif                          /* HAVE_GTKSOURCEVIEW */
 
+#ifdef HAVE_CANBERRA
+gboolean libbalsa_play_sound(const gchar *soundfile, GError **error);
+#endif /* HAVE_CANBERRA*/
+
 #endif                          /* __LIBBALSA_H__ */
diff --git a/libbalsa/meson.build b/libbalsa/meson.build
index c4945695e..14c31724f 100644
--- a/libbalsa/meson.build
+++ b/libbalsa/meson.build
@@ -128,6 +128,8 @@ libbalsa_a_sources = [
   'smtp-server.c',
   'smtp-server.h',
   'source-viewer.c',
+  'system-tray.c',
+  'system-tray.h',
   'url.c',
   'url.h',
   'geometry-manager.c',
diff --git a/libbalsa/system-tray.c b/libbalsa/system-tray.c
new file mode 100644
index 000000000..02a09d5f4
--- /dev/null
+++ b/libbalsa/system-tray.c
@@ -0,0 +1,142 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ *
+ * Copyright (C) 1997-2022 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
+# include "config.h"
+#endif                          /* HAVE_CONFIG_H */
+
+
+#ifdef ENABLE_SYSTRAY
+
+#include <glib/gi18n.h>
+#include <libxapp/xapp-status-icon.h>
+#include "files.h"
+#include "system-tray.h"
+
+
+static XAppStatusIcon *status_icon;
+static gchar *icon_path[2] = {NULL, NULL};
+static gboolean icon_attention;
+static GtkMenu *icon_menu;
+static libbalsa_systray_cb_t icon_activate_cb;
+static gpointer icon_activate_cb_data;
+
+
+static void systray_cb_internal(XAppStatusIcon *icon,
+                                                               guint           button,
+                                                               guint           time,
+                                                               gpointer        user_data);
+
+
+void
+libbalsa_systray_icon_init(GtkMenu *menu, libbalsa_systray_cb_t activate_cb, gpointer activate_cb_data)
+{
+       g_return_if_fail(icon_path[0] == NULL);
+
+       icon_path[0] = balsa_pixmap_finder("balsa_icon.png");
+       icon_path[1] = balsa_pixmap_finder("balsa_attention.png");
+       icon_activate_cb = activate_cb;
+       icon_activate_cb_data = activate_cb_data;
+       if (menu != NULL) {
+               icon_menu = g_object_ref(menu);
+       }
+}
+
+
+void
+libbalsa_systray_icon_enable(gboolean enable)
+{
+       g_return_if_fail(icon_path[0] != NULL);
+
+       if (enable) {
+               if (status_icon == NULL) {
+                       status_icon = xapp_status_icon_new();
+                       if (icon_menu != NULL) {
+                               xapp_status_icon_set_secondary_menu(status_icon, icon_menu);
+                       }
+                       if (icon_activate_cb != NULL) {
+                               g_signal_connect(status_icon, "activate", G_CALLBACK(systray_cb_internal), 
icon_activate_cb_data);
+                       }
+                       xapp_status_icon_set_visible(status_icon, TRUE);
+                       icon_attention = TRUE;
+                       libbalsa_systray_icon_attention(FALSE);
+               }
+       } else {
+               if (status_icon != NULL) {
+                       g_object_unref(status_icon);
+                       status_icon = NULL;
+               }
+       }
+}
+
+
+void
+libbalsa_systray_icon_attention(gboolean attention)
+{
+       g_return_if_fail(icon_path[0] != NULL);
+
+       if ((status_icon != NULL) && (attention != icon_attention)) {
+               icon_attention = attention;
+               if (attention) {
+                       xapp_status_icon_set_icon_name(status_icon, icon_path[1]);
+                       xapp_status_icon_set_tooltip_text(status_icon, _("Balsa: you have new mail"));
+               } else {
+                       xapp_status_icon_set_icon_name(status_icon, icon_path[0]);
+                       xapp_status_icon_set_tooltip_text(status_icon, _("Balsa"));
+               }
+       }
+}
+
+
+void
+libbalsa_systray_icon_destroy(void)
+{
+       size_t n;
+
+       if (icon_path[0] != NULL) {
+               icon_activate_cb = NULL;
+               if (icon_menu != NULL) {
+                       g_object_unref(icon_menu);
+                       icon_menu = NULL;
+               }
+               for (n = 0; n < G_N_ELEMENTS(icon_path); n++) {
+                       g_free(icon_path[n]);
+                       icon_path[n] = NULL;
+               }
+               if (status_icon != NULL) {
+                       g_object_unref(status_icon);
+                       status_icon = NULL;
+               }
+       }
+}
+
+
+static void
+systray_cb_internal(XAppStatusIcon G_GNUC_UNUSED *icon, guint button, guint G_GNUC_UNUSED time, gpointer 
user_data)
+{
+       g_return_if_fail(status_icon != NULL);
+
+       if ((button == 1) && (icon_activate_cb != NULL)) {
+               icon_activate_cb(user_data);
+       }
+}
+
+
+#endif /* ENABLE_SYSTRAY */
diff --git a/libbalsa/system-tray.h b/libbalsa/system-tray.h
new file mode 100644
index 000000000..6bb0e1507
--- /dev/null
+++ b/libbalsa/system-tray.h
@@ -0,0 +1,55 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ *
+ * Copyright (C) 1997-2022 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SYSTEM_TRAY_H_
+#define _SYSTEM_TRAY_H_
+
+#ifndef BALSA_VERSION
+# error "Include config.h before this file."
+#endif
+
+
+#ifdef ENABLE_SYSTRAY
+
+
+#include <glib.h>
+
+
+G_BEGIN_DECLS
+
+
+typedef void (*libbalsa_systray_cb_t)(gpointer);
+
+
+void libbalsa_systray_icon_init(GtkMenu               *menu,
+                                                               libbalsa_systray_cb_t  activate_cb,
+                                                               gpointer               activate_cb_data);
+void libbalsa_systray_icon_enable(gboolean enable);
+void libbalsa_systray_icon_attention(gboolean attention);
+void libbalsa_systray_icon_destroy(void);
+
+
+G_END_DECLS
+
+
+#endif  /* ENABLE_SYSTRAY */
+
+
+#endif /* _SYSTEM_TRAY_H_ */
diff --git a/meson.build b/meson.build
index 601650cbd..b512edd7a 100644
--- a/meson.build
+++ b/meson.build
@@ -48,6 +48,7 @@ endif
 
 gnome_desktop = get_option('gnome-desktop')
 autocrypt     = get_option('autocrypt')
+systray       = get_option('systray')
 canberra      = get_option('canberra')
 compface      = get_option('compface')
 gss           = get_option('gss')
@@ -212,6 +213,17 @@ if autocrypt
   balsa_deps += autocrypt_dep
 endif
 
+# System Tray Icons
+if systray
+  systray_dep = dependency('xapp', required : true)
+  if systray_dep.found()
+    conf.set('ENABLE_SYSTRAY', 1, description : 'If defined, enable System Tray Icon support.')
+  else
+    error('*** You enabled System Tray Icon support but libxapp is not found.')
+  endif
+  balsa_deps += systray_dep
+endif
+
 # OpenLDAP configuration.
 #
 if ldap != 'false'
diff --git a/meson_options.txt b/meson_options.txt
index 09bcf2af4..98859887e 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -16,7 +16,12 @@ option('fcntl',
 option('autocrypt',
   type        : 'boolean',
   value       : false,
-  description : 'build with Autocrypt support (see https://autocrypt.org/), (default=false), requires gpgme 
and sqlite3')
+  description : 'build with Autocrypt support (see https://autocrypt.org/), (default=false), requires 
sqlite3')
+
+option('systray',
+  type        : 'boolean',
+  value       : false,
+  description : 'enable System Tray Icon support, (default=false), requires libxapp')
 
 option('canberra',
   type        : 'boolean',
diff --git a/src/balsa-app.c b/src/balsa-app.c
index 7c227cda4..88c07e589 100644
--- a/src/balsa-app.c
+++ b/src/balsa-app.c
@@ -412,9 +412,16 @@ balsa_app_init(void)
     balsa_app.error_message = 0;
     balsa_app.debug_message = 0;
 
-    balsa_app.notify_new_mail_sound = 1;
+#ifdef HAVE_CANBERRA
+    balsa_app.notify_new_mail_sound = 0;
+    balsa_app.new_mail_sound_file = NULL;
+#endif
+
     balsa_app.notify_new_mail_dialog = 0;
-    balsa_app.notify_new_mail_icon = 1;
+
+#ifdef ENABLE_SYSTRAY
+    balsa_app.enable_systray_icon = 0;
+#endif
 
     /* Local and IMAP */
     balsa_app.local_scan_depth = 1;
diff --git a/src/balsa-app.h b/src/balsa-app.h
index 2d51b2a36..c9e2c4c37 100644
--- a/src/balsa-app.h
+++ b/src/balsa-app.h
@@ -167,14 +167,16 @@ extern struct BalsaApplication {
     gint check_mail_timer;
     gint check_mail_timer_id;
 
-    /* This can be configured from the gnome control panel */
-    /* It's here just in case some other app also uses the same */
-    /* system wide sound event and you want Balsa to behave differently */
-    /* There is no prefs setting for this item */
+#ifdef HAVE_CANBERRA
     gint notify_new_mail_sound;
+    gchar *new_mail_sound_file;
+#endif
     
     gint notify_new_mail_dialog;
-    gint notify_new_mail_icon;
+
+#ifdef ENABLE_SYSTRAY
+    gint enable_systray_icon;
+#endif
 
     /* automatically close mailboxes after XX minutes */
     gboolean close_mailbox_auto;
diff --git a/src/main-window.c b/src/main-window.c
index e013e7ea5..a5c32749e 100644
--- a/src/main-window.c
+++ b/src/main-window.c
@@ -68,6 +68,7 @@
 #include "toolbar-factory.h"
 #include "libbalsa-progress.h"
 #include "geometry-manager.h"
+#include "system-tray.h"
 
 #include "filter.h"
 #include "filter-funcs.h"
@@ -785,6 +786,11 @@ bw_is_active_notify(GObject * gobject, GParamSpec * pspec,
             priv->new_mail_notification_sent = FALSE;
         }
         gtk_window_set_urgency_hint(gtk_window, FALSE);
+#ifdef ENABLE_SYSTRAY
+        if (balsa_app.enable_systray_icon) {
+            libbalsa_systray_icon_attention(FALSE);
+        }
+#endif
     }
 }
 
@@ -2254,6 +2260,65 @@ bw_enable_next_unread(BalsaWindow * window, gboolean has_unread_mailbox)
     bw_action_set_enabled(window, "next-unread", has_unread_mailbox);
 }
 
+#ifdef ENABLE_SYSTRAY
+static void
+on_systray_click(gpointer data)
+{
+       GtkWindow *window = GTK_WINDOW(data);
+
+       g_return_if_fail(window != NULL);
+       if (gtk_window_is_active(window)) {
+               gtk_window_iconify(window);
+       } else {
+               gtk_window_present_with_time(window, gtk_get_current_event_time());
+       }
+}
+
+static void
+on_systray_show_hide(GtkMenuItem G_GNUC_UNUSED *menuitem, gpointer user_data)
+{
+       /* process pending events, as otherwise the Balsa window will never be active... */
+       while (gtk_events_pending()) {
+               gtk_main_iteration();
+       }
+       on_systray_click(user_data);
+}
+
+static void
+on_systray_receive(GtkMenuItem G_GNUC_UNUSED *menuitem, gpointer user_data)
+{
+       if (g_atomic_int_get(&checking_mail) == 1) {
+               check_new_messages_real(BALSA_WINDOW(user_data), TRUE);
+       }
+}
+
+static void
+on_systray_new_msg(GtkMenuItem G_GNUC_UNUSED *menuitem, gpointer user_data)
+{
+       new_message_activated(NULL, NULL, user_data);
+}
+
+static void
+bw_init_systray(BalsaWindow *window)
+{
+    GtkWidget *menu;
+    GtkWidget *menuitem;
+
+    menu = gtk_menu_new();
+    menuitem = gtk_menu_item_new_with_label(_("Show/Hide"));
+    g_signal_connect(menuitem, "activate", G_CALLBACK(on_systray_show_hide), window);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+    menuitem = gtk_menu_item_new_with_label(_("Check"));
+    g_signal_connect(menuitem, "activate", G_CALLBACK(on_systray_receive), window);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+    menuitem = gtk_menu_item_new_with_label(_("Compose"));
+    g_signal_connect(menuitem, "activate", G_CALLBACK(on_systray_new_msg), window);
+    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+    gtk_widget_show_all(menu);
+    libbalsa_systray_icon_init(GTK_MENU(menu), on_systray_click, window);
+}
+#endif
+
 GtkWidget *
 balsa_window_new(GtkApplication *application)
 {
@@ -2435,6 +2500,11 @@ balsa_window_new(GtkApplication *application)
 
     g_timeout_add_seconds(30, (GSourceFunc) bw_close_mailbox_on_timer, window);
 
+#ifdef ENABLE_SYSTRAY
+    bw_init_systray(window);
+    libbalsa_systray_icon_enable(balsa_app.enable_systray_icon != 0);
+#endif
+
     gtk_widget_show(GTK_WIDGET(window));
     return GTK_WIDGET(window);
 }
@@ -3198,6 +3268,10 @@ balsa_window_dispose(GObject * object)
     G_OBJECT_CLASS(balsa_window_parent_class)->dispose(object);
 
     balsa_unregister_pixmaps();
+
+#ifdef ENABLE_SYSTRAY
+    libbalsa_systray_icon_destroy();
+#endif
 }
 
 /*
@@ -3626,6 +3700,10 @@ bw_display_new_mail_notification(int num_new, int has_new)
     GtkWindow *window = GTK_WINDOW(balsa_app.main_window);
     BalsaWindowPrivate *priv =
         balsa_window_get_instance_private(balsa_app.main_window);
+#ifdef HAVE_CANBERRA
+    static gint64 last_new_mail_sound = -1;
+    gint64 now;
+#endif
 
     /* remove a running notification timeout task */
     if (notify_ctx.timeout_id > 0U) {
@@ -3633,10 +3711,32 @@ bw_display_new_mail_notification(int num_new, int has_new)
        notify_ctx.timeout_id = 0U;
     }
 
-    if (!balsa_app.notify_new_mail_dialog)
+    if (gtk_window_is_active(window))
         return;
 
-    if (gtk_window_is_active(window))
+#ifdef HAVE_CANBERRA
+    /* play sound if configured, but not too frequently (min. 30 seconds in between)*/
+    now = g_get_monotonic_time();
+    if ((balsa_app.notify_new_mail_sound != 0) && (balsa_app.new_mail_sound_file != NULL) &&
+       (now > (last_new_mail_sound + 30 * 1000000))) {
+       GError *error = NULL;
+
+       if (!libbalsa_play_sound(balsa_app.new_mail_sound_file, &error)) {
+               g_warning("%s: %s", __func__, (error != NULL) ? error->message : "unknown");
+               g_clear_error(&error);
+       } else {
+               last_new_mail_sound = now;
+       }
+    }
+#endif
+
+#ifdef ENABLE_SYSTRAY
+    if (balsa_app.enable_systray_icon) {
+       libbalsa_systray_icon_attention(TRUE);
+    }
+#endif
+
+    if (balsa_app.notify_new_mail_dialog == 0)
         return;
 
     gtk_window_set_urgency_hint(window, TRUE);
diff --git a/src/pref-manager.c b/src/pref-manager.c
index 1ca43cc4c..e8521cc51 100644
--- a/src/pref-manager.c
+++ b/src/pref-manager.c
@@ -44,6 +44,10 @@
 #include "smtp-server.h"
 #include "libbalsa-conf.h"
 
+#ifdef ENABLE_SYSTRAY
+#include "system-tray.h"
+#endif
+
 #include <glib/gi18n.h>
 
 #define NUM_ENCODING_MODES 3
@@ -87,8 +91,11 @@ typedef struct _PropertyUI {
     GtkWidget *check_imap;
     GtkWidget *check_imap_inbox;
     GtkWidget *notify_new_mail_dialog;
+#ifdef HAVE_CANBERRA
     GtkWidget *notify_new_mail_sound;
-    GtkWidget *notify_new_mail_icon;
+    GtkWidget *new_mail_sound_file;
+    GtkWidget *new_mail_sound_play;
+#endif
     GtkWidget *mdn_reply_clean_menu, *mdn_reply_notclean_menu;
 
     GtkWidget *close_mailbox_auto;
@@ -99,6 +106,9 @@ typedef struct _PropertyUI {
     GtkWidget *expunge_auto;
     GtkWidget *expunge_minutes;
     GtkWidget *action_after_move_menu;
+#ifdef ENABLE_SYSTRAY
+    GtkWidget *enable_systray_icon;
+#endif
 
     GtkWidget *previewpane;
     GtkWidget *layout_type;
@@ -464,12 +474,16 @@ apply_prefs(GtkDialog * pbox)
     balsa_app.notify_new_mail_dialog =
         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                      (pui->notify_new_mail_dialog));
+
+#ifdef HAVE_CANBERRA
     balsa_app.notify_new_mail_sound =
         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
                                      (pui->notify_new_mail_sound));
-    balsa_app.notify_new_mail_icon =
-        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
-                                     (pui->notify_new_mail_icon));
+    g_free(balsa_app.new_mail_sound_file);
+    balsa_app.new_mail_sound_file =
+        gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(pui->new_mail_sound_file));
+#endif
+
     balsa_app.mdn_reply_clean =
         pm_combo_box_get_level(pui->mdn_reply_clean_menu);
     balsa_app.mdn_reply_notclean =
@@ -519,6 +533,13 @@ apply_prefs(GtkDialog * pbox)
                                          (pui->close_mailbox_minutes)) *
         60;
 
+#ifdef ENABLE_SYSTRAY
+    balsa_app.enable_systray_icon =
+        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+                                     (pui->enable_systray_icon));
+    libbalsa_systray_icon_enable(balsa_app.enable_systray_icon != 0);
+#endif
+
     libbalsa_mailbox_set_filter(NULL, pui->filter);
     balsa_app.expunge_on_close =
         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
@@ -715,12 +736,14 @@ set_prefs(void)
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
                                  (pui->notify_new_mail_dialog),
                                  balsa_app.notify_new_mail_dialog);
+#ifdef HAVE_CANBERRA
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
                                  (pui->notify_new_mail_sound),
                                  balsa_app.notify_new_mail_sound);
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON
-                                 (pui->notify_new_mail_icon),
-                                 balsa_app.notify_new_mail_icon);
+    gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(pui->new_mail_sound_file),
+                                  balsa_app.new_mail_sound_file);
+#endif
+
     if (!balsa_app.check_imap)
         gtk_widget_set_sensitive(GTK_WIDGET(pui->check_imap_inbox), FALSE);
 
@@ -739,6 +762,11 @@ set_prefs(void)
                              gtk_toggle_button_get_active
                              (GTK_TOGGLE_BUTTON(pui->close_mailbox_auto)));
 
+#ifdef ENABLE_SYSTRAY
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pui->enable_systray_icon),
+                                 balsa_app.enable_systray_icon);
+#endif
+
     pui->filter = libbalsa_mailbox_get_filter(NULL);
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pui->hide_deleted),
                                  pui->filter & (1 << 0));
@@ -1728,6 +1756,47 @@ timer_modified_cb(GtkWidget * widget, GtkWidget * pbox)
     properties_modified_cb(widget, pbox);
 }
 
+#ifdef HAVE_CANBERRA
+static void
+sound_modified_cb(GtkWidget *widget, GtkWidget *pbox)
+{
+       gboolean newstate;
+
+       newstate = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pui->notify_new_mail_sound));
+
+       gtk_widget_set_sensitive(pui->new_mail_sound_file, newstate);
+       if (newstate) {
+               gchar *soundfile;
+
+               soundfile = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(pui->new_mail_sound_file));
+               gtk_widget_set_sensitive(pui->new_mail_sound_play, soundfile != NULL);
+               g_free(soundfile);
+       } else {
+               gtk_widget_set_sensitive(pui->new_mail_sound_play, FALSE);
+       }
+       properties_modified_cb(widget, pbox);
+}
+
+static void
+sound_play(GtkWidget G_GNUC_UNUSED *widget, gpointer G_GNUC_UNUSED data)
+{
+       if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pui->notify_new_mail_sound))) {
+               gchar *soundfile;
+
+               soundfile = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(pui->new_mail_sound_file));
+               if (soundfile != NULL) {
+                       GError *error = NULL;
+
+                       if (!libbalsa_play_sound(soundfile, &error)) {
+                               libbalsa_information(LIBBALSA_INFORMATION_ERROR, "%s", (error != NULL) ? 
error->message : _("unknown"));
+                               g_clear_error(&error);
+                       }
+                       g_free(soundfile);
+               }
+       }
+}
+#endif /* HAVE_CANBERRA */
+
 static void
 send_timer_modified_cb(GtkWidget * widget, GtkWidget * pbox)
 {
@@ -2106,7 +2175,6 @@ pm_grid_add_checking_group(GtkWidget * grid_widget)
     gint row = pm_grid_get_next_row(grid);
     GtkAdjustment *spinbutton_adj;
     GtkWidget *label;
-    GtkWidget *hbox;
 
     pm_grid_attach(grid, pm_group_label(_("Checking")), 0, row, 3, 1);
 
@@ -2131,29 +2199,6 @@ pm_grid_add_checking_group(GtkWidget * grid_widget)
         gtk_check_button_new_with_mnemonic(_("Check Inbox _only"));
     pm_grid_attach(grid, pui->check_imap_inbox, 2, row, 2, 1);
 
-    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, COL_SPACING);
-
-    label = gtk_label_new(_("When mail arrives:"));
-    gtk_widget_set_halign(label, GTK_ALIGN_START);
-    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
-
-    pui->notify_new_mail_dialog =
-        gtk_check_button_new_with_label(_("Display message"));
-    gtk_box_pack_start(GTK_BOX(hbox), pui->notify_new_mail_dialog,
-                       FALSE, FALSE, 0);
-
-    pui->notify_new_mail_sound =
-        gtk_check_button_new_with_label(_("Play sound"));
-    gtk_box_pack_start(GTK_BOX(hbox), pui->notify_new_mail_sound,
-                       FALSE, FALSE, 0);
-
-    pui->notify_new_mail_icon =
-        gtk_check_button_new_with_label(_("Show icon"));
-    gtk_box_pack_start(GTK_BOX(hbox), pui->notify_new_mail_icon,
-                       FALSE, FALSE, 0);
-
-    pm_grid_attach(grid, hbox, 1, ++row, 3, 1);
-
     pui->quiet_background_check = gtk_check_button_new_with_label(
        _("Do background check quietly (no messages in status bar)"));
     pm_grid_attach(grid, pui->quiet_background_check, 1, ++row, 3, 1);
@@ -2173,6 +2218,35 @@ pm_grid_add_checking_group(GtkWidget * grid_widget)
     pm_grid_set_next_row(grid, ++row);
 }
 
+/*
+ * New messages notification group
+ */
+static void
+pm_grid_add_new_mail_notify_group(GtkWidget * grid_widget)
+{
+       GtkGrid *grid = (GtkGrid *) grid_widget;
+       gint row = pm_grid_get_next_row(grid);
+
+       pm_grid_attach(grid, pm_group_label(_("Notification about new messages")), 0, row, 3, 1);
+
+       pui->notify_new_mail_dialog = gtk_check_button_new_with_label(_("Display message"));
+       pm_grid_attach(grid, pui->notify_new_mail_dialog, 1, ++row, 1, 1);
+
+#ifdef HAVE_CANBERRA
+       pui->notify_new_mail_sound = gtk_check_button_new_with_label(_("Play sound"));
+       pm_grid_attach(grid, pui->notify_new_mail_sound, 1, ++row, 1, 1);
+
+       pui->new_mail_sound_file = gtk_file_chooser_button_new(_("New message sound"), 
GTK_FILE_CHOOSER_ACTION_OPEN);
+       pm_grid_attach(grid, pui->new_mail_sound_file, 2, row, 1, 1);
+
+       pui->new_mail_sound_play = gtk_button_new_from_icon_name("media-playback-start", 
GTK_ICON_SIZE_SMALL_TOOLBAR);
+       pm_grid_attach(grid, pui->new_mail_sound_play, 3, row, 1, 1);
+       gtk_widget_set_halign(pui->new_mail_sound_play, GTK_ALIGN_START);
+#endif
+
+       pm_grid_set_next_row(grid, ++row);
+}
+
 /*
  * MDN request group
  */
@@ -2935,6 +3009,11 @@ pm_grid_add_misc_group(GtkWidget * grid_widget)
 
     pm_grid_attach_label(grid, 3, row, 1, 1, _("minutes"));
 
+#ifdef ENABLE_SYSTRAY
+    pui->enable_systray_icon =
+        pm_grid_attach_check(grid, 1, ++row, 3, 1, _("Enable System Tray Icon support"));
+#endif
+
     pm_grid_set_next_row(grid, ++row);
 }
 
@@ -3042,6 +3121,7 @@ pm_incoming_page(void)
     GtkWidget *grid = pm_grid_new();
 
     pm_grid_add_checking_group(grid);
+    pm_grid_add_new_mail_notify_group(grid);
     pm_grid_add_mdn_group(grid);
 
     return grid;
@@ -3430,11 +3510,14 @@ open_preferences_manager(GtkWidget * widget, gpointer data)
     g_signal_connect(pui->notify_new_mail_dialog, "toggled",
                      G_CALLBACK(properties_modified_cb), property_box);
 
+#ifdef HAVE_CANBERRA
     g_signal_connect(pui->notify_new_mail_sound, "toggled",
-                     G_CALLBACK(properties_modified_cb), property_box);
-
-    g_signal_connect(pui->notify_new_mail_icon, "toggled",
-                     G_CALLBACK(properties_modified_cb), property_box);
+                     G_CALLBACK(sound_modified_cb), property_box);
+    g_signal_connect(pui->new_mail_sound_file, "selection-changed",
+                     G_CALLBACK(sound_modified_cb), property_box);
+    g_signal_connect(pui->new_mail_sound_play, "clicked",
+                     G_CALLBACK(sound_play), property_box);
+#endif
 
     g_signal_connect(pui->close_mailbox_auto, "toggled",
                      G_CALLBACK(mailbox_close_timer_modified_cb),
@@ -3443,6 +3526,11 @@ open_preferences_manager(GtkWidget * widget, gpointer data)
                      G_CALLBACK(mailbox_close_timer_modified_cb),
                      property_box);
 
+#ifdef ENABLE_SYSTRAY
+    g_signal_connect(pui->enable_systray_icon, "toggled",
+                     G_CALLBACK(properties_modified_cb), property_box);
+#endif
+
     g_signal_connect(pui->hide_deleted, "toggled",
                      G_CALLBACK(filter_modified_cb), property_box);
     g_signal_connect(pui->expunge_on_close, "toggled",
diff --git a/src/save-restore.c b/src/save-restore.c
index 0c5bae90a..a020c61c2 100644
--- a/src/save-restore.c
+++ b/src/save-restore.c
@@ -980,10 +980,12 @@ config_global_load(void)
 
     balsa_app.notify_new_mail_dialog =
        d_get_gint("NewMailNotificationDialog", 0);
+#ifdef HAVE_CANBERRA
     balsa_app.notify_new_mail_sound =
-       d_get_gint("NewMailNotificationSound", 1);
-    balsa_app.notify_new_mail_icon =
-       d_get_gint("NewMailNotificationIcon", 1);
+       d_get_gint("NewMailNotificationSound", 0);
+    balsa_app.new_mail_sound_file =
+       libbalsa_conf_get_string("NewMailNotificationSoundFile");
+#endif
     balsa_app.check_mail_upon_startup =
        libbalsa_conf_get_bool("OnStartup=false");
     balsa_app.check_mail_auto = libbalsa_conf_get_bool("Auto=false");
@@ -1140,6 +1142,11 @@ config_global_load(void)
     /* This setting is now per address book */
     libbalsa_conf_clean_key("AddressBookDistMode");
 
+#ifdef ENABLE_SYSTRAY
+    balsa_app.enable_systray_icon =
+       d_get_gint("EnableSystrayIcon", 0);
+#endif
+
     libbalsa_conf_pop_group();
     
     /* Toolbars */
@@ -1434,10 +1441,11 @@ config_save(void)
 
     libbalsa_conf_set_int("NewMailNotificationDialog",
                           balsa_app.notify_new_mail_dialog);
+#ifdef HAVE_CANBERRA
     libbalsa_conf_set_int("NewMailNotificationSound",
                           balsa_app.notify_new_mail_sound);
-    libbalsa_conf_set_int("NewMailNotificationIcon",
-                          balsa_app.notify_new_mail_icon);
+    libbalsa_conf_set_string("NewMailNotificationSoundFile", balsa_app.new_mail_sound_file);
+#endif
     libbalsa_conf_set_bool("OnStartup", balsa_app.check_mail_upon_startup);
     libbalsa_conf_set_bool("Auto", balsa_app.check_mail_auto);
     libbalsa_conf_set_int("AutoDelay", balsa_app.check_mail_timer);
@@ -1526,6 +1534,10 @@ config_save(void)
     else
        libbalsa_conf_clean_key("DefaultAddressBook");
 
+#ifdef ENABLE_SYSTRAY
+    libbalsa_conf_set_int("EnableSystrayIcon", balsa_app.enable_systray_icon);
+#endif
+
     libbalsa_conf_pop_group();
 
     /* Toolbars */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]