2 * Incoming Mail Checker
4 * (c) 2005--2015 Martin Mares <mj@ucw.cz>
5 * (c) 2020--2021 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
21 #include <sys/types.h>
27 #ifdef CONFIG_WIDE_CURSES
37 static int check_interval = 30;
38 static int osd_repeat_interval = 0; // 0 is disable
39 static int force_refresh;
40 static int allow_bells = 1;
41 static int allow_osd = 1;
42 static int minimum_priority;
43 static time_t last_scan_time;
44 static time_t last_osd_repeat_time;
45 static char *run_cmd = "mutt -f %s";
46 static int simple_tab;
79 static clist options, patterns;
80 static struct options global_options = {
87 #define MDIR_MAX_NAME_LEN 128
98 enum inotify_status inotify_status;
107 time_t display_valid_until;
108 int last_size, last_pos;
109 int total, new, flagged;
110 int last_total, last_new, last_flagged;
114 char sender_snippet[128];
115 char subject_snippet[128];
116 char mdir_best[MDIR_MAX_NAME_LEN];
117 int osd_repeat_last_total, osd_repeat_last_new, osd_repeat_last_flagged;
118 char osd_repeat_last_sender_snippet[128];
119 char osd_repeat_last_subject_snippet[128];
123 static struct mbox **mbox_array;
124 static int num_mboxes, mbox_array_size;
126 struct osd_opt_node {
132 static clist osd_opts;
133 static clist osd_repeat_opts;
134 static char *osd_mbox_opt="%a";
135 static char *osd_mbox_opt_separator=" ";
137 static void redraw_line(int i);
138 static void rethink_display(int notify);
140 static bool inotify_active;
141 static void inotify_cm_init(void);
142 static void inotify_add_mbox(struct mbox * b);
143 static void inotify_delete_mbox(struct mbox * b);
146 add_pattern(char *patt)
148 struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
149 strcpy(n->pattern, patt);
150 if (patt = strchr(n->pattern, '='))
157 clist_add_tail(&patterns, &n->n);
163 struct passwd *p = getpwuid(getuid());
165 die("You don't exist, go away!");
166 char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
167 sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
172 init_options(struct options *o)
176 o->hide_if_empty = -1;
180 o->show_flagged = -1;
181 o->sender_personal = -1;
188 o->unread_is_new = -1;
193 setup_options(struct mbox *b)
195 b->o = global_options;
196 CLIST_FOR_EACH(struct option_node *, n, options)
197 if (!fnmatch(n->pattern, b->name, 0))
199 debug("\tApplied options %s\n", n->pattern);
200 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
203 MERGE(hide_if_empty);
208 MERGE(sender_personal);
215 MERGE(unread_is_new);
221 add_osd_opt(char *arg, struct clist *opts)
223 struct osd_opt_node *n = xmalloc(sizeof(*n) + strlen(arg));
225 n->val = strchr(n->key, '=');
227 die("Malformed OSD option");
229 clist_add_tail(opts, &n->n);
233 mbox_name(char *path)
235 char *c = strrchr(path, '/');
246 new_mbox(char *path, char *name)
248 struct mbox *b = xmalloc(sizeof(*b));
249 bzero(b, sizeof(*b));
250 b->inotify_status = INOTIFY_OFF;
251 b->path = xstrdup(path);
252 b->name = xstrdup(name);
257 add_mbox(clist *l, struct mbox *b)
261 cnode *prev = l->head.prev;
262 struct mbox *prev_mbox;
263 while (prev != &l->head &&
264 ((prev_mbox = (struct mbox *)prev)->o.sort_order > b->o.sort_order ||
265 prev_mbox->o.sort_order == b->o.sort_order && strcmp(((struct mbox *)prev)->name, b->name) > 0))
267 clist_insert_after(&b->n, prev);
270 clist_add_tail(l, &b->n);
274 del_mbox(struct mbox *b)
283 find_mbox(clist *l, char *path)
285 CLIST_FOR_EACH(struct mbox *, b, *l)
286 if (!strcmp(b->path, path))
292 mbox_active_p(struct mbox *b)
294 if (b->o.priority < minimum_priority)
302 mbox_visible_p(struct mbox *b)
304 if (!mbox_active_p(b))
308 if (b->o.hide_if_empty && !b->total)
314 prepare_snippets(struct mbox *b, char *sender, char *subject)
318 while (*sender == ' ' || *sender == '\t')
320 while (*subject == ' ' || *subject == '\t')
323 pos = b->sender_snippet;
324 term = pos + sizeof(b->sender_snippet) - 1;
325 if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
326 add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal);
330 pos = b->subject_snippet;
331 term = pos + sizeof(b->subject_snippet) - 1;
333 add_subject_snippet(&pos, term, subject);
335 add_snippet_raw(&pos, term, "No subject");
339 build_snippet(char *buf, char *term, struct mbox *b)
341 if (b->sender_snippet[0])
343 add_snippet(&buf, term, b->sender_snippet);
344 add_snippet_raw(&buf, term, ": ");
346 add_snippet(&buf, term, b->subject_snippet);
349 static int mb_fd, mb_pos, mb_seekable;
350 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
355 mb_cc = mb_end = mb_buf;
362 lseek(mb_fd, pos, SEEK_SET);
369 return mb_pos - (mb_end - mb_cc);
375 int len = read(mb_fd, mb_buf, sizeof(mb_buf));
384 mb_end = mb_buf + len;
393 return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
404 mb_check(const char *p, int len)
408 if (mb_get() != *p++)
419 int avail = mb_end - mb_cc;
422 if (mb_seekable && len >= (int) sizeof(mb_buf))
424 int next = len - len % sizeof(mb_buf);
425 mb_seek(mb_tell() + next);
435 int next = (avail < len) ? avail : len;
442 #define HDR_BUF_SIZE 1024
445 read_hdr_field(char *buf)
453 debug("[truncated] ");
466 while (c == ' ' || c == '\t');
474 if (i < HDR_BUF_SIZE - 1)
479 debug("Header: <%s>\n", buf);
480 return buf[0] ? 1 : 0;
484 scan_mbox(struct mbox *b, struct stat *st)
486 char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
489 const char from[] = "\nFrom ";
491 b->best_time = st->st_mtime;
494 b->total = b->new = b->flagged = 0;
499 mb_fd = open(b->path, O_RDONLY);
502 debug("[open failed: %m] ");
503 b->total = b->new = b->flagged = -1;
509 c = read(mb_fd, signature, 2);
510 lseek(mb_fd, 0, SEEK_SET);
512 if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip
514 debug("[decompressing] ");
517 die("pipe failed: %m");
520 die("fork failed: %m");
523 if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0)
524 die("dup2 failed: %m");
528 execlp("gzip", "gzip", "-cd", NULL);
529 die("Cannot execute gzip: %m");
540 if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && mb_seekable)
542 mb_seek(b->last_pos);
543 if (mb_check(from, 6))
545 debug("[incremental] ");
550 debug("[incremental failed] ");
556 if (!mb_check(from+1, 5))
558 debug("[inconsistent] ");
559 b->total = b->new = b->flagged = -1;
562 b->total = b->new = b->flagged = 0;
563 b->last_total = b->last_new = b->last_flagged = 0;
568 b->total = b->last_total;
569 b->new = b->last_new;
570 b->flagged = b->last_flagged;
575 b->last_pos = mb_tell() - 5;
577 b->last_pos--; // last_pos should be the previous \n character
578 b->last_total = b->total;
579 b->last_new = b->new;
580 b->last_flagged = b->flagged;
581 while ((c = mb_get()) >= 0 && c != '\n')
584 int content_length = -1;
591 int c = read_hdr_field(buf);
596 if (!strncasecmp(buf, "Status:", 7))
598 if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
601 else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
603 else if (!strncasecmp(buf, "From:", 5))
604 strcpy(sender, buf+5);
605 else if (!strncasecmp(buf, "Subject:", 8))
606 strcpy(subject, buf+8);
607 else if (!strncasecmp(buf, "Content-Length:", 15))
608 content_length = atoi(buf + 15);
617 debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
618 if (new || (flagged && !b->best_is_new))
620 b->best_is_new = new;
621 prepare_snippets(b, sender, subject);
624 if (content_length >= 0)
625 mb_skip(content_length);
643 if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
644 b->total = b->new = b->flagged = -1;
649 mdir_mtime(struct mbox *b)
652 char path[strlen(b->path) + 5];
654 for (int new=0; new<2; new++)
656 sprintf(path, "%s/%s", b->path, (new ? "new" : "cur"));
658 if (stat(path, &st) < 0)
659 debug("[cannot stat %s] ", path);
660 else if (st.st_mtime > mtime)
667 mdir_get_snippet(struct mbox *b)
669 char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
673 char path[strlen(b->path) + MDIR_MAX_NAME_LEN];
674 sprintf(path, "%s/%s", b->path, b->mdir_best);
675 mb_fd = open(path, O_RDONLY);
678 debug("[open failed: %m] ");
679 prepare_snippets(b, sender, subject);
685 while (read_hdr_field(buf) > 0)
687 if (!strncasecmp(buf, "From:", 5))
688 strcpy(sender, buf+5);
689 else if (!strncasecmp(buf, "Subject:", 8))
690 strcpy(subject, buf+8);
694 prepare_snippets(b, sender, subject);
698 scan_mdir(struct mbox *b)
700 int dir_len = strlen(b->path);
701 char path[dir_len + MDIR_MAX_NAME_LEN];
702 strcpy(path, b->path);
704 b->total = b->new = b->flagged = 0;
708 for (int new=0; new<2; new++)
710 strcpy(path + dir_len, (new ? "/new" : "/cur"));
711 DIR *d = opendir(path);
714 debug("[cannot open %s: %m] ");
718 while (de = readdir(d))
720 char *name = de->d_name;
721 if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN)
723 path[dir_len + 4] = '/';
724 strcpy(path + dir_len + 5, name);
726 char *colon = strchr(name, ':');
729 if (colon && colon[1] == '2' && colon[2] == ',')
731 // Another comma can separate extended flags (Dovecot extension)
732 for (int i=3; colon[i] && colon[i] != ','; i++)
745 if (b->o.unread_is_new && !seen)
754 debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
756 if (is_new || flagged)
758 // Need to pick the best message to display
760 if (stat(path, &st) < 0)
761 debug("[cannot stat %s: %m] ", path);
762 else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
764 b->best_is_new = is_new;
765 b->best_time = st.st_mtime;
766 strcpy(b->mdir_best, path + dir_len + 1);
773 if (b->best_time && b->o.snippets)
775 debug("best <%s> ", b->mdir_best);
783 debug("Searching for mailboxes (notify=%d)...\n", notify);
784 last_scan_time = time(NULL);
785 CLIST_FOR_EACH(struct pattern_node *, p, patterns)
787 debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
789 int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
790 if (err && err != GLOB_NOMATCH)
791 die("Failed to glob %s: %m", p->pattern);
792 for (uns i=0; i<g.gl_pathc; i++)
794 char *name = g.gl_pathv[i];
795 struct mbox *b = find_mbox(&mboxes, name);
798 b = new_mbox(name, (p->name ? p->name : mbox_name(name)));
799 debug("Discovered mailbox %s (%s)\n", b->name, b->path);
801 add_mbox(&mboxes, b);
811 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
813 if(b->inotify_status != INOTIFY_ON)
818 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
824 debug("Lost mailbox %s\n", b->name);
825 inotify_delete_mbox(b);
832 debug("Scanning mailboxes...\n");
833 CLIST_FOR_EACH(struct mbox *, b, mboxes)
836 debug("%s: ", b->name);
837 if (!mbox_active_p(b))
843 b->force_refresh = 1;
844 if (stat(b->path, &st) < 0)
846 b->total = b->new = b->flagged = -1;
851 time_t current_mtime;
854 if (S_ISREG(st.st_mode))
858 current_mtime = st.st_mtime;
859 current_size = st.st_size;
862 else if (S_ISDIR(st.st_mode))
866 current_mtime = mdir_mtime(b);
872 debug("neither file nor directory\n");
876 if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
879 redraw_line(b->index);
886 b->last_time = current_mtime;
887 b->last_size = current_size;
888 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
891 redraw_line(b->index);
893 b->force_refresh = 0;
895 else if (b->display_valid_until <= last_scan_time)
897 debug("not changed, but needs redraw\n");
898 redraw_line(b->index);
901 debug("not changed\n");
905 debug("Scan finished\n");
906 last_scan_time = time(NULL);
907 rethink_display(notify);
912 #include <X11/Xlib.h>
913 #include <X11/Xatom.h>
915 static Display *x11_dpy;
916 static unsigned leds_care, leds_have, leds_want;
919 static unsigned osd_care;
920 #define OSD_MSG_SIZE 1024
921 static char osd_last_msg[OSD_MSG_SIZE];
922 static time_t osd_last_time;
927 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
928 osd_care = (global_options.osd >= 0);
929 CLIST_FOR_EACH(struct option_node *, o, options)
932 leds_care |= (1 << o->o.led);
936 if (osd_repeat_interval)
939 if (!leds_care && !osd_care)
941 debug("X11: No mailbox wants LEDs or OSD\n");
944 if (!getenv("DISPLAY"))
946 debug("X11: Do not have X display\n");
949 if (!(x11_dpy = XOpenDisplay(NULL)))
950 die("Cannot open X display, although the DISPLAY variable is set");
954 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
956 die("Cannot intern OSD_QUEUE atom");
958 // If OSD options contain no message, add one
960 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
965 add_osd_opt("=%40f", &osd_opts);
966 add_osd_opt("=%40s", &osd_opts);
967 add_osd_opt("=", &osd_opts);
968 add_osd_opt("=(and %N more)", &osd_opts);
973 debug("X11: Initialized\n");
979 if (leds_want == leds_have)
982 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
983 for (int i=1; i<10; i++)
984 if (leds_care & (leds_have ^ leds_want) & (1 << i))
988 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
989 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
992 leds_have = leds_want;
998 if (!leds_care || !x11_dpy)
1002 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1003 if (b->o.led > 0 && b->new)
1004 leds_want |= (1 << b->o.led);
1008 static bool // 1 if string is to long
1009 append_osd_string(char * dest, unsigned *pos, char * source)
1011 *pos += snprintf(dest+*pos, OSD_MSG_SIZE-*pos-1, "%s", source);
1012 if (*pos > OSD_MSG_SIZE-1)
1014 *pos = sprintf(dest, "OSD message too long!");
1028 static int format_osd_string(char *dest, char *src, struct osd_params *par);
1031 format_osd_string_mailboxes(char * dest, bool all, struct osd_params *par)
1035 sprintf(dest, "[no mboxes]");
1039 struct osd_params p = *par;
1041 CLIST_FOR_EACH(struct mbox *, b, *par->mboxes)
1049 p.flagged = b->flagged;
1051 char buf[OSD_MSG_SIZE];
1054 if (format_osd_string(buf, osd_mbox_opt_separator, &p))
1055 if (append_osd_string(dest, &pos, buf)) break;
1057 if (!format_osd_string(buf, osd_mbox_opt, &p))
1059 if (append_osd_string(dest, &pos, buf)) break;
1067 format_osd_string(char *dest, char *src, struct osd_params *par)
1069 char *stop = dest + OSD_MSG_SIZE - 1;
1070 char buf[OSD_MSG_SIZE];
1072 while (*src && dest < stop)
1078 while (*src >= '0' && *src <= '9')
1079 size = 10*size + *src++ - '0';
1080 if (!size || size > stop-dest)
1091 arg = par->mbox ? par->mbox->sender_snippet : "";
1094 arg = par->mbox ? par->mbox->subject_snippet : "";
1097 arg = par->mbox ? par->mbox->name : "";
1100 arg = par->mbox ? par->mbox->path : "";
1103 snprintf(buf, sizeof(buf), "%d", par->new);
1106 snprintf(buf, sizeof(buf), "%d", par->flagged);
1111 snprintf(buf, sizeof(buf), "%d", par->new - 1);
1114 snprintf(buf, sizeof(buf), "%d", par->total);
1117 snprintf(buf, sizeof(buf), "%lld", (long long)time(NULL));
1120 format_osd_string_mailboxes(buf, 0, par);
1123 format_osd_string_mailboxes(buf, 1, par);
1133 while (*arg && size)
1147 format_osd(char *msg, struct clist *opts, struct osd_params *par)
1150 unsigned have_text = 0;
1151 CLIST_FOR_EACH(struct osd_opt_node *, n, *opts)
1153 char buf[OSD_MSG_SIZE];
1154 if (!format_osd_string(buf, n->val, par))
1156 if (!n->key[0] && buf[0])
1158 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1159 if (pos > OSD_MSG_SIZE-1)
1161 pos = sprintf(msg, "OSD message too long!\n");
1173 debug_osd_msg(char *msg)
1177 fprintf(stderr, "OSD: <");
1180 fputc((*msg != '\n' ? *msg : '|'), stderr);
1183 fprintf(stderr, ">\n");
1187 osd_send(char * msg)
1191 debug("OSD: Sending to daemon\n");
1192 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) msg, strlen(msg));
1197 static struct osd_params
1198 create_osd_params(bool (* if_use_box)(struct mbox *))
1200 struct osd_params p = { .mbox = NULL, .new = 0, .flagged=0, .total=0, .mboxes=&mboxes };
1201 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1205 p.flagged += b->flagged;
1207 if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1213 static bool if_use_box_osd(struct mbox *b)
1215 return b->o.osd > 0;
1218 static bool if_use_box_osd_repeat(struct mbox *b)
1220 return b->o.osd_repeat > 0;
1224 rethink_osd(int notify)
1226 if (!osd_care || !x11_dpy || !allow_osd)
1228 osd_last_msg[0] = 0;
1232 struct osd_params p = create_osd_params(if_use_box_osd);
1234 char new_msg[OSD_MSG_SIZE];
1236 format_osd(new_msg, &osd_opts, &p);
1239 debug_osd_msg(new_msg);
1240 if (strcmp(new_msg, osd_last_msg))
1242 strcpy(osd_last_msg, new_msg);
1246 debug("OSD: No changes\n");
1247 osd_last_time = time(NULL);
1264 if(!osd_repeat_interval) return;
1265 last_osd_repeat_time = time(NULL);
1266 char new_msg[OSD_MSG_SIZE];
1267 struct osd_params p = create_osd_params(if_use_box_osd_repeat);
1268 format_osd(new_msg, &osd_repeat_opts, &p);
1269 debug_osd_msg(new_msg);
1271 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1273 b->osd_repeat_last_total = b->total ;
1274 b->osd_repeat_last_new = b->new ;
1275 b->osd_repeat_last_flagged = b->flagged ;
1276 strcpy(b->osd_repeat_last_sender_snippet , b->sender_snippet );
1277 strcpy(b->osd_repeat_last_subject_snippet , b->subject_snippet);
1282 rethink_osd_repeat(void)
1284 if(!osd_repeat_interval) return;
1286 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1288 if( b->osd_repeat_last_total != b->total ) change = 1;
1289 if( b->osd_repeat_last_new != b->new ) change = 1;
1290 if( b->osd_repeat_last_flagged != b->flagged ) change = 1;
1291 if(strcmp(b->osd_repeat_last_sender_snippet , b->sender_snippet )) change = 1;
1292 if(strcmp(b->osd_repeat_last_subject_snippet , b->subject_snippet)) change = 1;
1294 if(change) osd_repeat();
1299 static void x11_init(void) { }
1300 static void rethink_leds(void) { }
1301 static void rethink_osd(int notify UNUSED) { }
1302 static void x11_cleanup(void) { }
1303 static void osd_repeat(void) { }
1304 static void rethink_osd_repeat(void) { }
1308 static int cursor_at, cursor_max;
1310 static unsigned is_active, is_pos; // incremental search
1311 static char is_buf[64];
1322 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
1330 struct mbox *b = mbox_array[i];
1331 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1332 int hi = b->o.highlight;
1333 unsigned namepos = 0;
1334 unsigned namelen = strlen(b->name);
1337 attrset(attrs[cc][hi][M_IDLE]);
1339 printw("%c ", b->o.hotkey);
1346 attrset(attrs[cc][hi][M_INCSEARCH]);
1347 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1348 addch(is_buf[namepos]);
1351 attrset(attrs[cc][hi][M_NEW]);
1352 else if (b->flagged && b->o.show_flagged)
1353 attrset(attrs[cc][hi][M_FLAG]);
1355 attrset(attrs[cc][hi][M_IDLE]);
1356 while (namepos < namelen)
1357 addch(b->name[namepos++]);
1358 while (namepos++ < 20)
1360 if (b->scanning < 0)
1362 else if (b->scanning)
1364 attrset(attrs[cc][hi][M_SCAN]);
1365 printw("[SCANNING]");
1367 else if (b->total < 0)
1369 attrset(attrs[cc][hi][M_BAD]);
1374 attrset(attrs[cc][hi][M_IDLE]);
1375 printw("%6d ", b->total);
1379 attrset(attrs[cc][hi][M_NEW]);
1380 printw("%6d ", b->new);
1381 attrset(attrs[cc][hi][M_IDLE]);
1382 int age = (last_scan_time - b->best_time);
1387 printw("%2d min ", age/60);
1390 else if (age < 86400)
1391 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1396 else if (b->flagged && b->o.show_flagged)
1398 attrset(attrs[cc][hi][M_FLAG]);
1399 printw("%6d ", b->flagged);
1400 attrset(attrs[cc][hi][M_IDLE]);
1402 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1405 if (snip && b->o.snippets)
1408 getyx(stdscr, yy, xx);
1409 int remains = COLS-1-xx;
1413 build_snippet(snip, snip + sizeof(snip) - 1, b);
1415 if (snip[0] && remains > 2)
1417 #ifdef CONFIG_WIDE_CURSES
1418 size_t len = strlen(snip)+1;
1420 mbstowcs(snip2, snip, len);
1421 addnwstr(snip2, remains);
1423 printw("%-.*s", remains, snip);
1428 b->display_valid_until = last_scan_time + valid;
1430 attrset(attrs[0][0][M_IDLE]);
1437 cursor_max = num_mboxes;
1438 if (cursor_max > LINES-1)
1439 cursor_max = LINES-1;
1440 if (cursor_at >= cursor_max)
1441 cursor_at = cursor_max - 1;
1445 for (int i=0; i<cursor_max; i++)
1447 move(cursor_max, 0);
1450 printw("(no mailboxes found)");
1458 rethink_display(int notify)
1463 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1464 if (mbox_visible_p(b))
1467 if (i >= num_mboxes || mbox_array[i] != b)
1470 if (i >= mbox_array_size)
1472 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1473 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1477 if (b->o.beep && b->new > b->last_beep_new)
1479 b->last_beep_new = b->new;
1482 if (i != num_mboxes)
1492 rethink_osd(notify);
1493 rethink_osd_repeat();
1494 if (beeeep && allow_bells && notify)
1505 intrflush(stdscr, FALSE);
1506 keypad(stdscr, TRUE);
1509 static const int attrs_mono[2][M_MAX] = {
1510 [0] = { [M_IDLE] = 0,
1515 [M_INCSEARCH] = A_REVERSE },
1516 [1] = { [M_IDLE] = 0,
1518 [M_NEW] = A_REVERSE | A_BOLD,
1519 [M_FLAG] = A_REVERSE,
1521 [M_INCSEARCH] = A_REVERSE },
1523 for (int i=0; i<2; i++)
1524 for (int j=0; j<M_MAX; j++)
1526 attrs[0][i][j] = attrs_mono[i][j];
1527 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1528 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1534 if (COLOR_PAIRS >= 12)
1536 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1537 init_pair(2, COLOR_RED, COLOR_BLACK);
1538 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1539 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1540 init_pair(5, COLOR_RED, COLOR_BLUE);
1541 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1542 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1543 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1544 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1545 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1546 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1547 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1548 static const int attrs_color[3][2][M_MAX] = {
1549 [0][0] = { [M_IDLE] = 0,
1550 [M_SCAN] = COLOR_PAIR(1),
1551 [M_NEW] = COLOR_PAIR(1),
1552 [M_FLAG] = COLOR_PAIR(6),
1553 [M_BAD] = COLOR_PAIR(2) },
1554 [0][1] = { [M_IDLE] = A_BOLD,
1555 [M_SCAN] = COLOR_PAIR(1),
1556 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1557 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1558 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1559 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1560 [M_SCAN] = COLOR_PAIR(4),
1561 [M_NEW] = COLOR_PAIR(4),
1562 [M_FLAG] = COLOR_PAIR(7),
1563 [M_BAD] = COLOR_PAIR(5) },
1564 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1565 [M_SCAN] = COLOR_PAIR(4),
1566 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1567 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1568 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1569 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1570 [M_SCAN] = COLOR_PAIR(9),
1571 [M_NEW] = COLOR_PAIR(9),
1572 [M_FLAG] = COLOR_PAIR(10),
1573 [M_BAD] = COLOR_PAIR(11),
1574 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1575 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1576 [M_SCAN] = COLOR_PAIR(9),
1577 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1578 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1579 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1580 [M_INCSEARCH] = COLOR_PAIR(12) },
1582 memcpy(attrs, attrs_color, sizeof(attrs));
1594 print_status(char *status)
1598 printw("%s", status);
1604 scan_and_redraw(int notify)
1606 print_status("Busy...");
1614 if (i >= 0 && i < cursor_max && i != cursor_at)
1616 int old = cursor_at;
1624 next_active(int since, int step)
1628 since = (since+cursor_max) % cursor_max;
1629 step = (step+cursor_max) % cursor_max;
1635 struct mbox *b = mbox_array[i];
1646 if (b->new && b->o.priority > bestp)
1649 bestp = b->o.priority;
1652 i = (i+step) % cursor_max;
1660 mbox_run(struct mbox *b)
1662 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1663 sprintf(cmd, run_cmd, b->path);
1669 b->force_refresh = 1;
1674 enter_incsearch(void)
1676 print_status("Incremental search...");
1679 redraw_line(cursor_at);
1683 handle_incsearch(int ch)
1685 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1687 else if (ch >= ' ' && ch <= '~')
1689 if (is_pos < sizeof(is_buf) - 1)
1690 is_buf[is_pos++] = ch;
1697 redraw_line(cursor_at);
1702 for (int i=0; i<cursor_max; i++)
1704 struct mbox *b = mbox_array[i];
1705 if (!strncmp(b->name, is_buf, is_pos))
1716 redraw_line(cursor_at);
1721 without_inotify_wait(int timeout)
1724 halfdelay((timeout > 255) ? 255 : timeout);
1727 #ifdef CONFIG_INOTIFY
1729 #include <sys/inotify.h>
1731 static int inotify_fd;
1734 inotify_cm_init(void)
1736 if (!inotify_active) return;
1737 inotify_fd = inotify_init();
1739 die("Inotify init faild.\n");
1743 inotify_add_mbox(struct mbox * b)
1745 if (!inotify_active) return;
1746 b->inotify_status = INOTIFY_ON;
1747 char *path = xmalloc(strlen(b->path)+100);
1749 if (stat(b->path, &st) < 0)
1751 debug("Get stat of %s faild\n", b->path);
1754 if (S_ISREG(st.st_mode))
1757 sprintf(path, "%s", b->path);
1758 if (inotify_add_watch(inotify_fd, path, IN_MOVED_TO | IN_MOVED_FROM | IN_DELETE | IN_CLOSE_WRITE ) < 0)
1760 debug("Inotify add %s faild\n", path);
1761 b->inotify_status = INOTIFY_ERROR;
1764 debug("Inotify add %s OK\n", path);
1766 else if (S_ISDIR(st.st_mode))
1769 char *watch_dir[] = {"cur", "new"};
1770 for (int i=0; i < (int)(sizeof(watch_dir)/sizeof(*watch_dir)); i++)
1772 sprintf(path, "%s/%s", b->path, watch_dir[i]);
1773 if (inotify_add_watch(inotify_fd, path, IN_MOVED_TO | IN_MOVED_FROM | IN_DELETE | IN_CLOSE_WRITE ) < 0)
1775 debug("Inotify add %s faild\n", path);
1776 b->inotify_status = INOTIFY_ERROR;
1782 debug("neither file nor directory\n");
1783 b->inotify_status = INOTIFY_ERROR;
1790 inotify_delete_mbox(struct mbox *b UNUSED)
1795 static bool // true if inotify change detected
1796 inotify_wait(int timeout)
1800 without_inotify_wait(timeout);
1804 bool inotify_change = 0;
1807 tv.tv_sec = timeout;
1814 FD_SET(inotify_fd, &rfds);
1816 debug("Select begin (timeout %d %d)\n", tv.tv_sec, tv.tv_usec);
1817 int ret = select(1+inotify_fd, &rfds, NULL, NULL, &tv);
1818 debug("Select end\n");
1819 if (ret>0 && FD_ISSET(0, &rfds))
1820 return inotify_change;
1821 if (ret>0 && FD_ISSET(inotify_fd, &rfds))
1823 debug("Read notify begin\n");
1824 char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
1825 ssize_t len = read(inotify_fd, buf, sizeof(buf));
1826 if (len == -1 && errno != EAGAIN)
1827 perror ("read inotify faild");
1829 debug("Read notify end\n");
1832 return inotify_change;
1841 inotify_cm_init(void) {}
1844 inotify_add_mbox(struct mbox *mbox UNUSED) {}
1847 inotify_delete_mbox(struct mbox *mbox UNUSED) {}
1850 inotify_wait(int timeout)
1852 without_inotify_wait(timeout);
1859 #define STR(c) STR2(c)
1864 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1867 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1868 -d\t\t\tLog debug messages to stderr\n\
1869 -i\t\t\tInclude user's INBOX (/var/mail/<user>=INBOX)\n\
1870 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1871 -o <pattern>=<opts>\tSet mailbox options\n\
1872 -o <opts>\t\tSet default options for all mailboxes\n\
1873 -p <pri>\t\tSet minimum priority to show\n\
1874 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1875 -r <key>=<val>\t\tSet on-screen display repeat notification options (consult OSDD docs)\n\
1876 -b <val>\t\tSet on-screen display mailbox specification string (for %%b %%B expansions)\n\
1877 -B <val>\t\tSet on-screen display mailbox specification string separator (for %%b %%B expansions)\n\
1878 -C <interval>\t\tSend repeat notifications every <interval> seconds (default is disable repeat not.)\n\
1879 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1880 -f\t\t\tEnable inotify (wait for file system changes)\n\
1882 On-screen display replacement (in all OSD values, notification lines and -b -B options):\n\
1884 %%f\t\tFrom - mail author name (last unread mail if any exist)\n\
1885 %%s\t\tMail subject (last unread mail if any exist)\n\
1886 %%a\t\tMailbox name of last unread mail (if any exit) or actual mailbox in -b\n\
1887 %%A\t\tMailbox path of last unread mail (if any exit) or actual mailbox in -b\n\
1888 %%n\t\tCount of new mails\n\
1889 %%N\t\tCount of new mails minus one; if is less than one, whole line will be hidden\n\
1890 %%F\t\tCount of flagged mails\n\
1891 %%m\t\tCount of all mails\n\
1892 %%t\t\tActual unix time\n\
1893 %%b\t\tPrint all mailboxes with at least one new mail (format by -b -B options)\n\
1894 %%B\t\tPrint all mailbox (format by -b -B options)\n\
1896 Mailbox options (set with `-o', use upper case to negate):\n\
1897 0-9\t\t\tSet mailbox priority (0=default)\n\
1898 b\t\t\tBeep when a message arrives\n\
1899 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1900 x\t\t\tCount this mailbox in on-screen display repeat notification (default on)\n\
1901 y\t\t\tUse this mailbox in notification mailbox list (%%b %%B expansions) (default on)\n\
1902 e\t\t\tHide from display if empty\n\
1903 f\t\t\tShow flagged messages if there are no new ones\n\
1904 h\t\t\tHide from display\n\
1905 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1906 m\t\t\tShow mailbox name of the sender\n\
1907 o\t\t\tCount old, but unread messages as new\n\
1908 p\t\t\tShow personal info (full name) of the sender\n\
1909 r<int>\t\t\tSort order (default=1000)\n\
1910 s\t\t\tShow message snippets\n\
1911 t\t\t\tHighlight the entry\n\
1912 !<key>\t\t\tSet hot key\n\
1914 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>, Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>\n\
1915 It can be freely distributed and used according to the GNU GPL v2.\n\
1921 parse_options(char *c)
1925 if (sep = strchr(c, '='))
1927 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1928 memcpy(n->pattern, c, sep-c);
1929 n->pattern[sep-c] = 0;
1930 clist_add_tail(&options, &n->n);
1936 o = &global_options;
1940 if (x >= '0' && x <= '9')
1941 o->priority = x - '0';
1942 else if (x == '!' && *c)
1944 else if (x == 'l' && *c >= '1' && *c <= '9')
1945 o->led = *c++ - '0';
1946 else if (x == 'r' && *c >= '0' && *c <= '9')
1949 while (*c >= '0' && *c <= '9')
1950 o->sort_order = 10*o->sort_order + *c++ - '0';
1954 int value = !!islower(x);
1961 o->osd_repeat = value;
1964 o->osd_list = value;
1970 o->hide_if_empty = value;
1973 o->show_flagged = value;
1979 o->sender_mbox = value;
1982 o->unread_is_new = value;
1985 o->sender_personal = value;
1988 o->snippets = value;
1991 o->highlight = value;
1994 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
2001 main(int argc, char **argv)
2003 clist_init(&mboxes);
2004 clist_init(&options);
2005 clist_init(&patterns);
2006 clist_init(&osd_opts);
2007 clist_init(&osd_repeat_opts);
2009 keypad(stdscr,TRUE);
2012 while ((c = getopt(argc, argv, "c:C:dim:o:p:s:r:b:B:tif")) >= 0)
2016 check_interval = atol(optarg);
2017 if (check_interval <= 0)
2021 osd_repeat_interval = atol(optarg);
2022 if (check_interval <= 0)
2035 parse_options(optarg);
2038 minimum_priority = atol(optarg);
2041 add_osd_opt(optarg, &osd_opts);
2044 add_osd_opt(optarg, &osd_repeat_opts);
2047 osd_mbox_opt = optarg;
2050 osd_mbox_opt_separator = optarg;
2061 while (optind < argc)
2062 add_pattern(argv[optind++]);
2072 int inotify_rescan = 0;
2073 int should_exit = 0;
2075 while (!should_exit)
2077 time_t now = time(NULL);
2078 int remains = last_scan_time + check_interval - now;
2079 int osd_repeat_remains = last_osd_repeat_time + osd_repeat_interval - now;
2080 if(!osd_repeat_interval) osd_repeat_remains = 1<<30;
2083 if (remains <= 0 || inotify_rescan)
2085 if (inotify_rescan) debug("Inotify rescan\n");
2089 if (osd_repeat_remains <= 0)
2099 int min_remains = remains;
2100 if(min_remains > osd_repeat_remains) min_remains = osd_repeat_remains;
2101 inotify_rescan |= inotify_wait(min_remains);
2106 if (is_active && handle_incsearch(ch))
2111 for (int i=0; i<num_mboxes; i++)
2112 if (ch == mbox_array[i]->o.hotkey)
2116 mbox_run(mbox_array[i]);
2126 move_cursor(cursor_at+1);
2130 move_cursor(cursor_at-1);
2140 move_cursor(cursor_max-1);
2143 next_active(cursor_at+1, 1);
2146 next_active(cursor_at-1, -1);
2150 if (cursor_at < cursor_max)
2151 mbox_run(mbox_array[cursor_at]);
2155 clearok(stdscr, TRUE);
2164 print_status("Bells and whistles are now enabled. Toot!");
2168 print_status("Bells and whistles are now disabled. Pssst!");
2172 print_status("On-screen display is now enabled.");
2176 print_status("On-screen display is now disabled. Watch your step.");
2182 if (ch >= '0' && ch <= '9')
2184 minimum_priority = ch - '0';
2188 debug("Pressed unknown key %d\n", ch);