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