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>
26 #ifdef CONFIG_WIDE_CURSES
36 static int check_interval = 30;
37 static int force_refresh;
38 static int allow_bells = 1;
39 static int allow_osd = 1;
40 static int minimum_priority;
41 static time_t last_scan_time;
42 static char *run_cmd = "mutt -f %s";
43 static int simple_tab;
74 static clist options, patterns;
75 static struct options global_options = {
80 #define MDIR_MAX_NAME_LEN 128
92 time_t display_valid_until;
93 int last_size, last_pos;
94 int total, new, flagged;
95 int last_total, last_new, last_flagged;
99 char sender_snippet[128];
100 char subject_snippet[128];
101 char mdir_best[MDIR_MAX_NAME_LEN];
105 static struct mbox **mbox_array;
106 static int num_mboxes, mbox_array_size;
108 struct osd_opt_node {
114 static clist osd_opts;
116 static void redraw_line(int i);
117 static void rethink_display(int notify);
120 add_pattern(char *patt)
122 struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
123 strcpy(n->pattern, patt);
124 if (patt = strchr(n->pattern, '='))
131 clist_add_tail(&patterns, &n->n);
137 struct passwd *p = getpwuid(getuid());
139 die("You don't exist, go away!");
140 char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
141 sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
146 init_options(struct options *o)
150 o->hide_if_empty = -1;
154 o->show_flagged = -1;
155 o->sender_personal = -1;
160 o->unread_is_new = -1;
165 setup_options(struct mbox *b)
167 b->o = global_options;
168 CLIST_FOR_EACH(struct option_node *, n, options)
169 if (!fnmatch(n->pattern, b->name, 0))
171 debug("\tApplied options %s\n", n->pattern);
172 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
175 MERGE(hide_if_empty);
180 MERGE(sender_personal);
185 MERGE(unread_is_new);
191 add_osd_opt(char *arg)
193 struct osd_opt_node *n = xmalloc(sizeof(*n) + strlen(arg));
195 n->val = strchr(n->key, '=');
197 die("Malformed OSD option");
199 clist_add_tail(&osd_opts, &n->n);
203 mbox_name(char *path)
205 char *c = strrchr(path, '/');
216 new_mbox(char *path, char *name)
218 struct mbox *b = xmalloc(sizeof(*b));
219 bzero(b, sizeof(*b));
220 b->path = xstrdup(path);
221 b->name = xstrdup(name);
226 add_mbox(clist *l, struct mbox *b)
230 cnode *prev = l->head.prev;
231 struct mbox *prev_mbox;
232 while (prev != &l->head &&
233 ((prev_mbox = (struct mbox *)prev)->o.sort_order > b->o.sort_order ||
234 prev_mbox->o.sort_order == b->o.sort_order && strcmp(((struct mbox *)prev)->name, b->name) > 0))
236 clist_insert_after(&b->n, prev);
239 clist_add_tail(l, &b->n);
243 del_mbox(struct mbox *b)
252 find_mbox(clist *l, char *path)
254 CLIST_FOR_EACH(struct mbox *, b, *l)
255 if (!strcmp(b->path, path))
261 mbox_active_p(struct mbox *b)
263 if (b->o.priority < minimum_priority)
271 mbox_visible_p(struct mbox *b)
273 if (!mbox_active_p(b))
277 if (b->o.hide_if_empty && !b->total)
283 prepare_snippets(struct mbox *b, char *sender, char *subject)
287 while (*sender == ' ' || *sender == '\t')
289 while (*subject == ' ' || *subject == '\t')
292 pos = b->sender_snippet;
293 term = pos + sizeof(b->sender_snippet) - 1;
294 if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
295 add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal);
299 pos = b->subject_snippet;
300 term = pos + sizeof(b->subject_snippet) - 1;
302 add_subject_snippet(&pos, term, subject);
304 add_snippet_raw(&pos, term, "No subject");
308 build_snippet(char *buf, char *term, struct mbox *b)
310 if (b->sender_snippet[0])
312 add_snippet(&buf, term, b->sender_snippet);
313 add_snippet_raw(&buf, term, ": ");
315 add_snippet(&buf, term, b->subject_snippet);
318 static int mb_fd, mb_pos, mb_seekable;
319 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
324 mb_cc = mb_end = mb_buf;
331 lseek(mb_fd, pos, SEEK_SET);
338 return mb_pos - (mb_end - mb_cc);
344 int len = read(mb_fd, mb_buf, sizeof(mb_buf));
353 mb_end = mb_buf + len;
362 return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
373 mb_check(const char *p, int len)
377 if (mb_get() != *p++)
388 int avail = mb_end - mb_cc;
391 if (mb_seekable && len >= (int) sizeof(mb_buf))
393 int next = len - len % sizeof(mb_buf);
394 mb_seek(mb_tell() + next);
404 int next = (avail < len) ? avail : len;
411 #define HDR_BUF_SIZE 1024
414 read_hdr_field(char *buf)
422 debug("[truncated] ");
435 while (c == ' ' || c == '\t');
443 if (i < HDR_BUF_SIZE - 1)
448 debug("Header: <%s>\n", buf);
449 return buf[0] ? 1 : 0;
453 scan_mbox(struct mbox *b, struct stat *st)
455 char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
458 const char from[] = "\nFrom ";
460 b->best_time = st->st_mtime;
463 b->total = b->new = b->flagged = 0;
468 mb_fd = open(b->path, O_RDONLY);
471 debug("[open failed: %m] ");
472 b->total = b->new = b->flagged = -1;
478 c = read(mb_fd, signature, 2);
479 lseek(mb_fd, 0, SEEK_SET);
481 if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip
483 debug("[decompressing] ");
486 die("pipe failed: %m");
489 die("fork failed: %m");
492 if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0)
493 die("dup2 failed: %m");
497 execlp("gzip", "gzip", "-cd", NULL);
498 die("Cannot execute gzip: %m");
509 if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && mb_seekable)
511 mb_seek(b->last_pos);
512 if (mb_check(from, 6))
514 debug("[incremental] ");
519 debug("[incremental failed] ");
525 if (!mb_check(from+1, 5))
527 debug("[inconsistent] ");
528 b->total = b->new = b->flagged = -1;
531 b->total = b->new = b->flagged = 0;
532 b->last_total = b->last_new = b->last_flagged = 0;
537 b->total = b->last_total;
538 b->new = b->last_new;
539 b->flagged = b->last_flagged;
544 b->last_pos = mb_tell() - 5;
546 b->last_pos--; // last_pos should be the previous \n character
547 b->last_total = b->total;
548 b->last_new = b->new;
549 b->last_flagged = b->flagged;
550 while ((c = mb_get()) >= 0 && c != '\n')
553 int content_length = -1;
560 int c = read_hdr_field(buf);
565 if (!strncasecmp(buf, "Status:", 7))
567 if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
570 else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
572 else if (!strncasecmp(buf, "From:", 5))
573 strcpy(sender, buf+5);
574 else if (!strncasecmp(buf, "Subject:", 8))
575 strcpy(subject, buf+8);
576 else if (!strncasecmp(buf, "Content-Length:", 15))
577 content_length = atoi(buf + 15);
586 debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
587 if (new || (flagged && !b->best_is_new))
589 b->best_is_new = new;
590 prepare_snippets(b, sender, subject);
593 if (content_length >= 0)
594 mb_skip(content_length);
612 if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
613 b->total = b->new = b->flagged = -1;
618 mdir_mtime(struct mbox *b)
621 char path[strlen(b->path) + 5];
623 for (int new=0; new<2; new++)
625 sprintf(path, "%s/%s", b->path, (new ? "new" : "cur"));
627 if (stat(path, &st) < 0)
628 debug("[cannot stat %s] ", path);
629 else if (st.st_mtime > mtime)
636 mdir_get_snippet(struct mbox *b)
638 char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
642 char path[strlen(b->path) + MDIR_MAX_NAME_LEN];
643 sprintf(path, "%s/%s", b->path, b->mdir_best);
644 mb_fd = open(path, O_RDONLY);
647 debug("[open failed: %m] ");
648 prepare_snippets(b, sender, subject);
654 while (read_hdr_field(buf) > 0)
656 if (!strncasecmp(buf, "From:", 5))
657 strcpy(sender, buf+5);
658 else if (!strncasecmp(buf, "Subject:", 8))
659 strcpy(subject, buf+8);
663 prepare_snippets(b, sender, subject);
667 scan_mdir(struct mbox *b)
669 int dir_len = strlen(b->path);
670 char path[dir_len + MDIR_MAX_NAME_LEN];
671 strcpy(path, b->path);
673 b->total = b->new = b->flagged = 0;
677 for (int new=0; new<2; new++)
679 strcpy(path + dir_len, (new ? "/new" : "/cur"));
680 DIR *d = opendir(path);
683 debug("[cannot open %s: %m] ");
687 while (de = readdir(d))
689 char *name = de->d_name;
690 if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN)
692 path[dir_len + 4] = '/';
693 strcpy(path + dir_len + 5, name);
695 char *colon = strchr(name, ':');
698 if (colon && colon[1] == '2' && colon[2] == ',')
700 // Another comma can separate extended flags (Dovecot extension)
701 for (int i=3; colon[i] && colon[i] != ','; i++)
714 if (b->o.unread_is_new && !seen)
723 debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
725 if (is_new || flagged)
727 // Need to pick the best message to display
729 if (stat(path, &st) < 0)
730 debug("[cannot stat %s: %m] ", path);
731 else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
733 b->best_is_new = is_new;
734 b->best_time = st.st_mtime;
735 strcpy(b->mdir_best, path + dir_len + 1);
742 if (b->best_time && b->o.snippets)
744 debug("best <%s> ", b->mdir_best);
752 debug("Searching for mailboxes (notify=%d)...\n", notify);
753 last_scan_time = time(NULL);
754 CLIST_FOR_EACH(struct pattern_node *, p, patterns)
756 debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
758 int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
759 if (err && err != GLOB_NOMATCH)
760 die("Failed to glob %s: %m", p->pattern);
761 for (uns i=0; i<g.gl_pathc; i++)
763 char *name = g.gl_pathv[i];
764 struct mbox *b = find_mbox(&mboxes, name);
767 b = new_mbox(name, (p->name ? p->name : mbox_name(name)));
768 debug("Discovered mailbox %s (%s)\n", b->name, b->path);
770 add_mbox(&mboxes, b);
779 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
785 debug("Lost mailbox %s\n", b->name);
792 debug("Scanning mailboxes...\n");
793 CLIST_FOR_EACH(struct mbox *, b, mboxes)
796 debug("%s: ", b->name);
797 if (!mbox_active_p(b))
803 b->force_refresh = 1;
804 if (stat(b->path, &st) < 0)
806 b->total = b->new = b->flagged = -1;
811 time_t current_mtime;
814 if (S_ISREG(st.st_mode))
818 current_mtime = st.st_mtime;
819 current_size = st.st_size;
822 else if (S_ISDIR(st.st_mode))
826 current_mtime = mdir_mtime(b);
832 debug("neither file nor directory\n");
836 if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
839 redraw_line(b->index);
846 b->last_time = current_mtime;
847 b->last_size = current_size;
848 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
851 redraw_line(b->index);
853 b->force_refresh = 0;
855 else if (b->display_valid_until <= last_scan_time)
857 debug("not changed, but needs redraw\n");
858 redraw_line(b->index);
861 debug("not changed\n");
865 debug("Scan finished\n");
866 last_scan_time = time(NULL);
867 rethink_display(notify);
872 #include <X11/Xlib.h>
873 #include <X11/Xatom.h>
875 static Display *x11_dpy;
876 static unsigned leds_care, leds_have, leds_want;
879 static unsigned osd_care;
880 #define OSD_MSG_SIZE 1024
881 static char osd_last_msg[OSD_MSG_SIZE];
882 static time_t osd_last_time;
887 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
888 osd_care = (global_options.osd >= 0);
889 CLIST_FOR_EACH(struct option_node *, o, options)
892 leds_care |= (1 << o->o.led);
897 if (!leds_care && !osd_care)
899 debug("X11: No mailbox wants LEDs or OSD\n");
902 if (!getenv("DISPLAY"))
904 debug("X11: Do not have X display\n");
907 if (!(x11_dpy = XOpenDisplay(NULL)))
908 die("Cannot open X display, although the DISPLAY variable is set");
912 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
914 die("Cannot intern OSD_QUEUE atom");
916 // If OSD options contain no message, add one
918 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
923 add_osd_opt("=%40f");
924 add_osd_opt("=%40s");
926 add_osd_opt("=(and %m more)");
931 debug("X11: Initialized\n");
937 if (leds_want == leds_have)
940 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
941 for (int i=1; i<10; i++)
942 if (leds_care & (leds_have ^ leds_want) & (1 << i))
946 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
947 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
950 leds_have = leds_want;
956 if (!leds_care || !x11_dpy)
960 CLIST_FOR_EACH(struct mbox *, b, mboxes)
961 if (b->o.led > 0 && b->new)
962 leds_want |= (1 << b->o.led);
972 format_osd_string(char *dest, char *src, struct osd_params *par)
974 char *stop = dest + OSD_MSG_SIZE - 1;
977 while (*src && dest < stop)
983 while (*src >= '0' && *src <= '9')
984 size = 10*size + *src++ - '0';
985 if (!size || size > stop-dest)
996 arg = par->mbox->sender_snippet;
999 arg = par->mbox->subject_snippet;
1002 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
1005 if (par->total_new < 2)
1007 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
1017 while (*arg && size)
1031 format_osd(char *msg, struct osd_params *par)
1040 unsigned have_text = 0;
1041 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
1043 char buf[OSD_MSG_SIZE];
1044 if (!format_osd_string(buf, n->val, par))
1046 if (!n->key[0] && buf[0])
1048 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
1049 if (pos > OSD_MSG_SIZE-1)
1051 pos = sprintf(msg, "OSD message too long!\n");
1063 debug_osd_msg(char *msg)
1067 fprintf(stderr, "OSD: <");
1070 fputc((*msg != '\n' ? *msg : '|'), stderr);
1073 fprintf(stderr, ">\n");
1077 rethink_osd(int notify)
1079 if (!osd_care || !x11_dpy || !allow_osd)
1081 osd_last_msg[0] = 0;
1085 struct osd_params p = { .mbox = NULL, .total_new = 0 };
1086 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1089 p.total_new += b->new;
1090 if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
1094 char new_msg[OSD_MSG_SIZE];
1095 format_osd(new_msg, &p);
1096 debug_osd_msg(new_msg);
1097 if (strcmp(new_msg, osd_last_msg))
1099 strcpy(osd_last_msg, new_msg);
1100 if (notify && new_msg[0])
1102 debug("OSD: Sending to daemon\n");
1103 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
1107 debug("OSD: No changes\n");
1108 osd_last_time = time(NULL);
1124 static void x11_init(void) { }
1125 static void rethink_leds(void) { }
1126 static void rethink_osd(int notify UNUSED) { }
1127 static void x11_cleanup(void) { }
1131 static int cursor_at, cursor_max;
1133 static unsigned is_active, is_pos; // incremental search
1134 static char is_buf[64];
1145 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
1153 struct mbox *b = mbox_array[i];
1154 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
1155 int hi = b->o.highlight;
1156 unsigned namepos = 0;
1157 unsigned namelen = strlen(b->name);
1160 attrset(attrs[cc][hi][M_IDLE]);
1162 printw("%c ", b->o.hotkey);
1169 attrset(attrs[cc][hi][M_INCSEARCH]);
1170 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
1171 addch(is_buf[namepos]);
1174 attrset(attrs[cc][hi][M_NEW]);
1175 else if (b->flagged && b->o.show_flagged)
1176 attrset(attrs[cc][hi][M_FLAG]);
1178 attrset(attrs[cc][hi][M_IDLE]);
1179 while (namepos < namelen)
1180 addch(b->name[namepos++]);
1181 while (namepos++ < 20)
1183 if (b->scanning < 0)
1185 else if (b->scanning)
1187 attrset(attrs[cc][hi][M_SCAN]);
1188 printw("[SCANNING]");
1190 else if (b->total < 0)
1192 attrset(attrs[cc][hi][M_BAD]);
1197 attrset(attrs[cc][hi][M_IDLE]);
1198 printw("%6d ", b->total);
1202 attrset(attrs[cc][hi][M_NEW]);
1203 printw("%6d ", b->new);
1204 attrset(attrs[cc][hi][M_IDLE]);
1205 int age = (last_scan_time - b->best_time);
1210 printw("%2d min ", age/60);
1213 else if (age < 86400)
1214 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1219 else if (b->flagged && b->o.show_flagged)
1221 attrset(attrs[cc][hi][M_FLAG]);
1222 printw("%6d ", b->flagged);
1223 attrset(attrs[cc][hi][M_IDLE]);
1225 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1228 if (snip && b->o.snippets)
1231 getyx(stdscr, yy, xx);
1232 int remains = COLS-1-xx;
1236 build_snippet(snip, snip + sizeof(snip) - 1, b);
1238 if (snip[0] && remains > 2)
1240 #ifdef CONFIG_WIDE_CURSES
1241 size_t len = strlen(snip)+1;
1243 mbstowcs(snip2, snip, len);
1244 addnwstr(snip2, remains);
1246 printw("%-.*s", remains, snip);
1251 b->display_valid_until = last_scan_time + valid;
1253 attrset(attrs[0][0][M_IDLE]);
1260 cursor_max = num_mboxes;
1261 if (cursor_max > LINES-1)
1262 cursor_max = LINES-1;
1263 if (cursor_at >= cursor_max)
1264 cursor_at = cursor_max - 1;
1268 for (int i=0; i<cursor_max; i++)
1270 move(cursor_max, 0);
1273 printw("(no mailboxes found)");
1281 rethink_display(int notify)
1286 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1287 if (mbox_visible_p(b))
1290 if (i >= num_mboxes || mbox_array[i] != b)
1293 if (i >= mbox_array_size)
1295 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1296 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1300 if (b->o.beep && b->new > b->last_beep_new)
1302 b->last_beep_new = b->new;
1305 if (i != num_mboxes)
1315 rethink_osd(notify);
1316 if (beeeep && allow_bells && notify)
1327 intrflush(stdscr, FALSE);
1328 keypad(stdscr, TRUE);
1331 static const int attrs_mono[2][M_MAX] = {
1332 [0] = { [M_IDLE] = 0,
1337 [M_INCSEARCH] = A_REVERSE },
1338 [1] = { [M_IDLE] = 0,
1340 [M_NEW] = A_REVERSE | A_BOLD,
1341 [M_FLAG] = A_REVERSE,
1343 [M_INCSEARCH] = A_REVERSE },
1345 for (int i=0; i<2; i++)
1346 for (int j=0; j<M_MAX; j++)
1348 attrs[0][i][j] = attrs_mono[i][j];
1349 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1350 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1356 if (COLOR_PAIRS >= 12)
1358 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1359 init_pair(2, COLOR_RED, COLOR_BLACK);
1360 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1361 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1362 init_pair(5, COLOR_RED, COLOR_BLUE);
1363 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1364 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1365 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1366 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1367 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1368 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1369 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1370 static const int attrs_color[3][2][M_MAX] = {
1371 [0][0] = { [M_IDLE] = 0,
1372 [M_SCAN] = COLOR_PAIR(1),
1373 [M_NEW] = COLOR_PAIR(1),
1374 [M_FLAG] = COLOR_PAIR(6),
1375 [M_BAD] = COLOR_PAIR(2) },
1376 [0][1] = { [M_IDLE] = A_BOLD,
1377 [M_SCAN] = COLOR_PAIR(1),
1378 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1379 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1380 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1381 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1382 [M_SCAN] = COLOR_PAIR(4),
1383 [M_NEW] = COLOR_PAIR(4),
1384 [M_FLAG] = COLOR_PAIR(7),
1385 [M_BAD] = COLOR_PAIR(5) },
1386 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1387 [M_SCAN] = COLOR_PAIR(4),
1388 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1389 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1390 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1391 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1392 [M_SCAN] = COLOR_PAIR(9),
1393 [M_NEW] = COLOR_PAIR(9),
1394 [M_FLAG] = COLOR_PAIR(10),
1395 [M_BAD] = COLOR_PAIR(11),
1396 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1397 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1398 [M_SCAN] = COLOR_PAIR(9),
1399 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1400 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1401 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1402 [M_INCSEARCH] = COLOR_PAIR(12) },
1404 memcpy(attrs, attrs_color, sizeof(attrs));
1416 print_status(char *status)
1420 printw("%s", status);
1426 scan_and_redraw(int notify)
1428 print_status("Busy...");
1436 if (i >= 0 && i < cursor_max && i != cursor_at)
1438 int old = cursor_at;
1446 next_active(int since, int step)
1450 since = (since+cursor_max) % cursor_max;
1451 step = (step+cursor_max) % cursor_max;
1457 struct mbox *b = mbox_array[i];
1468 if (b->new && b->o.priority > bestp)
1471 bestp = b->o.priority;
1474 i = (i+step) % cursor_max;
1482 mbox_run(struct mbox *b)
1484 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1485 sprintf(cmd, run_cmd, b->path);
1491 b->force_refresh = 1;
1496 enter_incsearch(void)
1498 print_status("Incremental search...");
1501 redraw_line(cursor_at);
1505 handle_incsearch(int ch)
1507 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1509 else if (ch >= ' ' && ch <= '~')
1511 if (is_pos < sizeof(is_buf) - 1)
1512 is_buf[is_pos++] = ch;
1519 redraw_line(cursor_at);
1524 for (int i=0; i<cursor_max; i++)
1526 struct mbox *b = mbox_array[i];
1527 if (!strncmp(b->name, is_buf, is_pos))
1538 redraw_line(cursor_at);
1543 #define STR(c) STR2(c)
1548 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1551 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1552 -d\t\t\tLog debug messages to stderr\n\
1553 -i\t\t\tInclude user's INBOX (/var/mail/<user>=INBOX)\n\
1554 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1555 -o <pattern>=<opts>\tSet mailbox options\n\
1556 -o <opts>\t\tSet default options for all mailboxes\n\
1557 -p <pri>\t\tSet minimum priority to show\n\
1558 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1559 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1561 On-screen display replacement (in all OSD values and notification lines):\n\
1563 %%f\t\tFrom - mail author name\n\
1564 %%s\t\tMail subject\n\
1565 %%n\t\tCount of new mails\n\
1566 %%m\t\tCount of new mails minus one; if is less than one, whole line will be hidden\n\
1568 Mailbox options (set with `-o', use upper case to negate):\n\
1569 0-9\t\t\tSet mailbox priority (0=default)\n\
1570 b\t\t\tBeep when a message arrives\n\
1571 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1572 e\t\t\tHide from display if empty\n\
1573 f\t\t\tShow flagged messages if there are no new ones\n\
1574 h\t\t\tHide from display\n\
1575 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1576 m\t\t\tShow mailbox name of the sender\n\
1577 o\t\t\tCount old, but unread messages as new\n\
1578 p\t\t\tShow personal info (full name) of the sender\n\
1579 r<int>\t\t\tSort order (default=1000)\n\
1580 s\t\t\tShow message snippets\n\
1581 t\t\t\tHighlight the entry\n\
1582 !<key>\t\t\tSet hot key\n\
1584 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>, Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>\n\
1585 It can be freely distributed and used according to the GNU GPL v2.\n\
1591 parse_options(char *c)
1595 if (sep = strchr(c, '='))
1597 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1598 memcpy(n->pattern, c, sep-c);
1599 n->pattern[sep-c] = 0;
1600 clist_add_tail(&options, &n->n);
1606 o = &global_options;
1610 if (x >= '0' && x <= '9')
1611 o->priority = x - '0';
1612 else if (x == '!' && *c)
1614 else if (x == 'l' && *c >= '1' && *c <= '9')
1615 o->led = *c++ - '0';
1616 else if (x == 'r' && *c >= '0' && *c <= '9')
1619 while (*c >= '0' && *c <= '9')
1620 o->sort_order = 10*o->sort_order + *c++ - '0';
1624 int value = !!islower(x);
1634 o->hide_if_empty = value;
1637 o->show_flagged = value;
1643 o->sender_mbox = value;
1646 o->unread_is_new = value;
1649 o->sender_personal = value;
1652 o->snippets = value;
1655 o->highlight = value;
1658 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1665 main(int argc, char **argv)
1667 clist_init(&mboxes);
1668 clist_init(&options);
1669 clist_init(&patterns);
1670 clist_init(&osd_opts);
1673 while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1677 check_interval = atol(optarg);
1678 if (check_interval <= 0)
1691 parse_options(optarg);
1694 minimum_priority = atol(optarg);
1697 add_osd_opt(optarg);
1705 while (optind < argc)
1706 add_pattern(argv[optind++]);
1714 int should_exit = 0;
1716 while (!should_exit)
1718 time_t now = time(NULL);
1719 int remains = last_scan_time + check_interval - now;
1727 halfdelay((remains > 255) ? 255 : remains);
1731 if (is_active && handle_incsearch(ch))
1736 for (int i=0; i<num_mboxes; i++)
1737 if (ch == mbox_array[i]->o.hotkey)
1741 mbox_run(mbox_array[i]);
1751 move_cursor(cursor_at+1);
1755 move_cursor(cursor_at-1);
1765 move_cursor(cursor_max-1);
1768 next_active(cursor_at+1, 1);
1771 next_active(cursor_at-1, -1);
1775 if (cursor_at < cursor_max)
1776 mbox_run(mbox_array[cursor_at]);
1779 clearok(stdscr, TRUE);
1788 print_status("Bells and whistles are now enabled. Toot!");
1792 print_status("Bells and whistles are now disabled. Pssst!");
1796 print_status("On-screen display is now enabled.");
1800 print_status("On-screen display is now disabled. Watch your step.");
1806 if (ch >= '0' && ch <= '9')
1808 minimum_priority = ch - '0';
1812 debug("Pressed unknown key %d\n", ch);