2 * Incoming Mail Checker
4 * (c) 2005--2010 Martin Mares <mj@ucw.cz>
21 #include <sys/types.h>
24 #ifdef CONFIG_WIDE_CURSES
25 #include <ncursesw/ncurses.h>
34 static int check_interval = 30;
35 static int force_refresh;
36 static int allow_bells = 1;
37 static int allow_osd = 1;
38 static int minimum_priority;
39 static time_t last_scan_time;
40 static char *run_cmd = "mutt -f %s";
41 static int simple_tab;
71 static clist options, patterns;
72 static struct options global_options = {
85 time_t display_valid_until;
86 int last_size, last_pos;
87 int total, new, flagged;
88 int last_total, last_new, last_flagged;
92 char sender_snippet[128];
93 char subject_snippet[128];
97 static struct mbox **mbox_array;
98 static int num_mboxes, mbox_array_size;
100 struct osd_opt_node {
106 static clist osd_opts;
108 static void redraw_line(int i);
109 static void rethink_display(int notify);
112 add_pattern(char *patt)
114 struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt));
115 strcpy(n->pattern, patt);
116 if (patt = strchr(n->pattern, '='))
123 clist_add_tail(&patterns, &n->n);
129 struct passwd *p = getpwuid(getuid());
131 die("You don't exist, go away!");
132 char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7];
133 sprintf(buf, "/var/mail/%s=INBOX", p->pw_name);
138 init_options(struct options *o)
142 o->hide_if_empty = -1;
146 o->show_flagged = -1;
147 o->sender_personal = -1;
152 o->unread_is_new = -1;
156 setup_options(struct mbox *b)
158 b->o = global_options;
159 CLIST_FOR_EACH(struct option_node *, n, options)
160 if (!fnmatch(n->pattern, b->name, 0))
162 debug("\tApplied options %s\n", n->pattern);
163 #define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f
166 MERGE(hide_if_empty);
171 MERGE(sender_personal);
176 MERGE(unread_is_new);
181 add_osd_opt(char *arg)
183 struct osd_opt_node *n = xmalloc(sizeof(*n) + strlen(arg));
185 n->val = strchr(n->key, '=');
187 die("Malformed OSD option");
189 clist_add_tail(&osd_opts, &n->n);
193 mbox_name(char *path)
195 char *c = strrchr(path, '/');
196 return c ? (c+1) : path;
200 add_mbox(clist *l, char *path, char *name)
202 struct mbox *b = xmalloc(sizeof(*b));
203 bzero(b, sizeof(*b));
204 b->path = xstrdup(path);
205 b->name = xstrdup(name);
209 cnode *prev = l->head.prev;
210 while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0)
212 clist_insert_after(&b->n, prev);
215 clist_add_tail(l, &b->n);
221 del_mbox(struct mbox *b)
230 find_mbox(clist *l, char *path)
232 CLIST_FOR_EACH(struct mbox *, b, *l)
233 if (!strcmp(b->path, path))
239 mbox_active_p(struct mbox *b)
241 if (b->o.priority < minimum_priority)
249 mbox_visible_p(struct mbox *b)
251 if (!mbox_active_p(b))
255 if (b->o.hide_if_empty && !b->total)
261 prepare_snippets(struct mbox *b, char *sender, char *subject)
265 while (*sender == ' ' || *sender == '\t')
267 while (*subject == ' ' || *subject == '\t')
270 pos = b->sender_snippet;
271 term = pos + sizeof(b->sender_snippet) - 1;
272 if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
273 add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal);
277 pos = b->subject_snippet;
278 term = pos + sizeof(b->subject_snippet) - 1;
280 add_subject_snippet(&pos, term, subject);
282 add_snippet_raw(&pos, term, "No subject");
286 build_snippet(char *buf, char *term, struct mbox *b)
288 if (b->sender_snippet[0])
290 add_snippet(&buf, term, b->sender_snippet);
291 add_snippet_raw(&buf, term, ": ");
293 add_snippet(&buf, term, b->subject_snippet);
296 static int mb_fd, mb_pos;
297 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
302 mb_cc = mb_end = mb_buf;
309 lseek(mb_fd, pos, SEEK_SET);
316 return mb_pos - (mb_end - mb_cc);
322 int len = read(mb_fd, mb_buf, sizeof(mb_buf));
331 mb_end = mb_buf + len;
340 return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
351 mb_check(const char *p, int len)
355 if (mb_get() != *p++)
366 int avail = mb_end - mb_cc;
375 int next = (avail < len) ? avail : len;
383 scan_mbox(struct mbox *b, struct stat *st)
385 char buf[1024], sender[1024], subject[1024];
388 const char from[] = "\nFrom ";
392 b->total = b->new = b->flagged = 0;
397 /* FIXME: Should we do some locking? */
399 mb_fd = open(b->path, O_RDONLY);
402 debug("[open failed: %m] ");
403 b->total = b->new = b->flagged = -1;
408 c = read(mb_fd, signature, 2);
409 lseek(mb_fd, 0, SEEK_SET);
411 if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip
413 debug("[decompressing] ");
416 die("pipe failed: %m");
419 die("fork failed: %m");
422 if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0)
423 die("dup2 failed: %m");
427 execlp("gzip", "gzip", "-cd", NULL);
428 die("Cannot execute gzip: %m");
438 if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && !compressed)
440 mb_seek(b->last_pos);
441 if (mb_check(from, 6))
443 debug("[incremental] ");
448 debug("[incremental failed] ");
454 if (!mb_check(from+1, 5))
456 debug("[inconsistent] ");
457 b->total = b->new = b->flagged = -1;
460 b->total = b->new = b->flagged = 0;
461 b->last_total = b->last_new = b->last_flagged = 0;
462 b->snippet_is_new = 0;
466 b->total = b->last_total;
467 b->new = b->last_new;
468 b->flagged = b->last_flagged;
473 b->last_pos = mb_tell() - 5;
475 b->last_pos--; // last_pos should be the previous \n character
476 b->last_total = b->total;
477 b->last_new = b->new;
478 b->last_flagged = b->flagged;
479 while ((c = mb_get()) >= 0 && c != '\n')
482 int content_length = -1;
495 debug("[truncated] ");
506 while (c == ' ' || c == '\t');
514 if (i < sizeof(buf) - 1)
520 if (!strncasecmp(buf, "Status:", 7))
522 if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
525 else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
527 else if (!strncasecmp(buf, "From:", 5))
528 strcpy(sender, buf+5);
529 else if (!strncasecmp(buf, "Subject:", 8))
530 strcpy(subject, buf+8);
531 else if (!strncasecmp(buf, "Content-Length:", 15))
532 content_length = atoi(buf + 15);
541 debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
542 if (new || (flagged && !b->snippet_is_new))
544 b->snippet_is_new = new;
545 prepare_snippets(b, sender, subject);
548 if (content_length >= 0)
549 mb_skip(content_length);
567 if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
568 b->total = b->new = b->flagged = -1;
575 debug("Searching for mailboxes (notify=%d)...\n", notify);
576 last_scan_time = time(NULL);
577 CLIST_FOR_EACH(struct pattern_node *, p, patterns)
579 debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
581 int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
582 if (err && err != GLOB_NOMATCH)
583 die("Failed to glob %s: %m", p->pattern);
584 for (uns i=0; i<g.gl_pathc; i++)
586 char *name = g.gl_pathv[i];
587 struct mbox *b = find_mbox(&mboxes, name);
590 b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
591 debug("Discovered mailbox %s (%s)\n", b->name, b->path);
601 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
607 debug("Lost mailbox %s\n", b->name);
614 debug("Scanning mailboxes...\n");
615 CLIST_FOR_EACH(struct mbox *, b, mboxes)
618 debug("%s: ", b->name);
619 if (!mbox_active_p(b))
625 b->force_refresh = 1;
626 if (stat(b->path, &st) < 0)
628 b->total = b->new = b->flagged = -1;
631 else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
634 redraw_line(b->index);
638 b->last_time = st.st_mtime;
639 b->last_size = st.st_size;
640 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
643 redraw_line(b->index);
646 else if (b->display_valid_until <= last_scan_time)
648 debug("not changed, but needs redraw\n");
649 redraw_line(b->index);
652 debug("not changed\n");
653 b->force_refresh = 0;
657 debug("Scan finished\n");
658 last_scan_time = time(NULL);
659 rethink_display(notify);
664 #include <X11/Xlib.h>
665 #include <X11/Xatom.h>
667 static Display *x11_dpy;
668 static unsigned leds_care, leds_have, leds_want;
671 static unsigned osd_care;
672 #define OSD_MSG_SIZE 1024
673 static char osd_last_msg[OSD_MSG_SIZE];
674 static time_t osd_last_time;
679 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
680 osd_care = (global_options.osd >= 0);
681 CLIST_FOR_EACH(struct option_node *, o, options)
684 leds_care |= (1 << o->o.led);
689 if (!leds_care && !osd_care)
691 debug("X11: No mailbox wants LEDs or OSD\n");
694 if (!getenv("DISPLAY"))
696 debug("X11: Do not have X display\n");
699 if (!(x11_dpy = XOpenDisplay(NULL)))
700 die("Cannot open X display, although the DISPLAY variable is set");
704 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
706 die("Cannot intern OSD_QUEUE atom");
708 // If OSD options contain no message, add one
710 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
715 add_osd_opt("=%40f");
716 add_osd_opt("=%40s");
718 add_osd_opt("=(and %m more)");
723 debug("X11: Initialized\n");
729 if (leds_want == leds_have)
732 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
733 for (int i=1; i<10; i++)
734 if (leds_care & (leds_have ^ leds_want) & (1 << i))
738 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
739 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
742 leds_have = leds_want;
748 if (!leds_care || !x11_dpy)
752 CLIST_FOR_EACH(struct mbox *, b, mboxes)
753 if (b->o.led > 0 && b->new)
754 leds_want |= (1 << b->o.led);
764 format_osd_string(char *dest, char *src, struct osd_params *par)
766 char *stop = dest + OSD_MSG_SIZE - 1;
769 while (*src && dest < stop)
775 while (*src >= '0' && *src <= '9')
776 size = 10*size + *src++ - '0';
777 if (!size || size > stop-dest)
788 arg = par->mbox->sender_snippet;
791 arg = par->mbox->subject_snippet;
794 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
797 if (par->total_new < 2)
799 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
823 format_osd(char *msg, struct osd_params *par)
832 unsigned have_text = 0;
833 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
835 char buf[OSD_MSG_SIZE];
836 if (!format_osd_string(buf, n->val, par))
838 if (!n->key[0] && buf[0])
840 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
841 if (pos > OSD_MSG_SIZE-1)
843 pos = sprintf(msg, "OSD message too long!\n");
855 debug_osd_msg(char *msg)
859 fprintf(stderr, "OSD: <");
862 fputc((*msg != '\n' ? *msg : '|'), stderr);
865 fprintf(stderr, ">\n");
869 rethink_osd(int notify)
871 if (!osd_care || !x11_dpy || !allow_osd)
877 struct osd_params p = { .mbox = NULL, .total_new = 0 };
878 CLIST_FOR_EACH(struct mbox *, b, mboxes)
881 p.total_new += b->new;
882 if (b->new && b->last_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
886 char new_msg[OSD_MSG_SIZE];
887 format_osd(new_msg, &p);
888 debug_osd_msg(new_msg);
889 if (strcmp(new_msg, osd_last_msg))
891 strcpy(osd_last_msg, new_msg);
892 if (notify && new_msg[0])
894 debug("OSD: Sending to daemon\n");
895 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
899 debug("OSD: No changes\n");
900 osd_last_time = time(NULL);
916 static void x11_init(void) { }
917 static void rethink_leds(void) { }
918 static void rethink_osd(int notify UNUSED) { }
919 static void x11_cleanup(void) { }
923 static int cursor_at, cursor_max;
925 static unsigned is_active, is_pos; // incremental search
926 static char is_buf[64];
937 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
945 struct mbox *b = mbox_array[i];
946 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
947 int hi = b->o.highlight;
948 unsigned namepos = 0;
949 unsigned namelen = strlen(b->name);
952 attrset(attrs[cc][hi][M_IDLE]);
954 printw("%c ", b->o.hotkey);
961 attrset(attrs[cc][hi][M_INCSEARCH]);
962 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
963 addch(is_buf[namepos]);
966 attrset(attrs[cc][hi][M_NEW]);
967 else if (b->flagged && b->o.show_flagged)
968 attrset(attrs[cc][hi][M_FLAG]);
970 attrset(attrs[cc][hi][M_IDLE]);
971 while (namepos < namelen)
972 addch(b->name[namepos++]);
973 while (namepos++ < 20)
977 else if (b->scanning)
979 attrset(attrs[cc][hi][M_SCAN]);
980 printw("[SCANNING]");
982 else if (b->total < 0)
984 attrset(attrs[cc][hi][M_BAD]);
989 attrset(attrs[cc][hi][M_IDLE]);
990 printw("%6d ", b->total);
994 attrset(attrs[cc][hi][M_NEW]);
995 printw("%6d ", b->new);
996 attrset(attrs[cc][hi][M_IDLE]);
997 int age = (last_scan_time - b->last_time);
1002 printw("%2d min ", age/60);
1005 else if (age < 86400)
1006 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1011 else if (b->flagged && b->o.show_flagged)
1013 attrset(attrs[cc][hi][M_FLAG]);
1014 printw("%6d ", b->flagged);
1015 attrset(attrs[cc][hi][M_IDLE]);
1017 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1020 if (snip && b->o.snippets)
1023 getyx(stdscr, yy, xx);
1024 int remains = COLS-1-xx;
1028 build_snippet(snip, snip + sizeof(snip) - 1, b);
1030 if (snip[0] && remains > 2)
1032 #ifdef CONFIG_WIDE_CURSES
1033 size_t len = strlen(snip)+1;
1035 mbstowcs(snip2, snip, len);
1036 addnwstr(snip2, remains);
1038 printw("%-.*s", remains, snip);
1043 b->display_valid_until = last_scan_time + valid;
1045 attrset(attrs[0][0][M_IDLE]);
1052 cursor_max = num_mboxes;
1053 if (cursor_max > LINES-1)
1054 cursor_max = LINES-1;
1055 if (cursor_at >= cursor_max)
1056 cursor_at = cursor_max - 1;
1060 for (int i=0; i<cursor_max; i++)
1062 move(cursor_max, 0);
1065 printw("(no mailboxes found)");
1073 rethink_display(int notify)
1078 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1079 if (mbox_visible_p(b))
1082 if (i >= num_mboxes || mbox_array[i] != b)
1085 if (i >= mbox_array_size)
1087 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1088 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1092 if (b->o.beep && b->new > b->last_beep_new)
1094 b->last_beep_new = b->new;
1097 if (i != num_mboxes)
1107 rethink_osd(notify);
1108 if (beeeep && allow_bells && notify)
1119 intrflush(stdscr, FALSE);
1120 keypad(stdscr, TRUE);
1123 static const int attrs_mono[2][M_MAX] = {
1124 [0] = { [M_IDLE] = 0,
1129 [M_INCSEARCH] = A_REVERSE },
1130 [1] = { [M_IDLE] = 0,
1132 [M_NEW] = A_REVERSE | A_BOLD,
1133 [M_FLAG] = A_REVERSE,
1135 [M_INCSEARCH] = A_REVERSE },
1137 for (int i=0; i<2; i++)
1138 for (int j=0; j<M_MAX; j++)
1140 attrs[0][i][j] = attrs_mono[i][j];
1141 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1142 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1148 if (COLOR_PAIRS >= 12)
1150 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1151 init_pair(2, COLOR_RED, COLOR_BLACK);
1152 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1153 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1154 init_pair(5, COLOR_RED, COLOR_BLUE);
1155 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1156 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1157 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1158 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1159 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1160 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1161 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1162 static const int attrs_color[3][2][M_MAX] = {
1163 [0][0] = { [M_IDLE] = 0,
1164 [M_SCAN] = COLOR_PAIR(1),
1165 [M_NEW] = COLOR_PAIR(1),
1166 [M_FLAG] = COLOR_PAIR(6),
1167 [M_BAD] = COLOR_PAIR(2) },
1168 [0][1] = { [M_IDLE] = A_BOLD,
1169 [M_SCAN] = COLOR_PAIR(1),
1170 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1171 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1172 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1173 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1174 [M_SCAN] = COLOR_PAIR(4),
1175 [M_NEW] = COLOR_PAIR(4),
1176 [M_FLAG] = COLOR_PAIR(7),
1177 [M_BAD] = COLOR_PAIR(5) },
1178 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1179 [M_SCAN] = COLOR_PAIR(4),
1180 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1181 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1182 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1183 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1184 [M_SCAN] = COLOR_PAIR(9),
1185 [M_NEW] = COLOR_PAIR(9),
1186 [M_FLAG] = COLOR_PAIR(10),
1187 [M_BAD] = COLOR_PAIR(11),
1188 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1189 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1190 [M_SCAN] = COLOR_PAIR(9),
1191 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1192 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1193 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1194 [M_INCSEARCH] = COLOR_PAIR(12) },
1196 memcpy(attrs, attrs_color, sizeof(attrs));
1208 print_status(char *status)
1212 printw("%s", status);
1218 scan_and_redraw(int notify)
1220 print_status("Busy...");
1228 if (i >= 0 && i < cursor_max && i != cursor_at)
1230 int old = cursor_at;
1238 next_active(int since, int step)
1242 since = (since+cursor_max) % cursor_max;
1243 step = (step+cursor_max) % cursor_max;
1249 struct mbox *b = mbox_array[i];
1260 if (b->new && b->o.priority > bestp)
1263 bestp = b->o.priority;
1266 i = (i+step) % cursor_max;
1274 mbox_run(struct mbox *b)
1276 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1277 sprintf(cmd, run_cmd, b->path);
1283 b->force_refresh = 1;
1288 enter_incsearch(void)
1290 print_status("Incremental search...");
1293 redraw_line(cursor_at);
1297 handle_incsearch(int ch)
1299 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1301 else if (ch >= ' ' && ch <= '~')
1303 if (is_pos < sizeof(is_buf) - 1)
1304 is_buf[is_pos++] = ch;
1311 redraw_line(cursor_at);
1316 for (int i=0; i<cursor_max; i++)
1318 struct mbox *b = mbox_array[i];
1319 if (!strncmp(b->name, is_buf, is_pos))
1330 redraw_line(cursor_at);
1335 #define STR(c) STR2(c)
1340 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1343 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1344 -d\t\t\tLog debug messages to stderr\n\
1345 -i\t\t\tInclude user's INBOX\n\
1346 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1347 -o <pattern>=<opts>\tSet mailbox options\n\
1348 -o <opts>\t\tSet default options for all mailboxes\n\
1349 -p <pri>\t\tSet minimum priority to show\n\
1350 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1351 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1353 Mailbox options (set with `-o', use upper case to negate):\n\
1354 0-9\t\t\tSet mailbox priority (0=default)\n\
1355 b\t\t\tBeep when a message arrives\n\
1356 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1357 e\t\t\tHide from display if empty\n\
1358 f\t\t\tShow flagged messages if there are no new ones\n\
1359 h\t\t\tHide from display\n\
1360 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1361 m\t\t\tShow mailbox name of the sender\n\
1362 o\t\t\tCount old, but unread messages as new\n\
1363 p\t\t\tShow personal info (full name) of the sender\n\
1364 s\t\t\tShow message snippets\n\
1365 t\t\t\tHighlight the entry\n\
1366 !<key>\t\t\tSet hot key\n\
1368 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1369 It can be freely distributed and used according to the GNU GPL v2.\n\
1375 parse_options(char *c)
1379 if (sep = strchr(c, '='))
1381 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1382 memcpy(n->pattern, c, sep-c);
1383 n->pattern[sep-c] = 0;
1384 clist_add_tail(&options, &n->n);
1390 o = &global_options;
1394 if (x >= '0' && x <= '9')
1395 o->priority = x - '0';
1396 else if (x == '!' && *c)
1398 else if (x == 'l' && *c >= '1' && *c <= '9')
1399 o->led = *c++ - '0';
1402 int value = !!islower(x);
1412 o->hide_if_empty = value;
1415 o->show_flagged = value;
1421 o->sender_mbox = value;
1424 o->unread_is_new = value;
1427 o->sender_personal = value;
1430 o->snippets = value;
1433 o->highlight = value;
1436 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1443 main(int argc, char **argv)
1445 clist_init(&mboxes);
1446 clist_init(&options);
1447 clist_init(&patterns);
1448 clist_init(&osd_opts);
1451 while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1455 check_interval = atol(optarg);
1456 if (check_interval <= 0)
1469 parse_options(optarg);
1472 minimum_priority = atol(optarg);
1475 add_osd_opt(optarg);
1483 while (optind < argc)
1484 add_pattern(argv[optind++]);
1492 int should_exit = 0;
1494 while (!should_exit)
1496 time_t now = time(NULL);
1497 int remains = last_scan_time + check_interval - now;
1505 halfdelay((remains > 255) ? 255 : remains);
1509 if (is_active && handle_incsearch(ch))
1514 for (int i=0; i<num_mboxes; i++)
1515 if (ch == mbox_array[i]->o.hotkey)
1519 mbox_run(mbox_array[i]);
1529 move_cursor(cursor_at+1);
1533 move_cursor(cursor_at-1);
1543 move_cursor(cursor_max-1);
1546 next_active(cursor_at+1, 1);
1549 next_active(cursor_at-1, -1);
1553 if (cursor_at < cursor_max)
1554 mbox_run(mbox_array[cursor_at]);
1557 clearok(stdscr, TRUE);
1566 print_status("Bells and whistles are now enabled. Toot!");
1570 print_status("Bells and whistles are now disabled. Pssst!");
1574 print_status("On-screen display is now enabled.");
1578 print_status("On-screen display is now disabled. Watch your step.");
1584 if (ch >= '0' && ch <= '9')
1586 minimum_priority = ch - '0';
1590 debug("Pressed unknown key %d\n", ch);