<<< Date Index >>>     <<< Thread Index >>>

[PATCH] Add support for new mail notifications to Growl (OSX)



 INSTALL      |    6 ++
 buffy.c      |    4 +
 configure.ac |   12 +++++
 curs_main.c  |  138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 globals.h    |    7 ++
 hdrline.c    |    2 +-
 init.h       |   46 +++++++++++++++++++
 main.c       |    6 ++
 mutt.h       |   11 ++++
 protos.h     |    3 +
 status.c     |   21 +++++---
 11 files changed, 246 insertions(+), 10 deletions(-)


# HG changeset patch
# User David Champion <dgc@xxxxxxxxxxxx>
# Date 1271708010 18000
# Branch HEAD
# Node ID d23a888328102ea6f629dbf2782a0359a9c0d57c
# Parent  15b9d6f3284f78139abefe74c2607fa2d338641b
Add support for new mail notifications to Growl (OSX).

A minimum working configuration (if you run Mutt on a Mac with Growl
configured to "Listen for incoming notifications") is:

    set growl = yes
    set growl_password = 'my growl password'

But you can customize things more; see configuration variables
beginning with "growl_" for setup details.  In particular, you can use
$growl_target to post network notifications over UDP to a different
computer from the one you run Mutt on.  This is useful for those using a
Mac desktop but running mutt elsewhere over ssh.

The first time you use a Growl-enabled mutt, you must "Allow remote
application registration" in the Growl control panel.  After this you
can disable remote registration.

There are two types of notifications posted:

* Mailbox Updated - the basic buffy notification when there is new
  mail in some mailbox that you're not currently reading
* New Mail - a description of a new message in your current mailbox

New Mail notifications for messages that arrive in your mailbox already
flagged (~F) will be sticky in Growl.

