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