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))
784 b->force_refresh = force_refresh;
785 if (stat(b->path, &st) < 0)
787 b->total = b->new = b->flagged = -1;
792 time_t current_mtime;
795 if (S_ISREG(st.st_mode))
799 current_mtime = st.st_mtime;
800 current_size = st.st_size;
803 else if (S_ISDIR(st.st_mode))
807 current_mtime = mdir_mtime(b);
813 debug("neither file nor directory\n");
817 if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
820 redraw_line(b->index);
827 b->last_time = current_mtime;
828 b->last_size = current_size;
829 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
832 redraw_line(b->index);
834 b->force_refresh = 0;
836 else if (b->display_valid_until <= last_scan_time)
838 debug("not changed, but needs redraw\n");
839 redraw_line(b->index);
842 debug("not changed\n");
846 debug("Scan finished\n");
847 last_scan_time = time(NULL);
848 rethink_display(notify);
853 #include <X11/Xlib.h>
854 #include <X11/Xatom.h>
856 static Display *x11_dpy;
857 static unsigned leds_care, leds_have, leds_want;
860 static unsigned osd_care;
861 #define OSD_MSG_SIZE 1024
862 static char osd_last_msg[OSD_MSG_SIZE];
863 static time_t osd_last_time;
868 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
869 osd_care = (global_options.osd >= 0);
870 CLIST_FOR_EACH(struct option_node *, o, options)
873 leds_care |= (1 << o->o.led);
878 if (!leds_care && !osd_care)
880 debug("X11: No mailbox wants LEDs or OSD\n");
883 if (!getenv("DISPLAY"))
885 debug("X11: Do not have X display\n");
888 if (!(x11_dpy = XOpenDisplay(NULL)))
889 die("Cannot open X display, although the DISPLAY variable is set");
893 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
895 die("Cannot intern OSD_QUEUE atom");
897 // If OSD options contain no message, add one
899 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
904 add_osd_opt("=%40f");
905 add_osd_opt("=%40s");
907 add_osd_opt("=(and %m more)");
912 debug("X11: Initialized\n");
918 if (leds_want == leds_have)
921 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
922 for (int i=1; i<10; i++)
923 if (leds_care & (leds_have ^ leds_want) & (1 << i))
927 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
928 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
931 leds_have = leds_want;
937 if (!leds_care || !x11_dpy)
941 CLIST_FOR_EACH(struct mbox *, b, mboxes)
942 if (b->o.led > 0 && b->new)
943 leds_want |= (1 << b->o.led);
953 format_osd_string(char *dest, char *src, struct osd_params *par)
955 char *stop = dest + OSD_MSG_SIZE - 1;
958 while (*src && dest < stop)
964 while (*src >= '0' && *src <= '9')
965 size = 10*size + *src++ - '0';
966 if (!size || size > stop-dest)
977 arg = par->mbox->sender_snippet;
980 arg = par->mbox->subject_snippet;
983 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
986 if (par->total_new < 2)
988 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
1012 format_osd(char *msg, struct osd_params *par)
1021 unsigned have_text = 0;
1022 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
1024 char buf[OSD_MSG_SIZE];
1025 if (!format_osd_string(buf, n->val, par))
1027 if (!n->key[0] && buf[0])
1029 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1030 if (pos > OSD_MSG_SIZE-1)
1032 pos = sprintf(msg, "OSD message too long!\n");
1044 debug_osd_msg(char *msg)
1048 fprintf(stderr, "OSD: <");
1051 fputc((*msg != '\n' ? *msg : '|'), stderr);
1054 fprintf(stderr, ">\n");
1058 rethink_osd(int notify)
1060 if (!osd_care || !x11_dpy || !allow_osd)
1062 osd_last_msg[0] = 0;
1066 struct osd_params p = { .mbox = NULL, .total_new = 0 };
1067 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1070 p.total_new += b->new;
1071 if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1075 char new_msg[OSD_MSG_SIZE];
1076 format_osd(new_msg, &p);
1077 debug_osd_msg(new_msg);
1078 if (strcmp(new_msg, osd_last_msg))
1080 strcpy(osd_last_msg, new_msg);
1081 if (notify && new_msg[0])
1083 debug("OSD: Sending to daemon\n");
1084 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
1088 debug("OSD: No changes\n");
1089 osd_last_time = time(NULL);
1105 static void x11_init(void) { }
1106 static void rethink_leds(void) { }
1107 static void rethink_osd(int notify UNUSED) { }
1108 static void x11_cleanup(void) { }
1112 static int cursor_at, cursor_max;
1114 static unsigned is_active, is_pos; // incremental search
1115 static char is_buf[64];
1126 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
1134 struct mbox *b = mbox_array[i];
1135 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1136 int hi = b->o.highlight;
1137 unsigned namepos = 0;
1138 unsigned namelen = strlen(b->name);
1141 attrset(attrs[cc][hi][M_IDLE]);
1143 printw("%c ", b->o.hotkey);
1150 attrset(attrs[cc][hi][M_INCSEARCH]);
1151 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1152 addch(is_buf[namepos]);
1155 attrset(attrs[cc][hi][M_NEW]);
1156 else if (b->flagged && b->o.show_flagged)
1157 attrset(attrs[cc][hi][M_FLAG]);
1159 attrset(attrs[cc][hi][M_IDLE]);
1160 while (namepos < namelen)
1161 addch(b->name[namepos++]);
1162 while (namepos++ < 20)
1164 if (b->scanning < 0)
1166 else if (b->scanning)
1168 attrset(attrs[cc][hi][M_SCAN]);
1169 printw("[SCANNING]");
1171 else if (b->total < 0)
1173 attrset(attrs[cc][hi][M_BAD]);
1178 attrset(attrs[cc][hi][M_IDLE]);
1179 printw("%6d ", b->total);
1183 attrset(attrs[cc][hi][M_NEW]);
1184 printw("%6d ", b->new);
1185 attrset(attrs[cc][hi][M_IDLE]);
1186 int age = (last_scan_time - b->best_time);
1191 printw("%2d min ", age/60);
1194 else if (age < 86400)
1195 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1200 else if (b->flagged && b->o.show_flagged)
1202 attrset(attrs[cc][hi][M_FLAG]);
1203 printw("%6d ", b->flagged);
1204 attrset(attrs[cc][hi][M_IDLE]);
1206 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1209 if (snip && b->o.snippets)
1212 getyx(stdscr, yy, xx);
1213 int remains = COLS-1-xx;
1217 build_snippet(snip, snip + sizeof(snip) - 1, b);
1219 if (snip[0] && remains > 2)
1221 #ifdef CONFIG_WIDE_CURSES
1222 size_t len = strlen(snip)+1;
1224 mbstowcs(snip2, snip, len);
1225 addnwstr(snip2, remains);
1227 printw("%-.*s", remains, snip);
1232 b->display_valid_until = last_scan_time + valid;
1234 attrset(attrs[0][0][M_IDLE]);
1241 cursor_max = num_mboxes;
1242 if (cursor_max > LINES-1)
1243 cursor_max = LINES-1;
1244 if (cursor_at >= cursor_max)
1245 cursor_at = cursor_max - 1;
1249 for (int i=0; i<cursor_max; i++)
1251 move(cursor_max, 0);
1254 printw("(no mailboxes found)");
1262 rethink_display(int notify)
1267 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1268 if (mbox_visible_p(b))
1271 if (i >= num_mboxes || mbox_array[i] != b)
1274 if (i >= mbox_array_size)
1276 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1277 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1281 if (b->o.beep && b->new > b->last_beep_new)
1283 b->last_beep_new = b->new;
1286 if (i != num_mboxes)
1296 rethink_osd(notify);
1297 if (beeeep && allow_bells && notify)
1308 intrflush(stdscr, FALSE);
1309 keypad(stdscr, TRUE);
1312 static const int attrs_mono[2][M_MAX] = {
1313 [0] = { [M_IDLE] = 0,
1318 [M_INCSEARCH] = A_REVERSE },
1319 [1] = { [M_IDLE] = 0,
1321 [M_NEW] = A_REVERSE | A_BOLD,
1322 [M_FLAG] = A_REVERSE,
1324 [M_INCSEARCH] = A_REVERSE },
1326 for (int i=0; i<2; i++)
1327 for (int j=0; j<M_MAX; j++)
1329 attrs[0][i][j] = attrs_mono[i][j];
1330 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1331 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1337 if (COLOR_PAIRS >= 12)
1339 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1340 init_pair(2, COLOR_RED, COLOR_BLACK);
1341 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1342 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1343 init_pair(5, COLOR_RED, COLOR_BLUE);
1344 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1345 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1346 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1347 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1348 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1349 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1350 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1351 static const int attrs_color[3][2][M_MAX] = {
1352 [0][0] = { [M_IDLE] = 0,
1353 [M_SCAN] = COLOR_PAIR(1),
1354 [M_NEW] = COLOR_PAIR(1),
1355 [M_FLAG] = COLOR_PAIR(6),
1356 [M_BAD] = COLOR_PAIR(2) },
1357 [0][1] = { [M_IDLE] = A_BOLD,
1358 [M_SCAN] = COLOR_PAIR(1),
1359 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1360 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1361 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1362 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1363 [M_SCAN] = COLOR_PAIR(4),
1364 [M_NEW] = COLOR_PAIR(4),
1365 [M_FLAG] = COLOR_PAIR(7),
1366 [M_BAD] = COLOR_PAIR(5) },
1367 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1368 [M_SCAN] = COLOR_PAIR(4),
1369 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1370 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1371 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1372 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1373 [M_SCAN] = COLOR_PAIR(9),
1374 [M_NEW] = COLOR_PAIR(9),
1375 [M_FLAG] = COLOR_PAIR(10),
1376 [M_BAD] = COLOR_PAIR(11),
1377 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1378 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1379 [M_SCAN] = COLOR_PAIR(9),
1380 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1381 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1382 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1383 [M_INCSEARCH] = COLOR_PAIR(12) },
1385 memcpy(attrs, attrs_color, sizeof(attrs));
1397 print_status(char *status)
1401 printw("%s", status);
1407 scan_and_redraw(int notify)
1409 print_status("Busy...");
1417 if (i >= 0 && i < cursor_max && i != cursor_at)
1419 int old = cursor_at;
1427 next_active(int since, int step)
1431 since = (since+cursor_max) % cursor_max;
1432 step = (step+cursor_max) % cursor_max;
1438 struct mbox *b = mbox_array[i];
1449 if (b->new && b->o.priority > bestp)
1452 bestp = b->o.priority;
1455 i = (i+step) % cursor_max;
1463 mbox_run(struct mbox *b)
1465 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1466 sprintf(cmd, run_cmd, b->path);
1472 b->force_refresh = 1;
1477 enter_incsearch(void)
1479 print_status("Incremental search...");
1482 redraw_line(cursor_at);
1486 handle_incsearch(int ch)
1488 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1490 else if (ch >= ' ' && ch <= '~')
1492 if (is_pos < sizeof(is_buf) - 1)
1493 is_buf[is_pos++] = ch;
1500 redraw_line(cursor_at);
1505 for (int i=0; i<cursor_max; i++)
1507 struct mbox *b = mbox_array[i];
1508 if (!strncmp(b->name, is_buf, is_pos))
1519 redraw_line(cursor_at);
1524 #define STR(c) STR2(c)
1529 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1532 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1533 -d\t\t\tLog debug messages to stderr\n\
1534 -i\t\t\tInclude user's INBOX\n\
1535 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1536 -o <pattern>=<opts>\tSet mailbox options\n\
1537 -o <opts>\t\tSet default options for all mailboxes\n\
1538 -p <pri>\t\tSet minimum priority to show\n\
1539 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1540 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1542 Mailbox options (set with `-o', use upper case to negate):\n\
1543 0-9\t\t\tSet mailbox priority (0=default)\n\
1544 b\t\t\tBeep when a message arrives\n\
1545 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1546 e\t\t\tHide from display if empty\n\
1547 f\t\t\tShow flagged messages if there are no new ones\n\
1548 h\t\t\tHide from display\n\
1549 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1550 m\t\t\tShow mailbox name of the sender\n\
1551 o\t\t\tCount old, but unread messages as new\n\
1552 p\t\t\tShow personal info (full name) of the sender\n\
1553 s\t\t\tShow message snippets\n\
1554 t\t\t\tHighlight the entry\n\
1555 !<key>\t\t\tSet hot key\n\
1557 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1558 It can be freely distributed and used according to the GNU GPL v2.\n\
1564 parse_options(char *c)
1568 if (sep = strchr(c, '='))
1570 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1571 memcpy(n->pattern, c, sep-c);
1572 n->pattern[sep-c] = 0;
1573 clist_add_tail(&options, &n->n);
1579 o = &global_options;
1583 if (x >= '0' && x <= '9')
1584 o->priority = x - '0';
1585 else if (x == '!' && *c)
1587 else if (x == 'l' && *c >= '1' && *c <= '9')
1588 o->led = *c++ - '0';
1591 int value = !!islower(x);
1601 o->hide_if_empty = value;
1604 o->show_flagged = value;
1610 o->sender_mbox = value;
1613 o->unread_is_new = value;
1616 o->sender_personal = value;
1619 o->snippets = value;
1622 o->highlight = value;
1625 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1632 main(int argc, char **argv)
1634 clist_init(&mboxes);
1635 clist_init(&options);
1636 clist_init(&patterns);
1637 clist_init(&osd_opts);
1640 while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1644 check_interval = atol(optarg);
1645 if (check_interval <= 0)
1658 parse_options(optarg);
1661 minimum_priority = atol(optarg);
1664 add_osd_opt(optarg);
1672 while (optind < argc)
1673 add_pattern(argv[optind++]);
1681 int should_exit = 0;
1683 while (!should_exit)
1685 time_t now = time(NULL);
1686 int remains = last_scan_time + check_interval - now;
1694 halfdelay((remains > 255) ? 255 : remains);
1698 if (is_active && handle_incsearch(ch))
1703 for (int i=0; i<num_mboxes; i++)
1704 if (ch == mbox_array[i]->o.hotkey)
1708 mbox_run(mbox_array[i]);
1718 move_cursor(cursor_at+1);
1722 move_cursor(cursor_at-1);
1732 move_cursor(cursor_max-1);
1735 next_active(cursor_at+1, 1);
1738 next_active(cursor_at-1, -1);
1742 if (cursor_at < cursor_max)
1743 mbox_run(mbox_array[cursor_at]);
1746 clearok(stdscr, TRUE);
1755 print_status("Bells and whistles are now enabled. Toot!");
1759 print_status("Bells and whistles are now disabled. Pssst!");
1763 print_status("On-screen display is now enabled.");
1767 print_status("On-screen display is now disabled. Watch your step.");
1773 if (ch >= '0' && ch <= '9')
1775 minimum_priority = ch - '0';
1779 debug("Pressed unknown key %d\n", ch);