]> mj.ucw.cz Git - checkmail.git/blob - cm.c
Released 1.9
[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               // Another comma can separate extended flags (Dovecot extension)
684               for (int i=3; colon[i] && colon[i] != ','; i++)
685                 switch (colon[i])
686                   {
687                   case 'S':
688                     seen = 1;
689                     break;
690                   case 'F':
691                     flagged = 1;
692                     break;
693                   }
694             }
695
696           int is_new = new;
697           if (b->o.unread_is_new && !seen)
698             is_new = 1;
699
700           b->total++;
701           if (is_new)
702             b->new++;
703           if (flagged)
704             b->flagged++;
705           if (debug_mode > 1)
706             debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
707
708           if (is_new || flagged)
709             {
710               // Need to pick the best message to display
711               struct stat st;
712               if (stat(path, &st) < 0)
713                 debug("[cannot stat %s: %m] ", path);
714               else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
715                 {
716                   b->best_is_new = is_new;
717                   b->best_time = st.st_mtime;
718                   strcpy(b->mdir_best, path + dir_len + 1);
719                 }
720             }
721         }
722       closedir(d);
723     }
724
725   if (b->best_time && b->o.snippets)
726     {
727       debug("best <%s> ", b->mdir_best);
728       mdir_get_snippet(b);
729     }
730 }
731
732 static void
733 scan(int notify)
734 {
735   debug("Searching for mailboxes (notify=%d)...\n", notify);
736   last_scan_time = time(NULL);
737   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
738     {
739       debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
740       glob_t g;
741       int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
742       if (err && err != GLOB_NOMATCH)
743         die("Failed to glob %s: %m", p->pattern);
744       for (uns i=0; i<g.gl_pathc; i++)
745         {
746           char *name = g.gl_pathv[i];
747           struct mbox *b = find_mbox(&mboxes, name);
748           if (!b)
749             {
750               b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
751               debug("Discovered mailbox %s (%s)\n", b->name, b->path);
752               setup_options(b);
753               b->scanning = -1;
754             }
755           b->seen = 1;
756         }
757       globfree(&g);
758     }
759
760   struct mbox *tmp;
761   CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
762     {
763       if (b->seen)
764         b->seen = 0;
765       else
766         {
767           debug("Lost mailbox %s\n", b->name);
768           del_mbox(b);
769         }
770     }
771
772   rethink_display(0);
773
774   debug("Scanning mailboxes...\n");
775   CLIST_FOR_EACH(struct mbox *, b, mboxes)
776     {
777       struct stat st;
778       debug("%s: ", b->name);
779       if (!mbox_active_p(b))
780         {
781           debug("inactive\n");
782           continue;
783         }
784       if (force_refresh)
785         b->force_refresh = 1;
786       if (stat(b->path, &st) < 0)
787         {
788           b->total = b->new = b->flagged = -1;
789           debug("%m\n");
790           continue;
791         }
792
793       time_t current_mtime;
794       int current_size;
795       int is_mdir;
796       if (S_ISREG(st.st_mode))
797         {
798           // Regular mailbox
799           is_mdir = 0;
800           current_mtime = st.st_mtime;
801           current_size = st.st_size;
802           debug("[mbox] ");
803         }
804       else if (S_ISDIR(st.st_mode))
805         {
806           // Maildir
807           is_mdir = 1;
808           current_mtime = mdir_mtime(b);
809           current_size = 0;
810           debug("[mdir] ");
811         }
812       else
813         {
814           debug("neither file nor directory\n");
815           continue;
816         }
817
818       if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
819         {
820           b->scanning = 1;
821           redraw_line(b->index);
822           refresh();
823
824           if (is_mdir)
825             scan_mdir(b);
826           else
827             scan_mbox(b, &st);
828           b->last_time = current_mtime;
829           b->last_size = current_size;
830           debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
831
832           b->scanning = 0;
833           redraw_line(b->index);
834           refresh();
835           b->force_refresh = 0;
836         }
837       else if (b->display_valid_until <= last_scan_time)
838         {
839           debug("not changed, but needs redraw\n");
840           redraw_line(b->index);
841         }
842       else
843         debug("not changed\n");
844     }
845   force_refresh = 0;
846
847   debug("Scan finished\n");
848   last_scan_time = time(NULL);
849   rethink_display(notify);
850 }
851
852 #ifdef CONFIG_X11
853
854 #include <X11/Xlib.h>
855 #include <X11/Xatom.h>
856
857 static Display *x11_dpy;
858 static unsigned leds_care, leds_have, leds_want;
859
860 static Atom osd_pty;
861 static unsigned osd_care;
862 #define OSD_MSG_SIZE 1024
863 static char osd_last_msg[OSD_MSG_SIZE];
864 static time_t osd_last_time;
865
866 static void
867 x11_init(void)
868 {
869   leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
870   osd_care = (global_options.osd >= 0);
871   CLIST_FOR_EACH(struct option_node *, o, options)
872     {
873       if (o->o.led > 0)
874         leds_care |= (1 << o->o.led);
875       if (o->o.osd > 0)
876         osd_care = 1;
877     }
878
879   if (!leds_care && !osd_care)
880     {
881       debug("X11: No mailbox wants LEDs or OSD\n");
882       return;
883     }
884   if (!getenv("DISPLAY"))
885     {
886       debug("X11: Do not have X display\n");
887       return;
888     }
889   if (!(x11_dpy = XOpenDisplay(NULL)))
890     die("Cannot open X display, although the DISPLAY variable is set");
891
892   if (osd_care)
893     {
894       osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
895       if (!osd_pty)
896         die("Cannot intern OSD_QUEUE atom");
897
898       // If OSD options contain no message, add one
899       int seen_msg = 0;
900       CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
901         if (!n->key[0])
902           seen_msg = 1;
903       if (!seen_msg)
904         {
905           add_osd_opt("=%40f");
906           add_osd_opt("=%40s");
907           add_osd_opt("=");
908           add_osd_opt("=(and %m more)");
909         }
910     }
911
912   leds_have = ~0U;
913   debug("X11: Initialized\n");
914 }
915
916 static void
917 sync_leds(void)
918 {
919   if (leds_want == leds_have)
920     return;
921
922   debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
923   for (int i=1; i<10; i++)
924     if (leds_care & (leds_have ^ leds_want) & (1 << i))
925       {
926         XKeyboardControl cc;
927         cc.led = i;
928         cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
929         XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
930       }
931   XFlush(x11_dpy);
932   leds_have = leds_want;
933 }
934
935 static void
936 rethink_leds(void)
937 {
938   if (!leds_care || !x11_dpy)
939     return;
940
941   leds_want = 0;
942   CLIST_FOR_EACH(struct mbox *, b, mboxes)
943     if (b->o.led > 0 && b->new)
944       leds_want |= (1 << b->o.led);
945   sync_leds();
946 }
947
948 struct osd_params {
949   struct mbox *mbox;
950   int total_new;
951 };
952
953 static int
954 format_osd_string(char *dest, char *src, struct osd_params *par)
955 {
956   char *stop = dest + OSD_MSG_SIZE - 1;
957   char numbuf[16];
958
959   while (*src && dest < stop)
960     {
961       if (*src == '%')
962         {
963           src++;
964           int size = 0;
965           while (*src >= '0' && *src <= '9')
966             size = 10*size + *src++ - '0';
967           if (!size || size > stop-dest)
968             size = dest-stop;
969
970           int spec = *src++;
971           if (!spec)
972             break;
973
974           char *arg = numbuf;
975           switch (spec)
976             {
977             case 'f':
978               arg = par->mbox->sender_snippet;
979               break;
980             case 's':
981               arg = par->mbox->subject_snippet;
982               break;
983             case 'n':
984               snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
985               break;
986             case 'm':
987               if (par->total_new < 2)
988                 return 0;
989               snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
990               break;
991             case '%':
992               arg = "%";
993               break;
994             default:
995               arg = "???";
996               break;
997             }
998
999           while (*arg && size)
1000             {
1001               *dest++ = *arg++;
1002               size--;
1003             }
1004         }
1005       else
1006         *dest++ = *src++;
1007     }
1008   *dest = 0;
1009   return 1;
1010 }
1011
1012 static void
1013 format_osd(char *msg, struct osd_params *par)
1014 {
1015   if (!par->mbox)
1016     {
1017       msg[0] = 0;
1018       return;
1019     }
1020
1021   unsigned pos = 0;
1022   unsigned have_text = 0;
1023   CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
1024     {
1025       char buf[OSD_MSG_SIZE];
1026       if (!format_osd_string(buf, n->val, par))
1027         continue;
1028       if (!n->key[0] && buf[0])
1029         have_text = 1;
1030       pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1031       if (pos > OSD_MSG_SIZE-1)
1032         {
1033           pos = sprintf(msg, "OSD message too long!\n");
1034           break;
1035         }
1036     }
1037   if (have_text)
1038     msg[pos++] = '\n';
1039   else
1040     pos = 0;
1041   msg[pos] = 0;
1042 }
1043
1044 static void
1045 debug_osd_msg(char *msg)
1046 {
1047   if (!debug_mode)
1048     return;
1049   fprintf(stderr, "OSD: <");
1050   while (*msg)
1051     {
1052       fputc((*msg != '\n' ? *msg : '|'), stderr);
1053       msg++;
1054     }
1055   fprintf(stderr, ">\n");
1056 }
1057
1058 static void
1059 rethink_osd(int notify)
1060 {
1061   if (!osd_care || !x11_dpy || !allow_osd)
1062     {
1063       osd_last_msg[0] = 0;
1064       return;
1065     }
1066
1067   struct osd_params p = { .mbox = NULL, .total_new = 0 };
1068   CLIST_FOR_EACH(struct mbox *, b, mboxes)
1069     if (b->o.osd > 0)
1070       {
1071         p.total_new += b->new;
1072         if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1073           p.mbox = b;
1074       }
1075
1076   char new_msg[OSD_MSG_SIZE];
1077   format_osd(new_msg, &p);
1078   debug_osd_msg(new_msg);
1079   if (strcmp(new_msg, osd_last_msg))
1080     {
1081       strcpy(osd_last_msg, new_msg);
1082       if (notify && new_msg[0])
1083         {
1084           debug("OSD: Sending to daemon\n");
1085           XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
1086           XFlush(x11_dpy);
1087         }
1088       else
1089         debug("OSD: No changes\n");
1090       osd_last_time = time(NULL);
1091     }
1092 }
1093
1094 static void
1095 x11_cleanup(void)
1096 {
1097   if (!x11_dpy)
1098     return;
1099
1100   leds_want = 0;
1101   sync_leds();
1102 }
1103
1104 #else
1105
1106 static void x11_init(void) { }
1107 static void rethink_leds(void) { }
1108 static void rethink_osd(int notify UNUSED) { }
1109 static void x11_cleanup(void) { }
1110
1111 #endif
1112
1113 static int cursor_at, cursor_max;
1114
1115 static unsigned is_active, is_pos;      // incremental search
1116 static char is_buf[64];
1117
1118 enum {
1119   M_IDLE,
1120   M_SCAN,
1121   M_NEW,
1122   M_FLAG,
1123   M_BAD,
1124   M_INCSEARCH,
1125   M_MAX
1126 };
1127 static int attrs[3][2][M_MAX];          // active (2=incsearch), hilite, status
1128
1129 static void
1130 redraw_line(int i)
1131 {
1132   move(i, 0);
1133   if (i < cursor_max)
1134     {
1135       struct mbox *b = mbox_array[i];
1136       int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1137       int hi = b->o.highlight;
1138       unsigned namepos = 0;
1139       unsigned namelen = strlen(b->name);
1140       int valid = 3600;
1141
1142       attrset(attrs[cc][hi][M_IDLE]);
1143       if (b->o.hotkey)
1144         printw("%c ", b->o.hotkey);
1145       else if (cc)
1146         printw("> ");
1147       else
1148         printw("  ");
1149       if (cc == 2)
1150         {
1151           attrset(attrs[cc][hi][M_INCSEARCH]);
1152           for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1153             addch(is_buf[namepos]);
1154         }
1155       if (b->new)
1156         attrset(attrs[cc][hi][M_NEW]);
1157       else if (b->flagged && b->o.show_flagged)
1158         attrset(attrs[cc][hi][M_FLAG]);
1159       else
1160         attrset(attrs[cc][hi][M_IDLE]);
1161       while (namepos < namelen)
1162         addch(b->name[namepos++]);
1163       while (namepos++ < 20)
1164         addch(' ');
1165       if (b->scanning < 0)
1166         ;
1167       else if (b->scanning)
1168         {
1169           attrset(attrs[cc][hi][M_SCAN]);
1170           printw("[SCANNING]");
1171         }
1172       else if (b->total < 0)
1173         {
1174           attrset(attrs[cc][hi][M_BAD]);
1175           printw("BROKEN");
1176         }
1177       else
1178         {
1179           attrset(attrs[cc][hi][M_IDLE]);
1180           printw("%6d ", b->total);
1181           int snip = 0;
1182           if (b->new)
1183             {
1184               attrset(attrs[cc][hi][M_NEW]);
1185               printw("%6d  ", b->new);
1186               attrset(attrs[cc][hi][M_IDLE]);
1187               int age = (last_scan_time - b->best_time);
1188               if (age < 0)
1189                 age = 0;
1190               if (age < 3600)
1191                 {
1192                   printw("%2d min  ", age/60);
1193                   valid = 60;
1194                 }
1195               else if (age < 86400)
1196                 printw("%2d hr%c  ", age/3600, (age >= 7200 ? 's' : ' '));
1197               else
1198                 printw("        ");
1199               snip = 1;
1200             }
1201           else if (b->flagged && b->o.show_flagged)
1202             {
1203               attrset(attrs[cc][hi][M_FLAG]);
1204               printw("%6d  ", b->flagged);
1205               attrset(attrs[cc][hi][M_IDLE]);
1206               printw("        ");
1207               attrset(attrs[cc][0][M_FLAG]);    /* We avoid the highlight intentionally */
1208               snip = 1;
1209             }
1210           if (snip && b->o.snippets)
1211             {
1212               int xx, yy;
1213               getyx(stdscr, yy, xx);
1214               int remains = COLS-1-xx;
1215               (void) yy;
1216
1217               char snip[256];
1218               build_snippet(snip, snip + sizeof(snip) - 1, b);
1219
1220               if (snip[0] && remains > 2)
1221                 {
1222 #ifdef CONFIG_WIDE_CURSES
1223                   size_t len = strlen(snip)+1;
1224                   wchar_t snip2[len];
1225                   mbstowcs(snip2, snip, len);
1226                   addnwstr(snip2, remains);
1227 #else
1228                   printw("%-.*s", remains, snip);
1229 #endif
1230                 }
1231             }
1232         }
1233       b->display_valid_until = last_scan_time + valid;
1234     }
1235   attrset(attrs[0][0][M_IDLE]);
1236   clrtoeol();
1237 }
1238
1239 static void
1240 redraw_all(void)
1241 {
1242   cursor_max = num_mboxes;
1243   if (cursor_max > LINES-1)
1244     cursor_max = LINES-1;
1245   if (cursor_at >= cursor_max)
1246     cursor_at = cursor_max - 1;
1247   if (cursor_at < 0)
1248     cursor_at = 0;
1249
1250   for (int i=0; i<cursor_max; i++)
1251     redraw_line(i);
1252   move(cursor_max, 0);
1253   if (!cursor_max)
1254     {
1255       printw("(no mailboxes found)");
1256       clrtoeol();
1257       move(1, 0);
1258     }
1259   clrtobot();
1260 }
1261
1262 static void
1263 rethink_display(int notify)
1264 {
1265   int i = 0;
1266   int changed = 0;
1267   int beeeep = 0;
1268   CLIST_FOR_EACH(struct mbox *, b, mboxes)
1269     if (mbox_visible_p(b))
1270       {
1271         b->index = i;
1272         if (i >= num_mboxes || mbox_array[i] != b)
1273           {
1274             changed = 1;
1275             if (i >= mbox_array_size)
1276               {
1277                 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1278                 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1279               }
1280             mbox_array[i] = b;
1281           }
1282         if (b->o.beep && b->new > b->last_beep_new)
1283           beeeep = 1;
1284         b->last_beep_new = b->new;
1285         i++;
1286       }
1287   if (i != num_mboxes)
1288     changed = 1;
1289   num_mboxes = i;
1290
1291   if (changed)
1292     {
1293       redraw_all();
1294       refresh();
1295     }
1296   rethink_leds();
1297   rethink_osd(notify);
1298   if (beeeep && allow_bells && notify)
1299     beep();
1300 }
1301
1302 static void
1303 term_init(void)
1304 {
1305   initscr();
1306   cbreak();
1307   noecho();
1308   nonl();
1309   intrflush(stdscr, FALSE);
1310   keypad(stdscr, TRUE);
1311   curs_set(0);
1312
1313   static const int attrs_mono[2][M_MAX] = {
1314         [0] = { [M_IDLE] = 0,
1315                 [M_SCAN] = A_BOLD,
1316                 [M_NEW] = A_BOLD,
1317                 [M_FLAG] = 0,
1318                 [M_BAD] = A_DIM,
1319                 [M_INCSEARCH] = A_REVERSE },
1320         [1] = { [M_IDLE] = 0,
1321                 [M_SCAN] = A_BOLD,
1322                 [M_NEW] = A_REVERSE | A_BOLD,
1323                 [M_FLAG] = A_REVERSE,
1324                 [M_BAD] = A_DIM,
1325                 [M_INCSEARCH] = A_REVERSE },
1326   };
1327   for (int i=0; i<2; i++)
1328     for (int j=0; j<M_MAX; j++)
1329       {
1330         attrs[0][i][j] = attrs_mono[i][j];
1331         attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1332         attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1333       }
1334
1335   if (has_colors())
1336     {
1337       start_color();
1338       if (COLOR_PAIRS >= 12)
1339         {
1340           init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1341           init_pair(2, COLOR_RED, COLOR_BLACK);
1342           init_pair(3, COLOR_WHITE, COLOR_BLUE);
1343           init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1344           init_pair(5, COLOR_RED, COLOR_BLUE);
1345           init_pair(6, COLOR_GREEN, COLOR_BLACK);
1346           init_pair(7, COLOR_GREEN, COLOR_BLUE);
1347           init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1348           init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1349           init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1350           init_pair(11, COLOR_RED, COLOR_MAGENTA);
1351           init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1352           static const int attrs_color[3][2][M_MAX] = {
1353              [0][0] = { [M_IDLE] = 0,
1354                         [M_SCAN] = COLOR_PAIR(1),
1355                         [M_NEW] = COLOR_PAIR(1),
1356                         [M_FLAG] = COLOR_PAIR(6),
1357                         [M_BAD] = COLOR_PAIR(2) },
1358              [0][1] = { [M_IDLE] = A_BOLD,
1359                         [M_SCAN] = COLOR_PAIR(1),
1360                         [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1361                         [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1362                         [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1363              [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1364                         [M_SCAN] = COLOR_PAIR(4),
1365                         [M_NEW] = COLOR_PAIR(4),
1366                         [M_FLAG] = COLOR_PAIR(7),
1367                         [M_BAD] = COLOR_PAIR(5) },
1368              [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1369                         [M_SCAN] = COLOR_PAIR(4),
1370                         [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1371                         [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1372                         [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1373              [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1374                         [M_SCAN] = COLOR_PAIR(9),
1375                         [M_NEW] = COLOR_PAIR(9),
1376                         [M_FLAG] = COLOR_PAIR(10),
1377                         [M_BAD] = COLOR_PAIR(11),
1378                         [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1379              [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1380                         [M_SCAN] = COLOR_PAIR(9),
1381                         [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1382                         [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1383                         [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1384                         [M_INCSEARCH] = COLOR_PAIR(12) },
1385           };
1386           memcpy(attrs, attrs_color, sizeof(attrs));
1387         }
1388     }
1389 }
1390
1391 static void
1392 term_cleanup(void)
1393 {
1394   endwin();
1395 }
1396
1397 static void
1398 print_status(char *status)
1399 {
1400   move(LINES-1, 0);
1401   if (status)
1402     printw("%s", status);
1403   clrtoeol();
1404   refresh();
1405 }
1406
1407 static void
1408 scan_and_redraw(int notify)
1409 {
1410   print_status("Busy...");
1411   scan(notify);
1412   print_status(NULL);
1413 }
1414
1415 static void
1416 move_cursor(int i)
1417 {
1418   if (i >= 0 && i < cursor_max && i != cursor_at)
1419     {
1420       int old = cursor_at;
1421       cursor_at = i;
1422       redraw_line(old);
1423       redraw_line(i);
1424     }
1425 }
1426
1427 static void
1428 next_active(int since, int step)
1429 {
1430   if (!cursor_max)
1431     return;
1432   since = (since+cursor_max) % cursor_max;
1433   step = (step+cursor_max) % cursor_max;
1434   int besti = -1;
1435   int bestp = -1;
1436   int i = since;
1437   do
1438     {
1439       struct mbox *b = mbox_array[i];
1440       if (simple_tab)
1441         {
1442           if (b->new)
1443             {
1444               besti = i;
1445               break;
1446             }
1447         }
1448       else
1449         {
1450           if (b->new && b->o.priority > bestp)
1451             {
1452               besti = i;
1453               bestp = b->o.priority;
1454             }
1455         }
1456       i = (i+step) % cursor_max;
1457     }
1458   while (i != since);
1459   if (besti >= 0)
1460     move_cursor(besti);
1461 }
1462
1463 static void
1464 mbox_run(struct mbox *b)
1465 {
1466   char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1467   sprintf(cmd, run_cmd, b->path);
1468   term_cleanup();
1469   system(cmd);
1470   term_init();
1471   redraw_all();
1472   refresh();
1473   b->force_refresh = 1;
1474   scan_and_redraw(0);
1475 }
1476
1477 static void
1478 enter_incsearch(void)
1479 {
1480   print_status("Incremental search...");
1481   is_active = 1;
1482   is_pos = 0;
1483   redraw_line(cursor_at);
1484 }
1485
1486 static int
1487 handle_incsearch(int ch)
1488 {
1489   if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1490     --is_pos;
1491   else if (ch >= ' ' && ch <= '~')
1492     {
1493       if (is_pos < sizeof(is_buf) - 1)
1494         is_buf[is_pos++] = ch;
1495     }
1496   else
1497     {
1498       print_status(NULL);
1499       is_active = 0;
1500       is_pos = 0;
1501       redraw_line(cursor_at);
1502       return 0;
1503     }
1504
1505   is_buf[is_pos] = 0;
1506   for (int i=0; i<cursor_max; i++)
1507     {
1508       struct mbox *b = mbox_array[i];
1509       if (!strncmp(b->name, is_buf, is_pos))
1510         {
1511           if (i != cursor_at)
1512             {
1513               move_cursor(i);
1514               return 1;
1515             }
1516           break;
1517         }
1518     }
1519
1520   redraw_line(cursor_at);
1521   return 1;
1522 }
1523
1524 #define STR2(c) #c
1525 #define STR(c) STR2(c)
1526
1527 static void NONRET
1528 usage(void)
1529 {
1530   fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1531 \n\
1532 Options:\n\
1533 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1534 -d\t\t\tLog debug messages to stderr\n\
1535 -i\t\t\tInclude user's INBOX\n\
1536 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1537 -o <pattern>=<opts>\tSet mailbox options\n\
1538 -o <opts>\t\tSet default options for all mailboxes\n\
1539 -p <pri>\t\tSet minimum priority to show\n\
1540 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1541 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1542 \n\
1543 Mailbox options (set with `-o', use upper case to negate):\n\
1544 0-9\t\t\tSet mailbox priority (0=default)\n\
1545 b\t\t\tBeep when a message arrives\n\
1546 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1547 e\t\t\tHide from display if empty\n\
1548 f\t\t\tShow flagged messages if there are no new ones\n\
1549 h\t\t\tHide from display\n\
1550 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1551 m\t\t\tShow mailbox name of the sender\n\
1552 o\t\t\tCount old, but unread messages as new\n\
1553 p\t\t\tShow personal info (full name) of the sender\n\
1554 s\t\t\tShow message snippets\n\
1555 t\t\t\tHighlight the entry\n\
1556 !<key>\t\t\tSet hot key\n\
1557 \n\
1558 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1559 It can be freely distributed and used according to the GNU GPL v2.\n\
1560 ");
1561   exit(1);
1562 }
1563
1564 static void
1565 parse_options(char *c)
1566 {
1567   struct options *o;
1568   char *sep;
1569   if (sep = strchr(c, '='))
1570     {
1571       struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1572       memcpy(n->pattern, c, sep-c);
1573       n->pattern[sep-c] = 0;
1574       clist_add_tail(&options, &n->n);
1575       o = &n->o;
1576       init_options(o);
1577       c = sep+1;
1578     }
1579   else
1580     o = &global_options;
1581
1582   int x;
1583   while (x = *c++)
1584     if (x >= '0' && x <= '9')
1585       o->priority = x - '0';
1586     else if (x == '!' && *c)
1587       o->hotkey = *c++;
1588     else if (x == 'l' && *c >= '1' && *c <= '9')
1589       o->led = *c++ - '0';
1590     else
1591       {
1592         int value = !!islower(x);
1593         switch (tolower(x))
1594           {
1595           case 'b':
1596             o->beep = value;
1597             break;
1598           case 'd':
1599             o->osd = value;
1600             break;
1601           case 'e':
1602             o->hide_if_empty = value;
1603             break;
1604           case 'f':
1605             o->show_flagged = value;
1606             break;
1607           case 'h':
1608             o->hide = value;
1609             break;
1610           case 'm':
1611             o->sender_mbox = value;
1612             break;
1613           case 'o':
1614             o->unread_is_new = value;
1615             break;
1616           case 'p':
1617             o->sender_personal = value;
1618             break;
1619           case 's':
1620             o->snippets = value;
1621             break;
1622           case 't':
1623             o->highlight = value;
1624             break;
1625           default:
1626             fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1627             usage();
1628           }
1629       }
1630 }
1631
1632 int
1633 main(int argc, char **argv)
1634 {
1635   clist_init(&mboxes);
1636   clist_init(&options);
1637   clist_init(&patterns);
1638   clist_init(&osd_opts);
1639
1640   int c;
1641   while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1642     switch (c)
1643       {
1644       case 'c':
1645         check_interval = atol(optarg);
1646         if (check_interval <= 0)
1647           usage();
1648         break;
1649       case 'd':
1650         debug_mode++;
1651         break;
1652       case 'i':
1653         add_inbox();
1654         break;
1655       case 'm':
1656         run_cmd = optarg;
1657         break;
1658       case 'o':
1659         parse_options(optarg);
1660         break;
1661       case 'p':
1662         minimum_priority = atol(optarg);
1663         break;
1664       case 's':
1665         add_osd_opt(optarg);
1666         break;
1667       case 't':
1668         simple_tab = 1;
1669         break;
1670       default:
1671         usage();
1672       }
1673   while (optind < argc)
1674     add_pattern(argv[optind++]);
1675
1676   charset_init();
1677   term_init();
1678   x11_init();
1679   scan_and_redraw(0);
1680   next_active(0, 1);
1681
1682   int should_exit = 0;
1683 restart:
1684   while (!should_exit)
1685     {
1686       time_t now = time(NULL);
1687       int remains = last_scan_time + check_interval - now;
1688       if (force_refresh)
1689         scan_and_redraw(0);
1690       if (remains <= 0)
1691         scan_and_redraw(1);
1692       else
1693         {
1694           remains *= 10;
1695           halfdelay((remains > 255) ? 255 : remains);
1696           int ch = getch();
1697           if (ch < 0)
1698             continue;
1699           if (is_active && handle_incsearch(ch))
1700             {
1701               refresh();
1702               continue;
1703             }
1704           for (int i=0; i<num_mboxes; i++)
1705             if (ch == mbox_array[i]->o.hotkey)
1706               {
1707                 if (i < cursor_max)
1708                   cursor_at = i;
1709                 mbox_run(mbox_array[i]);
1710                 goto restart;
1711               }
1712           switch (ch)
1713             {
1714             case 'q':
1715               should_exit = 1;
1716               break;
1717             case 'j':
1718             case KEY_DOWN:
1719               move_cursor(cursor_at+1);
1720               break;
1721             case 'k':
1722             case KEY_UP:
1723               move_cursor(cursor_at-1);
1724               break;
1725             case '^':
1726             case KEY_HOME:
1727             case KEY_PPAGE:
1728               move_cursor(0);
1729               break;
1730             case '$':
1731             case KEY_END:
1732             case KEY_NPAGE:
1733               move_cursor(cursor_max-1);
1734               break;
1735             case '\t':
1736               next_active(cursor_at+1, 1);
1737               break;
1738             case '`':
1739               next_active(cursor_at-1, -1);
1740               break;
1741             case '\r':
1742             case '\n':
1743               if (cursor_at < cursor_max)
1744                 mbox_run(mbox_array[cursor_at]);
1745               break;
1746             case 'l' & 0x1f:
1747               clearok(stdscr, TRUE);
1748               redraw_all();
1749               refresh();
1750               break;
1751             case 'r' & 0x1f:
1752               force_refresh = 1;
1753               break;
1754             case 'b':
1755               allow_bells = 1;
1756               print_status("Bells and whistles are now enabled. Toot!");
1757               break;
1758             case 'B':
1759               allow_bells = 0;
1760               print_status("Bells and whistles are now disabled. Pssst!");
1761               break;
1762             case 'd':
1763               allow_osd = 1;
1764               print_status("On-screen display is now enabled.");
1765               break;
1766             case 'D':
1767               allow_osd = 0;
1768               print_status("On-screen display is now disabled. Watch your step.");
1769               break;
1770             case '/':
1771               enter_incsearch();
1772               break;
1773             default:
1774               if (ch >= '0' && ch <= '9')
1775                 {
1776                   minimum_priority = ch - '0';
1777                   scan_and_redraw(0);
1778                 }
1779               else
1780                 debug("Pressed unknown key %d\n", ch);
1781             }
1782           refresh();
1783         }
1784     }
1785
1786   x11_cleanup();
1787   term_cleanup();
1788   return 0;
1789 }