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