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 <[email protected]> # 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':
