A year or three ago I wrote a patch to allow Mutt to send new mail notifications to me on my Mac, using a Growl/UDP binding for C that I developed around the same time. It works very well, even while running mutt and Growl on different computers, but I've been quite uncomfortable with (1) the oddball cGrowl dependency that basically only I use, and (2) the non-portable nature of Growl as a notification endpoint. (MacOS's free Growl still doesn't support GNTP, so even though you can Growl on Linux and Windows now, you can't do it with the same client code as on the Mac.)
This patch rewrites all of that to use DBus as the message bus instead of Growl's native UDP protocol or GNTP, making external new mail notifications from mutt available on any platform that DBus supports. It also means that you need some kind of shim to proxy DBus signals to your session notification system of choice, but fortunately these are not too hard to develop. One is included in contrib/growlbus for Growl/OSX; it works locally or across a network. There may be an easy-to-configure DBus signal manager for Linux/Gnome already -- I don't know, since I don't use it. Thoughts? "./configure --enable-dbus" and "set dbus" to try it. There is no specific documentation of the feature other than the commit message, the variable autodoc, and the growlbus README, so if this is appealing enough to commit I'll need to add some. To observe the new mail events, run dbus-monitor --session --monitor interface=org.mutt.mutt (Note that $dbus is currently unset by default.) # HG changeset patch # User David Champion <[email protected]> # Date 1352157645 21600 # Branch HEAD # Node ID ade034b9fcd52b8f8401fa52f10aed29467e07d1 # Parent d414971f0c486570352b573eaca656800d5cef4c Add support for new mail notifications to DBus. DBus signals may work out of the box for you, if DBus support is configured. By default mutt signals to the SESSION bus. You can override this: set dbus_socket = system set dbus_socket = session set dbus_socket = unix:path=/path/to/my/bus See http://dbus.freedesktop.org/doc/dbus-specification.html#addresses for details on socket addressing. Mutt signals the /org/mutt/mutt object, using the org.mutt.mutt interface. Initially, there are two types of notifications (signals) posted: * folderUpdated - the basic buffy notification when there is new mail in some mailbox that you're not currently reading * newMail - a description of a new message in your current mailbox "set dbus" enables dbus, and "unset dbus" disables it. A newMail signal will have two associated string values: a "title" and a "message", governed by the following format strings: set dbus_new_title = "..." set dbus_new_message = "..." "title" is formatted using status bar expandos, and "message" is formatted using index expandos. It is trivially possible to extend dbus support to other events; just call mutt_dbus_event((char *)signalname, arguments...). To watch for mutt's dbus signals, use (for example): $ dbus-monitor --system --monitor interface=org.mutt.mutt diff -r d414971f0c48 -r ade034b9fcd5 INSTALL --- a/INSTALL Tue Oct 16 18:13:13 2012 -0500 +++ b/INSTALL Mon Nov 05 17:20:45 2012 -0600 @@ -194,6 +194,11 @@ addresses in the same form they are parsed. NOTE: this requires significantly more memory. +--enable-dbus + If you have libdbus installed, Mutt can perform event notifications + over DBus. + + Once ``configure'' has completed, simply type ``make install.'' Mutt should compile cleanly (without errors) and you should end up with a diff -r d414971f0c48 -r ade034b9fcd5 buffy.c --- a/buffy.c Tue Oct 16 18:13:13 2012 -0500 +++ b/buffy.c Mon Nov 05 17:20:45 2012 -0600 @@ -516,6 +516,10 @@ if (!first) { mutt_message ("%s", buffylist); +#ifdef USE_DBUS + if (option(OPTDBUS)) + mutt_dbus_notify(M_DBUS_FOLDER, buffylist); +#endif return (1); } /* there were no mailboxes needing to be notified, so clean up since diff -r d414971f0c48 -r ade034b9fcd5 configure.ac --- a/configure.ac Tue Oct 16 18:13:13 2012 -0500 +++ b/configure.ac Mon Nov 05 17:20:45 2012 -0600 @@ -830,6 +830,14 @@ fi]) +AC_ARG_ENABLE(dbus, AC_HELP_STRING([--enable-dbus@<:@=PFX@:>@], [Use DBus signalling for new mail]), + [if test $enableval = yes; then + LDFLAGS="$LDFLAGS $(pkg-config --libs dbus-1)" + CPPFLAGS="$CPPFLAGS $(pkg-config --cflags dbus-1)" + AC_DEFINE(USE_DBUS,1,[Enable DBus new mail notifications?]) + fi]) + + dnl -- start cache -- db_found=no db_requested=auto diff -r d414971f0c48 -r ade034b9fcd5 contrib/growlbus/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/growlbus/README Mon Nov 05 17:20:45 2012 -0600 @@ -0,0 +1,37 @@ +Growlbus is a simple bridge service for handing off mutt's dbus +notifications to Growl.app. To use it you will need to install +both dbus and the Python dbus bindings. + +For example, with MacPorts: +sudo port install dbus dbus-python27 + +You can also install dbus bindings with easy_install. + + +To compile Mutt for MacOS with DBus support, you will need to install +dbus-devel and configure with the --enable-dbus option. + + +Growlbus uses Growl's remote call interface (over UDP). To enable it, +visit Growl's preferences. Go to the Network panel, check "Listen for +incoming notifications" and "Allow remote application registration", and +set a server password. (You may need to open UDP port 9887 if you're +going to growl remotely.) + +On the system where you're running mutt, create ~/.growlrc with the +following: + + [growlbus] + host = <hostname or IP of your Mac, or localhost> + password = <the password you entered into Growl preferences> + +Now run 'growlbus register'. In Growl Preferences you should now see an +entry for 'growlbus' under the Applications tab, where you can configure +how Growl displays alerts from mutt. + +Henceforth when you run 'growlbus' with no arguments, it will subscribe +to DBus signals from mutt (org.mutt.mutt) and forward them to Growl. + +Because DBus is platform-neutral, using DBus for notifications allows +anyone to (relatively) easily write a program similar to growlbus that +passes DBus signals to some other event handler. diff -r d414971f0c48 -r ade034b9fcd5 contrib/growlbus/growl.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/growlbus/growl.py Mon Nov 05 17:20:45 2012 -0600 @@ -0,0 +1,189 @@ +#!/usr/bin/env python + +import socket +import struct + +try: + import hashlib as md5 +except: + import md5 + + +# from http://code.activestate.com/recipes/142812-hex-dumper/ +def dump(src, length=8): + FILTER = ''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) + N = 0 + result='' + while src: + s,src = src[:length],src[length:] + hexa = ' '.join(["%02X" % ord(x) for x in s]) + s = s.translate(FILTER) + result += "%04X %-*s %s\n" % (N, length*3, hexa, s) + N += length + return result + + +class endpoint(object): + def __init__(self, host="localhost", port=9887, password="", sticky=None, priority=None): + if ':' in host: + if host.startswith('['): + # ipv6 address, so : could be anything + try: + off = host.index(']') + port = int(host[off+1:]) + host = host[:off] + except: + pass + else: + host, port = host.split(':') + port = int(port) + + self.address = (host, port) + self.password = password + self.sticky = sticky + self.priority = priority + + + def sign(self, data): + sig = md5.md5() + sig.update(data) + sig.update(self.password) + return sig.digest() + + + def send(self, data): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + #print 'packet to', self.address + #print dump(data) + s.sendto(data, self.address) + + +class application(object): + packetTypeRegistration = 0 + packetTypeNotification = 1 + protocolVersion = 1 + _endpoint = endpoint + + def __init__(self, name, endpoint=None): + self.name = name.encode('utf-8') + self.notifications = {} + if endpoint is None: + self.endpoint = self._endpoint() + else: + self.endpoint = endpoint + + + def addNotification(self, name, enabled=True, priority=0, sticky=False): + #print 'adding %s to' % name, self + self.notifications[name] = (name.encode('utf-8'), enabled, priority, sticky) + + + def _regPacket(self): + data = '' + data += struct.pack('B', self.protocolVersion) + data += struct.pack('B', self.packetTypeRegistration) + data += struct.pack('!H', len(self.name)) + data += struct.pack('B', len(self.notifications)) + data += struct.pack('B', len([n for n in self.notifications + if n[1]])) + + data += self.name + + for n in self.notifications.values(): + data += struct.pack('!H', len(n[0])) + n[0] + + for i in xrange(0, len(self.notifications)): + if n[1]: + data += struct.pack('B', i) + + return data + + + def register(self, endpoint=None): + endpoint = endpoint or self.endpoint + data = self._regPacket() + data += endpoint.sign(data) + endpoint.send(data) + + + def _nfnPacket(self, nfn, title, desc, priority, sticky): + if priority < -2: + priority = -2 + elif priority > 2: + priority = 2 + + data = '' + data += struct.pack('B', self.protocolVersion) + data += struct.pack('B', self.packetTypeNotification) + + # Flags. This is poorly documented, but it appears that + # bit 8 is a sticky flag and bits 1-3 are a signed priority + # in the range [-2, 2]. (Network byte order, not native.) + # Bits 0, 4-7, and 9-15 are unused. + flags = 0 + flags |= (priority & 0x03) << 1 + if flags < 0: + flags |= 0x0008 + if sticky: + flags |= 0x0100 + data += struct.pack('!H', flags) + + data += struct.pack('!HHHH', len(nfn[0]), len(title), + len(desc), len(self.name)) + data += str(nfn[0]) + str(title) + str(desc) + str(self.name) + return data + + + def notify(self, nfn, title, desc, endpoint=None, priority=None, sticky=None): + nfn = self.notifications[nfn] + endpoint = endpoint or self.endpoint + priority = priority or endpoint.priority or nfn[2] + sticky = sticky or endpoint.sticky or nfn[3] + data = self._nfnPacket(nfn, title, desc, priority, sticky) + data += endpoint.sign(data) + endpoint.send(data) + + +def main(args): + import getopt + try: + opts, args = getopt.getopt(args, 'a:p:h:rs', []) + except getopt.GetoptError, e: + print e + return 10 + + app = 'growl.py' + password = '' + host = 'localhost' + register = False + sticky = False + + for opt, arg in opts: + if opt == '-a': + app = arg + if opt == '-p': + password = arg + if opt == '-h': + host = arg + if opt == '-r': + register = True + if opt == '-s': + sticky = True + + e = endpoint(host=host, password=password) + g = application(app, endpoint=e) + + if register: + for name in args: + g.addNotification(name) + g.register() + return 0 + + g.addNotification(args[0]) + g.notify(args[0], args[1], ' '.join(args[2:]), sticky=sticky) + return 0 + + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv[1:])) diff -r d414971f0c48 -r ade034b9fcd5 contrib/growlbus/growlbus --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/growlbus/growlbus Mon Nov 05 17:20:45 2012 -0600 @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import os +import sys +import dbus +import growl +import gobject +from dbus.mainloop.glib import DBusGMainLoop +import ConfigParser + +def dbussetup(growler): + # DBus signal handler(s) + def newMail(*args, **kwargs): + growler.notify('newMail', args[0], args[1]) + + def folderUpdated(*args, **kwargs): + growler.notify('folderUpdated', args[0], args[1]) + + DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + + for signal in 'newMail folderUpdated'.split(): + handler = locals()[signal] + bus.add_signal_receiver(handler, signal, 'org.mutt.mutt', None, '/org/mutt/mutt') + + +def growlsetup(app=None, host='localhost', password='', register=False): + notifications = 'newMail folderUpdated'.split() + + if app is None: + app = os.path.basename(sys.argv[0]) + + e = growl.endpoint(host=host, password=password) + g = growl.application(app, endpoint=e) + + for name in notifications: + g.addNotification(name) + + if register: + g.register() + + return g + + +def main(args): + def get(cfg, section, option): + if cfg is None: + return None + try: + return cfg.get(section, option) + except: + return None + + name = os.path.basename(sys.argv[0]) + cfg = ConfigParser.ConfigParser() + cfg.read(os.path.expanduser('~/.growlrc')) + + host = get(cfg, name, 'host') or 'localhost' + password = get(cfg, name, 'password') or '' + + if args and args[0] == 'register': + growlsetup(host=host, password=password, register=True) + return 0 + + growler = growlsetup(host=host, password=password) + dbussetup(growler) + + loop = gobject.MainLoop() + try: + loop.run() + except KeyboardInterrupt: + print '\nbreak' + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff -r d414971f0c48 -r ade034b9fcd5 curs_main.c --- a/curs_main.c Tue Oct 16 18:13:13 2012 -0500 +++ b/curs_main.c Mon Nov 05 17:20:45 2012 -0600 @@ -266,6 +266,42 @@ return 0; } +#ifdef USE_DBUS +#define DBUS_SIGNAL_MESSAGE "newMail" +#define DBUS_SIGNAL_FOLDER "folderUpdated" + +void +mutt_dbus_notify(int type, void *data) +{ + char titlebuf[1024]; + char msgbuf[1024]; + + /* For a new mail notification, format message strings and post */ + if (type == M_DBUS_MESSAGE) + { + struct hdr_format_info hfi; + hfi.ctx = Context; + hfi.hdr = (HEADER *)data; + hfi.pager_progress = 0; + + mutt_FormatString(titlebuf, sizeof(titlebuf), 0, NONULL(DBusNewTitle), + status_format_str, (unsigned long)0, 0); + mutt_FormatString(msgbuf, sizeof(msgbuf), 0, NONULL(DBusNewMessage), + hdr_format_str, (unsigned long)&hfi, 0); + mutt_dbus_event(DBUS_SIGNAL_MESSAGE, DBUS_STR, titlebuf, DBUS_STR, msgbuf, DBUS_END); + } + + else if (type == M_DBUS_FOLDER) + { + snprintf(titlebuf, sizeof(titlebuf), "New Mail"); + snprintf(msgbuf, sizeof(msgbuf), "%s", (char *)data); + mutt_dbus_event(DBUS_SIGNAL_FOLDER, DBUS_STR, titlebuf, DBUS_STR, msgbuf, DBUS_END); + } + + return; +} +#endif + static void update_index (MUTTMENU *menu, CONTEXT *ctx, int check, int oldcount, int index_hint) { @@ -282,6 +318,19 @@ oldcount = 0; /* invalid message number! */ } +#ifdef USE_DBUS + /* DBus notifications */ + if (option(OPTDBUS) && 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_dbus_notify(M_DBUS_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 d414971f0c48 -r ade034b9fcd5 globals.h --- a/globals.h Tue Oct 16 18:13:13 2012 -0500 +++ b/globals.h Mon Nov 05 17:20:45 2012 -0600 @@ -145,6 +145,12 @@ WHERE char *CurrentFolder; WHERE char *LastFolder; +#ifdef USE_DBUS +WHERE int DBusEnable INITVAL (TRUE); +WHERE char *DBusNewTitle INITVAL (NULL); +WHERE char *DBusNewMessage INITVAL (NULL); +WHERE char *DBusSocket INITVAL (NULL); +#endif WHERE const char *ReleaseDate; diff -r d414971f0c48 -r ade034b9fcd5 hdrline.c --- a/hdrline.c Tue Oct 16 18:13:13 2012 -0500 +++ b/hdrline.c Mon Nov 05 17:20:45 2012 -0600 @@ -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 d414971f0c48 -r ade034b9fcd5 init.h --- a/init.h Tue Oct 16 18:13:13 2012 -0500 +++ b/init.h Mon Nov 05 17:20:45 2012 -0600 @@ -880,6 +880,38 @@ ** a regular expression that will match the whole name so mutt will expand ** ``Franklin'' to ``Franklin, Steve''. */ +#ifdef USE_DBUS + { "dbus", DT_BOOL, R_NONE, OPTDBUS, 0 }, + /* + ** .pp + ** When set, perform DBus notifications for new mail. + */ + { "dbus_new_title", DT_STR, R_NONE, UL &DBusNewTitle, UL "New Mail in %f" }, + /* + ** .pp + ** A template for DBus notifications' title strings, using + ** ``$$status_format'' formatting codes. + */ + { "dbus_new_message", DT_STR, R_NONE, UL &DBusNewMessage, UL "From: %n\nSubject: %s" }, + /* + ** .pp + ** A template for DBus notifications' message strings, using + ** ``$$index_format'' formatting codes. + */ + { "dbus_socket", DT_STR, R_NONE, UL &DBusSocket, UL "session" }, + /* + ** .pp + ** Socket name to use for DBus signals. The following literals and formats + ** are accepted: + ** .pp + ** .ts + ** . "system" + ** . "session" + ** . [any other format that dbus typically accepts; see + ** http://dbus.freedesktop.org/doc/dbus-specification.html#addresses] + ** .te + */ +#endif /* USE_DBUS */ { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 }, /* */ diff -r d414971f0c48 -r ade034b9fcd5 main.c --- a/main.c Tue Oct 16 18:13:13 2012 -0500 +++ b/main.c Mon Nov 05 17:20:45 2012 -0600 @@ -318,6 +318,12 @@ "-USE_GSS " #endif +#ifdef USE_DBUS + "+USE_DBUS " +#else + "-USE_DBUS " +#endif + #if HAVE_GETADDRINFO "+HAVE_GETADDRINFO " #else diff -r d414971f0c48 -r ade034b9fcd5 mutt.h --- a/mutt.h Tue Oct 16 18:13:13 2012 -0500 +++ b/mutt.h Mon Nov 05 17:20:45 2012 -0600 @@ -507,6 +507,10 @@ OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */ OPTUNBUFFEREDINPUT, /* (pseudo) don't use key buffer */ +#ifdef USE_DBUS + OPTDBUS, /* enable DBus notifications */ +#endif + OPTMAX }; @@ -951,6 +955,19 @@ #define M_PARTS_TOPLEVEL (1<<0) /* is the top-level part */ +#ifdef USE_DBUS +#define M_DBUS_MESSAGE 0 +#define M_DBUS_FOLDER 1 +#define M_DBUS_END 2 +void mutt_dbus_notify(int type, void *data); + +#define DBUS_END 0 +#define DBUS_BOOL 1 +#define DBUS_STR 2 +#define DBUS_INT 3 +#define DBUS_BYTE 4 +#endif + #include "ascii.h" #include "protos.h" #include "lib.h" diff -r d414971f0c48 -r ade034b9fcd5 muttlib.c --- a/muttlib.c Tue Oct 16 18:13:13 2012 -0500 +++ b/muttlib.c Mon Nov 05 17:20:45 2012 -0600 @@ -46,6 +46,10 @@ #include <sys/types.h> #include <utime.h> +#ifdef USE_DBUS +#include <dbus/dbus.h> +#endif + BODY *mutt_new_body (void) { BODY *p = (BODY *) safe_calloc (1, sizeof (BODY)); @@ -1959,3 +1963,143 @@ strfcpy (dest, (rc == 0) ? NONULL(p) : NONULL(src), dlen); FREE (&p); } + + +#ifdef USE_DBUS +void +mutt_dbus_event(char *signal, ...) +{ + static char *dbus_object = "/org/mutt/mutt"; + static char *dbus_interface = "org.mutt.mutt"; + static DBusConnection *conn = NULL; + static char *lastbus = NULL; + DBusMessage *msg; + DBusMessageIter args; + DBusError err; + dbus_uint32_t serial = 0; + int argtype; + int rc = 0; + va_list ap; + + if (!option(OPTDBUS)) + return; + + va_start(ap, signal); + + dbus_error_init(&err); + + /* Check whether bus has changed */ + if (lastbus && DBusSocket && strcmp(lastbus, DBusSocket)) + { + free(lastbus); + lastbus = NULL; + if (conn) + { + dbus_connection_flush(conn); + conn = NULL; + } + } + + /* remember last bus */ + if (lastbus == NULL) + { + lastbus = strdup(DBusSocket); + } + + if (conn == NULL) + { + if (!mutt_strcasecmp(DBusSocket, "system")) + conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + else if (!mutt_strcasecmp(DBusSocket, "session")) + conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + else + conn = dbus_connection_open(DBusSocket, &err); + + if (dbus_error_is_set(&err)) + { + dprint(4, (debugfile, "dbus connection error: %s", err.message)); + dbus_error_free(&err); + } + } + + if (conn == NULL) + return; + + msg = dbus_message_new_signal(dbus_object, dbus_interface, signal); + if (msg == NULL) + { + dbus_connection_unref(conn); + return; + } + + /* append arguments to signal */ + dbus_message_iter_init_append(msg, &args); + while ((argtype = va_arg(ap, int)) != DBUS_END) + { + rc = 0; + + switch (argtype) + { + case DBUS_BOOL: + { + int arg; + arg = va_arg(ap, int); + rc = dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &arg); + } + break; + + case DBUS_INT: + { + int arg; + arg = va_arg(ap, int); + rc = dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &arg); + } + break; + + case DBUS_STR: + { + char *arg; + arg = va_arg(ap, char *); + rc = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &arg); + } + break; + + case DBUS_BYTE: + { + char arg; + int intarg; + /* char is promoted to int via varargs */ + intarg = va_arg(ap, int); + arg = (char) intarg; + rc = dbus_message_iter_append_basic(&args, DBUS_TYPE_BYTE, &arg); + } + break; + + } + + if (rc == 0) + { + dbus_message_unref(msg); + dbus_connection_unref(conn); + va_end(ap); + return; + } + } + va_end(ap); + + if (dbus_connection_send(conn, msg, &serial)) + { + dbus_connection_flush(conn); + } + else + { + /* close connection and clear lastbus cache to force reopen */ + dbus_connection_unref(conn); + conn = NULL; + lastbus = NULL; + } + + dbus_message_unref(msg); + return; +} +#endif /* USE_DBUS */ diff -r d414971f0c48 -r ade034b9fcd5 protos.h --- a/protos.h Tue Oct 16 18:13:13 2012 -0500 +++ b/protos.h Mon Nov 05 17:20:45 2012 -0600 @@ -79,6 +79,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 *); @@ -175,6 +178,9 @@ void mutt_check_rescore (CONTEXT *); void mutt_clear_error (void); void mutt_create_alias (ENVELOPE *, ADDRESS *); +#ifdef USE_DBUS +void mutt_dbus_event(char *signal, ...); +#endif void mutt_decode_attachment (BODY *, STATE *); void mutt_decode_base64 (STATE *s, long len, int istext, iconv_t cd); void mutt_default_save (char *, size_t, HEADER *); diff -r d414971f0c48 -r ade034b9fcd5 status.c --- a/status.c Tue Oct 16 18:13:13 2012 -0500 +++ b/status.c Mon Nov 05 17:20:45 2012 -0600 @@ -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':