The cGrowl library (https://bitbucket.org/dgc/cgrowl) is an external
dependency for this feature.  You must ./configure or ./prepare mutt
--with-growl[=PFX] to enable Growl support.

diff -r 15b9d6f3284f -r d23a88832810 INSTALL
--- a/INSTALL   Wed Apr 14 15:47:16 2010 -0700
+++ b/INSTALL   Mon Apr 19 15:13:30 2010 -0500
@@ -194,6 +194,12 @@
        addresses in the same form they are parsed.  NOTE: this requires
        significantly more memory.
 
+--with-growl[=PFX]
+    If you have cGrowl (https://bitbucket.org/dgc/cgrowl) installed,
+    Mutt can perform network notifications over UDP to a MacOS X system
+    running the Growl event notification system.
+
+
 Once ``configure'' has completed, simply type ``make install.''
 
 Mutt should compile cleanly (without errors) and you should end up with a
diff -r 15b9d6f3284f -r d23a88832810 buffy.c
--- a/buffy.c   Wed Apr 14 15:47:16 2010 -0700
+++ b/buffy.c   Mon Apr 19 15:13:30 2010 -0500
@@ -497,6 +497,10 @@
   if (!first)
   {
     mutt_message ("%s", buffylist);
+#ifdef USE_GROWL
+    if (option(OPTGROWL))
+      mutt_growl_notify(M_GROWL_FOLDER, buffylist);
+#endif
     return (1);
   }
   /* there were no mailboxes needing to be notified, so clean up since 
diff -r 15b9d6f3284f -r d23a88832810 configure.ac
--- a/configure.ac      Wed Apr 14 15:47:16 2010 -0700
+++ b/configure.ac      Mon Apr 19 15:13:30 2010 -0500
@@ -827,6 +827,18 @@
 
         fi])
 
+AC_ARG_WITH(growl, AC_HELP_STRING([--with-growl@<:@=PFX@:>@], [Use Growl (OSX) 
notification for new mail]),
+        [if test $withval != yes; then
+                mutt_cv_growl=$withval
+        fi
+        if test x$mutt_cv_growl != x; then
+                LDFLAGS="$LDFLAGS -L${mutt_cv_growl}/lib"
+                CPPFLAGS="$CPPFLAGS -I${mutt_cv_growl}/include"
+        fi
+        MUTTLIBS="$MUTTLIBS -lgrowl"
+        AC_DEFINE(USE_GROWL,1,[Enable Growl (OSX) new mail notifications?])])
+
+
 dnl -- start cache --
 db_found=no
 db_requested=auto
diff -r 15b9d6f3284f -r d23a88832810 curs_main.c
--- a/curs_main.c       Wed Apr 14 15:47:16 2010 -0700
+++ b/curs_main.c       Mon Apr 19 15:13:30 2010 -0500
@@ -49,6 +49,10 @@
 
 #include <assert.h>
 
+#ifdef USE_GROWL
+#include <growl.h>
+#endif
+
 static const char *No_mailbox_is_open = N_("No mailbox is open.");
 static const char *There_are_no_messages = N_("There are no messages.");
 static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
@@ -266,6 +270,127 @@
   return 0;
 }
 
+#ifdef USE_GROWL
+
+void
+mutt_growl_notify(int type, void *data)
+{
+  static growl_session *session = NULL;
+  static growl_endpoint *endpoint = NULL;
+  int rc;
+  char titlebuf[1024];
+  char msgbuf[1024];
+  static char *curpass = NULL;
+  static char *curhost = NULL;
+  int sticky = FALSE;
+
+  char *notifications[] = {
+    "New Mail",              /* M_GROWL_MESSAGE */
+    "Mailbox Updated",       /* M_GROWL_FOLDER */
+    NULL
+  };
+
+  /* Check whether password or host has changed */
+  if (curpass && GrowlPassword && strcmp(curpass, GrowlPassword))
+  {
+    free(curpass);
+    curpass = NULL;
+    /* session is still OK; endpoint is invalid */
+    if (endpoint)
+    {
+      growl_endpoint_free(endpoint);
+      endpoint = NULL;
+    }
+  }
+  if (curhost && GrowlHost && strcmp(curhost, GrowlHost))
+  {
+    free(curhost);
+    curhost = NULL;
+    /* session and endpoint are invalid */
+    if (session)
+    {
+      growl_session_free(session);
+      session = NULL;
+    }
+    if (endpoint)
+    {
+      growl_endpoint_free(endpoint);
+      endpoint = NULL;
+    }
+  }
+
+  /* Remember password and host in current session and endpoint */
+  if (GrowlPassword && curpass == NULL)
+    curpass = strdup(GrowlPassword);
+  if (GrowlHost && curhost == NULL)
+    curhost = strdup(GrowlHost);
+
+  /* Set up session on first call, and send a registration packet in
+   * case Growl has never been used. */
+  if (session == NULL)
+  {
+    growl_registration *r;
+    int i;
+
+    /* Always send a registration packet on first call */
+    r = growl_registration_init("Mutt", GrowlPassword);
+    if (r == NULL)
+      return;
+    for (i = 0; i < M_GROWL_END; i++)
+      growl_notification_add(r, notifications[i], 1);  /* Add not'n, enabled */
+    rc = growl_registration_send(r, GrowlHost);
+    growl_registration_free(r);
+    if (rc == 1)
+    {
+      mutt_error(_("Cannot send Growl registration to %s."), GrowlHost);
+      return;
+    }
+    else if (rc == 2)
+    {
+      mutt_error(_("Invalid Growl endpoint: %s"), GrowlHost);
+      return;
+    }
+
+    session = growl_session_init("Mutt");
+  }
+
+  /* Set up endpoint on first call */
+  if (endpoint == NULL)
+    endpoint = growl_endpoint_init(GrowlHost, GrowlPassword);
+
+  if (session == NULL || endpoint == NULL)
+    return;
+
+  /* For a new mail notification, format message strings and post */
+  if (type == M_GROWL_MESSAGE)
+  {
+    struct hdr_format_info hfi;
+    hfi.ctx = Context;
+    hfi.hdr = (HEADER *)data;
+    hfi.pager_progress = 0;
+
+    mutt_FormatString(titlebuf, sizeof(titlebuf), 0, NONULL(GrowlNewTitle),
+                      status_format_str, (unsigned long)0, 0);
+    mutt_FormatString(msgbuf, sizeof(msgbuf), 0, NONULL(GrowlNewMessage),
+                      hdr_format_str, (unsigned long)&hfi, 0);
+    if (hfi.hdr->flagged)
+      sticky = TRUE;
+  }
+
+  else if (type == M_GROWL_FOLDER)
+  {
+    snprintf(titlebuf, sizeof(titlebuf), "New Mail");
+    snprintf(msgbuf, sizeof(msgbuf), "%s", (char *)data);
+  }
+
+  if (sticky)
+    growl_endpoint_set(endpoint, GROWL_OPTION_STICKY, GROWL_TRUE);
+  else
+    growl_endpoint_set(endpoint, GROWL_OPTION_STICKY, GROWL_FALSE);
+  growl_send(session, endpoint, notifications[type], titlebuf, msgbuf);
+}
+#endif
+
 static void update_index (MUTTMENU *menu, CONTEXT *ctx, int check,
                          int oldcount, int index_hint)
 {
@@ -282,6 +407,19 @@
       oldcount = 0; /* invalid message number! */
   }
 
