]> mj.ucw.cz Git - checkmail.git/blob - cm.c
36a29e9145e86904a10acb9bac8d0788cad1f56c
[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               snip = b->o.show_flagged;
601             }
602           if (snip && b->o.snippets && b->snippet[0])
603             {
604               int xx, yy;
605               getyx(stdscr, yy, xx);
606               int remains = COLS-1-xx;
607               if (remains > 2)
608                 printw("%-.*s", remains, b->snippet);
609             }
610         }
611     }
612   attrset(attrs[0][0][M_IDLE]);
613   clrtoeol();
614 }
615
616 static void
617 redraw_all(void)
618 {
619   cursor_max = num_mboxes;
620   if (cursor_max > LINES-1)
621     cursor_max = LINES-1;
622   if (cursor_at >= cursor_max)
623     cursor_at = cursor_max - 1;
624   if (cursor_at < 0)
625     cursor_at = 0;
626
627   for (int i=0; i<cursor_max; i++)
628     redraw_line(i);
629   move(cursor_max, 0);
630   if (!cursor_max)
631     {
632       printw("(no mailboxes found)");
633       clrtoeol();
634       move(1, 0);
635     }
636   clrtobot();
637 }
638
639 static void
640 rethink_display(void)
641 {
642   int i = 0;
643   int changed = 0;
644   int beeeep = 0;
645   CLIST_FOR_EACH(struct mbox *, b, mboxes)
646     if (mbox_visible_p(b))
647       {
648         b->index = i;
649         if (i >= num_mboxes || mbox_array[i] != b)
650           {
651             changed = 1;
652             if (i >= mbox_array_size)
653               {
654                 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
655                 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
656               }
657             mbox_array[i] = b;
658           }
659         if (b->o.beep && b->new > b->last_beep_new)
660           beeeep = 1;
661         b->last_beep_new = b->new;
662         i++;
663       }
664   if (i != num_mboxes)
665     changed = 1;
666   num_mboxes = i;
667
668   if (changed)
669     {
670       redraw_all();
671       refresh();
672     }
673   if (beeeep && allow_bells)
674     beep();
675 }
676
677 static void
678 term_init(void)
679 {
680   initscr();
681   cbreak();
682   noecho();
683   nonl();
684   intrflush(stdscr, FALSE);
685   keypad(stdscr, TRUE);
686   curs_set(0);
687
688   static const int attrs_mono[2][M_MAX] = {
689     [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_FLAG] = 0, [M_BAD] = A_DIM },
690     [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_FLAG] = A_REVERSE, [M_BAD] = A_DIM },
691   };
692   for (int i=0; i<2; i++)
693     for (int j=0; j<M_MAX; j++)
694       {
695         attrs[0][i][j] = attrs_mono[i][j];
696         attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
697       }
698
699   if (has_colors())
700     {
701       start_color();
702       if (COLOR_PAIRS >= 5)
703         {
704           init_pair(1, COLOR_YELLOW, COLOR_BLACK);
705           init_pair(2, COLOR_RED, COLOR_BLACK);
706           init_pair(3, COLOR_WHITE, COLOR_BLUE);
707           init_pair(4, COLOR_YELLOW, COLOR_BLUE);
708           init_pair(5, COLOR_RED, COLOR_BLUE);
709           init_pair(6, COLOR_GREEN, COLOR_BLACK);
710           init_pair(7, COLOR_GREEN, COLOR_BLUE);
711           static const int attrs_color[2][2][M_MAX] = {
712             [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) },
713             [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 },
714             [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) },
715             [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 },
716           };
717           memcpy(attrs, attrs_color, sizeof(attrs));
718         }
719     }
720 }
721
722 static void
723 term_cleanup(void)
724 {
725   endwin();
726 }
727
728 static void
729 print_status(char *status)
730 {
731   move(LINES-1, 0);
732   if (status)
733     printw("%s", status);
734   clrtoeol();
735   refresh();
736 }
737
738 static void
739 scan_and_redraw(void)
740 {
741   print_status("Busy...");
742   scan();
743   print_status(NULL);
744 }
745
746 static void
747 move_cursor(int i)
748 {
749   if (i >= 0 && i < cursor_max && i != cursor_at)
750     {
751       int old = cursor_at;
752       cursor_at = i;
753       redraw_line(old);
754       redraw_line(i);
755     }
756 }
757
758 static void
759 next_active(int since, int step)
760 {
761   if (!cursor_max)
762     return;
763   since = (since+cursor_max) % cursor_max;
764   step = (step+cursor_max) % cursor_max;
765   int besti = -1;
766   int bestp = -1;
767   int i = since;
768   do
769     {
770       struct mbox *b = mbox_array[i];
771       if (b->new && b->o.priority > bestp)
772         {
773           besti = i;
774           bestp = b->o.priority;
775         }
776       i = (i+step) % cursor_max;
777     }
778   while (i != since);
779   if (besti >= 0)
780     move_cursor(besti);
781 }
782
783 #define STR2(c) #c
784 #define STR(c) STR2(c)
785
786 static void NONRET
787 usage(void)
788 {
789   fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
790 \n\
791 Options:\n\
792 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
793 -d\t\t\tLog debug messages to stderr\n\
794 -i\t\t\tInclude user's INBOX\n\
795 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
796 -o <pattern>=<opts>\tSet mailbox options\n\
797 -o <opts>\t\tSet default options for all mailboxes\n\
798 -p <pri>\t\tSet minimum priority to show\n\
799 \n\
800 Mailbox options (set with `-o', use upper case to negate):\n\
801 0-9\t\t\tSet mailbox priority (0=default)\n\
802 b\t\t\tBeep when a message arrives\n\
803 e\t\t\tHide from display if empty\n\
804 f\t\t\tShow flagged messages if there are no new ones\n\
805 h\t\t\tHide from display\n\
806 m\t\t\tShow mailbox name of the sender\n\
807 p\t\t\tShow personal info (full name) of the sender\n\
808 s\t\t\tShow message snippets\n\
809 t\t\t\tHighlight the entry\n\
810 \n\
811 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
812 It can be freely distributed and used according to the GNU GPL v2.\n\
813 ");
814   exit(1);
815 }
816
817 static void
818 parse_options(char *c)
819 {
820   struct options *o;
821   char *sep;
822   if (sep = strchr(c, '='))
823     {
824       struct option_node *n = xmalloc(sizeof(*n) + sep-c);
825       memcpy(n->pattern, c, sep-c);
826       n->pattern[sep-c] = 0;
827       clist_add_tail(&options, &n->n);
828       o = &n->o;
829       init_options(o);
830       c = sep+1;
831     }
832   else
833     o = &global_options;
834
835   int x;
836   while (x = *c++)
837     if (x >= '0' && x <= '9')
838       o->priority = x - '0';
839     else
840       {
841         int value = !!islower(x);
842         switch (tolower(x))
843           {
844           case 'b':
845             o->beep = value;
846             break;
847           case 'e':
848             o->hide_if_empty = value;
849             break;
850           case 'f':
851             o->show_flagged = value;
852             break;
853           case 'h':
854             o->hide = value;
855             break;
856           case 'm':
857             o->sender_mbox = value;
858             break;
859           case 'p':
860             o->sender_personal = value;
861             break;
862           case 's':
863             o->snippets = value;
864             break;
865           case 't':
866             o->highlight = value;
867             break;
868           default:
869             fprintf(stderr, "Invalid mailbox option `%c'\n", x);
870             usage();
871           }
872       }
873 }
874
875 int
876 main(int argc, char **argv)
877 {
878   clist_init(&mboxes);
879   clist_init(&options);
880   clist_init(&patterns);
881
882   int c;
883   while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0)
884     switch (c)
885       {
886       case 'c':
887         check_interval = atol(optarg);
888         if (check_interval <= 0)
889           usage();
890         break;
891       case 'd':
892         debug_mode++;
893         break;
894       case 'i':
895         add_inbox();
896         break;
897       case 'm':
898         run_cmd = optarg;
899         break;
900       case 'o':
901         parse_options(optarg);
902         break;
903       case 'p':
904         minimum_priority = atol(optarg);
905         break;
906       default:
907         usage();
908       }
909   while (optind < argc)
910     add_pattern(argv[optind++]);
911
912   charset_init();
913   term_init();
914   scan_and_redraw();
915   next_active(0, 1);
916
917   int should_exit = 0;
918   while (!should_exit)
919     {
920       time_t now = time(NULL);
921       int remains = last_scan_time + check_interval - now;
922       if (remains <= 0 || force_refresh)
923         scan_and_redraw();
924       else
925         {
926           remains *= 10;
927           halfdelay((remains > 255) ? 255 : remains);
928           int ch = getch();
929           switch (ch)
930             {
931             case 'q':
932               should_exit = 1;
933               break;
934             case 'j':
935             case KEY_DOWN:
936               move_cursor(cursor_at+1);
937               break;
938             case 'k':
939             case KEY_UP:
940               move_cursor(cursor_at-1);
941               break;
942             case '^':
943             case KEY_HOME:
944             case KEY_PPAGE:
945               move_cursor(0);
946               break;
947             case '$':
948             case KEY_END:
949             case KEY_NPAGE:
950               move_cursor(cursor_max-1);
951               break;
952             case '\t':
953               next_active(cursor_at+1, 1);
954               break;
955             case '`':
956               next_active(cursor_at-1, -1);
957               break;
958             case '\r':
959             case '\n':
960               if (cursor_at < cursor_max)
961                 {
962                   struct mbox *b = mbox_array[cursor_at];
963                   char cmd[strlen(run_cmd) + strlen(b->path) + 16];
964                   sprintf(cmd, run_cmd, b->path);
965                   term_cleanup();
966                   system(cmd);
967                   term_init();
968                   redraw_all();
969                   refresh();
970                   b->force_refresh = 1;
971                   scan_and_redraw();
972                 }
973               break;
974             case 'l' & 0x1f:
975               clearok(stdscr, TRUE);
976               redraw_all();
977               refresh();
978               break;
979             case 'r' & 0x1f:
980               force_refresh = 1;
981               break;
982             case 'b':
983               allow_bells = 1;
984               print_status("Bells and whistles are now enabled. Toot!");
985               break;
986             case 'B':
987               allow_bells = 0;
988               print_status("Bells and whistles are now disabled. Pssst!");
989               break;
990             default:
991               if (ch >= '0' && ch <= '9')
992                 {
993                   minimum_priority = ch - '0';
994                   scan_and_redraw();
995                 }
996               else
997                 debug("Pressed unknown key %d\n", ch);
998             }
999           refresh();
1000         }
1001     }
1002
1003   term_cleanup();
1004   return 0;
1005 }