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