+#ifdef USE_GROWL
+  /* Growl notifications */
+  if (option(OPTGROWL) && check == M_NEW_MAIL && oldcount < ctx->msgcount)
+  {
+    for (j = oldcount; j < ctx->msgcount; j++)
+    {
+      if (ctx->hdrs[j]->old || ctx->hdrs[j]->read)
+        continue;
+      mutt_growl_notify(M_GROWL_MESSAGE, ctx->hdrs[j]);
+    }
+  }
+#endif
+
   /* We are in a limited view. Check if the new message(s) satisfy
    * the limit criteria. If they do, set their virtual msgno so that
    * they will be visible in the limited view */
diff -r 15b9d6f3284f -r d23a88832810 globals.h
--- a/globals.h Wed Apr 14 15:47:16 2010 -0700
+++ b/globals.h Mon Apr 19 15:13:30 2010 -0500
@@ -145,6 +145,13 @@
 WHERE char *CurrentFolder;
 WHERE char *LastFolder;
 
+#ifdef USE_GROWL
+WHERE int GrowlEnable INITVAL (TRUE);
+WHERE char *GrowlNewTitle INITVAL (NULL);
+WHERE char *GrowlNewMessage INITVAL (NULL);
+WHERE char *GrowlPassword INITVAL (NULL);
+WHERE char *GrowlHost INITVAL (NULL);
+#endif
 
 WHERE const char *ReleaseDate;
 
diff -r 15b9d6f3284f -r d23a88832810 hdrline.c
--- a/hdrline.c Wed Apr 14 15:47:16 2010 -0700
+++ b/hdrline.c Mon Apr 19 15:13:30 2010 -0500
@@ -230,7 +230,7 @@
  * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label)
  * %Z = status flags   */
 
