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