]> mj.ucw.cz Git - checkmail.git/blob - cm.c
Better help for -i
[checkmail.git] / cm.c
1 /*
2  *      Incoming Mail Checker
3  *
4  *      (c) 2005--2015 Martin Mares <mj@ucw.cz>
5  */
6
7 #define _GNU_SOURCE
8
9 #include <ctype.h>
10 #include <dirent.h>
11 #include <fcntl.h>
12 #include <fnmatch.h>
13 #include <getopt.h>
14 #include <glob.h>
15 #include <pwd.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <time.h>
23 #include <unistd.h>
24
25 #ifdef CONFIG_WIDE_CURSES
26 #include <ncurses.h>
27 #else
28 #include <curses.h>
29 #endif
30
31 #include "util.h"
32 #include "clists.h"
33 #include "charset.h"
34
35 static int check_interval = 30;
36 static int force_refresh;
37 static int allow_bells = 1;
38 static int allow_osd = 1;
39 static int minimum_priority;
40 static time_t last_scan_time;
41 static char *run_cmd = "mutt -f %s";
42 static int simple_tab;
43
44 struct options {
45   int priority;
46   int hide;
47   int hide_if_empty;
48   int highlight;
49   int beep;
50   int snippets;
51   int show_flagged;
52   int sender_personal;
53   int sender_mbox;
54   int hotkey;
55   int led;
56   int osd;
57   int unread_is_new;
58   int sort_order;
59 };
60
61 struct option_node {
62   cnode n;
63   struct options o;
64   char pattern[1];
65 };
66
67 struct pattern_node {
68   cnode n;
69   char *name;
70   char pattern[1];
71 };
72
73 static clist options, patterns;
74 static struct options global_options = {
75   .sender_personal = 1,
76   .sort_order = 1000,
77 };
78
79 #define MDIR_MAX_NAME_LEN 128
80
81 struct mbox {
82   cnode n;
83   struct options o;
84   char *name;
85   char *path;
86   int index;
87   int scanning;
88   int seen;
89   time_t last_time;
90   time_t best_time;
91   time_t display_valid_until;
92   int last_size, last_pos;
93   int total, new, flagged;
94   int last_total, last_new, last_flagged;
95   int last_beep_new;
96   int force_refresh;
97   int best_is_new;
98   char sender_snippet[128];
99   char subject_snippet[128];
100   char mdir_best[MDIR_MAX_NAME_LEN];
101 };
102
103 static clist mboxes;
104 static struct mbox **mbox_array;
105 static int num_mboxes, mbox_array_size;
106
107 struct osd_opt_node {
108   cnode n;
109   char *val;
110   char key[1];
111 };
112
113 static clist osd_opts;
114
115 static void redraw_line(int i);
116 static void rethink_display(int notify);
117
118 static void
119 add_pattern(char *patt)
120 {
121   struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
122   strcpy(n->pattern, patt);
123   if (patt = strchr(n->pattern, '='))
124     {
125       *patt++ = 0;
126       n->name = patt;
127     }
128   else
129     n->name = NULL;
130   clist_add_tail(&patterns, &n->n);
131 }
132
133 static void
134 add_inbox(void)
135 {
136   struct passwd *p = getpwuid(getuid());
137   if (!p)
138     die("You don't exist, go away!");
139   char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
140   sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
141   add_pattern(buf);
142 }
143
144 static void
145 init_options(struct options *o)
146 {
147   o->priority = -1;
148   o->hide = -1;
149   o->hide_if_empty = -1;
150   o->beep = -1;
151   o->highlight = -1;
152   o->snippets = -1;
153   o->show_flagged = -1;
154   o->sender_personal = -1;
155   o->sender_mbox = -1;
156   o->hotkey = -1;
157   o->led = -1;
158   o->osd = -1;
159   o->unread_is_new = -1;
160   o->sort_order = -1;
161 }
162
163 static void
164 setup_options(struct mbox *b)
165 {
166   b->o = global_options;
167   CLIST_FOR_EACH(struct option_node *, n, options)
168     if (!fnmatch(n->pattern, b->name, 0))
169       {
170         debug("\tApplied options %s\n", n->pattern);
171 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
172         MERGE(priority);
173         MERGE(hide);
174         MERGE(hide_if_empty);
175         MERGE(highlight);
176         MERGE(beep);
177         MERGE(snippets);
178         MERGE(show_flagged);
179         MERGE(sender_personal);
180         MERGE(sender_mbox);
181         MERGE(hotkey);
182         MERGE(led);
183         MERGE(osd);
184         MERGE(unread_is_new);
185         MERGE(sort_order);
186       }
187 }
188
189 static void
190 add_osd_opt(char *arg)
191 {
192   struct osd_opt_node *n = xmalloc(sizeof(*n) + strlen(arg));
193   strcpy(n->key, arg);
194   n->val = strchr(n->key, '=');
195   if (!n->val)
196     die("Malformed OSD option");
197   *n->val++ = 0;
198   clist_add_tail(&osd_opts, &n->n);
199 }
200
201 static char *
202 mbox_name(char *path)
203 {
204   char *c = strrchr(path, '/');
205   if (c)
206     c++;
207   else
208     c = path;
209   if (*c == '.')
210     c++;
211   return c;
212 }
213
214 static struct mbox *
215 new_mbox(char *path, char *name)
216 {
217   struct mbox *b = xmalloc(sizeof(*b));
218   bzero(b, sizeof(*b));
219   b->path = xstrdup(path);
220   b->name = xstrdup(name);
221   return b;
222 }
223
224 static void
225 add_mbox(clist *l, struct mbox *b)
226 {
227   if (b->name)
228     {
229       cnode *prev = l->head.prev;
230       struct mbox *prev_mbox;
231       while (prev != &l->head &&
232           ((prev_mbox = (struct mbox *)prev)->o.sort_order > b->o.sort_order ||
233            prev_mbox->o.sort_order == b->o.sort_order && strcmp(((struct mbox *)prev)->name, b->name) > 0))
234         prev = prev->prev;
235       clist_insert_after(&b->n, prev);
236     }
237   else
238     clist_add_tail(l, &b->n);
239 }
240
241 static void
242 del_mbox(struct mbox *b)
243 {
244   clist_remove(&b->n);
245   free(b->path);
246   free(b->name);
247   free(b);
248 }
249
250 static struct mbox *
251 find_mbox(clist *l, char *path)
252 {
253   CLIST_FOR_EACH(struct mbox *, b, *l)
254     if (!strcmp(b->path, path))
255       return b;
256   return NULL;
257 }
258
259 static inline int
260 mbox_active_p(struct mbox *b)
261 {
262   if (b->o.priority < minimum_priority)
263     return 0;
264   if (b->o.hide)
265     return 0;
266   return 1;
267 }
268
269 static inline int
270 mbox_visible_p(struct mbox *b)
271 {
272   if (!mbox_active_p(b))
273     return 0;
274   if (b->scanning < 0)
275     return 1;
276   if (b->o.hide_if_empty && !b->total)
277     return 0;
278   return 1;
279 }
280
281 static void
282 prepare_snippets(struct mbox *b, char *sender, char *subject)
283 {
284   char *pos, *term;
285
286   while (*sender == ' ' || *sender == '\t')
287     sender++;
288   while (*subject == ' ' || *subject == '\t')
289     subject++;
290
291   pos = b->sender_snippet;
292   term = pos + sizeof(b->sender_snippet) - 1;
293   if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
294     add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal);
295   else
296     *pos = 0;
297
298   pos = b->subject_snippet;
299   term = pos + sizeof(b->subject_snippet) - 1;
300   if (subject[0])
301     add_subject_snippet(&pos, term, subject);
302   else
303     add_snippet_raw(&pos, term, "No subject");
304 }
305
306 static void
307 build_snippet(char *buf, char *term, struct mbox *b)
308 {
309   if (b->sender_snippet[0])
310     {
311       add_snippet(&buf, term, b->sender_snippet);
312       add_snippet_raw(&buf, term, ": ");
313     }
314   add_snippet(&buf, term, b->subject_snippet);
315 }
316
317 static int mb_fd, mb_pos, mb_seekable;
318 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
319
320 static void
321 mb_reset(int pos)
322 {
323   mb_cc = mb_end = mb_buf;
324   mb_pos = pos;
325 }
326
327 static void
328 mb_seek(uns pos)
329 {
330   lseek(mb_fd, pos, SEEK_SET);
331   mb_reset(pos);
332 }
333
334 static int
335 mb_tell(void)
336 {
337   return mb_pos - (mb_end - mb_cc);
338 }
339
340 static int
341 mb_ll_get(void)
342 {
343   int len = read(mb_fd, mb_buf, sizeof(mb_buf));
344   mb_cc = mb_buf;
345   if (len <= 0)
346     {
347       mb_end = mb_buf;
348       return -1;
349     }
350   else
351     {
352       mb_end = mb_buf + len;
353       mb_pos += len;
354       return *mb_cc++;
355     }
356 }
357
358 static inline int
359 mb_get(void)
360 {
361   return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
362 }
363
364 static void
365 mb_unget(int c)
366 {
367   if (c >= 0)
368     mb_cc--;
369 }
370
371 static int
372 mb_check(const char *p, int len)
373 {
374   while (len--)
375     {
376       if (mb_get() != *p++)
377         return 0;
378     }
379   return 1;
380 }
381
382 static void
383 mb_skip(int len)
384 {
385   while (len)
386     {
387       int avail = mb_end - mb_cc;
388       if (!avail)
389         {
390           if (mb_seekable && len >= (int) sizeof(mb_buf))
391             {
392               int next = len - len % sizeof(mb_buf);
393               mb_seek(mb_tell() + next);
394               len -= next;
395               continue;
396             }
397           if (mb_ll_get() < 0)
398             return;
399           len--;
400         }
401       else
402         {
403           int next = (avail < len) ? avail : len;
404           len -= next;
405           mb_cc += next;
406         }
407     }
408 }
409
410 #define HDR_BUF_SIZE 1024
411
412 static int
413 read_hdr_field(char *buf)
414 {
415   uns i = 0;
416   for (;;)
417     {
418       int c = mb_get();
419       if (c < 0)
420         {
421           debug("[truncated] ");
422           return -1;
423         }
424       if (c == '\n')
425         {
426           if (!i)
427             break;
428           int fold = -1;
429           do
430             {
431               fold++;
432               c = mb_get();
433             }
434           while (c == ' ' || c == '\t');
435           mb_unget(c);
436           if (!fold)
437             break;
438           c = ' ';
439         }
440       if (c == '\r')
441         continue;
442       if (i < HDR_BUF_SIZE - 1)
443         buf[i++] = c;
444     }
445   buf[i] = 0;
446   if (debug_mode > 2)
447     debug("Header: <%s>\n", buf);
448   return buf[0] ? 1 : 0;
449 }
450
451 static void
452 scan_mbox(struct mbox *b, struct stat *st)
453 {
454   char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
455   int c;
456   int compressed = 0;
457   const char from[] = "\nFrom ";
458
459   b->best_time = st->st_mtime;
460   if (!st->st_size)
461     {
462       b->total = b->new = b->flagged = 0;
463       b->last_pos = 0;
464       return;
465     }
466
467   mb_fd = open(b->path, O_RDONLY);
468   if (mb_fd < 0)
469     {
470       debug("[open failed: %m] ");
471       b->total = b->new = b->flagged = -1;
472       return;
473     }
474   mb_seekable = 1;
475
476   char signature[2];
477   c = read(mb_fd, signature, 2);
478   lseek(mb_fd, 0, SEEK_SET);
479
480   if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip
481     {
482       debug("[decompressing] ");
483       int fds[2];
484       if (pipe(fds))
485         die("pipe failed: %m");
486       int pid = fork();
487       if (pid < 0)
488         die("fork failed: %m");
489       if (!pid)
490         {
491           if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0)
492             die("dup2 failed: %m");
493           close(fds[0]);
494           close(fds[1]);
495           close(mb_fd);
496           execlp("gzip", "gzip", "-cd", NULL);
497           die("Cannot execute gzip: %m");
498         }
499       close(fds[1]);
500       close(mb_fd);
501       mb_fd = fds[0];
502       compressed = 1;
503       mb_seekable = 0;
504     }
505   mb_reset(0);
506
507   int incremental = 0;
508   if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && mb_seekable)
509     {
510       mb_seek(b->last_pos);
511       if (mb_check(from, 6))
512         {
513           debug("[incremental] ");
514           incremental = 1;
515         }
516       else
517         {
518           debug("[incremental failed] ");
519           mb_seek(0);
520         }
521     }
522   if (!incremental)
523     {
524       if (!mb_check(from+1, 5))
525         {
526           debug("[inconsistent] ");
527           b->total = b->new = b->flagged = -1;
528           goto done;
529         }
530       b->total = b->new = b->flagged = 0;
531       b->last_total = b->last_new = b->last_flagged = 0;
532       b->best_is_new = 0;
533     }
534   else
535     {
536       b->total = b->last_total;
537       b->new = b->last_new;
538       b->flagged = b->last_flagged;
539     }
540
541   for(;;)
542     {
543       b->last_pos = mb_tell() - 5;
544       if (b->last_pos)
545         b->last_pos--;          // last_pos should be the previous \n character
546       b->last_total = b->total;
547       b->last_new = b->new;
548       b->last_flagged = b->flagged;
549       while ((c = mb_get()) >= 0 && c != '\n')
550         ;
551
552       int content_length = -1;
553       int new = 1;
554       int flagged = 0;
555       sender[0] = 0;
556       subject[0] = 0;
557       for (;;)
558         {
559           int c = read_hdr_field(buf);
560           if (c < 0)
561             goto done;
562           if (!c)
563             break;
564           if (!strncasecmp(buf, "Status:", 7))
565             {
566               if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
567                 new = 0;
568             }
569           else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
570             flagged = 1;
571           else if (!strncasecmp(buf, "From:", 5))
572             strcpy(sender, buf+5);
573           else if (!strncasecmp(buf, "Subject:", 8))
574             strcpy(subject, buf+8);
575           else if (!strncasecmp(buf, "Content-Length:", 15))
576             content_length = atoi(buf + 15);
577         }
578
579       b->total++;
580       if (new)
581         b->new++;
582       if (flagged)
583         b->flagged++;
584       if (debug_mode > 1)
585         debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
586       if (new || (flagged && !b->best_is_new))
587         {
588           b->best_is_new = new;
589           prepare_snippets(b, sender, subject);
590         }
591
592       if (content_length >= 0)
593         mb_skip(content_length);
594
595       int ct = 1;
596       while (from[ct])
597         {
598           c = mb_get();
599           if (c < 0)
600             goto done;
601           if (c != from[ct++])
602             ct = (c == '\n');
603         }
604     }
605
606  done:
607   close(mb_fd);
608   if (compressed)
609     {
610       int status;
611       if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
612         b->total = b->new = b->flagged = -1;
613     }
614 }
615
616 static time_t
617 mdir_mtime(struct mbox *b)
618 {
619   time_t mtime = 0;
620   char path[strlen(b->path) + 5];
621
622   for (int new=0; new<2; new++)
623     {
624       sprintf(path, "%s/%s", b->path, (new ? "new" : "cur"));
625       struct stat st;
626       if (stat(path, &st) < 0)
627         debug("[cannot stat %s] ", path);
628       else if (st.st_mtime > mtime)
629         mtime = st.st_mtime;
630     }
631   return mtime;
632 }
633
634 static void
635 mdir_get_snippet(struct mbox *b)
636 {
637   char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
638   sender[0] = 0;
639   subject[0] = 0;
640
641   char path[strlen(b->path) + MDIR_MAX_NAME_LEN];
642   sprintf(path, "%s/%s", b->path, b->mdir_best);
643   mb_fd = open(path, O_RDONLY);
644   if (mb_fd < 0)
645     {
646       debug("[open failed: %m] ");
647       prepare_snippets(b, sender, subject);
648       return;
649     }
650   mb_seekable = 1;
651   mb_reset(0);
652
653   while (read_hdr_field(buf) > 0)
654     {
655       if (!strncasecmp(buf, "From:", 5))
656         strcpy(sender, buf+5);
657       else if (!strncasecmp(buf, "Subject:", 8))
658         strcpy(subject, buf+8);
659     }
660
661   close(mb_fd);
662   prepare_snippets(b, sender, subject);
663 }
664
665 static void
666 scan_mdir(struct mbox *b)
667 {
668   int dir_len = strlen(b->path);
669   char path[dir_len + MDIR_MAX_NAME_LEN];
670   strcpy(path, b->path);
671
672   b->total = b->new = b->flagged = 0;
673   b->best_time = 0;
674   b->best_is_new = 0;
675
676   for (int new=0; new<2; new++)
677     {
678       strcpy(path + dir_len, (new ? "/new" : "/cur"));
679       DIR *d = opendir(path);
680       if (!d)
681         {
682           debug("[cannot open %s: %m] ");
683           continue;
684         }
685       struct dirent *de;
686       while (de = readdir(d))
687         {
688           char *name = de->d_name;
689           if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN)
690             continue;
691           path[dir_len + 4] = '/';
692           strcpy(path + dir_len + 5, name);
693
694           char *colon = strchr(name, ':');
695           int seen = 0;
696           int flagged = 0;
697           if (colon && colon[1] == '2' && colon[2] == ',')
698             {
699               // Another comma can separate extended flags (Dovecot extension)
700               for (int i=3; colon[i] && colon[i] != ','; i++)
701                 switch (colon[i])
702                   {
703                   case 'S':
704                     seen = 1;
705                     break;
706                   case 'F':
707                     flagged = 1;
708                     break;
709                   }
710             }
711
712           int is_new = new;
713           if (b->o.unread_is_new && !seen)
714             is_new = 1;
715
716           b->total++;
717           if (is_new)
718             b->new++;
719           if (flagged)
720             b->flagged++;
721           if (debug_mode > 1)
722             debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
723
724           if (is_new || flagged)
725             {
726               // Need to pick the best message to display
727               struct stat st;
728               if (stat(path, &st) < 0)
729                 debug("[cannot stat %s: %m] ", path);
730               else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
731                 {
732                   b->best_is_new = is_new;
733                   b->best_time = st.st_mtime;
734                   strcpy(b->mdir_best, path + dir_len + 1);
735                 }
736             }
737         }
738       closedir(d);
739     }
740
741   if (b->best_time && b->o.snippets)
742     {
743       debug("best <%s> ", b->mdir_best);
744       mdir_get_snippet(b);
745     }
746 }
747
748 static void
749 scan(int notify)
750 {
751   debug("Searching for mailboxes (notify=%d)...\n", notify);
752   last_scan_time = time(NULL);
753   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
754     {
755       debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
756       glob_t g;
757       int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
758       if (err && err != GLOB_NOMATCH)
759         die("Failed to glob %s: %m", p->pattern);
760       for (uns i=0; i<g.gl_pathc; i++)
761         {
762           char *name = g.gl_pathv[i];
763           struct mbox *b = find_mbox(&mboxes, name);
764           if (!b)
765             {
766               b = new_mbox(name, (p->name ? p->name : mbox_name(name)));
767               debug("Discovered mailbox %s (%s)\n", b->name, b->path);
768               setup_options(b);
769               add_mbox(&mboxes, b);
770               b->scanning = -1;
771             }
772           b->seen = 1;
773         }
774       globfree(&g);
775     }
776
777   struct mbox *tmp;
778   CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
779     {
780       if (b->seen)
781         b->seen = 0;
782       else
783         {
784           debug("Lost mailbox %s\n", b->name);
785           del_mbox(b);
786         }
787     }
788
789   rethink_display(0);
790
791   debug("Scanning mailboxes...\n");
792   CLIST_FOR_EACH(struct mbox *, b, mboxes)
793     {
794       struct stat st;
795       debug("%s: ", b->name);
796       if (!mbox_active_p(b))
797         {
798           debug("inactive\n");
799           continue;
800         }
801       if (force_refresh)
802         b->force_refresh = 1;
803       if (stat(b->path, &st) < 0)
804         {
805           b->total = b->new = b->flagged = -1;
806           debug("%m\n");
807           continue;
808         }
809
810       time_t current_mtime;
811       int current_size;
812       int is_mdir;
813       if (S_ISREG(st.st_mode))
814         {
815           // Regular mailbox
816           is_mdir = 0;
817           current_mtime = st.st_mtime;
818           current_size = st.st_size;
819           debug("[mbox] ");
820         }
821       else if (S_ISDIR(st.st_mode))
822         {
823           // Maildir
824           is_mdir = 1;
825           current_mtime = mdir_mtime(b);
826           current_size = 0;
827           debug("[mdir] ");
828         }
829       else
830         {
831           debug("neither file nor directory\n");
832           continue;
833         }
834
835       if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
836         {
837           b->scanning = 1;
838           redraw_line(b->index);
839           refresh();
840
841           if (is_mdir)
842             scan_mdir(b);
843           else
844             scan_mbox(b, &st);
845           b->last_time = current_mtime;
846           b->last_size = current_size;
847           debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
848
849           b->scanning = 0;
850           redraw_line(b->index);
851           refresh();
852           b->force_refresh = 0;
853         }
854       else if (b->display_valid_until <= last_scan_time)
855         {
856           debug("not changed, but needs redraw\n");
857           redraw_line(b->index);
858         }
859       else
860         debug("not changed\n");
861     }
862   force_refresh = 0;
863
864   debug("Scan finished\n");
865   last_scan_time = time(NULL);
866   rethink_display(notify);
867 }
868
869 #ifdef CONFIG_X11
870
871 #include <X11/Xlib.h>
872 #include <X11/Xatom.h>
873
874 static Display *x11_dpy;
875 static unsigned leds_care, leds_have, leds_want;
876
877 static Atom osd_pty;
878 static unsigned osd_care;
879 #define OSD_MSG_SIZE 1024
880 static char osd_last_msg[OSD_MSG_SIZE];
881 static time_t osd_last_time;
882
883 static void
884 x11_init(void)
885 {
886   leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
887   osd_care = (global_options.osd >= 0);
888   CLIST_FOR_EACH(struct option_node *, o, options)
889     {
890       if (o->o.led > 0)
891         leds_care |= (1 << o->o.led);
892       if (o->o.osd > 0)
893         osd_care = 1;
894     }
895
896   if (!leds_care && !osd_care)
897     {
898       debug("X11: No mailbox wants LEDs or OSD\n");
899       return;
900     }
901   if (!getenv("DISPLAY"))
902     {
903       debug("X11: Do not have X display\n");
904       return;
905     }
906   if (!(x11_dpy = XOpenDisplay(NULL)))
907     die("Cannot open X display, although the DISPLAY variable is set");
908
909   if (osd_care)
910     {
911       osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
912       if (!osd_pty)
913         die("Cannot intern OSD_QUEUE atom");
914
915       // If OSD options contain no message, add one
916       int seen_msg = 0;
917       CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
918         if (!n->key[0])
919           seen_msg = 1;
920       if (!seen_msg)
921         {
922           add_osd_opt("=%40f");
923           add_osd_opt("=%40s");
924           add_osd_opt("=");
925           add_osd_opt("=(and %m more)");
926         }
927     }
928
929   leds_have = ~0U;
930   debug("X11: Initialized\n");
931 }
932
933 static void
934 sync_leds(void)
935 {
936   if (leds_want == leds_have)
937     return;
938
939   debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
940   for (int i=1; i<10; i++)
941     if (leds_care & (leds_have ^ leds_want) & (1 << i))
942       {
943         XKeyboardControl cc;
944         cc.led = i;
945         cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
946         XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
947       }
948   XFlush(x11_dpy);
949   leds_have = leds_want;
950 }
951
952 static void
953 rethink_leds(void)
954 {
955   if (!leds_care || !x11_dpy)
956     return;
957
958   leds_want = 0;
959   CLIST_FOR_EACH(struct mbox *, b, mboxes)
960     if (b->o.led > 0 && b->new)
961       leds_want |= (1 << b->o.led);
962   sync_leds();
963 }
964
965 struct osd_params {
966   struct mbox *mbox;
967   int total_new;
968 };
969
970 static int
971 format_osd_string(char *dest, char *src, struct osd_params *par)
972 {
973   char *stop = dest + OSD_MSG_SIZE - 1;
974   char numbuf[16];
975
976   while (*src && dest < stop)
977     {
978       if (*src == '%')
979         {
980           src++;
981           int size = 0;
982           while (*src >= '0' && *src <= '9')
983             size = 10*size + *src++ - '0';
984           if (!size || size > stop-dest)
985             size = dest-stop;
986
987           int spec = *src++;
988           if (!spec)
989             break;
990
991           char *arg = numbuf;
992           switch (spec)
993             {
994             case 'f':
995               arg = par->mbox->sender_snippet;
996               break;
997             case 's':
998               arg = par->mbox->subject_snippet;
999               break;
1000             case 'n':
1001               snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
1002               break;
1003             case 'm':
1004               if (par->total_new < 2)
1005                 return 0;
1006               snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
1007               break;
1008             case '%':
1009               arg = "%";
1010               break;
1011             default:
1012               arg = "???";
1013               break;
1014             }
1015
1016           while (*arg && size)
1017             {
1018               *dest++ = *arg++;
1019               size--;
1020             }
1021         }
1022       else
1023         *dest++ = *src++;
1024     }
1025   *dest = 0;
1026   return 1;
1027 }
1028
1029 static void
1030 format_osd(char *msg, struct osd_params *par)
1031 {
1032   if (!par->mbox)
1033     {
1034       msg[0] = 0;
1035       return;
1036     }
1037
1038   unsigned pos = 0;
1039   unsigned have_text = 0;
1040   CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
1041     {
1042       char buf[OSD_MSG_SIZE];
1043       if (!format_osd_string(buf, n->val, par))
1044         continue;
1045       if (!n->key[0] && buf[0])
1046         have_text = 1;
1047       pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1048       if (pos > OSD_MSG_SIZE-1)
1049         {
1050           pos = sprintf(msg, "OSD message too long!\n");
1051           break;
1052         }
1053     }
1054   if (have_text)
1055     msg[pos++] = '\n';
1056   else
1057     pos = 0;
1058   msg[pos] = 0;
1059 }
1060
1061 static void
1062 debug_osd_msg(char *msg)
1063 {
1064   if (!debug_mode)
1065     return;
1066   fprintf(stderr, "OSD: <");
1067   while (*msg)
1068     {
1069       fputc((*msg != '\n' ? *msg : '|'), stderr);
1070       msg++;
1071     }
1072   fprintf(stderr, ">\n");
1073 }
1074
1075 static void
1076 rethink_osd(int notify)
1077 {
1078   if (!osd_care || !x11_dpy || !allow_osd)
1079     {
1080       osd_last_msg[0] = 0;
1081       return;
1082     }
1083
1084   struct osd_params p = { .mbox = NULL, .total_new = 0 };
1085   CLIST_FOR_EACH(struct mbox *, b, mboxes)
1086     if (b->o.osd > 0)
1087       {
1088         p.total_new += b->new;
1089         if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1090           p.mbox = b;
1091       }
1092
1093   char new_msg[OSD_MSG_SIZE];
1094   format_osd(new_msg, &p);
1095   debug_osd_msg(new_msg);
1096   if (strcmp(new_msg, osd_last_msg))
1097     {
1098       strcpy(osd_last_msg, new_msg);
1099       if (notify && new_msg[0])
1100         {
1101           debug("OSD: Sending to daemon\n");
1102           XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
1103           XFlush(x11_dpy);
1104         }
1105       else
1106         debug("OSD: No changes\n");
1107       osd_last_time = time(NULL);
1108     }
1109 }
1110
1111 static void
1112 x11_cleanup(void)
1113 {
1114   if (!x11_dpy)
1115     return;
1116
1117   leds_want = 0;
1118   sync_leds();
1119 }
1120
1121 #else
1122
1123 static void x11_init(void) { }
1124 static void rethink_leds(void) { }
1125 static void rethink_osd(int notify UNUSED) { }
1126 static void x11_cleanup(void) { }
1127
1128 #endif
1129
1130 static int cursor_at, cursor_max;
1131
1132 static unsigned is_active, is_pos;      // incremental search
1133 static char is_buf[64];
1134
1135 enum {
1136   M_IDLE,
1137   M_SCAN,
1138   M_NEW,
1139   M_FLAG,
1140   M_BAD,
1141   M_INCSEARCH,
1142   M_MAX
1143 };
1144 static int attrs[3][2][M_MAX];          // active (2=incsearch), hilite, status
1145
1146 static void
1147 redraw_line(int i)
1148 {
1149   move(i, 0);
1150   if (i < cursor_max)
1151     {
1152       struct mbox *b = mbox_array[i];
1153       int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1154       int hi = b->o.highlight;
1155       unsigned namepos = 0;
1156       unsigned namelen = strlen(b->name);
1157       int valid = 3600;
1158
1159       attrset(attrs[cc][hi][M_IDLE]);
1160       if (b->o.hotkey)
1161         printw("%c ", b->o.hotkey);
1162       else if (cc)
1163         printw("> ");
1164       else
1165         printw("  ");
1166       if (cc == 2)
1167         {
1168           attrset(attrs[cc][hi][M_INCSEARCH]);
1169           for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1170             addch(is_buf[namepos]);
1171         }
1172       if (b->new)
1173         attrset(attrs[cc][hi][M_NEW]);
1174       else if (b->flagged && b->o.show_flagged)
1175         attrset(attrs[cc][hi][M_FLAG]);
1176       else
1177         attrset(attrs[cc][hi][M_IDLE]);
1178       while (namepos < namelen)
1179         addch(b->name[namepos++]);
1180       while (namepos++ < 20)
1181         addch(' ');
1182       if (b->scanning < 0)
1183         ;
1184       else if (b->scanning)
1185         {
1186           attrset(attrs[cc][hi][M_SCAN]);
1187           printw("[SCANNING]");
1188         }
1189       else if (b->total < 0)
1190         {
1191           attrset(attrs[cc][hi][M_BAD]);
1192           printw("BROKEN");
1193         }
1194       else
1195         {
1196           attrset(attrs[cc][hi][M_IDLE]);
1197           printw("%6d ", b->total);
1198           int snip = 0;
1199           if (b->new)
1200             {
1201               attrset(attrs[cc][hi][M_NEW]);
1202               printw("%6d  ", b->new);
1203               attrset(attrs[cc][hi][M_IDLE]);
1204               int age = (last_scan_time - b->best_time);
1205               if (age < 0)
1206                 age = 0;
1207               if (age < 3600)
1208                 {
1209                   printw("%2d min  ", age/60);
1210                   valid = 60;
1211                 }
1212               else if (age < 86400)
1213                 printw("%2d hr%c  ", age/3600, (age >= 7200 ? 's' : ' '));
1214               else
1215                 printw("        ");
1216               snip = 1;
1217             }
1218           else if (b->flagged && b->o.show_flagged)
1219             {
1220               attrset(attrs[cc][hi][M_FLAG]);
1221               printw("%6d  ", b->flagged);
1222               attrset(attrs[cc][hi][M_IDLE]);
1223               printw("        ");
1224               attrset(attrs[cc][0][M_FLAG]);    /* We avoid the highlight intentionally */
1225               snip = 1;
1226             }
1227           if (snip && b->o.snippets)
1228             {
1229               int xx, yy;
1230               getyx(stdscr, yy, xx);
1231               int remains = COLS-1-xx;
1232               (void) yy;
1233
1234               char snip[256];
1235               build_snippet(snip, snip + sizeof(snip) - 1, b);
1236
1237               if (snip[0] && remains > 2)
1238                 {
1239 #ifdef CONFIG_WIDE_CURSES
1240                   size_t len = strlen(snip)+1;
1241                   wchar_t snip2[len];
1242                   mbstowcs(snip2, snip, len);
1243                   addnwstr(snip2, remains);
1244 #else
1245                   printw("%-.*s", remains, snip);
1246 #endif
1247                 }
1248             }
1249         }
1250       b->display_valid_until = last_scan_time + valid;
1251     }
1252   attrset(attrs[0][0][M_IDLE]);
1253   clrtoeol();
1254 }
1255
1256 static void
1257 redraw_all(void)
1258 {
1259   cursor_max = num_mboxes;
1260   if (cursor_max > LINES-1)
1261     cursor_max = LINES-1;
1262   if (cursor_at >= cursor_max)
1263     cursor_at = cursor_max - 1;
1264   if (cursor_at < 0)
1265     cursor_at = 0;
1266
1267   for (int i=0; i<cursor_max; i++)
1268     redraw_line(i);
1269   move(cursor_max, 0);
1270   if (!cursor_max)
1271     {
1272       printw("(no mailboxes found)");
1273       clrtoeol();
1274       move(1, 0);
1275     }
1276   clrtobot();
1277 }
1278
1279 static void
1280 rethink_display(int notify)
1281 {
1282   int i = 0;
1283   int changed = 0;
1284   int beeeep = 0;
1285   CLIST_FOR_EACH(struct mbox *, b, mboxes)
1286     if (mbox_visible_p(b))
1287       {
1288         b->index = i;
1289         if (i >= num_mboxes || mbox_array[i] != b)
1290           {
1291             changed = 1;
1292             if (i >= mbox_array_size)
1293               {
1294                 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1295                 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1296               }
1297             mbox_array[i] = b;
1298           }
1299         if (b->o.beep && b->new > b->last_beep_new)
1300           beeeep = 1;
1301         b->last_beep_new = b->new;
1302         i++;
1303       }
1304   if (i != num_mboxes)
1305     changed = 1;
1306   num_mboxes = i;
1307
1308   if (changed)
1309     {
1310       redraw_all();
1311       refresh();
1312     }
1313   rethink_leds();
1314   rethink_osd(notify);
1315   if (beeeep && allow_bells && notify)
1316     beep();
1317 }
1318
1319 static void
1320 term_init(void)
1321 {
1322   initscr();
1323   cbreak();
1324   noecho();
1325   nonl();
1326   intrflush(stdscr, FALSE);
1327   keypad(stdscr, TRUE);
1328   curs_set(0);
1329
1330   static const int attrs_mono[2][M_MAX] = {
1331         [0] = { [M_IDLE] = 0,
1332                 [M_SCAN] = A_BOLD,
1333                 [M_NEW] = A_BOLD,
1334                 [M_FLAG] = 0,
1335                 [M_BAD] = A_DIM,
1336                 [M_INCSEARCH] = A_REVERSE },
1337         [1] = { [M_IDLE] = 0,
1338                 [M_SCAN] = A_BOLD,
1339                 [M_NEW] = A_REVERSE | A_BOLD,
1340                 [M_FLAG] = A_REVERSE,
1341                 [M_BAD] = A_DIM,
1342                 [M_INCSEARCH] = A_REVERSE },
1343   };
1344   for (int i=0; i<2; i++)
1345     for (int j=0; j<M_MAX; j++)
1346       {
1347         attrs[0][i][j] = attrs_mono[i][j];
1348         attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1349         attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1350       }
1351
1352   if (has_colors())
1353     {
1354       start_color();
1355       if (COLOR_PAIRS >= 12)
1356         {
1357           init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1358           init_pair(2, COLOR_RED, COLOR_BLACK);
1359           init_pair(3, COLOR_WHITE, COLOR_BLUE);
1360           init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1361           init_pair(5, COLOR_RED, COLOR_BLUE);
1362           init_pair(6, COLOR_GREEN, COLOR_BLACK);
1363           init_pair(7, COLOR_GREEN, COLOR_BLUE);
1364           init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1365           init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1366           init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1367           init_pair(11, COLOR_RED, COLOR_MAGENTA);
1368           init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1369           static const int attrs_color[3][2][M_MAX] = {
1370              [0][0] = { [M_IDLE] = 0,
1371                         [M_SCAN] = COLOR_PAIR(1),
1372                         [M_NEW] = COLOR_PAIR(1),
1373                         [M_FLAG] = COLOR_PAIR(6),
1374                         [M_BAD] = COLOR_PAIR(2) },
1375              [0][1] = { [M_IDLE] = A_BOLD,
1376                         [M_SCAN] = COLOR_PAIR(1),
1377                         [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1378                         [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1379                         [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1380              [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1381                         [M_SCAN] = COLOR_PAIR(4),
1382                         [M_NEW] = COLOR_PAIR(4),
1383                         [M_FLAG] = COLOR_PAIR(7),
1384                         [M_BAD] = COLOR_PAIR(5) },
1385              [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1386                         [M_SCAN] = COLOR_PAIR(4),
1387                         [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1388                         [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1389                         [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1390              [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1391                         [M_SCAN] = COLOR_PAIR(9),
1392                         [M_NEW] = COLOR_PAIR(9),
1393                         [M_FLAG] = COLOR_PAIR(10),
1394                         [M_BAD] = COLOR_PAIR(11),
1395                         [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1396              [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1397                         [M_SCAN] = COLOR_PAIR(9),
1398                         [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1399                         [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1400                         [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1401                         [M_INCSEARCH] = COLOR_PAIR(12) },
1402           };
1403           memcpy(attrs, attrs_color, sizeof(attrs));
1404         }
1405     }
1406 }
1407
1408 static void
1409 term_cleanup(void)
1410 {
1411   endwin();
1412 }
1413
1414 static void
1415 print_status(char *status)
1416 {
1417   move(LINES-1, 0);
1418   if (status)
1419     printw("%s", status);
1420   clrtoeol();
1421   refresh();
1422 }
1423
1424 static void
1425 scan_and_redraw(int notify)
1426 {
1427   print_status("Busy...");
1428   scan(notify);
1429   print_status(NULL);
1430 }
1431
1432 static void
1433 move_cursor(int i)
1434 {
1435   if (i >= 0 && i < cursor_max && i != cursor_at)
1436     {
1437       int old = cursor_at;
1438       cursor_at = i;
1439       redraw_line(old);
1440       redraw_line(i);
1441     }
1442 }
1443
1444 static void
1445 next_active(int since, int step)
1446 {
1447   if (!cursor_max)
1448     return;
1449   since = (since+cursor_max) % cursor_max;
1450   step = (step+cursor_max) % cursor_max;
1451   int besti = -1;
1452   int bestp = -1;
1453   int i = since;
1454   do
1455     {
1456       struct mbox *b = mbox_array[i];
1457       if (simple_tab)
1458         {
1459           if (b->new)
1460             {
1461               besti = i;
1462               break;
1463             }
1464         }
1465       else
1466         {
1467           if (b->new && b->o.priority > bestp)
1468             {
1469               besti = i;
1470               bestp = b->o.priority;
1471             }
1472         }
1473       i = (i+step) % cursor_max;
1474     }
1475   while (i != since);
1476   if (besti >= 0)
1477     move_cursor(besti);
1478 }
1479
1480 static void
1481 mbox_run(struct mbox *b)
1482 {
1483   char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1484   sprintf(cmd, run_cmd, b->path);
1485   term_cleanup();
1486   system(cmd);
1487   term_init();
1488   redraw_all();
1489   refresh();
1490   b->force_refresh = 1;
1491   scan_and_redraw(0);
1492 }
1493
1494 static void
1495 enter_incsearch(void)
1496 {
1497   print_status("Incremental search...");
1498   is_active = 1;
1499   is_pos = 0;
1500   redraw_line(cursor_at);
1501 }
1502
1503 static int
1504 handle_incsearch(int ch)
1505 {
1506   if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1507     --is_pos;
1508   else if (ch >= ' ' && ch <= '~')
1509     {
1510       if (is_pos < sizeof(is_buf) - 1)
1511         is_buf[is_pos++] = ch;
1512     }
1513   else
1514     {
1515       print_status(NULL);
1516       is_active = 0;
1517       is_pos = 0;
1518       redraw_line(cursor_at);
1519       return 0;
1520     }
1521
1522   is_buf[is_pos] = 0;
1523   for (int i=0; i<cursor_max; i++)
1524     {
1525       struct mbox *b = mbox_array[i];
1526       if (!strncmp(b->name, is_buf, is_pos))
1527         {
1528           if (i != cursor_at)
1529             {
1530               move_cursor(i);
1531               return 1;
1532             }
1533           break;
1534         }
1535     }
1536
1537   redraw_line(cursor_at);
1538   return 1;
1539 }
1540
1541 #define STR2(c) #c
1542 #define STR(c) STR2(c)
1543
1544 static void NONRET
1545 usage(void)
1546 {
1547   fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1548 \n\
1549 Options:\n\
1550 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1551 -d\t\t\tLog debug messages to stderr\n\
1552 -i\t\t\tInclude user's INBOX (/var/mail/<user>=INBOX)\n\
1553 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1554 -o <pattern>=<opts>\tSet mailbox options\n\
1555 -o <opts>\t\tSet default options for all mailboxes\n\
1556 -p <pri>\t\tSet minimum priority to show\n\
1557 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1558 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1559 \n\
1560 Mailbox options (set with `-o', use upper case to negate):\n\
1561 0-9\t\t\tSet mailbox priority (0=default)\n\
1562 b\t\t\tBeep when a message arrives\n\
1563 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1564 e\t\t\tHide from display if empty\n\
1565 f\t\t\tShow flagged messages if there are no new ones\n\
1566 h\t\t\tHide from display\n\
1567 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1568 m\t\t\tShow mailbox name of the sender\n\
1569 o\t\t\tCount old, but unread messages as new\n\
1570 p\t\t\tShow personal info (full name) of the sender\n\
1571 r<int>\t\t\tSort order (default=1000)\n\
1572 s\t\t\tShow message snippets\n\
1573 t\t\t\tHighlight the entry\n\
1574 !<key>\t\t\tSet hot key\n\
1575 \n\
1576 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1577 It can be freely distributed and used according to the GNU GPL v2.\n\
1578 ");
1579   exit(1);
1580 }
1581
1582 static void
1583 parse_options(char *c)
1584 {
1585   struct options *o;
1586   char *sep;
1587   if (sep = strchr(c, '='))
1588     {
1589       struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1590       memcpy(n->pattern, c, sep-c);
1591       n->pattern[sep-c] = 0;
1592       clist_add_tail(&options, &n->n);
1593       o = &n->o;
1594       init_options(o);
1595       c = sep+1;
1596     }
1597   else
1598     o = &global_options;
1599
1600   int x;
1601   while (x = *c++)
1602     if (x >= '0' && x <= '9')
1603       o->priority = x - '0';
1604     else if (x == '!' && *c)
1605       o->hotkey = *c++;
1606     else if (x == 'l' && *c >= '1' && *c <= '9')
1607       o->led = *c++ - '0';
1608     else if (x == 'r' && *c >= '0' && *c <= '9')
1609       {
1610         o->sort_order = 0;
1611         while (*c >= '0' && *c <= '9')
1612           o->sort_order = 10*o->sort_order + *c++ - '0';
1613       }
1614     else
1615       {
1616         int value = !!islower(x);
1617         switch (tolower(x))
1618           {
1619           case 'b':
1620             o->beep = value;
1621             break;
1622           case 'd':
1623             o->osd = value;
1624             break;
1625           case 'e':
1626             o->hide_if_empty = value;
1627             break;
1628           case 'f':
1629             o->show_flagged = value;
1630             break;
1631           case 'h':
1632             o->hide = value;
1633             break;
1634           case 'm':
1635             o->sender_mbox = value;
1636             break;
1637           case 'o':
1638             o->unread_is_new = value;
1639             break;
1640           case 'p':
1641             o->sender_personal = value;
1642             break;
1643           case 's':
1644             o->snippets = value;
1645             break;
1646           case 't':
1647             o->highlight = value;
1648             break;
1649           default:
1650             fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1651             usage();
1652           }
1653       }
1654 }
1655
1656 int
1657 main(int argc, char **argv)
1658 {
1659   clist_init(&mboxes);
1660   clist_init(&options);
1661   clist_init(&patterns);
1662   clist_init(&osd_opts);
1663
1664   int c;
1665   while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1666     switch (c)
1667       {
1668       case 'c':
1669         check_interval = atol(optarg);
1670         if (check_interval <= 0)
1671           usage();
1672         break;
1673       case 'd':
1674         debug_mode++;
1675         break;
1676       case 'i':
1677         add_inbox();
1678         break;
1679       case 'm':
1680         run_cmd = optarg;
1681         break;
1682       case 'o':
1683         parse_options(optarg);
1684         break;
1685       case 'p':
1686         minimum_priority = atol(optarg);
1687         break;
1688       case 's':
1689         add_osd_opt(optarg);
1690         break;
1691       case 't':
1692         simple_tab = 1;
1693         break;
1694       default:
1695         usage();
1696       }
1697   while (optind < argc)
1698     add_pattern(argv[optind++]);
1699
1700   charset_init();
1701   term_init();
1702   x11_init();
1703   scan_and_redraw(0);
1704   next_active(0, 1);
1705
1706   int should_exit = 0;
1707 restart:
1708   while (!should_exit)
1709     {
1710       time_t now = time(NULL);
1711       int remains = last_scan_time + check_interval - now;
1712       if (force_refresh)
1713         scan_and_redraw(0);
1714       if (remains <= 0)
1715         scan_and_redraw(1);
1716       else
1717         {
1718           remains *= 10;
1719           halfdelay((remains > 255) ? 255 : remains);
1720           int ch = getch();
1721           if (ch < 0)
1722             continue;
1723           if (is_active && handle_incsearch(ch))
1724             {
1725               refresh();
1726               continue;
1727             }
1728           for (int i=0; i<num_mboxes; i++)
1729             if (ch == mbox_array[i]->o.hotkey)
1730               {
1731                 if (i < cursor_max)
1732                   cursor_at = i;
1733                 mbox_run(mbox_array[i]);
1734                 goto restart;
1735               }
1736           switch (ch)
1737             {
1738             case 'q':
1739               should_exit = 1;
1740               break;
1741             case 'j':
1742             case KEY_DOWN:
1743               move_cursor(cursor_at+1);
1744               break;
1745             case 'k':
1746             case KEY_UP:
1747               move_cursor(cursor_at-1);
1748               break;
1749             case '^':
1750             case KEY_HOME:
1751             case KEY_PPAGE:
1752               move_cursor(0);
1753               break;
1754             case '$':
1755             case KEY_END:
1756             case KEY_NPAGE:
1757               move_cursor(cursor_max-1);
1758               break;
1759             case '\t':
1760               next_active(cursor_at+1, 1);
1761               break;
1762             case '`':
1763               next_active(cursor_at-1, -1);
1764               break;
1765             case '\r':
1766             case '\n':
1767               if (cursor_at < cursor_max)
1768                 mbox_run(mbox_array[cursor_at]);
1769               break;
1770             case 'l' & 0x1f:
1771               clearok(stdscr, TRUE);
1772               redraw_all();
1773               refresh();
1774               break;
1775             case 'r' & 0x1f:
1776               force_refresh = 1;
1777               break;
1778             case 'b':
1779               allow_bells = 1;
1780               print_status("Bells and whistles are now enabled. Toot!");
1781               break;
1782             case 'B':
1783               allow_bells = 0;
1784               print_status("Bells and whistles are now disabled. Pssst!");
1785               break;
1786             case 'd':
1787               allow_osd = 1;
1788               print_status("On-screen display is now enabled.");
1789               break;
1790             case 'D':
1791               allow_osd = 0;
1792               print_status("On-screen display is now disabled. Watch your step.");
1793               break;
1794             case '/':
1795               enter_incsearch();
1796               break;
1797             default:
1798               if (ch >= '0' && ch <= '9')
1799                 {
1800                   minimum_priority = ch - '0';
1801                   scan_and_redraw(0);
1802                 }
1803               else
1804                 debug("Pressed unknown key %d\n", ch);
1805             }
1806           refresh();
1807         }
1808     }
1809
1810   x11_cleanup();
1811   term_cleanup();
1812   return 0;
1813 }