-static const char *
+const char *
 hdr_format_str (char *dest,
                size_t destlen,
                size_t col,
diff -r 15b9d6f3284f -r d23a88832810 init.h
--- a/init.h    Wed Apr 14 15:47:16 2010 -0700
+++ b/init.h    Mon Apr 19 15:13:30 2010 -0500
@@ -879,6 +879,52 @@
   ** a regular expression that will match the whole name so mutt will expand
   ** ``Franklin'' to ``Franklin, Steve''.
   */
+#ifdef USE_GROWL
+  { "growl",        DT_BOOL, R_NONE, OPTGROWL, 0 },
+  /*
+  ** .pp
+  ** When set, perform Growl notifications for new mail.  Growl code uses
+  ** the Growl remote UDP protocol, so notifications can go to any destination
+  ** that mutt can reach.
+  */
+  { "growl_new_title", DT_STR, R_NONE, UL &GrowlNewTitle, UL "New Mail in %f" 
},
+  /*
+  ** .pp
+  ** A template for Growl notifications' title strings, using
+  ** ``$$status_format'' formatting codes.
+  */
+  { "growl_new_message", DT_STR, R_NONE, UL &GrowlNewMessage, UL "From: 
%n\nSubject: %s" },
+  /*
+  ** .pp
+  ** A template for Growl notifications' message strings, using
+  ** ``$$index_format'' formatting codes.
+  */
+  { "growl_password", DT_STR,  R_NONE, UL &GrowlPassword, UL "" },
+  /*
+  ** .pp
+  ** Your password for posting remote Growl notifications and registrations.
+  ** (See the Network pane of the Growl control panel.)
+  */
+  { "growl_target", DT_STR,  R_NONE, UL &GrowlHost, UL "localhost" },
+  /*
+  ** .pp
+  ** Internet address to which to send Growl notifications.  Any standard
+  ** format for naming a host and port is accepted:
+  ** .pp
+  ** .ts
+  ** .  hostname
+  ** .  hostname:port
+  ** .  ipv4address
+  ** .  ipv4address:port
+  ** .  ipv6address
+  ** .  [ipv6address]
+  ** .  [ipv6address]:port
+  ** .te
+  ** .pp
+  ** Service names may be used in place of a port number.  If no port number
+  ** is given, Growl's default UDP port (9887) is used.
+  */
+#endif /* USE_GROWL */
   { "hdr_format",      DT_SYN,  R_NONE, UL "index_format", 0 },
   /*
   */
diff -r 15b9d6f3284f -r d23a88832810 main.c
--- a/main.c    Wed Apr 14 15:47:16 2010 -0700
+++ b/main.c    Mon Apr 19 15:13:30 2010 -0500
@@ -291,6 +291,12 @@
        "-USE_GSS  "
 #endif
 
+#ifdef USE_GROWL
+       "+USE_GROWL  "
+#else
+       "-USE_GROWL  "
+#endif
+
 #if HAVE_GETADDRINFO
        "+HAVE_GETADDRINFO  "
 #else
diff -r 15b9d6f3284f -r d23a88832810 mutt.h
--- a/mutt.h    Wed Apr 14 15:47:16 2010 -0700
+++ b/mutt.h    Mon Apr 19 15:13:30 2010 -0500
@@ -507,6 +507,10 @@
   OPTDONTHANDLEPGPKEYS,        /* (pseudo) used to extract PGP keys */
   OPTUNBUFFEREDINPUT,   /* (pseudo) don't use key buffer */
 
+#ifdef USE_GROWL
+  OPTGROWL, /* enable Growl notifications */
+#endif
+
   OPTMAX
 };
 
@@ -951,6 +955,13 @@
 
 #define M_PARTS_TOPLEVEL       (1<<0)  /* is the top-level part */
 
+#ifdef USE_GROWL
+#define M_GROWL_MESSAGE  0
+#define M_GROWL_FOLDER   1
+#define M_GROWL_END      2
+void mutt_growl_notify(int type, void *data);
+#endif
+
 #include "ascii.h"
 #include "protos.h"
 #include "lib.h"
diff -r 15b9d6f3284f -r d23a88832810 protos.h
--- a/protos.h  Wed Apr 14 15:47:16 2010 -0700
+++ b/protos.h  Mon Apr 19 15:13:30 2010 -0500
@@ -78,6 +78,9 @@
 void mutt_delete_parameter (const char *attribute, PARAMETER **p);
 void mutt_set_parameter (const char *, const char *, PARAMETER **);
 
+const char *status_format_str (char *buf, size_t buflen, size_t col, char op, 
const char *src, const char *prefix, const char *ifstring, const char 
*elsestring, unsigned long data, format_flag flags);
+const char *hdr_format_str (char *dest, size_t destlen, size_t col, char op, 
const char *src, const char *prefix, const char *ifstring, const char 
*elsestring, unsigned long data, format_flag flags);
+
 
 FILE *mutt_open_read (const char *, pid_t *);
 
diff -r 15b9d6f3284f -r d23a88832810 status.c
--- a/status.c  Wed Apr 14 15:47:16 2010 -0700
+++ b/status.c  Mon Apr 19 15:13:30 2010 -0500
@@ -61,7 +61,7 @@
  * %u = number of unread messages [option]
  * %v = Mutt version 
  * %V = currently active limit pattern [option] */
-static const char *
+const char *
 status_format_str (char *buf, size_t buflen, size_t col, char op, const char 
*src,
                   const char *prefix, const char *ifstring,
                   const char *elsestring,
@@ -195,16 +195,19 @@
       break;
 
     case 'P':
-      if (menu->top + menu->pagelen >= menu->max)
-       cp = menu->top ? "end" : "all";
-      else
+      if (menu)
       {
-       count = (100 * (menu->top + menu->pagelen)) / menu->max;
-       snprintf (tmp, sizeof (tmp), "%d%%", count);
-       cp = tmp;
+        if (menu->top + menu->pagelen >= menu->max)
+         cp = menu->top ? "end" : "all";
+        else
+        {
+         count = (100 * (menu->top + menu->pagelen)) / menu->max;
+         snprintf (tmp, sizeof (tmp), "%d%%", count);
+         cp = tmp;
+        }
+        snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
+        snprintf (buf, buflen, fmt, cp);
       }
-      snprintf (fmt, sizeof (fmt), "%%%ss", prefix);
-      snprintf (buf, buflen, fmt, cp);
       break;
 
     case 'r':