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