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] ");
508 while (c == ' ' || c == '\t');
516 if (i < sizeof(buf) - 1)
522 if (!strncasecmp(buf, "Status:", 7))
524 if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
527 else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
529 else if (!strncasecmp(buf, "From:", 5))
530 strcpy(sender, buf+5);
531 else if (!strncasecmp(buf, "Subject:", 8))
532 strcpy(subject, buf+8);
533 else if (!strncasecmp(buf, "Content-Length:", 15))
534 content_length = atoi(buf + 15);
543 debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
544 if (new || (flagged && !b->snippet_is_new))
546 b->snippet_is_new = new;
547 prepare_snippets(b, sender, subject);
550 if (content_length >= 0)
551 mb_skip(content_length);
569 if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
570 b->total = b->new = b->flagged = -1;
577 debug("Searching for mailboxes (notify=%d)...\n", notify);
578 last_scan_time = time(NULL);
579 CLIST_FOR_EACH(struct pattern_node *, p, patterns)
581 debug("Trying pattern %s (name %s)\n", p->pattern, p->name);
583 int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
584 if (err && err != GLOB_NOMATCH)
585 die("Failed to glob %s: %m", p->pattern);
586 for (uns i=0; i<g.gl_pathc; i++)
588 char *name = g.gl_pathv[i];
589 struct mbox *b = find_mbox(&mboxes, name);
592 b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
593 debug("Discovered mailbox %s (%s)\n", b->name, b->path);
603 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
609 debug("Lost mailbox %s\n", b->name);
616 debug("Scanning mailboxes...\n");
617 CLIST_FOR_EACH(struct mbox *, b, mboxes)
620 debug("%s: ", b->name);
621 if (!mbox_active_p(b))
627 b->force_refresh = 1;
628 if (stat(b->path, &st) < 0)
630 b->total = b->new = b->flagged = -1;
633 else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
636 redraw_line(b->index);
640 b->last_time = st.st_mtime;
641 b->last_size = st.st_size;
642 debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
645 redraw_line(b->index);
648 else if (b->display_valid_until <= last_scan_time)
650 debug("not changed, but needs redraw\n");
651 redraw_line(b->index);
654 debug("not changed\n");
655 b->force_refresh = 0;
659 debug("Scan finished\n");
660 last_scan_time = time(NULL);
661 rethink_display(notify);
666 #include <X11/Xlib.h>
667 #include <X11/Xatom.h>
669 static Display *x11_dpy;
670 static unsigned leds_care, leds_have, leds_want;
673 static unsigned osd_care;
674 #define OSD_MSG_SIZE 1024
675 static char osd_last_msg[OSD_MSG_SIZE];
676 static time_t osd_last_time;
681 leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
682 osd_care = (global_options.osd >= 0);
683 CLIST_FOR_EACH(struct option_node *, o, options)
686 leds_care |= (1 << o->o.led);
691 if (!leds_care && !osd_care)
693 debug("X11: No mailbox wants LEDs or OSD\n");
696 if (!getenv("DISPLAY"))
698 debug("X11: Do not have X display\n");
701 if (!(x11_dpy = XOpenDisplay(NULL)))
702 die("Cannot open X display, although the DISPLAY variable is set");
706 osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
708 die("Cannot intern OSD_QUEUE atom");
710 // If OSD options contain no message, add one
712 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
717 add_osd_opt("=%40f");
718 add_osd_opt("=%40s");
720 add_osd_opt("=(and %m more)");
725 debug("X11: Initialized\n");
731 if (leds_want == leds_have)
734 debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
735 for (int i=1; i<10; i++)
736 if (leds_care & (leds_have ^ leds_want) & (1 << i))
740 cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
741 XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
744 leds_have = leds_want;
750 if (!leds_care || !x11_dpy)
754 CLIST_FOR_EACH(struct mbox *, b, mboxes)
755 if (b->o.led > 0 && b->new)
756 leds_want |= (1 << b->o.led);
766 format_osd_string(char *dest, char *src, struct osd_params *par)
768 char *stop = dest + OSD_MSG_SIZE - 1;
771 while (*src && dest < stop)
777 while (*src >= '0' && *src <= '9')
778 size = 10*size + *src++ - '0';
779 if (!size || size > stop-dest)
790 arg = par->mbox->sender_snippet;
793 arg = par->mbox->subject_snippet;
796 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
799 if (par->total_new < 2)
801 snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
825 format_osd(char *msg, struct osd_params *par)
834 unsigned have_text = 0;
835 CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
837 char buf[OSD_MSG_SIZE];
838 if (!format_osd_string(buf, n->val, par))
840 if (!n->key[0] && buf[0])
842 pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
843 if (pos > OSD_MSG_SIZE-1)
845 pos = sprintf(msg, "OSD message too long!\n");
857 debug_osd_msg(char *msg)
861 fprintf(stderr, "OSD: <");
864 fputc((*msg != '\n' ? *msg : '|'), stderr);
867 fprintf(stderr, ">\n");
871 rethink_osd(int notify)
873 if (!osd_care || !x11_dpy || !allow_osd)
879 struct osd_params p = { .mbox = NULL, .total_new = 0 };
880 CLIST_FOR_EACH(struct mbox *, b, mboxes)
883 p.total_new += b->new;
884 if (b->new && b->last_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
888 char new_msg[OSD_MSG_SIZE];
889 format_osd(new_msg, &p);
890 debug_osd_msg(new_msg);
891 if (strcmp(new_msg, osd_last_msg))
893 strcpy(osd_last_msg, new_msg);
894 if (notify && new_msg[0])
896 debug("OSD: Sending to daemon\n");
897 XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
901 debug("OSD: No changes\n");
902 osd_last_time = time(NULL);
918 static void x11_init(void) { }
919 static void rethink_leds(void) { }
920 static void rethink_osd(int notify UNUSED) { }
921 static void x11_cleanup(void) { }
925 static int cursor_at, cursor_max;
927 static unsigned is_active, is_pos; // incremental search
928 static char is_buf[64];
939 static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status
947 struct mbox *b = mbox_array[i];
948 int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0);
949 int hi = b->o.highlight;
950 unsigned namepos = 0;
951 unsigned namelen = strlen(b->name);
954 attrset(attrs[cc][hi][M_IDLE]);
956 printw("%c ", b->o.hotkey);
963 attrset(attrs[cc][hi][M_INCSEARCH]);
964 for (namepos=0; namepos < is_pos && namepos < 20; namepos++)
965 addch(is_buf[namepos]);
968 attrset(attrs[cc][hi][M_NEW]);
969 else if (b->flagged && b->o.show_flagged)
970 attrset(attrs[cc][hi][M_FLAG]);
972 attrset(attrs[cc][hi][M_IDLE]);
973 while (namepos < namelen)
974 addch(b->name[namepos++]);
975 while (namepos++ < 20)
979 else if (b->scanning)
981 attrset(attrs[cc][hi][M_SCAN]);
982 printw("[SCANNING]");
984 else if (b->total < 0)
986 attrset(attrs[cc][hi][M_BAD]);
991 attrset(attrs[cc][hi][M_IDLE]);
992 printw("%6d ", b->total);
996 attrset(attrs[cc][hi][M_NEW]);
997 printw("%6d ", b->new);
998 attrset(attrs[cc][hi][M_IDLE]);
999 int age = (last_scan_time - b->last_time);
1004 printw("%2d min ", age/60);
1007 else if (age < 86400)
1008 printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' '));
1013 else if (b->flagged && b->o.show_flagged)
1015 attrset(attrs[cc][hi][M_FLAG]);
1016 printw("%6d ", b->flagged);
1017 attrset(attrs[cc][hi][M_IDLE]);
1019 attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */
1022 if (snip && b->o.snippets)
1025 getyx(stdscr, yy, xx);
1026 int remains = COLS-1-xx;
1030 build_snippet(snip, snip + sizeof(snip) - 1, b);
1032 if (snip[0] && remains > 2)
1034 #ifdef CONFIG_WIDE_CURSES
1035 size_t len = strlen(snip)+1;
1037 mbstowcs(snip2, snip, len);
1038 addnwstr(snip2, remains);
1040 printw("%-.*s", remains, snip);
1045 b->display_valid_until = last_scan_time + valid;
1047 attrset(attrs[0][0][M_IDLE]);
1054 cursor_max = num_mboxes;
1055 if (cursor_max > LINES-1)
1056 cursor_max = LINES-1;
1057 if (cursor_at >= cursor_max)
1058 cursor_at = cursor_max - 1;
1062 for (int i=0; i<cursor_max; i++)
1064 move(cursor_max, 0);
1067 printw("(no mailboxes found)");
1075 rethink_display(int notify)
1080 CLIST_FOR_EACH(struct mbox *, b, mboxes)
1081 if (mbox_visible_p(b))
1084 if (i >= num_mboxes || mbox_array[i] != b)
1087 if (i >= mbox_array_size)
1089 mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16);
1090 mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size);
1094 if (b->o.beep && b->new > b->last_beep_new)
1096 b->last_beep_new = b->new;
1099 if (i != num_mboxes)
1109 rethink_osd(notify);
1110 if (beeeep && allow_bells && notify)
1121 intrflush(stdscr, FALSE);
1122 keypad(stdscr, TRUE);
1125 static const int attrs_mono[2][M_MAX] = {
1126 [0] = { [M_IDLE] = 0,
1131 [M_INCSEARCH] = A_REVERSE },
1132 [1] = { [M_IDLE] = 0,
1134 [M_NEW] = A_REVERSE | A_BOLD,
1135 [M_FLAG] = A_REVERSE,
1137 [M_INCSEARCH] = A_REVERSE },
1139 for (int i=0; i<2; i++)
1140 for (int j=0; j<M_MAX; j++)
1142 attrs[0][i][j] = attrs_mono[i][j];
1143 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1144 attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
1150 if (COLOR_PAIRS >= 12)
1152 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
1153 init_pair(2, COLOR_RED, COLOR_BLACK);
1154 init_pair(3, COLOR_WHITE, COLOR_BLUE);
1155 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
1156 init_pair(5, COLOR_RED, COLOR_BLUE);
1157 init_pair(6, COLOR_GREEN, COLOR_BLACK);
1158 init_pair(7, COLOR_GREEN, COLOR_BLUE);
1159 init_pair(8, COLOR_WHITE, COLOR_MAGENTA);
1160 init_pair(9, COLOR_YELLOW, COLOR_MAGENTA);
1161 init_pair(10, COLOR_GREEN, COLOR_MAGENTA);
1162 init_pair(11, COLOR_RED, COLOR_MAGENTA);
1163 init_pair(12, COLOR_BLACK, COLOR_YELLOW);
1164 static const int attrs_color[3][2][M_MAX] = {
1165 [0][0] = { [M_IDLE] = 0,
1166 [M_SCAN] = COLOR_PAIR(1),
1167 [M_NEW] = COLOR_PAIR(1),
1168 [M_FLAG] = COLOR_PAIR(6),
1169 [M_BAD] = COLOR_PAIR(2) },
1170 [0][1] = { [M_IDLE] = A_BOLD,
1171 [M_SCAN] = COLOR_PAIR(1),
1172 [M_NEW] = COLOR_PAIR(1) | A_BOLD,
1173 [M_FLAG] = COLOR_PAIR(6) | A_BOLD,
1174 [M_BAD] = COLOR_PAIR(2) | A_BOLD },
1175 [1][0] = { [M_IDLE] = COLOR_PAIR(3),
1176 [M_SCAN] = COLOR_PAIR(4),
1177 [M_NEW] = COLOR_PAIR(4),
1178 [M_FLAG] = COLOR_PAIR(7),
1179 [M_BAD] = COLOR_PAIR(5) },
1180 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD,
1181 [M_SCAN] = COLOR_PAIR(4),
1182 [M_NEW] = COLOR_PAIR(4) | A_BOLD,
1183 [M_FLAG] = COLOR_PAIR(7) | A_BOLD,
1184 [M_BAD] = COLOR_PAIR(5) | A_BOLD },
1185 [2][0] = { [M_IDLE] = COLOR_PAIR(8),
1186 [M_SCAN] = COLOR_PAIR(9),
1187 [M_NEW] = COLOR_PAIR(9),
1188 [M_FLAG] = COLOR_PAIR(10),
1189 [M_BAD] = COLOR_PAIR(11),
1190 [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM },
1191 [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD,
1192 [M_SCAN] = COLOR_PAIR(9),
1193 [M_NEW] = COLOR_PAIR(9) | A_BOLD,
1194 [M_FLAG] = COLOR_PAIR(10) | A_BOLD,
1195 [M_BAD] = COLOR_PAIR(11) | A_BOLD,
1196 [M_INCSEARCH] = COLOR_PAIR(12) },
1198 memcpy(attrs, attrs_color, sizeof(attrs));
1210 print_status(char *status)
1214 printw("%s", status);
1220 scan_and_redraw(int notify)
1222 print_status("Busy...");
1230 if (i >= 0 && i < cursor_max && i != cursor_at)
1232 int old = cursor_at;
1240 next_active(int since, int step)
1244 since = (since+cursor_max) % cursor_max;
1245 step = (step+cursor_max) % cursor_max;
1251 struct mbox *b = mbox_array[i];
1262 if (b->new && b->o.priority > bestp)
1265 bestp = b->o.priority;
1268 i = (i+step) % cursor_max;
1276 mbox_run(struct mbox *b)
1278 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
1279 sprintf(cmd, run_cmd, b->path);
1285 b->force_refresh = 1;
1290 enter_incsearch(void)
1292 print_status("Incremental search...");
1295 redraw_line(cursor_at);
1299 handle_incsearch(int ch)
1301 if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos)
1303 else if (ch >= ' ' && ch <= '~')
1305 if (is_pos < sizeof(is_buf) - 1)
1306 is_buf[is_pos++] = ch;
1313 redraw_line(cursor_at);
1318 for (int i=0; i<cursor_max; i++)
1320 struct mbox *b = mbox_array[i];
1321 if (!strncmp(b->name, is_buf, is_pos))
1332 redraw_line(cursor_at);
1337 #define STR(c) STR2(c)
1342 fprintf(stderr, "Usage: cm [<options>] [<mbox-pattern> | <mbox>[=<name>]] ...\n\
1345 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
1346 -d\t\t\tLog debug messages to stderr\n\
1347 -i\t\t\tInclude user's INBOX\n\
1348 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
1349 -o <pattern>=<opts>\tSet mailbox options\n\
1350 -o <opts>\t\tSet default options for all mailboxes\n\
1351 -p <pri>\t\tSet minimum priority to show\n\
1352 -s <key>=<val>\t\tSet on-screen display options (consult OSDD docs)\n\
1353 -t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\
1355 Mailbox options (set with `-o', use upper case to negate):\n\
1356 0-9\t\t\tSet mailbox priority (0=default)\n\
1357 b\t\t\tBeep when a message arrives\n\
1358 d\t\t\tSend an on-screen-display message (requires OSDD)\n\
1359 e\t\t\tHide from display if empty\n\
1360 f\t\t\tShow flagged messages if there are no new ones\n\
1361 h\t\t\tHide from display\n\
1362 l<led>\t\t\tLight a keyboard led (1-9) if running on X display\n\
1363 m\t\t\tShow mailbox name of the sender\n\
1364 o\t\t\tCount old, but unread messages as new\n\
1365 p\t\t\tShow personal info (full name) of the sender\n\
1366 s\t\t\tShow message snippets\n\
1367 t\t\t\tHighlight the entry\n\
1368 !<key>\t\t\tSet hot key\n\
1370 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
1371 It can be freely distributed and used according to the GNU GPL v2.\n\
1377 parse_options(char *c)
1381 if (sep = strchr(c, '='))
1383 struct option_node *n = xmalloc(sizeof(*n) + sep-c);
1384 memcpy(n->pattern, c, sep-c);
1385 n->pattern[sep-c] = 0;
1386 clist_add_tail(&options, &n->n);
1392 o = &global_options;
1396 if (x >= '0' && x <= '9')
1397 o->priority = x - '0';
1398 else if (x == '!' && *c)
1400 else if (x == 'l' && *c >= '1' && *c <= '9')
1401 o->led = *c++ - '0';
1404 int value = !!islower(x);
1414 o->hide_if_empty = value;
1417 o->show_flagged = value;
1423 o->sender_mbox = value;
1426 o->unread_is_new = value;
1429 o->sender_personal = value;
1432 o->snippets = value;
1435 o->highlight = value;
1438 fprintf(stderr, "Invalid mailbox option `%c'\n", x);
1445 main(int argc, char **argv)
1447 clist_init(&mboxes);
1448 clist_init(&options);
1449 clist_init(&patterns);
1450 clist_init(&osd_opts);
1453 while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
1457 check_interval = atol(optarg);
1458 if (check_interval <= 0)
1471 parse_options(optarg);
1474 minimum_priority = atol(optarg);
1477 add_osd_opt(optarg);
1485 while (optind < argc)
1486 add_pattern(argv[optind++]);
1494 int should_exit = 0;
1496 while (!should_exit)
1498 time_t now = time(NULL);
1499 int remains = last_scan_time + check_interval - now;
1507 halfdelay((remains > 255) ? 255 : remains);
1511 if (is_active && handle_incsearch(ch))
1516 for (int i=0; i<num_mboxes; i++)
1517 if (ch == mbox_array[i]->o.hotkey)
1521 mbox_run(mbox_array[i]);
1531 move_cursor(cursor_at+1);
1535 move_cursor(cursor_at-1);
1545 move_cursor(cursor_max-1);
1548 next_active(cursor_at+1, 1);
1551 next_active(cursor_at-1, -1);
1555 if (cursor_at < cursor_max)
1556 mbox_run(mbox_array[cursor_at]);
1559 clearok(stdscr, TRUE);
1568 print_status("Bells and whistles are now enabled. Toot!");
1572 print_status("Bells and whistles are now disabled. Pssst!");
1576 print_status("On-screen display is now enabled.");
1580 print_status("On-screen display is now disabled. Watch your step.");
1586 if (ch >= '0' && ch <= '9')
1588 minimum_priority = ch - '0';
1592 debug("Pressed unknown key %d\n", ch);