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