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 // Another comma can separate extended flags (Dovecot extension)
684 for (int i=3; colon[i] && colon[i] != ','; i++)
697 if (b->o.unread_is_new && !seen)
706 debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
708 if (is_new || flagged)
710 // Need to pick the best message to display
712 if (stat(path, &st) < 0)
713 debug("[cannot stat %s: %m] ", path);
714 else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
716 b->best_is_new = is_new;
717 b->best_time = st.st_mtime;
718 strcpy(b->mdir_best, path + dir_len + 1);
725 if (b->best_time && b->o.snippets)
727 debug("best <%s> ", b->mdir_best);
735 debug("Searching for mailboxes (notify=%d)...\n", notify);
736 last_scan_time = time(NULL);
737 CLIST_FOR_EACH(struct pattern_node *, p, patterns)
739 debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
741 int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
742 if (err && err != GLOB_NOMATCH)
743 die("Failed to glob %s: %m", p->pattern);
744 for (uns i=0; i<g.gl_pathc; i++)
746 char *name = g.gl_pathv[i];
747 struct mbox *b = find_mbox(&mboxes, name);
750 b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
751 debug("Discovered mailbox %s (%s)\n", b->name, b->path);
761 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
767 debug("Lost mailbox %s\n", b->name);
774 debug("Scanning mailboxes...\n");
775 CLIST_FOR_EACH(struct mbox *, b, mboxes)
778 debug("%s: ", b->name);
779 if (!mbox_active_p(b))
785 b->force_refresh = 1;
786 if (stat(b->path, &st) < 0)
788 b->total = b->new = b->flagged = -1;
793 time_t current_mtime;
796 if (S_ISREG(st.st_mode))
800 current_mtime = st.st_mtime;
801 current_size = st.st_size;
804 else if (S_ISDIR(st.st_mode))
808 current_mtime = mdir_mtime(b);
814 debug("neither file nor directory\n");
818 if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
821 redraw_line(b->index);
828 b->last_time = current_mtime;
829 b->last_size = current_size;
830 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
833 redraw_line(b->index);
835 b->force_refresh = 0;
837 else if (b->display_valid_until <= last_scan_time)
839 debug("not changed, but needs redraw\n");
840 redraw_line(b->index);
843 debug("not changed\n");
847 debug("Scan finished\n");
848 last_scan_time = time(NULL);
849 rethink_display(notify);
854 #include <X11/Xlib.h>
855 #include <X11/Xatom.h>
857 static Display *x11_dpy;
858 static unsigned leds_care, leds_have, leds_want;
861 static unsigned osd_care;
862 #define OSD_MSG_SIZE 1024
863 static char osd_last_msg[OSD_MSG_SIZE];
864 static time_t osd_last_time;
869 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
870 osd_care = (global_options.osd >= 0);
871 CLIST_FOR_EACH(struct option_node *, o, options)
874 leds_care |= (1 << o->o.led);
879 if (!leds_care && !osd_care)
881 debug("X11: No mailbox wants LEDs or OSD\n");
884 if (!getenv("DISPLAY"))
886 debug("X11: Do not have X display\n");
889 if (!(x11_dpy = XOpenDisplay(NULL)))
890 die("Cannot open X display, although the DISPLAY variable is set");
894 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
896 die("Cannot intern OSD_QUEUE atom");
898 // If OSD options contain no message, add one
900 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
905 add_osd_opt("=%40f");
906 add_osd_opt("=%40s");
908 add_osd_opt("=(and %m more)");
913 debug("X11: Initialized\n");
919 if (leds_want == leds_have)
922 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
923 for (int i=1; i<10; i++)
924 if (leds_care & (leds_have ^ leds_want) & (1 << i))
928 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
929 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
932 leds_have = leds_want;
938 if (!leds_care || !x11_dpy)
942 CLIST_FOR_EACH(struct mbox *, b, mboxes)
943 if (b->o.led > 0 && b->new)
944 leds_want |= (1 << b->o.led);
954 format_osd_string(char *dest, char *src, struct osd_params *par)
956 char *stop = dest + OSD_MSG_SIZE - 1;
959 while (*src && dest < stop)
965 while (*src >= '0' && *src <= '9')
966 size = 10*size + *src++ - '0';
967 if (!size || size > stop-dest)
978 arg = par->mbox->sender_snippet;
981 arg = par->mbox->subject_snippet;
984 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
987 if (par->total_new < 2)
989 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
1013 format_osd(char *msg, struct osd_params *par)
1022 unsigned have_text = 0;
1023 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
1025 char buf[OSD_MSG_SIZE];
1026 if (!format_osd_string(buf, n->val, par))
1028 if (!n->key[0] && buf[0])
1030 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1031 if (pos > OSD_MSG_SIZE-1)
1033 pos = sprintf(msg, "OSD message too long!\n");
1045 debug_osd_msg(char *msg)
1049 fprintf(stderr, "OSD: <");
1052 fputc((*msg != '\n' ? *msg : '|'), stderr);
1055 fprintf(stderr, ">\n");
1059 rethink_osd(int notify)
1061 if (!osd_care || !x11_dpy || !allow_osd)
1063 osd_last_msg[0] = 0;
1067 struct osd_params p = { .mbox = NULL, .total_new = 0 };
1068 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1071 p.total_new += b->new;
1072 if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1076 char new_msg[OSD_MSG_SIZE];
1077 format_osd(new_msg, &p);
1078 debug_osd_msg(new_msg);
1079 if (strcmp(new_msg, osd_last_msg))
1081 strcpy(osd_last_msg, new_msg);
1082 if (notify && new_msg[0])
1084 debug("OSD: Sending to daemon\n");
1085 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
1089 debug("OSD: No changes\n");
1090 osd_last_time = time(NULL);
1106 static void x11_init(void) { }
1107 static void rethink_leds(void) { }
1108 static void rethink_osd(int notify UNUSED) { }
1109 static void x11_cleanup(void) { }
1113 static int cursor_at, cursor_max;
1115 static unsigned is_active, is_pos; // incremental search
1116 static char is_buf[64];
1127 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
1135 struct mbox *b = mbox_array[i];
1136 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1137 int hi = b->o.highlight;
1138 unsigned namepos = 0;
1139 unsigned namelen = strlen(b->name);
1142 attrset(attrs[cc][hi][M_IDLE]);
1144 printw("%c ", b->o.hotkey);
1151 attrset(attrs[cc][hi][M_INCSEARCH]);
1152 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1153 addch(is_buf[namepos]);
1156 attrset(attrs[cc][hi][M_NEW]);
1157 else if (b->flagged && b->o.show_flagged)
1158 attrset(attrs[cc][hi][M_FLAG]);
1160 attrset(attrs[cc][hi][M_IDLE]);
1161 while (namepos < namelen)
1162 addch(b->name[namepos++]);
1163 while (namepos++ < 20)
1165 if (b->scanning < 0)
1167 else if (b->scanning)
1169 attrset(attrs[cc][hi][M_SCAN]);
1170 printw("[SCANNING]");
1172 else if (b->total < 0)
1174 attrset(attrs[cc][hi][M_BAD]);
1179 attrset(attrs[cc][hi][M_IDLE]);
1180 printw("%6d ", b->total);
1184 attrset(attrs[cc][hi][M_NEW]);
1185 printw("%6d ", b->new);
1186 attrset(attrs[cc][hi][M_IDLE]);
1187 int age = (last_scan_time - b->best_time);
1192 printw("%2d min ", age/60);
1195 else if (age < 86400)
1196 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1201 else if (b->flagged && b->o.show_flagged)
1203 attrset(attrs[cc][hi][M_FLAG]);
1204 printw("%6d ", b->flagged);
1205 attrset(attrs[cc][hi][M_IDLE]);
1207 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1210 if (snip && b->o.snippets)
1213 getyx(stdscr, yy, xx);
1214 int remains = COLS-1-xx;
1218 build_snippet(snip, snip + sizeof(snip) - 1, b);
1220 if (snip[0] && remains > 2)
1222 #ifdef CONFIG_WIDE_CURSES
1223 size_t len = strlen(snip)+1;
1225 mbstowcs(snip2, snip, len);
1226 addnwstr(snip2, remains);
1228 printw("%-.*s", remains, snip);
1233 b->display_valid_until = last_scan_time + valid;
1235 attrset(attrs[0][0][M_IDLE]);
1242 cursor_max = num_mboxes;
1243 if (cursor_max > LINES-1)
1244 cursor_max = LINES-1;
1245 if (cursor_at >= cursor_max)
1246 cursor_at = cursor_max - 1;
1250 for (int i=0; i<cursor_max; i++)
1252 move(cursor_max, 0);
1255 printw("(no mailboxes found)");
1263 rethink_display(int notify)
1268 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1269 if (mbox_visible_p(b))
1272 if (i >= num_mboxes || mbox_array[i] != b)
1275 if (i >= mbox_array_size)
1277 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1278 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1282 if (b->o.beep && b->new > b->last_beep_new)
1284 b->last_beep_new = b->new;
1287 if (i != num_mboxes)
1297 rethink_osd(notify);
1298 if (beeeep && allow_bells && notify)
1309 intrflush(stdscr, FALSE);
1310 keypad(stdscr, TRUE);
1313 static const int attrs_mono[2][M_MAX] = {
1314 [0] = { [M_IDLE] = 0,
1319 [M_INCSEARCH] = A_REVERSE },
1320 [1] = { [M_IDLE] = 0,
1322 [M_NEW] = A_REVERSE | A_BOLD,
1323 [M_FLAG] = A_REVERSE,
1325 [M_INCSEARCH] = A_REVERSE },
1327 for (int i=0; i<2; i++)
1328 for (int j=0; j<M_MAX; j++)
1330 attrs[0][i][j] = attrs_mono[i][j];
1331 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1332 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1338 if (COLOR_PAIRS >= 12)
1340 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1341 init_pair(2, COLOR_RED, COLOR_BLACK);
1342 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1343 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1344 init_pair(5, COLOR_RED, COLOR_BLUE);
1345 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1346 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1347 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1348 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1349 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1350 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1351 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1352 static const int attrs_color[3][2][M_MAX] = {
1353 [0][0] = { [M_IDLE] = 0,
1354 [M_SCAN] = COLOR_PAIR(1),
1355 [M_NEW] = COLOR_PAIR(1),
1356 [M_FLAG] = COLOR_PAIR(6),
1357 [M_BAD] = COLOR_PAIR(2) },
1358 [0][1] = { [M_IDLE] = A_BOLD,
1359 [M_SCAN] = COLOR_PAIR(1),
1360 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1361 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1362 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1363 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1364 [M_SCAN] = COLOR_PAIR(4),
1365 [M_NEW] = COLOR_PAIR(4),
1366 [M_FLAG] = COLOR_PAIR(7),
1367 [M_BAD] = COLOR_PAIR(5) },
1368 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1369 [M_SCAN] = COLOR_PAIR(4),
1370 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1371 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1372 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1373 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1374 [M_SCAN] = COLOR_PAIR(9),
1375 [M_NEW] = COLOR_PAIR(9),
1376 [M_FLAG] = COLOR_PAIR(10),
1377 [M_BAD] = COLOR_PAIR(11),
1378 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1379 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1380 [M_SCAN] = COLOR_PAIR(9),
1381 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1382 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1383 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1384 [M_INCSEARCH] = COLOR_PAIR(12) },
1386 memcpy(attrs, attrs_color, sizeof(attrs));
1398 print_status(char *status)
1402 printw("%s", status);
1408 scan_and_redraw(int notify)
1410 print_status("Busy...");
1418 if (i >= 0 && i < cursor_max && i != cursor_at)
1420 int old = cursor_at;
1428 next_active(int since, int step)
1432 since = (since+cursor_max) % cursor_max;
1433 step = (step+cursor_max) % cursor_max;
1439 struct mbox *b = mbox_array[i];
1450 if (b->new && b->o.priority > bestp)
1453 bestp = b->o.priority;
1456 i = (i+step) % cursor_max;
1464 mbox_run(struct mbox *b)
1466 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1467 sprintf(cmd, run_cmd, b->path);
1473 b->force_refresh = 1;
1478 enter_incsearch(void)
1480 print_status("Incremental search...");
1483 redraw_line(cursor_at);
1487 handle_incsearch(int ch)
1489 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1491 else if (ch >= ' ' && ch <= '~')
1493 if (is_pos < sizeof(is_buf) - 1)
1494 is_buf[is_pos++] = ch;
1501 redraw_line(cursor_at);
1506 for (int i=0; i<cursor_max; i++)
1508 struct mbox *b = mbox_array[i];
1509 if (!strncmp(b->name, is_buf, is_pos))
1520 redraw_line(cursor_at);
1525 #define STR(c) STR2(c)
1530 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1533 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1534 -d\t\t\tLog debug messages to stderr\n\
1535 -i\t\t\tInclude user's INBOX\n\
1536 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1537 -o <pattern>=<opts>\tSet mailbox options\n\
1538 -o <opts>\t\tSet default options for all mailboxes\n\
1539 -p <pri>\t\tSet minimum priority to show\n\
1540 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1541 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1543 Mailbox options (set with `-o', use upper case to negate):\n\
1544 0-9\t\t\tSet mailbox priority (0=default)\n\
1545 b\t\t\tBeep when a message arrives\n\
1546 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1547 e\t\t\tHide from display if empty\n\
1548 f\t\t\tShow flagged messages if there are no new ones\n\
1549 h\t\t\tHide from display\n\
1550 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1551 m\t\t\tShow mailbox name of the sender\n\
1552 o\t\t\tCount old, but unread messages as new\n\
1553 p\t\t\tShow personal info (full name) of the sender\n\
1554 s\t\t\tShow message snippets\n\
1555 t\t\t\tHighlight the entry\n\
1556 !<key>\t\t\tSet hot key\n\
1558 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1559 It can be freely distributed and used according to the GNU GPL v2.\n\
1565 parse_options(char *c)
1569 if (sep = strchr(c, '='))
1571 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1572 memcpy(n->pattern, c, sep-c);
1573 n->pattern[sep-c] = 0;
1574 clist_add_tail(&options, &n->n);
1580 o = &global_options;
1584 if (x >= '0' && x <= '9')
1585 o->priority = x - '0';
1586 else if (x == '!' && *c)
1588 else if (x == 'l' && *c >= '1' && *c <= '9')
1589 o->led = *c++ - '0';
1592 int value = !!islower(x);
1602 o->hide_if_empty = value;
1605 o->show_flagged = value;
1611 o->sender_mbox = value;
1614 o->unread_is_new = value;
1617 o->sender_personal = value;
1620 o->snippets = value;
1623 o->highlight = value;
1626 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1633 main(int argc, char **argv)
1635 clist_init(&mboxes);
1636 clist_init(&options);
1637 clist_init(&patterns);
1638 clist_init(&osd_opts);
1641 while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1645 check_interval = atol(optarg);
1646 if (check_interval <= 0)
1659 parse_options(optarg);
1662 minimum_priority = atol(optarg);
1665 add_osd_opt(optarg);
1673 while (optind < argc)
1674 add_pattern(argv[optind++]);
1682 int should_exit = 0;
1684 while (!should_exit)
1686 time_t now = time(NULL);
1687 int remains = last_scan_time + check_interval - now;
1695 halfdelay((remains > 255) ? 255 : remains);
1699 if (is_active && handle_incsearch(ch))
1704 for (int i=0; i<num_mboxes; i++)
1705 if (ch == mbox_array[i]->o.hotkey)
1709 mbox_run(mbox_array[i]);
1719 move_cursor(cursor_at+1);
1723 move_cursor(cursor_at-1);
1733 move_cursor(cursor_max-1);
1736 next_active(cursor_at+1, 1);
1739 next_active(cursor_at-1, -1);
1743 if (cursor_at < cursor_max)
1744 mbox_run(mbox_array[cursor_at]);
1747 clearok(stdscr, TRUE);
1756 print_status("Bells and whistles are now enabled. Toot!");
1760 print_status("Bells and whistles are now disabled. Pssst!");
1764 print_status("On-screen display is now enabled.");
1768 print_status("On-screen display is now disabled. Watch your step.");
1774 if (ch >= '0' && ch <= '9')
1776 minimum_priority = ch - '0';
1780 debug("Pressed unknown key %d\n", ch);