X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=cm.c;h=2d8c6ce1395d6abb7c3b75ad274875a0ce56b109;hb=7282cb4be8fa742e87b14a5c530eaf67dc72e2f0;hp=963fe049541f4b031d53c3fc5e20f70bc3e97600;hpb=7cb8f6cbd4f8d0ac713daabef3b07d4b5275b9fb;p=checkmail.git diff --git a/cm.c b/cm.c index 963fe04..2d8c6ce 100644 --- a/cm.c +++ b/cm.c @@ -1,9 +1,11 @@ /* * Incoming Mail Checker * - * (c) 2005--2007 Martin Mares + * (c) 2005--2010 Martin Mares */ +#define _GNU_SOURCE + #include #include #include @@ -16,7 +18,14 @@ #include #include #include +#include +#include + +#ifdef CONFIG_WIDE_CURSES +#include +#else #include +#endif #include "util.h" #include "clists.h" @@ -25,6 +34,7 @@ static int check_interval = 30; static int force_refresh; static int allow_bells = 1; +static int allow_osd = 1; static int minimum_priority; static time_t last_scan_time; static char *run_cmd = "mutt -f %s"; @@ -37,6 +47,11 @@ struct options { int beep; int snippets; int show_flagged; + int sender_personal; + int sender_mbox; + int hotkey; + int led; + int osd; }; struct option_node { @@ -52,7 +67,9 @@ struct pattern_node { }; static clist options, patterns; -static struct options global_options; +static struct options global_options = { + .sender_personal = 1 +}; struct mbox { cnode n; @@ -76,6 +93,14 @@ static clist mboxes; static struct mbox **mbox_array; static int num_mboxes, mbox_array_size; +struct osd_opt_node { + cnode n; + char *val; + char key[1]; +}; + +static clist osd_opts; + static void redraw_line(int i); static void rethink_display(void); @@ -115,6 +140,11 @@ init_options(struct options *o) o->highlight = -1; o->snippets = -1; o->show_flagged = -1; + o->sender_personal = -1; + o->sender_mbox = -1; + o->hotkey = -1; + o->led = -1; + o->osd = -1; } static void @@ -133,9 +163,26 @@ setup_options(struct mbox *b) MERGE(beep); MERGE(snippets); MERGE(show_flagged); + MERGE(sender_personal); + MERGE(sender_mbox); + MERGE(hotkey); + MERGE(led); + MERGE(osd); } } +static void +add_osd_opt(char *arg) +{ + struct osd_opt_node *n = xmalloc(sizeof(*n) + strlen(arg)); + strcpy(n->key, arg); + n->val = strchr(n->key, '='); + if (!n->val) + die("Malformed OSD option"); + *n->val++ = 0; + clist_add_tail(&osd_opts, &n->n); +} + static char * mbox_name(char *path) { @@ -214,13 +261,13 @@ prepare_snippet(struct mbox *b, char *sender, char *subject) char *pos = b->snippet; char *term = b->snippet + sizeof(b->snippet) - 1; - if (sender[0]) + if (sender[0] && (b->o.sender_mbox || b->o.sender_personal)) { - add_snippet(&pos, term, sender); + add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal); add_snippet(&pos, term, ": "); } if (subject[0]) - add_snippet(&pos, term, subject); + add_subject_snippet(&pos, term, subject); else add_snippet(&pos, term, "No subject"); } @@ -295,6 +342,7 @@ scan_mbox(struct mbox *b, struct stat *st) { char buf[1024], sender[1024], subject[1024]; int c; + int compressed = 0; const char from[] = "\nFrom "; if (!st->st_size) @@ -304,7 +352,7 @@ scan_mbox(struct mbox *b, struct stat *st) return; } - /* FIXME: Locking! */ + /* FIXME: Should we do some locking? */ mb_fd = open(b->path, O_RDONLY); if (mb_fd < 0) @@ -313,10 +361,39 @@ scan_mbox(struct mbox *b, struct stat *st) b->total = b->new = b->flagged = -1; return; } + + char signature[2]; + c = read(mb_fd, signature, 2); + lseek(mb_fd, 0, SEEK_SET); + + if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip + { + debug("[decompressing] "); + int fds[2]; + if (pipe(fds)) + die("pipe failed: %m"); + int pid = fork(); + if (pid < 0) + die("fork failed: %m"); + if (!pid) + { + if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0) + die("dup2 failed: %m"); + close(fds[0]); + close(fds[1]); + close(mb_fd); + execlp("gzip", "gzip", "-cd", NULL); + die("Cannot execute gzip: %m"); + } + close(fds[1]); + close(mb_fd); + mb_fd = fds[0]; + compressed = 1; + } mb_reset(0); int incremental = 0; - if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh) + if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && !compressed) { mb_seek(b->last_pos); if (mb_check(from, 6)) @@ -431,6 +508,12 @@ scan_mbox(struct mbox *b, struct stat *st) done: close(mb_fd); + if (compressed) + { + int status; + if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status)) + b->total = b->new = b->flagged = -1; + } } static void @@ -518,17 +601,162 @@ scan(void) rethink_display(); } +#ifdef CONFIG_X11 + +#include +#include + +static Display *x11_dpy; +static unsigned leds_care, leds_have, leds_want; +static unsigned osd_care, osd_have, osd_want; +static Atom osd_pty; + +static void +x11_init(void) +{ + leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0); + osd_care = (global_options.osd >= 0); + CLIST_FOR_EACH(struct option_node *, o, options) + { + if (o->o.led > 0) + leds_care |= (1 << o->o.led); + if (o->o.osd > 0) + osd_care = 1; + } + + if (!leds_care && !osd_care) + { + debug("X11: No mailbox wants LEDs or OSD\n"); + return; + } + if (!getenv("DISPLAY")) + { + debug("X11: Do not have X display\n"); + return; + } + if (!(x11_dpy = XOpenDisplay(NULL))) + die("Cannot open X display, although the DISPLAY variable is set"); + + if (osd_care) + { + osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False); + if (!osd_pty) + die("Cannot intern OSD_QUEUE atom"); + + // If OSD options contain no message, add one + int seen_msg = 0; + CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts) + if (!n->key[0]) + seen_msg = 1; + if (!seen_msg) + add_osd_opt("=You have new mail"); + } + + leds_have = ~0U; + debug("X11: Initialized\n"); +} + +static void +sync_leds(void) +{ + if (leds_want == leds_have) + return; + + debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care); + for (int i=1; i<10; i++) + if (leds_care & (leds_have ^ leds_want) & (1 << i)) + { + XKeyboardControl cc; + cc.led = i; + cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff; + XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc); + } + XFlush(x11_dpy); + leds_have = leds_want; +} + +static void +sync_osd(void) +{ + if (!osd_want || !allow_osd) + { + osd_have = 0; + return; + } + if (osd_have) + return; + debug("OSD: Displaying\n"); + osd_have = 1; + + char msg[1024]; + unsigned pos = 0; + CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts) + { + pos += snprintf(msg+pos, sizeof(msg)-pos-1, "%s:%s\n", n->key, n->val); + if (pos > sizeof(msg)-1) + { + pos = sprintf(msg, "OSD message too long!\n"); + break; + } + } + msg[pos++] = '\n'; + + XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) msg, pos); + XFlush(x11_dpy); +} + +static void +rethink_leds(void) +{ + if (!x11_dpy) + return; + + leds_want = 0; + osd_want = 0; + CLIST_FOR_EACH(struct mbox *, b, mboxes) + { + if (b->o.led > 0 && b->new) + leds_want |= (1 << b->o.led); + if (b->o.osd > 0 && b->new) + osd_want = 1; + } + sync_leds(); + sync_osd(); +} + +static void +x11_cleanup(void) +{ + if (!x11_dpy) + return; + + leds_want = 0; + sync_leds(); +} + +#else + +static void x11_init(void) { } +static void rethink_leds(void) { } +static void x11_cleanup(void) { } + +#endif + static int cursor_at, cursor_max; +static unsigned is_active, is_pos; // incremental search +static char is_buf[64]; + enum { M_IDLE, M_SCAN, M_NEW, M_FLAG, M_BAD, + M_INCSEARCH, M_MAX }; -static int attrs[2][2][M_MAX]; // active, hilite, status +static int attrs[3][2][M_MAX]; // active (2=incsearch), hilite, status static void redraw_line(int i) @@ -537,19 +765,34 @@ redraw_line(int i) if (i < cursor_max) { struct mbox *b = mbox_array[i]; - int cc = (cursor_at == i); + int cc = (cursor_at == i ? (is_active ? 2 : 1) : 0); int hi = b->o.highlight; + unsigned namepos = 0; + unsigned namelen = strlen(b->name); attrset(attrs[cc][hi][M_IDLE]); - if (cc) + if (b->o.hotkey) + printw("%c ", b->o.hotkey); + else if (cc) printw("> "); else printw(" "); + if (cc == 2) + { + attrset(attrs[cc][hi][M_INCSEARCH]); + for (namepos=0; namepos < is_pos && namepos < 20; namepos++) + addch(is_buf[namepos]); + } if (b->new) attrset(attrs[cc][hi][M_NEW]); - else if (b->flagged) + else if (b->flagged && b->o.show_flagged) attrset(attrs[cc][hi][M_FLAG]); - printw("%-20s ", b->name); + else + attrset(attrs[cc][hi][M_IDLE]); + while (namepos < namelen) + addch(b->name[namepos++]); + while (namepos++ < 20) + addch(' '); if (b->scanning < 0) ; else if (b->scanning) @@ -583,13 +826,14 @@ redraw_line(int i) printw(" "); snip = 1; } - else if (b->flagged) + else if (b->flagged && b->o.show_flagged) { attrset(attrs[cc][hi][M_FLAG]); printw("%6d ", b->flagged); attrset(attrs[cc][hi][M_IDLE]); printw(" "); - snip = b->o.show_flagged; + attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */ + snip = 1; } if (snip && b->o.snippets && b->snippet[0]) { @@ -597,7 +841,16 @@ redraw_line(int i) getyx(stdscr, yy, xx); int remains = COLS-1-xx; if (remains > 2) - printw("%-.*s", remains, b->snippet); + { +#ifdef CONFIG_WIDE_CURSES + size_t len = strlen(b->snippet)+1; + wchar_t snip[len]; + mbstowcs(snip, b->snippet, len); + addnwstr(snip, remains); +#else + printw("%-.*s", remains, b->snippet); +#endif + } } } } @@ -662,6 +915,7 @@ rethink_display(void) redraw_all(); refresh(); } + rethink_leds(); if (beeeep && allow_bells) beep(); } @@ -678,20 +932,31 @@ term_init(void) curs_set(0); static const int attrs_mono[2][M_MAX] = { - [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_FLAG] = 0, [M_BAD] = A_DIM }, - [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_FLAG] = A_REVERSE, [M_BAD] = A_DIM }, + [0] = { [M_IDLE] = 0, + [M_SCAN] = A_BOLD, + [M_NEW] = A_BOLD, + [M_FLAG] = 0, + [M_BAD] = A_DIM, + [M_INCSEARCH] = A_REVERSE }, + [1] = { [M_IDLE] = 0, + [M_SCAN] = A_BOLD, + [M_NEW] = A_REVERSE | A_BOLD, + [M_FLAG] = A_REVERSE, + [M_BAD] = A_DIM, + [M_INCSEARCH] = A_REVERSE }, }; for (int i=0; i<2; i++) for (int j=0; j= 5) + if (COLOR_PAIRS >= 12) { init_pair(1, COLOR_YELLOW, COLOR_BLACK); init_pair(2, COLOR_RED, COLOR_BLACK); @@ -700,11 +965,44 @@ term_init(void) init_pair(5, COLOR_RED, COLOR_BLUE); init_pair(6, COLOR_GREEN, COLOR_BLACK); init_pair(7, COLOR_GREEN, COLOR_BLUE); - static const int attrs_color[2][2][M_MAX] = { - [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_FLAG] = COLOR_PAIR(6), [M_BAD] = COLOR_PAIR(2) }, - [0][1] = { [M_IDLE] = A_BOLD, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1) | A_BOLD, [M_FLAG] = COLOR_PAIR(6) | A_BOLD, [M_BAD] = COLOR_PAIR(2) | A_BOLD }, - [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_FLAG] = COLOR_PAIR(7), [M_BAD] = COLOR_PAIR(5) }, - [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD, [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4) | A_BOLD, [M_FLAG] = COLOR_PAIR(7) | A_BOLD, [M_BAD] = COLOR_PAIR(5) | A_BOLD }, + init_pair(8, COLOR_WHITE, COLOR_MAGENTA); + init_pair(9, COLOR_YELLOW, COLOR_MAGENTA); + init_pair(10, COLOR_GREEN, COLOR_MAGENTA); + init_pair(11, COLOR_RED, COLOR_MAGENTA); + init_pair(12, COLOR_BLACK, COLOR_YELLOW); + static const int attrs_color[3][2][M_MAX] = { + [0][0] = { [M_IDLE] = 0, + [M_SCAN] = COLOR_PAIR(1), + [M_NEW] = COLOR_PAIR(1), + [M_FLAG] = COLOR_PAIR(6), + [M_BAD] = COLOR_PAIR(2) }, + [0][1] = { [M_IDLE] = A_BOLD, + [M_SCAN] = COLOR_PAIR(1), + [M_NEW] = COLOR_PAIR(1) | A_BOLD, + [M_FLAG] = COLOR_PAIR(6) | A_BOLD, + [M_BAD] = COLOR_PAIR(2) | A_BOLD }, + [1][0] = { [M_IDLE] = COLOR_PAIR(3), + [M_SCAN] = COLOR_PAIR(4), + [M_NEW] = COLOR_PAIR(4), + [M_FLAG] = COLOR_PAIR(7), + [M_BAD] = COLOR_PAIR(5) }, + [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD, + [M_SCAN] = COLOR_PAIR(4), + [M_NEW] = COLOR_PAIR(4) | A_BOLD, + [M_FLAG] = COLOR_PAIR(7) | A_BOLD, + [M_BAD] = COLOR_PAIR(5) | A_BOLD }, + [2][0] = { [M_IDLE] = COLOR_PAIR(8), + [M_SCAN] = COLOR_PAIR(9), + [M_NEW] = COLOR_PAIR(9), + [M_FLAG] = COLOR_PAIR(10), + [M_BAD] = COLOR_PAIR(11), + [M_INCSEARCH] = COLOR_PAIR(12) | A_DIM }, + [2][1] = { [M_IDLE] = COLOR_PAIR(8) | A_BOLD, + [M_SCAN] = COLOR_PAIR(9), + [M_NEW] = COLOR_PAIR(9) | A_BOLD, + [M_FLAG] = COLOR_PAIR(10) | A_BOLD, + [M_BAD] = COLOR_PAIR(11) | A_BOLD, + [M_INCSEARCH] = COLOR_PAIR(12) }, }; memcpy(attrs, attrs_color, sizeof(attrs)); } @@ -772,6 +1070,67 @@ next_active(int since, int step) move_cursor(besti); } +static void +mbox_run(struct mbox *b) +{ + char cmd[strlen(run_cmd) + strlen(b->path) + 16]; + sprintf(cmd, run_cmd, b->path); + term_cleanup(); + system(cmd); + term_init(); + redraw_all(); + refresh(); + b->force_refresh = 1; + scan_and_redraw(); +} + +static void +enter_incsearch(void) +{ + print_status("Incremental search..."); + is_active = 1; + is_pos = 0; + redraw_line(cursor_at); +} + +static int +handle_incsearch(int ch) +{ + if ((ch == KEY_BACKSPACE || ch == KEY_DC) && is_pos) + --is_pos; + else if (ch >= ' ' && ch <= '~') + { + if (is_pos < sizeof(is_buf) - 1) + is_buf[is_pos++] = ch; + } + else + { + print_status(""); + is_active = 0; + is_pos = 0; + redraw_line(cursor_at); + return 0; + } + + is_buf[is_pos] = 0; + for (int i=0; iname, is_buf, is_pos)) + { + if (i != cursor_at) + { + move_cursor(i); + return 1; + } + break; + } + } + + redraw_line(cursor_at); + return 1; +} + #define STR2(c) #c #define STR(c) STR2(c) @@ -788,15 +1147,21 @@ Options:\n\ -o =\tSet mailbox options\n\ -o \t\tSet default options for all mailboxes\n\ -p \t\tSet minimum priority to show\n\ +-s =\t\tSet on-screen display options (consult OSDD docs)\n\ \n\ Mailbox options (set with `-o', use upper case to negate):\n\ 0-9\t\t\tSet mailbox priority (0=default)\n\ b\t\t\tBeep when a message arrives\n\ +d\t\t\tSend an on-screen-display message (requires OSDD)\n\ e\t\t\tHide from display if empty\n\ f\t\t\tShow flagged messages if there are no new ones\n\ h\t\t\tHide from display\n\ +l\t\t\tLight a keyboard led (1-9) if running on X display\n\ +m\t\t\tShow mailbox name of the sender\n\ +p\t\t\tShow personal info (full name) of the sender\n\ s\t\t\tShow message snippets\n\ t\t\t\tHighlight the entry\n\ +!\t\t\tSet hot key\n\ \n\ CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares \n\ It can be freely distributed and used according to the GNU GPL v2.\n\ @@ -826,6 +1191,10 @@ parse_options(char *c) while (x = *c++) if (x >= '0' && x <= '9') o->priority = x - '0'; + else if (x == '!' && *c) + o->hotkey = *c++; + else if (x == 'l' && *c >= '1' && *c <= '9') + o->led = *c++ - '0'; else { int value = !!islower(x); @@ -834,6 +1203,9 @@ parse_options(char *c) case 'b': o->beep = value; break; + case 'd': + o->osd = value; + break; case 'e': o->hide_if_empty = value; break; @@ -843,6 +1215,12 @@ parse_options(char *c) case 'h': o->hide = value; break; + case 'm': + o->sender_mbox = value; + break; + case 'p': + o->sender_personal = value; + break; case 's': o->snippets = value; break; @@ -862,9 +1240,10 @@ main(int argc, char **argv) clist_init(&mboxes); clist_init(&options); clist_init(&patterns); + clist_init(&osd_opts); int c; - while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0) + while ((c = getopt(argc, argv, "c:dim:o:p:s:")) >= 0) switch (c) { case 'c': @@ -887,6 +1266,9 @@ main(int argc, char **argv) case 'p': minimum_priority = atol(optarg); break; + case 's': + add_osd_opt(optarg); + break; default: usage(); } @@ -895,10 +1277,12 @@ main(int argc, char **argv) charset_init(); term_init(); + x11_init(); scan_and_redraw(); next_active(0, 1); int should_exit = 0; +restart: while (!should_exit) { time_t now = time(NULL); @@ -910,6 +1294,21 @@ main(int argc, char **argv) remains *= 10; halfdelay((remains > 255) ? 255 : remains); int ch = getch(); + if (ch < 0) + continue; + if (is_active && handle_incsearch(ch)) + { + refresh(); + continue; + } + for (int i=0; io.hotkey) + { + if (i < cursor_max) + cursor_at = i; + mbox_run(mbox_array[i]); + goto restart; + } switch (ch) { case 'q': @@ -942,18 +1341,7 @@ main(int argc, char **argv) case '\r': case '\n': if (cursor_at < cursor_max) - { - struct mbox *b = mbox_array[cursor_at]; - char cmd[strlen(run_cmd) + strlen(b->path) + 16]; - sprintf(cmd, run_cmd, b->path); - term_cleanup(); - system(cmd); - term_init(); - redraw_all(); - refresh(); - b->force_refresh = 1; - scan_and_redraw(); - } + mbox_run(mbox_array[cursor_at]); break; case 'l' & 0x1f: clearok(stdscr, TRUE); @@ -971,6 +1359,17 @@ main(int argc, char **argv) allow_bells = 0; print_status("Bells and whistles are now disabled. Pssst!"); break; + case 'd': + allow_osd = 1; + print_status("On-screen display is now enabled."); + break; + case 'D': + allow_osd = 0; + print_status("On-screen display is now disabled. Watch your step."); + break; + case '/': + enter_incsearch(); + break; default: if (ch >= '0' && ch <= '9') { @@ -984,6 +1383,7 @@ main(int argc, char **argv) } } + x11_cleanup(); term_cleanup(); return 0; }