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