/*
* Incoming Mail Checker
*
- * (c) 2005 Martin Mares <mj@ucw.cz>
+ * (c) 2005--2007 Martin Mares <mj@ucw.cz>
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
+#include <ctype.h>
#include <getopt.h>
#include <fcntl.h>
#include <glob.h>
+#include <fnmatch.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include "util.h"
#include "clists.h"
+#include "charset.h"
static int check_interval = 30;
static int force_refresh;
-static int lock_mboxes;
+static int allow_bells = 1;
+static int minimum_priority;
static time_t last_scan_time;
static char *run_cmd = "mutt -f %s";
+struct options {
+ int priority;
+ int hide;
+ int hide_if_empty;
+ int highlight;
+ int beep;
+ int snippets;
+ int show_flagged;
+ int sender_personal;
+ int sender_mbox;
+};
+
+struct option_node {
+ cnode n;
+ struct options o;
+ char pattern[1];
+};
+
+struct pattern_node {
+ cnode n;
+ char *name;
+ char pattern[1];
+};
+
+static clist options, patterns;
+static struct options global_options = {
+ .sender_personal = 1
+};
+
struct mbox {
cnode n;
+ struct options o;
+ char *name;
+ char *path;
int index;
- int hilited;
int scanning;
int seen;
time_t last_time;
int last_size, last_pos;
- char *name;
- char *path;
- int total, new;
- int last_total, last_new;
+ int total, new, flagged;
+ int last_total, last_new, last_flagged;
+ int last_beep_new;
int force_refresh;
+ int snippet_is_new;
+ char snippet[256];
};
-static clist mboxes, hilites, patterns;
+static clist mboxes;
static struct mbox **mbox_array;
-static int num_mboxes;
+static int num_mboxes, mbox_array_size;
static void redraw_line(int i);
-static void redraw_all(void);
+static void rethink_display(void);
+
+static void
+add_pattern(char *patt)
+{
+ struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
+ strcpy(n->pattern, patt);
+ if (patt = strchr(n->pattern, '='))
+ {
+ *patt++ = 0;
+ n->name = patt;
+ }
+ else
+ n->name = NULL;
+ clist_add_tail(&patterns, &n->n);
+}
+
+static void
+add_inbox(void)
+{
+ struct passwd *p = getpwuid(getuid());
+ if (!p)
+ die("You don't exist, go away!");
+ char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
+ sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
+ add_pattern(buf);
+}
+
+static void
+init_options(struct options *o)
+{
+ o->priority = -1;
+ o->hide = -1;
+ o->hide_if_empty = -1;
+ o->beep = -1;
+ o->highlight = -1;
+ o->snippets = -1;
+ o->show_flagged = -1;
+ o->sender_personal = -1;
+ o->sender_mbox = -1;
+}
+
+static void
+setup_options(struct mbox *b)
+{
+ b->o = global_options;
+ CLIST_FOR_EACH(struct option_node *, n, options)
+ if (!fnmatch(n->pattern, b->name, 0))
+ {
+ debug("\tApplied options %s\n", n->pattern);
+#define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
+ MERGE(priority);
+ MERGE(hide);
+ MERGE(hide_if_empty);
+ MERGE(highlight);
+ MERGE(beep);
+ MERGE(snippets);
+ MERGE(show_flagged);
+ MERGE(sender_personal);
+ MERGE(sender_mbox);
+ }
+}
static char *
mbox_name(char *path)
return NULL;
}
+static inline int
+mbox_active_p(struct mbox *b)
+{
+ if (b->o.priority < minimum_priority)
+ return 0;
+ if (b->o.hide)
+ return 0;
+ return 1;
+}
+
+static inline int
+mbox_visible_p(struct mbox *b)
+{
+ if (!mbox_active_p(b))
+ return 0;
+ if (b->scanning < 0)
+ return 1;
+ if (b->o.hide_if_empty && !b->total)
+ return 0;
+ return 1;
+}
+
static void
-add_inbox(clist *l)
+prepare_snippet(struct mbox *b, char *sender, char *subject)
{
- struct passwd *p = getpwuid(getuid());
- if (!p)
- die("You don't exist, go away!");
- char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 1];
- sprintf(buf, "/var/mail/%s", p->pw_name);
- add_mbox(l, buf, "INBOX");
+ while (*sender == ' ' || *sender == '\t')
+ sender++;
+ while (*subject == ' ' || *subject == '\t')
+ subject++;
+
+ char *pos = b->snippet;
+ char *term = b->snippet + sizeof(b->snippet) - 1;
+ if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
+ {
+ add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal);
+ add_snippet(&pos, term, ": ");
+ }
+ if (subject[0])
+ add_subject_snippet(&pos, term, subject);
+ else
+ add_snippet(&pos, term, "No subject");
}
static int mb_fd, mb_pos;
mb_pos = pos;
}
-static int
+static void
mb_seek(uns pos)
{
lseek(mb_fd, pos, SEEK_SET);
return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
}
+static void
+mb_unget(int c)
+{
+ if (c >= 0)
+ mb_cc--;
+}
+
static int
mb_check(const char *p, int len)
{
static void
scan_mbox(struct mbox *b, struct stat *st)
{
- char buf[1024];
+ char buf[1024], sender[1024], subject[1024];
int c;
const char from[] = "\nFrom ";
if (!st->st_size)
{
- b->total = b->new = 0;
+ b->total = b->new = b->flagged = 0;
b->last_pos = 0;
return;
}
if (mb_fd < 0)
{
debug("[open failed: %m] ");
- b->total = b->new = -1;
+ b->total = b->new = b->flagged = -1;
return;
}
mb_reset(0);
if (!mb_check(from+1, 5))
{
debug("[inconsistent] ");
- b->total = b->new = -1;
+ b->total = b->new = b->flagged = -1;
goto done;
}
- b->total = b->new = 0;
- b->last_total = b->last_new = 0;
+ b->total = b->new = b->flagged = 0;
+ b->last_total = b->last_new = b->last_flagged = 0;
+ b->snippet_is_new = 0;
}
else
{
b->total = b->last_total;
b->new = b->last_new;
+ b->flagged = b->last_flagged;
}
for(;;)
b->last_pos--; // last_pos should be the previous \n character
b->last_total = b->total;
b->last_new = b->new;
+ b->last_flagged = b->flagged;
while ((c = mb_get()) >= 0 && c != '\n')
;
int new = 1;
+ int flagged = 0;
+ sender[0] = 0;
+ subject[0] = 0;
for (;;)
{
uns i = 0;
goto done;
}
if (c == '\n')
- break;
+ {
+ int fold = -1;
+ do
+ {
+ fold++;
+ c = mb_get();
+ }
+ while (c == ' ' || c == '\t');
+ mb_unget(c);
+ if (!fold)
+ break;
+ c = ' ';
+ }
if (c == '\r')
continue;
if (i < sizeof(buf) - 1)
break;
if (!strncasecmp(buf, "Status:", 7))
new = 0;
+ else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
+ flagged = 1;
+ else if (!strncasecmp(buf, "From:", 5))
+ strcpy(sender, buf+5);
+ else if (!strncasecmp(buf, "Subject:", 8))
+ strcpy(subject, buf+8);
}
b->total++;
if (new)
b->new++;
+ if (flagged)
+ b->flagged++;
+ if (new || (flagged && !b->snippet_is_new))
+ {
+ b->snippet_is_new = new;
+ prepare_snippet(b, sender, subject);
+ }
int ct = 1;
while (from[ct])
{
debug("Searching for mailboxes...\n");
last_scan_time = time(NULL);
- int changed = 0;
- CLIST_FOR_EACH(struct mbox *, p, patterns)
+ CLIST_FOR_EACH(struct pattern_node *, p, patterns)
{
+ debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
glob_t g;
- int err = glob(p->path, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
+ int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
if (err && err != GLOB_NOMATCH)
- die("Failed to glob %s: %m", p->path);
+ die("Failed to glob %s: %m", p->pattern);
for (uns i=0; i<g.gl_pathc; i++)
{
char *name = g.gl_pathv[i];
if (!b)
{
b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
- if (find_mbox(&hilites, b->name))
- b->hilited = 1;
- debug("Discovered mailbox %s (%s) hilited=%d\n", b->name, b->path, b->hilited);
+ debug("Discovered mailbox %s (%s)\n", b->name, b->path);
+ setup_options(b);
b->scanning = -1;
- changed = 1;
}
b->seen = 1;
}
globfree(&g);
}
- num_mboxes = 0;
struct mbox *tmp;
CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
{
if (b->seen)
- {
- num_mboxes++;
- b->seen = 0;
- }
+ b->seen = 0;
else
{
debug("Lost mailbox %s\n", b->name);
- changed = 1;
del_mbox(b);
}
}
- if (changed)
- {
- debug("Reallocating mailbox array...\n");
- free(mbox_array);
- mbox_array = xmalloc(sizeof(struct mbox *) * (num_mboxes+1));
- int i = 0;
- CLIST_FOR_EACH(struct mbox *, b, mboxes)
- {
- b->index = i;
- mbox_array[i++] = b;
- }
- redraw_all();
- refresh();
- }
+ rethink_display();
debug("Scanning mailboxes...\n");
CLIST_FOR_EACH(struct mbox *, b, mboxes)
{
struct stat st;
debug("%s: ", b->name);
+ if (!mbox_active_p(b))
+ {
+ debug("inactive\n");
+ continue;
+ }
if (force_refresh)
b->force_refresh = 1;
if (stat(b->path, &st) < 0)
{
- b->total = b->new = -1;
+ b->total = b->new = b->flagged = -1;
debug("%m\n");
}
else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
scan_mbox(b, &st);
b->last_time = st.st_mtime;
b->last_size = st.st_size;
- debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size);
+ debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
b->scanning = 0;
redraw_line(b->index);
debug("Scan finished\n");
last_scan_time = time(NULL);
+ rethink_display();
}
static int cursor_at, cursor_max;
M_IDLE,
M_SCAN,
M_NEW,
+ M_FLAG,
M_BAD,
M_MAX
};
{
struct mbox *b = mbox_array[i];
int cc = (cursor_at == i);
- int hi = b->hilited;
+ int hi = b->o.highlight;
attrset(attrs[cc][hi][M_IDLE]);
if (cc)
printw(" ");
if (b->new)
attrset(attrs[cc][hi][M_NEW]);
+ else if (b->flagged)
+ attrset(attrs[cc][hi][M_FLAG]);
printw("%-20s ", b->name);
if (b->scanning < 0)
;
{
attrset(attrs[cc][hi][M_IDLE]);
printw("%6d ", b->total);
+ int snip = 0;
if (b->new)
{
attrset(attrs[cc][hi][M_NEW]);
if (age < 0)
age = 0;
if (age < 3600)
- printw("%2d min", age/60);
+ printw("%2d min ", age/60);
else if (age < 86400)
- printw("%2d hrs", age/3600);
+ printw("%2d hrs ", age/3600);
+ else
+ printw(" ");
+ snip = 1;
+ }
+ else if (b->flagged)
+ {
+ attrset(attrs[cc][hi][M_FLAG]);
+ printw("%6d ", b->flagged);
+ attrset(attrs[cc][hi][M_IDLE]);
+ printw(" ");
+ attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
+ snip = b->o.show_flagged;
+ }
+ if (snip && b->o.snippets && b->snippet[0])
+ {
+ int xx, yy;
+ getyx(stdscr, yy, xx);
+ int remains = COLS-1-xx;
+ if (remains > 2)
+ printw("%-.*s", remains, b->snippet);
}
}
- attrset(attrs[cc][hi][M_IDLE]);
}
- else
- attrset(attrs[0][0][M_IDLE]);
+ attrset(attrs[0][0][M_IDLE]);
clrtoeol();
}
if (!cursor_max)
{
printw("(no mailboxes found)");
+ clrtoeol();
move(1, 0);
}
clrtobot();
}
+static void
+rethink_display(void)
+{
+ int i = 0;
+ int changed = 0;
+ int beeeep = 0;
+ CLIST_FOR_EACH(struct mbox *, b, mboxes)
+ if (mbox_visible_p(b))
+ {
+ b->index = i;
+ if (i >= num_mboxes || mbox_array[i] != b)
+ {
+ changed = 1;
+ if (i >= mbox_array_size)
+ {
+ mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
+ mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
+ }
+ mbox_array[i] = b;
+ }
+ if (b->o.beep && b->new > b->last_beep_new)
+ beeeep = 1;
+ b->last_beep_new = b->new;
+ i++;
+ }
+ if (i != num_mboxes)
+ changed = 1;
+ num_mboxes = i;
+
+ if (changed)
+ {
+ redraw_all();
+ refresh();
+ }
+ if (beeeep && allow_bells)
+ beep();
+}
+
static void
term_init(void)
{
curs_set(0);
static const int attrs_mono[2][M_MAX] = {
- [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_BAD] = A_DIM },
- [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_BAD] = A_DIM },
+ [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_FLAG] = 0, [M_BAD] = A_DIM },
+ [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_FLAG] = A_REVERSE, [M_BAD] = A_DIM },
};
for (int i=0; i<2; i++)
for (int j=0; j<M_MAX; j++)
init_pair(3, COLOR_WHITE, COLOR_BLUE);
init_pair(4, COLOR_YELLOW, COLOR_BLUE);
init_pair(5, COLOR_RED, COLOR_BLUE);
+ init_pair(6, COLOR_GREEN, COLOR_BLACK);
+ init_pair(7, COLOR_GREEN, COLOR_BLUE);
static const int attrs_color[2][2][M_MAX] = {
- [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_BAD] = COLOR_PAIR(2) },
- [0][1] = { [M_IDLE] = A_BOLD, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1) | A_BOLD, [M_BAD] = COLOR_PAIR(2) | A_BOLD },
- [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_BAD] = COLOR_PAIR(5) },
- [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD, [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4) | A_BOLD, [M_BAD] = COLOR_PAIR(5) | A_BOLD },
+ [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_FLAG] = COLOR_PAIR(6), [M_BAD] = COLOR_PAIR(2) },
+ [0][1] = { [M_IDLE] = A_BOLD, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1) | A_BOLD, [M_FLAG] = COLOR_PAIR(6) | A_BOLD, [M_BAD] = COLOR_PAIR(2) | A_BOLD },
+ [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_FLAG] = COLOR_PAIR(7), [M_BAD] = COLOR_PAIR(5) },
+ [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD, [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4) | A_BOLD, [M_FLAG] = COLOR_PAIR(7) | A_BOLD, [M_BAD] = COLOR_PAIR(5) | A_BOLD },
};
memcpy(attrs, attrs_color, sizeof(attrs));
}
}
- clear();
}
static void
}
static void
-scan_and_redraw(void)
+print_status(char *status)
{
move(LINES-1, 0);
- printw("Busy...");
+ if (status)
+ printw("%s", status);
+ clrtoeol();
refresh();
+}
+
+static void
+scan_and_redraw(void)
+{
+ print_status("Busy...");
scan();
- redraw_all();
- refresh();
+ print_status(NULL);
}
static void
}
static void
-next_active(int since)
+next_active(int since, int step)
{
if (!cursor_max)
return;
- since %= cursor_max;
- for (int pass=1; pass >= 0; pass--)
+ since = (since+cursor_max) % cursor_max;
+ step = (step+cursor_max) % cursor_max;
+ int besti = -1;
+ int bestp = -1;
+ int i = since;
+ do
{
- int i = since;
- do {
- if (mbox_array[i]->hilited >= pass && mbox_array[i]->new)
- {
- move_cursor(i);
- return;
- }
- i = (i+1) % cursor_max;
- } while (i != since);
+ struct mbox *b = mbox_array[i];
+ if (b->new && b->o.priority > bestp)
+ {
+ besti = i;
+ bestp = b->o.priority;
+ }
+ i = (i+step) % cursor_max;
}
+ while (i != since);
+ if (besti >= 0)
+ move_cursor(besti);
}
+#define STR2(c) #c
+#define STR(c) STR2(c)
+
static void NONRET
usage(void)
{
- fprintf(stderr, "Usage: cm [<options>] <mbox-patterns>\n\
+ fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
\n\
Options:\n\
-c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
-d\t\t\tLog debug messages to stderr\n\
--h <mbox>\t\tHighlight and prefer the specified mailbox\n\
-i\t\t\tInclude user's INBOX\n\
--l\t\t\tLock mailboxes when scanning\n\
-m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
+-o <pattern>=<opts>\tSet mailbox options\n\
+-o <opts>\t\tSet default options for all mailboxes\n\
+-p <pri>\t\tSet minimum priority to show\n\
+\n\
+Mailbox options (set with `-o', use upper case to negate):\n\
+0-9\t\t\tSet mailbox priority (0=default)\n\
+b\t\t\tBeep when a message arrives\n\
+e\t\t\tHide from display if empty\n\
+f\t\t\tShow flagged messages if there are no new ones\n\
+h\t\t\tHide from display\n\
+m\t\t\tShow mailbox name of the sender\n\
+p\t\t\tShow personal info (full name) of the sender\n\
+s\t\t\tShow message snippets\n\
+t\t\t\tHighlight the entry\n\
\n\
-CheckMail 0.1, (c) 2005 Martin Mares <mj@ucw.cz>\n\
+CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
It can be freely distributed and used according to the GNU GPL v2.\n\
");
exit(1);
}
+static void
+parse_options(char *c)
+{
+ struct options *o;
+ char *sep;
+ if (sep = strchr(c, '='))
+ {
+ struct option_node *n = xmalloc(sizeof(*n) + sep-c);
+ memcpy(n->pattern, c, sep-c);
+ n->pattern[sep-c] = 0;
+ clist_add_tail(&options, &n->n);
+ o = &n->o;
+ init_options(o);
+ c = sep+1;
+ }
+ else
+ o = &global_options;
+
+ int x;
+ while (x = *c++)
+ if (x >= '0' && x <= '9')
+ o->priority = x - '0';
+ else
+ {
+ int value = !!islower(x);
+ switch (tolower(x))
+ {
+ case 'b':
+ o->beep = value;
+ break;
+ case 'e':
+ o->hide_if_empty = value;
+ break;
+ case 'f':
+ o->show_flagged = value;
+ break;
+ case 'h':
+ o->hide = value;
+ break;
+ case 'm':
+ o->sender_mbox = value;
+ break;
+ case 'p':
+ o->sender_personal = value;
+ break;
+ case 's':
+ o->snippets = value;
+ break;
+ case 't':
+ o->highlight = value;
+ break;
+ default:
+ fprintf(stderr, "Invalid mailbox option `%c'\n", x);
+ usage();
+ }
+ }
+}
+
int
main(int argc, char **argv)
{
clist_init(&mboxes);
- clist_init(&hilites);
+ clist_init(&options);
clist_init(&patterns);
int c;
- while ((c = getopt(argc, argv, "c:dh:ilm:")) >= 0)
+ while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0)
switch (c)
{
case 'c':
case 'd':
debug_mode++;
break;
- case 'h':
- add_mbox(&hilites, optarg, NULL);
- break;
case 'i':
- add_inbox(&patterns);
- break;
- case 'l':
- lock_mboxes = 1;
+ add_inbox();
break;
case 'm':
run_cmd = optarg;
break;
+ case 'o':
+ parse_options(optarg);
+ break;
+ case 'p':
+ minimum_priority = atol(optarg);
+ break;
default:
usage();
}
while (optind < argc)
- add_mbox(&patterns, argv[optind++], NULL);
+ add_pattern(argv[optind++]);
+ charset_init();
term_init();
scan_and_redraw();
- next_active(0);
+ next_active(0, 1);
int should_exit = 0;
while (!should_exit)
case KEY_UP:
move_cursor(cursor_at-1);
break;
- case '0':
+ case '^':
case KEY_HOME:
case KEY_PPAGE:
move_cursor(0);
move_cursor(cursor_max-1);
break;
case '\t':
- next_active(cursor_at+1);
+ next_active(cursor_at+1, 1);
+ break;
+ case '`':
+ next_active(cursor_at-1, -1);
break;
case '\r':
case '\n':
scan_and_redraw();
}
break;
+ case 'l' & 0x1f:
+ clearok(stdscr, TRUE);
+ redraw_all();
+ refresh();
+ break;
case 'r' & 0x1f:
force_refresh = 1;
break;
+ case 'b':
+ allow_bells = 1;
+ print_status("Bells and whistles are now enabled. Toot!");
+ break;
+ case 'B':
+ allow_bells = 0;
+ print_status("Bells and whistles are now disabled. Pssst!");
+ break;
+ default:
+ if (ch >= '0' && ch <= '9')
+ {
+ minimum_priority = ch - '0';
+ scan_and_redraw();
+ }
+ else
+ debug("Pressed unknown key %d\n", ch);
}
refresh();
}