]> mj.ucw.cz Git - checkmail.git/blob - cm.c
963fe049541f4b031d53c3fc5e20f70bc3e97600
[checkmail.git] / cm.c
1 /*
2  *      Incoming Mail Checker
3  *
4  *      (c) 2005--2007 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <ctype.h>
11 #include <getopt.h>
12 #include <fcntl.h>
13 #include <glob.h>
14 #include <fnmatch.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <pwd.h>
18 #include <time.h>
19 #include <curses.h>
20
21 #include "util.h"
22 #include "clists.h"
23 #include "charset.h"
24
25 static int check_interval = 30;
26 static int force_refresh;
27 static int allow_bells = 1;
28 static int minimum_priority;
29 static time_t last_scan_time;
30 static char *run_cmd = "mutt -f %s";
31
32 struct options {
33   int priority;
34   int hide;
35   int hide_if_empty;
36   int highlight;
37   int beep;
38   int snippets;
39   int show_flagged;
40 };
41
42 struct option_node {
43   cnode n;
44   struct options o;
45   char pattern[1];
46 };
47
48 struct pattern_node {
49   cnode n;
50   char *name;
51   char pattern[1];
52 };
53
54 static clist options, patterns;
55 static struct options global_options;
56
57 struct mbox {
58   cnode n;
59   struct options o;
60   char *name;
61   char *path;
62   int index;
63   int scanning;
64   int seen;
65   time_t last_time;
66   int last_size, last_pos;
67   int total, new, flagged;
68   int last_total, last_new, last_flagged;
69   int last_beep_new;
70   int force_refresh;
71   int snippet_is_new;
72   char snippet[256];
73 };
74
75 static clist mboxes;
76 static struct mbox **mbox_array;
77 static int num_mboxes, mbox_array_size;
78
79 static void redraw_line(int i);
80 static void rethink_display(void);
81
82 static void
83 add_pattern(char *patt)
84 {
85   struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
86   strcpy(n->pattern, patt);
87   if (patt = strchr(n->pattern, '='))
88     {
89       *patt++ = 0;
90       n->name = patt;
91     }
92   else
93     n->name = NULL;
94   clist_add_tail(&patterns, &n->n);
95 }
96
97 static void
98 add_inbox(void)
99 {
100   struct passwd *p = getpwuid(getuid());
101   if (!p)
102     die("You don't exist, go away!");
103   char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
104   sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
105   add_pattern(buf);
106 }
107
108 static void
109 init_options(struct options *o)
110 {
111   o->priority = -1;
112   o->hide = -1;
113   o->hide_if_empty = -1;
114   o->beep = -1;
115   o->highlight = -1;
116   o->snippets = -1;
117   o->show_flagged = -1;
118 }
119
120 static void
121 setup_options(struct mbox *b)
122 {
123   b->o = global_options;
124   CLIST_FOR_EACH(struct option_node *, n, options)
125     if (!fnmatch(n->pattern, b->name, 0))
126       {
127         debug("\tApplied options %s\n", n->pattern);
128 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
129         MERGE(priority);
130         MERGE(hide);
131         MERGE(hide_if_empty);
132         MERGE(highlight);
133         MERGE(beep);
134         MERGE(snippets);
135         MERGE(show_flagged);
136       }
137 }
138
139 static char *
140 mbox_name(char *path)
141 {
142   char *c = strrchr(path, '/');
143   return c ? (c+1) : path;
144 }
145
146 static struct mbox *
147 add_mbox(clist *l, char *path, char *name)
148 {
149   struct mbox *b = xmalloc(sizeof(*b));
150   bzero(b, sizeof(*b));
151   b->path = xstrdup(path);
152   b->name = xstrdup(name);
153
154   if (name)
155     {
156       cnode *prev = l->head.prev;
157       while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0)
158         prev = prev->prev;
159       clist_insert_after(&b->n, prev);
160     }
161   else
162     clist_add_tail(l, &b->n);
163
164   return b;
165 }
166
167 static void
168 del_mbox(struct mbox *b)
169 {
170   clist_remove(&b->n);
171   free(b->path);
172   free(b->name);
173   free(b);
174 }
175
176 static struct mbox *
177 find_mbox(clist *l, char *path)
178 {
179   CLIST_FOR_EACH(struct mbox *, b, *l)
180     if (!strcmp(b->path, path))
181       return b;
182   return NULL;
183 }
184
185 static inline int
186 mbox_active_p(struct mbox *b)
187 {
188   if (b->o.priority < minimum_priority)
189     return 0;
190   if (b->o.hide)
191     return 0;
192   return 1;
193 }
194
195 static inline int
196 mbox_visible_p(struct mbox *b)
197 {
198   if (!mbox_active_p(b))
199     return 0;
200   if (b->scanning < 0)
201     return 1;
202   if (b->o.hide_if_empty && !b->total)
203     return 0;
204   return 1;
205 }
206
207 static void
208 prepare_snippet(struct mbox *b, char *sender, char *subject)
209 {
210   while (*sender == ' ' || *sender == '\t')
211     sender++;
212   while (*subject == ' ' || *subject == '\t')
213     subject++;
214
215   char *pos = b->snippet;
216   char *term = b->snippet + sizeof(b->snippet) - 1;
217   if (sender[0])
218     {
219       add_snippet(&pos, term, sender);
220       add_snippet(&pos, term, ": ");
221     }
222   if (subject[0])
223     add_snippet(&pos, term, subject);
224   else
225     add_snippet(&pos, term, "No subject");
226 }
227
228 static int mb_fd, mb_pos;
229 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
230
231 static void
232 mb_reset(int pos)
233 {
234   mb_cc = mb_end = mb_buf;
235   mb_pos = pos;
236 }
237
238 static void
239 mb_seek(uns pos)
240 {
241   lseek(mb_fd, pos, SEEK_SET);
242   mb_reset(pos);
243 }
244
245 static int
246 mb_tell(void)
247 {
248   return mb_pos - (mb_end - mb_cc);
249 }
250
251 static int
252 mb_ll_get(void)
253 {
254   int len = read(mb_fd, mb_buf, sizeof(mb_buf));
255   mb_cc = mb_buf;
256   if (len <= 0)
257     {
258       mb_end = mb_buf;
259       return -1;
260     }
261   else
262     {
263       mb_end = mb_buf + len;
264       mb_pos += len;
265       return *mb_cc++;
266     }
267 }
268
269 static inline int
270 mb_get(void)
271 {
272   return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
273 }
274
275 static void
276 mb_unget(int c)
277 {
278   if (c >= 0)
279     mb_cc--;
280 }
281
282 static int
283 mb_check(const char *p, int len)
284 {
285   while (len--)
286     {
287       if (mb_get() != *p++)
288         return 0;
289     }
290   return 1;
291 }
292
293 static void
294 scan_mbox(struct mbox *b, struct stat *st)
295 {
296   char buf[1024], sender[1024], subject[1024];
297   int c;
298   const char from[] = "\nFrom ";
299
300   if (!st->st_size)
301     {
302       b->total = b->new = b->flagged = 0;
303       b->last_pos = 0;
304       return;
305     }
306
307   /* FIXME: Locking! */
308
309   mb_fd = open(b->path, O_RDONLY);
310   if (mb_fd < 0)
311     {
312       debug("[open failed: %m] ");
313       b->total = b->new = b->flagged = -1;
314       return;
315     }
316   mb_reset(0);
317
318   int incremental = 0;
319   if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh)
320     {
321       mb_seek(b->last_pos);
322       if (mb_check(from, 6))
323         {
324           debug("[incremental] ");
325           incremental = 1;
326         }
327       else
328         {
329           debug("[incremental failed] ");
330           mb_seek(0);
331         }
332     }
333   if (!incremental)
334     {
335       if (!mb_check(from+1, 5))
336         {
337           debug("[inconsistent] ");
338           b->total = b->new = b->flagged = -1;
339           goto done;
340         }
341       b->total = b->new = b->flagged = 0;
342       b->last_total = b->last_new = b->last_flagged = 0;
343       b->snippet_is_new = 0;
344     }
345   else
346     {
347       b->total = b->last_total;
348       b->new = b->last_new;
349       b->flagged = b->last_flagged;
350     }
351
352   for(;;)
353     {
354       b->last_pos = mb_tell() - 5;
355       if (b->last_pos)
356         b->last_pos--;          // last_pos should be the previous \n character
357       b->last_total = b->total;
358       b->last_new = b->new;
359       b->last_flagged = b->flagged;
360       while ((c = mb_get()) >= 0 && c != '\n')
361         ;
362
363       int new = 1;
364       int flagged = 0;
365       sender[0] = 0;
366       subject[0] = 0;
367       for (;;)
368         {
369           uns i = 0;
370           for (;;)
371             {
372               c = mb_get();
373               if (c < 0)
374                 {
375                   debug("[truncated] ");
376                   goto done;
377                 }
378               if (c == '\n')
379                 {
380                   int fold = -1;
381                   do
382                     {
383                       fold++;
384                       c = mb_get();
385                     }
386                   while (c == ' ' || c == '\t');
387                   mb_unget(c);
388                   if (!fold)
389                     break;
390                   c = ' ';
391                 }
392               if (c == '\r')
393                 continue;
394               if (i < sizeof(buf) - 1)
395                 buf[i++] = c;
396             }
397           buf[i] = 0;
398           if (!buf[0])
399             break;
400           if (!strncasecmp(buf, "Status:", 7))
401             new = 0;
402           else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
403             flagged = 1;
404           else if (!strncasecmp(buf, "From:", 5))
405             strcpy(sender, buf+5);
406           else if (!strncasecmp(buf, "Subject:", 8))
407             strcpy(subject, buf+8);
408         }
409
410       b->total++;
411       if (new)
412         b->new++;
413       if (flagged)
414         b->flagged++;
415       if (new || (flagged && !b->snippet_is_new))
416         {
417           b->snippet_is_new = new;
418           prepare_snippet(b, sender, subject);
419         }
420
421       int ct = 1;
422       while (from[ct])
423         {
424           c = mb_get();
425           if (c < 0)
426             goto done;
427           if (c != from[ct++])
428             ct = (c == '\n');
429         }
430     }
431
432  done:
433   close(mb_fd);
434 }
435
436 static void
437 scan(void)
438 {
439   debug("Searching for mailboxes...\n");
440   last_scan_time = time(NULL);
441   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
442     {
443       debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
444       glob_t g;
445       int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
446       if (err && err != GLOB_NOMATCH)
447         die("Failed to glob %s: %m", p->pattern);
448       for (uns i=0; i<g.gl_pathc; i++)
449         {
450           char *name = g.gl_pathv[i];
451           struct mbox *b = find_mbox(&mboxes, name);
452           if (!b)
453             {
454               b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
455               debug("Discovered mailbox %s (%s)\n", b->name, b->path);
456               setup_options(b);
457               b->scanning = -1;
458             }
459           b->seen = 1;
460         }
461       globfree(&g);
462     }
463
464   struct mbox *tmp;
465   CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
466     {
467       if (b->seen)
468         b->seen = 0;
469       else
470         {
471           debug("Lost mailbox %s\n", b->name);
472           del_mbox(b);
473         }
474     }
475
476   rethink_display();
477
478   debug("Scanning mailboxes...\n");
479   CLIST_FOR_EACH(struct mbox *, b, mboxes)
480     {
481       struct stat st;
482       debug("%s: ", b->name);
483       if (!mbox_active_p(b))
484         {
485           debug("inactive\n");
486           continue;
487         }
488       if (force_refresh)
489         b->force_refresh = 1;
490       if (stat(b->path, &st) < 0)
491         {
492           b->total = b->new = b->flagged = -1;
493           debug("%m\n");
494         }
495       else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
496         {
497           b->scanning = 1;
498           redraw_line(b->index);
499           refresh();
500
501           scan_mbox(b, &st);
502           b->last_time = st.st_mtime;
503           b->last_size = st.st_size;
504           debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
505
506           b->scanning = 0;
507           redraw_line(b->index);
508           refresh();
509         }
510       else
511         debug("not changed\n");
512       b->force_refresh = 0;
513     }
514   force_refresh = 0;
515
516   debug("Scan finished\n");
517   last_scan_time = time(NULL);
518   rethink_display();
519 }
520
521 static int cursor_at, cursor_max;
522
523 enum {
524   M_IDLE,
525   M_SCAN,
526   M_NEW,
527   M_FLAG,
528   M_BAD,
529   M_MAX
530 };
531 static int attrs[2][2][M_MAX];          // active, hilite, status
532
533 static void
534 redraw_line(int i)
535 {
536   move(i, 0);
537   if (i < cursor_max)
538     {
539       struct mbox *b = mbox_array[i];
540       int cc = (cursor_at == i);
541       int hi = b->o.highlight;
542
543       attrset(attrs[cc][hi][M_IDLE]);
544       if (cc)
545         printw("> ");
546       else
547         printw("  ");
548       if (b->new)
549         attrset(attrs[cc][hi][M_NEW]);
550       else if (b->flagged)
551         attrset(attrs[cc][hi][M_FLAG]);
552       printw("%-20s ", b->name);
553       if (b->scanning < 0)
554         ;
555       else if (b->scanning)
556         {
557           attrset(attrs[cc][hi][M_SCAN]);
558           printw("[SCANNING]");
559         }
560       else if (b->total < 0)
561         {
562           attrset(attrs[cc][hi][M_BAD]);
563           printw("BROKEN");
564         }
565       else
566         {
567           attrset(attrs[cc][hi][M_IDLE]);
568           printw("%6d ", b->total);
569           int snip = 0;
570           if (b->new)
571             {
572               attrset(attrs[cc][hi][M_NEW]);
573               printw("%6d  ", b->new);
574               attrset(attrs[cc][hi][M_IDLE]);
575               int age = (last_scan_time - b->last_time);
576               if (age < 0)
577                 age = 0;
578               if (age < 3600)
579                 printw("%2d min  ", age/60);
580               else if (age < 86400)
581                 printw("%2d hrs  ", age/3600);
582               else
583                 printw("        ");
584               snip = 1;
585             }
586           else if (b->flagged)
587             {
588               attrset(attrs[cc][hi][M_FLAG]);
589               printw("%6d  ", b->flagged);
590               attrset(attrs[cc][hi][M_IDLE]);
591               printw("        ");
592               snip = b->o.show_flagged;
593             }
594           if (snip && b->o.snippets && b->snippet[0])
595             {
596               int xx, yy;
597               getyx(stdscr, yy, xx);
598               int remains = COLS-1-xx;
599               if (remains > 2)
600                 printw("%-.*s", remains, b->snippet);
601             }
602         }
603     }
604   attrset(attrs[0][0][M_IDLE]);
605   clrtoeol();
606 }
607
608 static void
609 redraw_all(void)
610 {
611   cursor_max = num_mboxes;
612   if (cursor_max > LINES-1)
613     cursor_max = LINES-1;
614   if (cursor_at >= cursor_max)
615     cursor_at = cursor_max - 1;
616   if (cursor_at < 0)
617     cursor_at = 0;
618
619   for (int i=0; i<cursor_max; i++)
620     redraw_line(i);
621   move(cursor_max, 0);
622   if (!cursor_max)
623     {
624       printw("(no mailboxes found)");
625       clrtoeol();
626       move(1, 0);
627     }
628   clrtobot();
629 }
630
631 static void
632 rethink_display(void)
633 {
634   int i = 0;
635   int changed = 0;
636   int beeeep = 0;
637   CLIST_FOR_EACH(struct mbox *, b, mboxes)
638     if (mbox_visible_p(b))
639       {
640         b->index = i;
641         if (i >= num_mboxes || mbox_array[i] != b)
642           {
643             changed = 1;
644             if (i >= mbox_array_size)
645               {
646                 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
647                 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
648               }
649             mbox_array[i] = b;
650           }
651         if (b->o.beep && b->new > b->last_beep_new)
652           beeeep = 1;
653         b->last_beep_new = b->new;
654         i++;
655       }
656   if (i != num_mboxes)
657     changed = 1;
658   num_mboxes = i;
659
660   if (changed)
661     {
662       redraw_all();
663       refresh();
664     }
665   if (beeeep && allow_bells)
666     beep();
667 }
668
669 static void
670 term_init(void)
671 {
672   initscr();
673   cbreak();
674   noecho();
675   nonl();
676   intrflush(stdscr, FALSE);
677   keypad(stdscr, TRUE);
678   curs_set(0);
679
680   static const int attrs_mono[2][M_MAX] = {
681     [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_FLAG] = 0, [M_BAD] = A_DIM },
682     [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_FLAG] = A_REVERSE, [M_BAD] = A_DIM },
683   };
684   for (int i=0; i<2; i++)
685     for (int j=0; j<M_MAX; j++)
686       {
687         attrs[0][i][j] = attrs_mono[i][j];
688         attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
689       }
690
691   if (has_colors())
692     {
693       start_color();
694       if (COLOR_PAIRS >= 5)
695         {
696           init_pair(1, COLOR_YELLOW, COLOR_BLACK);
697           init_pair(2, COLOR_RED, COLOR_BLACK);
698           init_pair(3, COLOR_WHITE, COLOR_BLUE);
699           init_pair(4, COLOR_YELLOW, COLOR_BLUE);
700           init_pair(5, COLOR_RED, COLOR_BLUE);
701           init_pair(6, COLOR_GREEN, COLOR_BLACK);
702           init_pair(7, COLOR_GREEN, COLOR_BLUE);
703           static const int attrs_color[2][2][M_MAX] = {
704             [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) },
705             [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 },
706             [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) },
707             [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 },
708           };
709           memcpy(attrs, attrs_color, sizeof(attrs));
710         }
711     }
712 }
713
714 static void
715 term_cleanup(void)
716 {
717   endwin();
718 }
719
720 static void
721 print_status(char *status)
722 {
723   move(LINES-1, 0);
724   if (status)
725     printw("%s", status);
726   clrtoeol();
727   refresh();
728 }
729
730 static void
731 scan_and_redraw(void)
732 {
733   print_status("Busy...");
734   scan();
735   print_status(NULL);
736 }
737
738 static void
739 move_cursor(int i)
740 {
741   if (i >= 0 && i < cursor_max && i != cursor_at)
742     {
743       int old = cursor_at;
744       cursor_at = i;
745       redraw_line(old);
746       redraw_line(i);
747     }
748 }
749
750 static void
751 next_active(int since, int step)
752 {
753   if (!cursor_max)
754     return;
755   since = (since+cursor_max) % cursor_max;
756   step = (step+cursor_max) % cursor_max;
757   int besti = -1;
758   int bestp = -1;
759   int i = since;
760   do
761     {
762       struct mbox *b = mbox_array[i];
763       if (b->new && b->o.priority > bestp)
764         {
765           besti = i;
766           bestp = b->o.priority;
767         }
768       i = (i+step) % cursor_max;
769     }
770   while (i != since);
771   if (besti >= 0)
772     move_cursor(besti);
773 }
774
775 #define STR2(c) #c
776 #define STR(c) STR2(c)
777
778 static void NONRET
779 usage(void)
780 {
781   fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
782 \n\
783 Options:\n\
784 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
785 -d\t\t\tLog debug messages to stderr\n\
786 -i\t\t\tInclude user's INBOX\n\
787 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
788 -o <pattern>=<opts>\tSet mailbox options\n\
789 -o <opts>\t\tSet default options for all mailboxes\n\
790 -p <pri>\t\tSet minimum priority to show\n\
791 \n\
792 Mailbox options (set with `-o', use upper case to negate):\n\
793 0-9\t\t\tSet mailbox priority (0=default)\n\
794 b\t\t\tBeep when a message arrives\n\
795 e\t\t\tHide from display if empty\n\
796 f\t\t\tShow flagged messages if there are no new ones\n\
797 h\t\t\tHide from display\n\
798 s\t\t\tShow message snippets\n\
799 t\t\t\tHighlight the entry\n\
800 \n\
801 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
802 It can be freely distributed and used according to the GNU GPL v2.\n\
803 ");
804   exit(1);
805 }
806
807 static void
808 parse_options(char *c)
809 {
810   struct options *o;
811   char *sep;
812   if (sep = strchr(c, '='))
813     {
814       struct option_node *n = xmalloc(sizeof(*n) + sep-c);
815       memcpy(n->pattern, c, sep-c);
816       n->pattern[sep-c] = 0;
817       clist_add_tail(&options, &n->n);
818       o = &n->o;
819       init_options(o);
820       c = sep+1;
821     }
822   else
823     o = &global_options;
824
825   int x;
826   while (x = *c++)
827     if (x >= '0' && x <= '9')
828       o->priority = x - '0';
829     else
830       {
831         int value = !!islower(x);
832         switch (tolower(x))
833           {
834           case 'b':
835             o->beep = value;
836             break;
837           case 'e':
838             o->hide_if_empty = value;
839             break;
840           case 'f':
841             o->show_flagged = value;
842             break;
843           case 'h':
844             o->hide = value;
845             break;
846           case 's':
847             o->snippets = value;
848             break;
849           case 't':
850             o->highlight = value;
851             break;
852           default:
853             fprintf(stderr, "Invalid mailbox option `%c'\n", x);
854             usage();
855           }
856       }
857 }
858
859 int
860 main(int argc, char **argv)
861 {
862   clist_init(&mboxes);
863   clist_init(&options);
864   clist_init(&patterns);
865
866   int c;
867   while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0)
868     switch (c)
869       {
870       case 'c':
871         check_interval = atol(optarg);
872         if (check_interval <= 0)
873           usage();
874         break;
875       case 'd':
876         debug_mode++;
877         break;
878       case 'i':
879         add_inbox();
880         break;
881       case 'm':
882         run_cmd = optarg;
883         break;
884       case 'o':
885         parse_options(optarg);
886         break;
887       case 'p':
888         minimum_priority = atol(optarg);
889         break;
890       default:
891         usage();
892       }
893   while (optind < argc)
894     add_pattern(argv[optind++]);
895
896   charset_init();
897   term_init();
898   scan_and_redraw();
899   next_active(0, 1);
900
901   int should_exit = 0;
902   while (!should_exit)
903     {
904       time_t now = time(NULL);
905       int remains = last_scan_time + check_interval - now;
906       if (remains <= 0 || force_refresh)
907         scan_and_redraw();
908       else
909         {
910           remains *= 10;
911           halfdelay((remains > 255) ? 255 : remains);
912           int ch = getch();
913           switch (ch)
914             {
915             case 'q':
916               should_exit = 1;
917               break;
918             case 'j':
919             case KEY_DOWN:
920               move_cursor(cursor_at+1);
921               break;
922             case 'k':
923             case KEY_UP:
924               move_cursor(cursor_at-1);
925               break;
926             case '^':
927             case KEY_HOME:
928             case KEY_PPAGE:
929               move_cursor(0);
930               break;
931             case '$':
932             case KEY_END:
933             case KEY_NPAGE:
934               move_cursor(cursor_max-1);
935               break;
936             case '\t':
937               next_active(cursor_at+1, 1);
938               break;
939             case '`':
940               next_active(cursor_at-1, -1);
941               break;
942             case '\r':
943             case '\n':
944               if (cursor_at < cursor_max)
945                 {
946                   struct mbox *b = mbox_array[cursor_at];
947                   char cmd[strlen(run_cmd) + strlen(b->path) + 16];
948                   sprintf(cmd, run_cmd, b->path);
949                   term_cleanup();
950                   system(cmd);
951                   term_init();
952                   redraw_all();
953                   refresh();
954                   b->force_refresh = 1;
955                   scan_and_redraw();
956                 }
957               break;
958             case 'l' & 0x1f:
959               clearok(stdscr, TRUE);
960               redraw_all();
961               refresh();
962               break;
963             case 'r' & 0x1f:
964               force_refresh = 1;
965               break;
966             case 'b':
967               allow_bells = 1;
968               print_status("Bells and whistles are now enabled. Toot!");
969               break;
970             case 'B':
971               allow_bells = 0;
972               print_status("Bells and whistles are now disabled. Pssst!");
973               break;
974             default:
975               if (ch >= '0' && ch <= '9')
976                 {
977                   minimum_priority = ch - '0';
978                   scan_and_redraw();
979                 }
980               else
981                 debug("Pressed unknown key %d\n", ch);
982             }
983           refresh();
984         }
985     }
986
987   term_cleanup();
988   return 0;
989 }