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