2 * Incoming Mail Checker
4 * (c) 2005--2015 Martin Mares <mj@ucw.cz>
20 #include <sys/types.h>
25 #ifdef CONFIG_WIDE_CURSES
26 #include <ncursesw/ncurses.h>
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;
72 static clist options, patterns;
73 static struct options global_options = {
77 #define MDIR_MAX_NAME_LEN 128
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;
96 char sender_snippet[128];
97 char subject_snippet[128];
98 char mdir_best[MDIR_MAX_NAME_LEN];
102 static struct mbox **mbox_array;
103 static int num_mboxes, mbox_array_size;
105 struct osd_opt_node {
111 static clist osd_opts;
113 static void redraw_line(int i);
114 static void rethink_display(int notify);
117 add_pattern(char *patt)
119 struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
120 strcpy(n->pattern, patt);
121 if (patt = strchr(n->pattern, '='))
128 clist_add_tail(&patterns, &n->n);
134 struct passwd *p = getpwuid(getuid());
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);
143 init_options(struct options *o)
147 o->hide_if_empty = -1;
151 o->show_flagged = -1;
152 o->sender_personal = -1;
157 o->unread_is_new = -1;
161 setup_options(struct mbox *b)
163 b->o = global_options;
164 CLIST_FOR_EACH(struct option_node *, n, options)
165 if (!fnmatch(n->pattern, b->name, 0))
167 debug("\tApplied options %s\n", n->pattern);
168 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
171 MERGE(hide_if_empty);
176 MERGE(sender_personal);
181 MERGE(unread_is_new);
186 add_osd_opt(char *arg)
188 struct osd_opt_node *n = xmalloc(sizeof(*n) + strlen(arg));
190 n->val = strchr(n->key, '=');
192 die("Malformed OSD option");
194 clist_add_tail(&osd_opts, &n->n);
198 mbox_name(char *path)
200 char *c = strrchr(path, '/');
201 return c ? (c+1) : path;
205 add_mbox(clist *l, char *path, char *name)
207 struct mbox *b = xmalloc(sizeof(*b));
208 bzero(b, sizeof(*b));
209 b->path = xstrdup(path);
210 b->name = xstrdup(name);
214 cnode *prev = l->head.prev;
215 while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0)
217 clist_insert_after(&b->n, prev);
220 clist_add_tail(l, &b->n);
226 del_mbox(struct mbox *b)
235 find_mbox(clist *l, char *path)
237 CLIST_FOR_EACH(struct mbox *, b, *l)
238 if (!strcmp(b->path, path))
244 mbox_active_p(struct mbox *b)
246 if (b->o.priority < minimum_priority)
254 mbox_visible_p(struct mbox *b)
256 if (!mbox_active_p(b))
260 if (b->o.hide_if_empty && !b->total)
266 prepare_snippets(struct mbox *b, char *sender, char *subject)
270 while (*sender == ' ' || *sender == '\t')
272 while (*subject == ' ' || *subject == '\t')
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);
282 pos = b->subject_snippet;
283 term = pos + sizeof(b->subject_snippet) - 1;
285 add_subject_snippet(&pos, term, subject);
287 add_snippet_raw(&pos, term, "No subject");
291 build_snippet(char *buf, char *term, struct mbox *b)
293 if (b->sender_snippet[0])
295 add_snippet(&buf, term, b->sender_snippet);
296 add_snippet_raw(&buf, term, ": ");
298 add_snippet(&buf, term, b->subject_snippet);
301 static int mb_fd, mb_pos, mb_seekable;
302 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
307 mb_cc = mb_end = mb_buf;
314 lseek(mb_fd, pos, SEEK_SET);
321 return mb_pos - (mb_end - mb_cc);
327 int len = read(mb_fd, mb_buf, sizeof(mb_buf));
336 mb_end = mb_buf + len;
345 return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
356 mb_check(const char *p, int len)
360 if (mb_get() != *p++)
371 int avail = mb_end - mb_cc;
374 if (mb_seekable && len >= (int) sizeof(mb_buf))
376 int next = len - len % sizeof(mb_buf);
377 mb_seek(mb_tell() + next);
387 int next = (avail < len) ? avail : len;
394 #define HDR_BUF_SIZE 1024
397 read_hdr_field(char *buf)
405 debug("[truncated] ");
418 while (c == ' ' || c == '\t');
426 if (i < HDR_BUF_SIZE - 1)
431 debug("Header: <%s>\n", buf);
432 return buf[0] ? 1 : 0;
436 scan_mbox(struct mbox *b, struct stat *st)
438 char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
441 const char from[] = "\nFrom ";
443 b->best_time = st->st_mtime;
446 b->total = b->new = b->flagged = 0;
451 mb_fd = open(b->path, O_RDONLY);
454 debug("[open failed: %m] ");
455 b->total = b->new = b->flagged = -1;
461 c = read(mb_fd, signature, 2);
462 lseek(mb_fd, 0, SEEK_SET);
464 if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip
466 debug("[decompressing] ");
469 die("pipe failed: %m");
472 die("fork failed: %m");
475 if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0)
476 die("dup2 failed: %m");
480 execlp("gzip", "gzip", "-cd", NULL);
481 die("Cannot execute gzip: %m");
492 if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && mb_seekable)
494 mb_seek(b->last_pos);
495 if (mb_check(from, 6))
497 debug("[incremental] ");
502 debug("[incremental failed] ");
508 if (!mb_check(from+1, 5))
510 debug("[inconsistent] ");
511 b->total = b->new = b->flagged = -1;
514 b->total = b->new = b->flagged = 0;
515 b->last_total = b->last_new = b->last_flagged = 0;
520 b->total = b->last_total;
521 b->new = b->last_new;
522 b->flagged = b->last_flagged;
527 b->last_pos = mb_tell() - 5;
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')
536 int content_length = -1;
543 int c = read_hdr_field(buf);
548 if (!strncasecmp(buf, "Status:", 7))
550 if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
553 else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
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);
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))
572 b->best_is_new = new;
573 prepare_snippets(b, sender, subject);
576 if (content_length >= 0)
577 mb_skip(content_length);
595 if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
596 b->total = b->new = b->flagged = -1;
601 mdir_mtime(struct mbox *b)
604 char path[strlen(b->path) + 5];
606 for (int new=0; new<2; new++)
608 sprintf(path, "%s/%s", b->path, (new ? "new" : "cur"));
610 if (stat(path, &st) < 0)
611 debug("[cannot stat %s] ", path);
612 else if (st.st_mtime > mtime)
619 mdir_get_snippet(struct mbox *b)
621 char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
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);
630 debug("[open failed: %m] ");
631 prepare_snippets(b, sender, subject);
637 while (read_hdr_field(buf) > 0)
639 if (!strncasecmp(buf, "From:", 5))
640 strcpy(sender, buf+5);
641 else if (!strncasecmp(buf, "Subject:", 8))
642 strcpy(subject, buf+8);
646 prepare_snippets(b, sender, subject);
650 scan_mdir(struct mbox *b)
652 int dir_len = strlen(b->path);
653 char path[dir_len + MDIR_MAX_NAME_LEN];
654 strcpy(path, b->path);
656 b->total = b->new = b->flagged = 0;
660 for (int new=0; new<2; new++)
662 strcpy(path + dir_len, (new ? "/new" : "/cur"));
663 DIR *d = opendir(path);
666 debug("[cannot open %s: %m] ");
670 while (de = readdir(d))
672 char *name = de->d_name;
673 if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN)
675 path[dir_len + 4] = '/';
676 strcpy(path + dir_len + 5, name);
678 char *colon = strchr(name, ':');
681 if (colon && colon[1] == '2' && colon[2] == ',')
683 for (int i=3; colon[i]; i++)
696 if (b->o.unread_is_new && !seen)
705 debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
707 if (is_new || flagged)
709 // Need to pick the best message to display
711 if (stat(path, &st) < 0)
712 debug("[cannot stat %s: %m] ", path);
713 else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
715 b->best_is_new = is_new;
716 b->best_time = st.st_mtime;
717 strcpy(b->mdir_best, path + dir_len + 1);
724 if (b->best_time && b->o.snippets)
726 debug("best <%s> ", b->mdir_best);
734 debug("Searching for mailboxes (notify=%d)...\n", notify);
735 last_scan_time = time(NULL);
736 CLIST_FOR_EACH(struct pattern_node *, p, patterns)
738 debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
740 int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
741 if (err && err != GLOB_NOMATCH)
742 die("Failed to glob %s: %m", p->pattern);
743 for (uns i=0; i<g.gl_pathc; i++)
745 char *name = g.gl_pathv[i];
746 struct mbox *b = find_mbox(&mboxes, name);
749 b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
750 debug("Discovered mailbox %s (%s)\n", b->name, b->path);
760 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
766 debug("Lost mailbox %s\n", b->name);
773 debug("Scanning mailboxes...\n");
774 CLIST_FOR_EACH(struct mbox *, b, mboxes)
777 debug("%s: ", b->name);
778 if (!mbox_active_p(b))
783 b->force_refresh = force_refresh;
784 if (stat(b->path, &st) < 0)
786 b->total = b->new = b->flagged = -1;
791 time_t current_mtime;
794 if (S_ISREG(st.st_mode))
798 current_mtime = st.st_mtime;
799 current_size = st.st_size;
802 else if (S_ISDIR(st.st_mode))
806 current_mtime = mdir_mtime(b);
812 debug("neither file nor directory\n");
816 if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
819 redraw_line(b->index);
826 b->last_time = current_mtime;
827 b->last_size = current_size;
828 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
831 redraw_line(b->index);
833 b->force_refresh = 0;
835 else if (b->display_valid_until <= last_scan_time)
837 debug("not changed, but needs redraw\n");
838 redraw_line(b->index);
841 debug("not changed\n");
845 debug("Scan finished\n");
846 last_scan_time = time(NULL);
847 rethink_display(notify);
852 #include <X11/Xlib.h>
853 #include <X11/Xatom.h>
855 static Display *x11_dpy;
856 static unsigned leds_care, leds_have, leds_want;
859 static unsigned osd_care;
860 #define OSD_MSG_SIZE 1024
861 static char osd_last_msg[OSD_MSG_SIZE];
862 static time_t osd_last_time;
867 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
868 osd_care = (global_options.osd >= 0);
869 CLIST_FOR_EACH(struct option_node *, o, options)
872 leds_care |= (1 << o->o.led);
877 if (!leds_care && !osd_care)
879 debug("X11: No mailbox wants LEDs or OSD\n");
882 if (!getenv("DISPLAY"))
884 debug("X11: Do not have X display\n");
887 if (!(x11_dpy = XOpenDisplay(NULL)))
888 die("Cannot open X display, although the DISPLAY variable is set");
892 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
894 die("Cannot intern OSD_QUEUE atom");
896 // If OSD options contain no message, add one
898 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
903 add_osd_opt("=%40f");
904 add_osd_opt("=%40s");
906 add_osd_opt("=(and %m more)");
911 debug("X11: Initialized\n");
917 if (leds_want == leds_have)
920 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
921 for (int i=1; i<10; i++)
922 if (leds_care & (leds_have ^ leds_want) & (1 << i))
926 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
927 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
930 leds_have = leds_want;
936 if (!leds_care || !x11_dpy)
940 CLIST_FOR_EACH(struct mbox *, b, mboxes)
941 if (b->o.led > 0 && b->new)
942 leds_want |= (1 << b->o.led);
952 format_osd_string(char *dest, char *src, struct osd_params *par)
954 char *stop = dest + OSD_MSG_SIZE - 1;
957 while (*src && dest < stop)
963 while (*src >= '0' && *src <= '9')
964 size = 10*size + *src++ - '0';
965 if (!size || size > stop-dest)
976 arg = par->mbox->sender_snippet;
979 arg = par->mbox->subject_snippet;
982 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
985 if (par->total_new < 2)
987 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
1011 format_osd(char *msg, struct osd_params *par)
1020 unsigned have_text = 0;
1021 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
1023 char buf[OSD_MSG_SIZE];
1024 if (!format_osd_string(buf, n->val, par))
1026 if (!n->key[0] && buf[0])
1028 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1029 if (pos > OSD_MSG_SIZE-1)
1031 pos = sprintf(msg, "OSD message too long!\n");
1043 debug_osd_msg(char *msg)
1047 fprintf(stderr, "OSD: <");
1050 fputc((*msg != '\n' ? *msg : '|'), stderr);
1053 fprintf(stderr, ">\n");
1057 rethink_osd(int notify)
1059 if (!osd_care || !x11_dpy || !allow_osd)
1061 osd_last_msg[0] = 0;
1065 struct osd_params p = { .mbox = NULL, .total_new = 0 };
1066 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1069 p.total_new += b->new;
1070 if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1074 char new_msg[OSD_MSG_SIZE];
1075 format_osd(new_msg, &p);
1076 debug_osd_msg(new_msg);
1077 if (strcmp(new_msg, osd_last_msg))
1079 strcpy(osd_last_msg, new_msg);
1080 if (notify && new_msg[0])
1082 debug("OSD: Sending to daemon\n");
1083 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
1087 debug("OSD: No changes\n");
1088 osd_last_time = time(NULL);
1104 static void x11_init(void) { }
1105 static void rethink_leds(void) { }
1106 static void rethink_osd(int notify UNUSED) { }
1107 static void x11_cleanup(void) { }
1111 static int cursor_at, cursor_max;
1113 static unsigned is_active, is_pos; // incremental search
1114 static char is_buf[64];
1125 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
1133 struct mbox *b = mbox_array[i];
1134 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1135 int hi = b->o.highlight;
1136 unsigned namepos = 0;
1137 unsigned namelen = strlen(b->name);
1140 attrset(attrs[cc][hi][M_IDLE]);
1142 printw("%c ", b->o.hotkey);
1149 attrset(attrs[cc][hi][M_INCSEARCH]);
1150 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1151 addch(is_buf[namepos]);
1154 attrset(attrs[cc][hi][M_NEW]);
1155 else if (b->flagged && b->o.show_flagged)
1156 attrset(attrs[cc][hi][M_FLAG]);
1158 attrset(attrs[cc][hi][M_IDLE]);
1159 while (namepos < namelen)
1160 addch(b->name[namepos++]);
1161 while (namepos++ < 20)
1163 if (b->scanning < 0)
1165 else if (b->scanning)
1167 attrset(attrs[cc][hi][M_SCAN]);
1168 printw("[SCANNING]");
1170 else if (b->total < 0)
1172 attrset(attrs[cc][hi][M_BAD]);
1177 attrset(attrs[cc][hi][M_IDLE]);
1178 printw("%6d ", b->total);
1182 attrset(attrs[cc][hi][M_NEW]);
1183 printw("%6d ", b->new);
1184 attrset(attrs[cc][hi][M_IDLE]);
1185 int age = (last_scan_time - b->best_time);
1190 printw("%2d min ", age/60);
1193 else if (age < 86400)
1194 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1199 else if (b->flagged && b->o.show_flagged)
1201 attrset(attrs[cc][hi][M_FLAG]);
1202 printw("%6d ", b->flagged);
1203 attrset(attrs[cc][hi][M_IDLE]);
1205 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1208 if (snip && b->o.snippets)
1211 getyx(stdscr, yy, xx);
1212 int remains = COLS-1-xx;
1216 build_snippet(snip, snip + sizeof(snip) - 1, b);
1218 if (snip[0] && remains > 2)
1220 #ifdef CONFIG_WIDE_CURSES
1221 size_t len = strlen(snip)+1;
1223 mbstowcs(snip2, snip, len);
1224 addnwstr(snip2, remains);
1226 printw("%-.*s", remains, snip);
1231 b->display_valid_until = last_scan_time + valid;
1233 attrset(attrs[0][0][M_IDLE]);
1240 cursor_max = num_mboxes;
1241 if (cursor_max > LINES-1)
1242 cursor_max = LINES-1;
1243 if (cursor_at >= cursor_max)
1244 cursor_at = cursor_max - 1;
1248 for (int i=0; i<cursor_max; i++)
1250 move(cursor_max, 0);
1253 printw("(no mailboxes found)");
1261 rethink_display(int notify)
1266 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1267 if (mbox_visible_p(b))
1270 if (i >= num_mboxes || mbox_array[i] != b)
1273 if (i >= mbox_array_size)
1275 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1276 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1280 if (b->o.beep && b->new > b->last_beep_new)
1282 b->last_beep_new = b->new;
1285 if (i != num_mboxes)
1295 rethink_osd(notify);
1296 if (beeeep && allow_bells && notify)
1307 intrflush(stdscr, FALSE);
1308 keypad(stdscr, TRUE);
1311 static const int attrs_mono[2][M_MAX] = {
1312 [0] = { [M_IDLE] = 0,
1317 [M_INCSEARCH] = A_REVERSE },
1318 [1] = { [M_IDLE] = 0,
1320 [M_NEW] = A_REVERSE | A_BOLD,
1321 [M_FLAG] = A_REVERSE,
1323 [M_INCSEARCH] = A_REVERSE },
1325 for (int i=0; i<2; i++)
1326 for (int j=0; j<M_MAX; j++)
1328 attrs[0][i][j] = attrs_mono[i][j];
1329 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1330 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1336 if (COLOR_PAIRS >= 12)
1338 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1339 init_pair(2, COLOR_RED, COLOR_BLACK);
1340 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1341 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1342 init_pair(5, COLOR_RED, COLOR_BLUE);
1343 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1344 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1345 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1346 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1347 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1348 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1349 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1350 static const int attrs_color[3][2][M_MAX] = {
1351 [0][0] = { [M_IDLE] = 0,
1352 [M_SCAN] = COLOR_PAIR(1),
1353 [M_NEW] = COLOR_PAIR(1),
1354 [M_FLAG] = COLOR_PAIR(6),
1355 [M_BAD] = COLOR_PAIR(2) },
1356 [0][1] = { [M_IDLE] = A_BOLD,
1357 [M_SCAN] = COLOR_PAIR(1),
1358 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1359 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1360 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1361 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1362 [M_SCAN] = COLOR_PAIR(4),
1363 [M_NEW] = COLOR_PAIR(4),
1364 [M_FLAG] = COLOR_PAIR(7),
1365 [M_BAD] = COLOR_PAIR(5) },
1366 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1367 [M_SCAN] = COLOR_PAIR(4),
1368 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1369 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1370 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1371 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1372 [M_SCAN] = COLOR_PAIR(9),
1373 [M_NEW] = COLOR_PAIR(9),
1374 [M_FLAG] = COLOR_PAIR(10),
1375 [M_BAD] = COLOR_PAIR(11),
1376 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1377 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1378 [M_SCAN] = COLOR_PAIR(9),
1379 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1380 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1381 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1382 [M_INCSEARCH] = COLOR_PAIR(12) },
1384 memcpy(attrs, attrs_color, sizeof(attrs));
1396 print_status(char *status)
1400 printw("%s", status);
1406 scan_and_redraw(int notify)
1408 print_status("Busy...");
1416 if (i >= 0 && i < cursor_max && i != cursor_at)
1418 int old = cursor_at;
1426 next_active(int since, int step)
1430 since = (since+cursor_max) % cursor_max;
1431 step = (step+cursor_max) % cursor_max;
1437 struct mbox *b = mbox_array[i];
1448 if (b->new && b->o.priority > bestp)
1451 bestp = b->o.priority;
1454 i = (i+step) % cursor_max;
1462 mbox_run(struct mbox *b)
1464 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1465 sprintf(cmd, run_cmd, b->path);
1471 b->force_refresh = 1;
1476 enter_incsearch(void)
1478 print_status("Incremental search...");
1481 redraw_line(cursor_at);
1485 handle_incsearch(int ch)
1487 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1489 else if (ch >= ' ' && ch <= '~')
1491 if (is_pos < sizeof(is_buf) - 1)
1492 is_buf[is_pos++] = ch;
1499 redraw_line(cursor_at);
1504 for (int i=0; i<cursor_max; i++)
1506 struct mbox *b = mbox_array[i];
1507 if (!strncmp(b->name, is_buf, is_pos))
1518 redraw_line(cursor_at);
1523 #define STR(c) STR2(c)
1528 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1531 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1532 -d\t\t\tLog debug messages to stderr\n\
1533 -i\t\t\tInclude user's INBOX\n\
1534 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1535 -o <pattern>=<opts>\tSet mailbox options\n\
1536 -o <opts>\t\tSet default options for all mailboxes\n\
1537 -p <pri>\t\tSet minimum priority to show\n\
1538 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1539 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1541 Mailbox options (set with `-o', use upper case to negate):\n\
1542 0-9\t\t\tSet mailbox priority (0=default)\n\
1543 b\t\t\tBeep when a message arrives\n\
1544 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1545 e\t\t\tHide from display if empty\n\
1546 f\t\t\tShow flagged messages if there are no new ones\n\
1547 h\t\t\tHide from display\n\
1548 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1549 m\t\t\tShow mailbox name of the sender\n\
1550 o\t\t\tCount old, but unread messages as new\n\
1551 p\t\t\tShow personal info (full name) of the sender\n\
1552 s\t\t\tShow message snippets\n\
1553 t\t\t\tHighlight the entry\n\
1554 !<key>\t\t\tSet hot key\n\
1556 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1557 It can be freely distributed and used according to the GNU GPL v2.\n\
1563 parse_options(char *c)
1567 if (sep = strchr(c, '='))
1569 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1570 memcpy(n->pattern, c, sep-c);
1571 n->pattern[sep-c] = 0;
1572 clist_add_tail(&options, &n->n);
1578 o = &global_options;
1582 if (x >= '0' && x <= '9')
1583 o->priority = x - '0';
1584 else if (x == '!' && *c)
1586 else if (x == 'l' && *c >= '1' && *c <= '9')
1587 o->led = *c++ - '0';
1590 int value = !!islower(x);
1600 o->hide_if_empty = value;
1603 o->show_flagged = value;
1609 o->sender_mbox = value;
1612 o->unread_is_new = value;
1615 o->sender_personal = value;
1618 o->snippets = value;
1621 o->highlight = value;
1624 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1631 main(int argc, char **argv)
1633 clist_init(&mboxes);
1634 clist_init(&options);
1635 clist_init(&patterns);
1636 clist_init(&osd_opts);
1639 while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1643 check_interval = atol(optarg);
1644 if (check_interval <= 0)
1657 parse_options(optarg);
1660 minimum_priority = atol(optarg);
1663 add_osd_opt(optarg);
1671 while (optind < argc)
1672 add_pattern(argv[optind++]);
1680 int should_exit = 0;
1682 while (!should_exit)
1684 time_t now = time(NULL);
1685 int remains = last_scan_time + check_interval - now;
1693 halfdelay((remains > 255) ? 255 : remains);
1697 if (is_active && handle_incsearch(ch))
1702 for (int i=0; i<num_mboxes; i++)
1703 if (ch == mbox_array[i]->o.hotkey)
1707 mbox_run(mbox_array[i]);
1717 move_cursor(cursor_at+1);
1721 move_cursor(cursor_at-1);
1731 move_cursor(cursor_max-1);
1734 next_active(cursor_at+1, 1);
1737 next_active(cursor_at-1, -1);
1741 if (cursor_at < cursor_max)
1742 mbox_run(mbox_array[cursor_at]);
1745 clearok(stdscr, TRUE);
1754 print_status("Bells and whistles are now enabled. Toot!");
1758 print_status("Bells and whistles are now disabled. Pssst!");
1762 print_status("On-screen display is now enabled.");
1766 print_status("On-screen display is now disabled. Watch your step.");
1772 if (ch >= '0' && ch <= '9')
1774 minimum_priority = ch - '0';
1778 debug("Pressed unknown key %d\n", ch);