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