]> mj.ucw.cz Git - checkmail.git/blobdiff - cm.c
Avoid repeating the same mail in OSD announcements
[checkmail.git] / cm.c
diff --git a/cm.c b/cm.c
index 76db2488bd547a3e653fc9a24da4b6af60ef8b60..e14c8fa2ba937dbadf6a2d64331c05b9dc4fc27c 100644 (file)
--- a/cm.c
+++ b/cm.c
@@ -1,9 +1,11 @@
 /*
  *     Incoming Mail Checker
  *
 /*
  *     Incoming Mail Checker
  *
- *     (c) 2005--2007 Martin Mares <mj@ucw.cz>
+ *     (c) 2005--2010 Martin Mares <mj@ucw.cz>
  */
 
  */
 
+#define _GNU_SOURCE
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <pwd.h>
 #include <time.h>
 #include <unistd.h>
 #include <pwd.h>
 #include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#ifdef CONFIG_WIDE_CURSES
+#include <ncursesw/ncurses.h>
+#else
 #include <curses.h>
 #include <curses.h>
+#endif
 
 #include "util.h"
 #include "clists.h"
 
 #include "util.h"
 #include "clists.h"
-#include "rfc2047.h"
+#include "charset.h"
 
 static int check_interval = 30;
 static int force_refresh;
 static int allow_bells = 1;
 
 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";
 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 beep;
   int snippets;
   int show_flagged;
+  int sender_personal;
+  int sender_mbox;
+  int hotkey;
+  int led;
+  int osd;
 };
 
 struct option_node {
 };
 
 struct option_node {
@@ -52,7 +67,9 @@ struct pattern_node {
 };
 
 static clist options, patterns;
 };
 
 static clist options, patterns;
-static struct options global_options;
+static struct options global_options = {
+  .sender_personal = 1
+};
 
 struct mbox {
   cnode n;
 
 struct mbox {
   cnode n;
@@ -63,21 +80,31 @@ struct mbox {
   int scanning;
   int seen;
   time_t last_time;
   int scanning;
   int seen;
   time_t last_time;
+  time_t display_valid_until;
   int last_size, last_pos;
   int total, new, flagged;
   int last_size, last_pos;
   int total, new, flagged;
-  int last_total, last_new;
+  int last_total, last_new, last_flagged;
   int last_beep_new;
   int force_refresh;
   int snippet_is_new;
   int last_beep_new;
   int force_refresh;
   int snippet_is_new;
-  char snippet[256];
+  char sender_snippet[128];
+  char subject_snippet[128];
 };
 
 static clist mboxes;
 static struct mbox **mbox_array;
 static int num_mboxes, mbox_array_size;
 
 };
 
 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 redraw_line(int i);
-static void rethink_display(void);
+static void rethink_display(int notify);
 
 static void
 add_pattern(char *patt)
 
 static void
 add_pattern(char *patt)
@@ -115,6 +142,11 @@ init_options(struct options *o)
   o->highlight = -1;
   o->snippets = -1;
   o->show_flagged = -1;
   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
 }
 
 static void
@@ -133,9 +165,26 @@ setup_options(struct mbox *b)
        MERGE(beep);
        MERGE(snippets);
        MERGE(show_flagged);
        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)
 {
 static char *
 mbox_name(char *path)
 {
@@ -205,67 +254,39 @@ mbox_visible_p(struct mbox *b)
 }
 
 static void
 }
 
 static void
-do_add_snippet(char **ppos, char *term, unsigned char *add)
-{
-  char *pos = *ppos;
-  int space = 1;
-  while (*add && pos < term)
-    {
-      if (*add <= ' ')
-       {
-         if (!space)
-           *pos++ = ' ';
-         space = 1;
-       }
-      else if (*add >= 0x7f)
-       {
-         *pos++ = '?';
-         space = 0;
-       }
-      else
-       {
-         *pos++ = *add;
-         space = 0;
-       }
-      add++;
-    }
-  *ppos = pos;
-  *pos = 0;
-}
-
-static void
-add_snippet(char **ppos, char *term, unsigned char *add)
+prepare_snippets(struct mbox *b, char *sender, char *subject)
 {
 {
-#if 1
-  char *buf = xmalloc(strlen(add) + 1);
-  strcpy(buf, add);
-  rfc2047_decode(&buf);
-  do_add_snippet(ppos, term, buf);
-  free(buf);
-#else
-  do_add_snippet(ppos, term, add);
-#endif
-}
+  char *pos, *term;
 
 
-static void
-prepare_snippet(struct mbox *b, char *sender, char *subject)
-{
   while (*sender == ' ' || *sender == '\t')
     sender++;
   while (*subject == ' ' || *subject == '\t')
     subject++;
 
   while (*sender == ' ' || *sender == '\t')
     sender++;
   while (*subject == ' ' || *subject == '\t')
     subject++;
 
-  char *pos = b->snippet;
-  char *term = b->snippet + sizeof(b->snippet) - 1;
-  if (sender[0])
-    {
-      add_snippet(&pos, term, sender);
-      add_snippet(&pos, term, ": ");
-    }
+  pos = b->sender_snippet;
+  term = pos + sizeof(b->sender_snippet) - 1;
+  if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
+    add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal);
+  else
+    *pos = 0;
+
+  pos = b->subject_snippet;
+  term = pos + sizeof(b->subject_snippet) - 1;
   if (subject[0])
   if (subject[0])
-    add_snippet(&pos, term, subject);
+    add_subject_snippet(&pos, term, subject);
   else
   else
-    add_snippet(&pos, term, "No subject");
+    add_snippet_raw(&pos, term, "No subject");
+}
+
+static void
+build_snippet(char *buf, char *term, struct mbox *b)
+{
+  if (b->sender_snippet[0])
+    {
+      add_snippet(&buf, term, b->sender_snippet);
+      add_snippet_raw(&buf, term, ": ");
+    }
+  add_snippet(&buf, term, b->subject_snippet);
 }
 
 static int mb_fd, mb_pos;
 }
 
 static int mb_fd, mb_pos;
@@ -338,28 +359,58 @@ scan_mbox(struct mbox *b, struct stat *st)
 {
   char buf[1024], sender[1024], subject[1024];
   int c;
 {
   char buf[1024], sender[1024], subject[1024];
   int c;
+  int compressed = 0;
   const char from[] = "\nFrom ";
 
   if (!st->st_size)
     {
   const char from[] = "\nFrom ";
 
   if (!st->st_size)
     {
-      b->total = b->new = 0;
+      b->total = b->new = b->flagged = 0;
       b->last_pos = 0;
       return;
     }
 
       b->last_pos = 0;
       return;
     }
 
-  /* FIXME: Locking! */
+  /* FIXME: Should we do some locking? */
 
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
     {
       debug("[open failed: %m] ");
 
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
     {
       debug("[open failed: %m] ");
-      b->total = b->new = -1;
+      b->total = b->new = b->flagged = -1;
       return;
     }
       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;
   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))
     {
       mb_seek(b->last_pos);
       if (mb_check(from, 6))
@@ -378,17 +429,18 @@ scan_mbox(struct mbox *b, struct stat *st)
       if (!mb_check(from+1, 5))
        {
          debug("[inconsistent] ");
       if (!mb_check(from+1, 5))
        {
          debug("[inconsistent] ");
-         b->total = b->new = -1;
+         b->total = b->new = b->flagged = -1;
          goto done;
        }
          goto done;
        }
-      b->total = b->new = 0;
-      b->last_total = b->last_new = 0;
+      b->total = b->new = b->flagged = 0;
+      b->last_total = b->last_new = b->last_flagged = 0;
       b->snippet_is_new = 0;
     }
   else
     {
       b->total = b->last_total;
       b->new = b->last_new;
       b->snippet_is_new = 0;
     }
   else
     {
       b->total = b->last_total;
       b->new = b->last_new;
+      b->flagged = b->last_flagged;
     }
 
   for(;;)
     }
 
   for(;;)
@@ -398,6 +450,7 @@ scan_mbox(struct mbox *b, struct stat *st)
        b->last_pos--;          // last_pos should be the previous \n character
       b->last_total = b->total;
       b->last_new = b->new;
        b->last_pos--;          // last_pos should be the previous \n character
       b->last_total = b->total;
       b->last_new = b->new;
+      b->last_flagged = b->flagged;
       while ((c = mb_get()) >= 0 && c != '\n')
        ;
 
       while ((c = mb_get()) >= 0 && c != '\n')
        ;
 
@@ -453,10 +506,12 @@ scan_mbox(struct mbox *b, struct stat *st)
        b->new++;
       if (flagged)
        b->flagged++;
        b->new++;
       if (flagged)
        b->flagged++;
+      if (debug_mode > 1)
+       debug("new=%d flagged=%d sender=<%s> subject=<%s>\n", new, flagged, sender, subject);
       if (new || (flagged && !b->snippet_is_new))
        {
          b->snippet_is_new = new;
       if (new || (flagged && !b->snippet_is_new))
        {
          b->snippet_is_new = new;
-         prepare_snippet(b, sender, subject);
+         prepare_snippets(b, sender, subject);
        }
 
       int ct = 1;
        }
 
       int ct = 1;
@@ -472,12 +527,18 @@ scan_mbox(struct mbox *b, struct stat *st)
 
  done:
   close(mb_fd);
 
  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
 }
 
 static void
-scan(void)
+scan(int notify)
 {
 {
-  debug("Searching for mailboxes...\n");
+  debug("Searching for mailboxes (notify=%d)...\n", notify);
   last_scan_time = time(NULL);
   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
     {
   last_scan_time = time(NULL);
   CLIST_FOR_EACH(struct pattern_node *, p, patterns)
     {
@@ -514,7 +575,7 @@ scan(void)
        }
     }
 
        }
     }
 
-  rethink_display();
+  rethink_display(0);
 
   debug("Scanning mailboxes...\n");
   CLIST_FOR_EACH(struct mbox *, b, mboxes)
 
   debug("Scanning mailboxes...\n");
   CLIST_FOR_EACH(struct mbox *, b, mboxes)
@@ -530,7 +591,7 @@ scan(void)
        b->force_refresh = 1;
       if (stat(b->path, &st) < 0)
        {
        b->force_refresh = 1;
       if (stat(b->path, &st) < 0)
        {
-         b->total = b->new = -1;
+         b->total = b->new = b->flagged = -1;
          debug("%m\n");
        }
       else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
          debug("%m\n");
        }
       else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
@@ -542,12 +603,17 @@ scan(void)
          scan_mbox(b, &st);
          b->last_time = st.st_mtime;
          b->last_size = st.st_size;
          scan_mbox(b, &st);
          b->last_time = st.st_mtime;
          b->last_size = st.st_size;
-         debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size);
+         debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
 
          b->scanning = 0;
          redraw_line(b->index);
          refresh();
        }
 
          b->scanning = 0;
          redraw_line(b->index);
          refresh();
        }
+      else if (b->display_valid_until <= last_scan_time)
+       {
+         debug("not changed, but needs redraw\n");
+         redraw_line(b->index);
+       }
       else
        debug("not changed\n");
       b->force_refresh = 0;
       else
        debug("not changed\n");
       b->force_refresh = 0;
@@ -556,20 +622,285 @@ scan(void)
 
   debug("Scan finished\n");
   last_scan_time = time(NULL);
 
   debug("Scan finished\n");
   last_scan_time = time(NULL);
-  rethink_display();
+  rethink_display(notify);
+}
+
+#ifdef CONFIG_X11
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+static Display *x11_dpy;
+static unsigned leds_care, leds_have, leds_want;
+
+static Atom osd_pty;
+static unsigned osd_care;
+#define OSD_MSG_SIZE 1024
+static char osd_last_msg[OSD_MSG_SIZE];
+static time_t osd_last_time;
+
+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("=%40f");
+         add_osd_opt("=%40s");
+         add_osd_opt("=");
+         add_osd_opt("=(and %m more)");
+       }
+    }
+
+  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
+rethink_leds(void)
+{
+  if (!leds_care || !x11_dpy)
+    return;
+
+  leds_want = 0;
+  CLIST_FOR_EACH(struct mbox *, b, mboxes)
+    if (b->o.led > 0 && b->new)
+      leds_want |= (1 << b->o.led);
+  sync_leds();
+}
+
+struct osd_params {
+  struct mbox *mbox;
+  int total_new;
+};
+
+static int
+format_osd_string(char *dest, char *src, struct osd_params *par)
+{
+  char *stop = dest + OSD_MSG_SIZE - 1;
+  char numbuf[16];
+
+  while (*src && dest < stop)
+    {
+      if (*src == '%')
+       {
+         src++;
+         int size = 0;
+         while (*src >= '0' && *src <= '9')
+           size = 10*size + *src++ - '0';
+         if (!size || size > stop-dest)
+           size = dest-stop;
+
+         int spec = *src++;
+         if (!spec)
+           break;
+
+         char *arg = numbuf;
+         switch (spec)
+           {
+           case 'f':
+             arg = par->mbox->sender_snippet;
+             break;
+           case 's':
+             arg = par->mbox->subject_snippet;
+             break;
+           case 'n':
+             snprintf(numbuf, sizeof(numbuf), "%d", par->total_new);
+             break;
+           case 'm':
+             if (par->total_new < 2)
+               return 0;
+             snprintf(numbuf, sizeof(numbuf), "%d", par->total_new - 1);
+             break;
+           case '%':
+             arg = "%";
+             break;
+           default:
+             arg = "???";
+             break;
+           }
+
+         while (*arg && size)
+           {
+             *dest++ = *arg++;
+             size--;
+           }
+       }
+      else
+       *dest++ = *src++;
+    }
+  *dest = 0;
+  return 1;
+}
+
+static void
+format_osd(char *msg, struct osd_params *par)
+{
+  if (!par->mbox)
+    {
+      msg[0] = 0;
+      return;
+    }
+
+  unsigned pos = 0;
+  unsigned have_text = 0;
+  CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
+    {
+      char buf[OSD_MSG_SIZE];
+      if (!format_osd_string(buf, n->val, par))
+       continue;
+      if (!n->key[0] && buf[0])
+       have_text = 1;
+      pos += snprintf(msg+pos, OSD_MSG_SIZE-pos-1, "%s:%s\n", n->key, buf);
+      if (pos > OSD_MSG_SIZE-1)
+       {
+         pos = sprintf(msg, "OSD message too long!\n");
+         break;
+       }
+    }
+  if (have_text)
+    msg[pos++] = '\n';
+  else
+    pos = 0;
+  msg[pos] = 0;
+}
+
+static void
+debug_osd_msg(char *msg)
+{
+  if (!debug_mode)
+    return;
+  fprintf(stderr, "OSD: <");
+  while (*msg)
+    {
+      fputc((*msg != '\n' ? *msg : '|'), stderr);
+      msg++;
+    }
+  fprintf(stderr, ">\n");
+}
+
+static void
+rethink_osd(int notify)
+{
+  if (!osd_care || !x11_dpy || !allow_osd)
+    {
+      osd_last_msg[0] = 0;
+      return;
+    }
+
+  struct osd_params p = { .mbox = NULL, .total_new = 0 };
+  CLIST_FOR_EACH(struct mbox *, b, mboxes)
+    if (b->o.osd > 0)
+      {
+       p.total_new += b->new;
+       if (b->new && b->last_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
+         p.mbox = b;
+      }
+
+  char new_msg[OSD_MSG_SIZE];
+  format_osd(new_msg, &p);
+  debug_osd_msg(new_msg);
+  if (strcmp(new_msg, osd_last_msg))
+    {
+      strcpy(osd_last_msg, new_msg);
+      if (notify && new_msg[0])
+       {
+         debug("OSD: Sending to daemon\n");
+         XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) new_msg, strlen(new_msg));
+         XFlush(x11_dpy);
+       }
+      else
+       debug("OSD: No changes\n");
+      osd_last_time = time(NULL);
+    }
 }
 
 }
 
+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 rethink_osd(int notify UNUSED) { }
+static void x11_cleanup(void) { }
+
+#endif
+
 static int cursor_at, cursor_max;
 
 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,
 enum {
   M_IDLE,
   M_SCAN,
   M_NEW,
   M_FLAG,
   M_BAD,
+  M_INCSEARCH,
   M_MAX
 };
   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)
 
 static void
 redraw_line(int i)
@@ -578,19 +909,35 @@ redraw_line(int i)
   if (i < cursor_max)
     {
       struct mbox *b = mbox_array[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;
       int hi = b->o.highlight;
+      unsigned namepos = 0;
+      unsigned namelen = strlen(b->name);
+      int valid = 3600;
 
       attrset(attrs[cc][hi][M_IDLE]);
 
       attrset(attrs[cc][hi][M_IDLE]);
-      if (cc)
+      if (b->o.hotkey)
+       printw("%c ", b->o.hotkey);
+      else if (cc)
        printw("> ");
       else
        printw("  ");
        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]);
       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]);
        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)
       if (b->scanning < 0)
        ;
       else if (b->scanning)
@@ -617,30 +964,49 @@ redraw_line(int i)
              if (age < 0)
                age = 0;
              if (age < 3600)
              if (age < 0)
                age = 0;
              if (age < 3600)
-               printw("%2d min  ", age/60);
+               {
+                 printw("%2d min  ", age/60);
+                 valid = 60;
+               }
              else if (age < 86400)
              else if (age < 86400)
-               printw("%2d hrs  ", age/3600);
+               printw("%2d hr%c  ", age/3600, (age >= 7200 ? 's' : ' '));
              else
                printw("        ");
              snip = 1;
            }
              else
                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("        ");
            {
              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])
+         if (snip && b->o.snippets)
            {
              int xx, yy;
              getyx(stdscr, yy, xx);
              int remains = COLS-1-xx;
            {
              int xx, yy;
              getyx(stdscr, yy, xx);
              int remains = COLS-1-xx;
-             if (remains > 2)
-               printw("%-.*s", remains, b->snippet);
+             (void) yy;
+
+             char snip[256];
+             build_snippet(snip, snip + sizeof(snip) - 1, b);
+
+             if (snip[0] && remains > 2)
+               {
+#ifdef CONFIG_WIDE_CURSES
+                 size_t len = strlen(snip)+1;
+                 wchar_t snip2[len];
+                 mbstowcs(snip2, snip, len);
+                 addnwstr(snip2, remains);
+#else
+                 printw("%-.*s", remains, snip);
+#endif
+               }
            }
        }
            }
        }
+      b->display_valid_until = last_scan_time + valid;
     }
   attrset(attrs[0][0][M_IDLE]);
   clrtoeol();
     }
   attrset(attrs[0][0][M_IDLE]);
   clrtoeol();
@@ -670,7 +1036,7 @@ redraw_all(void)
 }
 
 static void
 }
 
 static void
-rethink_display(void)
+rethink_display(int notify)
 {
   int i = 0;
   int changed = 0;
 {
   int i = 0;
   int changed = 0;
@@ -703,7 +1069,9 @@ rethink_display(void)
       redraw_all();
       refresh();
     }
       redraw_all();
       refresh();
     }
-  if (beeeep && allow_bells)
+  rethink_leds();
+  rethink_osd(notify);
+  if (beeeep && allow_bells && notify)
     beep();
 }
 
     beep();
 }
 
@@ -719,20 +1087,31 @@ term_init(void)
   curs_set(0);
 
   static const int attrs_mono[2][M_MAX] = {
   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<M_MAX; j++)
       {
        attrs[0][i][j] = attrs_mono[i][j];
        attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
   };
   for (int i=0; i<2; i++)
     for (int j=0; j<M_MAX; j++)
       {
        attrs[0][i][j] = attrs_mono[i][j];
        attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
+       attrs[2][i][j] = attrs_mono[i][j] | A_UNDERLINE;
       }
 
   if (has_colors())
     {
       start_color();
       }
 
   if (has_colors())
     {
       start_color();
-      if (COLOR_PAIRS >= 5)
+      if (COLOR_PAIRS >= 12)
        {
          init_pair(1, COLOR_YELLOW, COLOR_BLACK);
          init_pair(2, COLOR_RED, COLOR_BLACK);
        {
          init_pair(1, COLOR_YELLOW, COLOR_BLACK);
          init_pair(2, COLOR_RED, COLOR_BLACK);
@@ -741,11 +1120,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);
          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));
        }
          };
          memcpy(attrs, attrs_color, sizeof(attrs));
        }
@@ -769,10 +1181,10 @@ print_status(char *status)
 }
 
 static void
 }
 
 static void
-scan_and_redraw(void)
+scan_and_redraw(int notify)
 {
   print_status("Busy...");
 {
   print_status("Busy...");
-  scan();
+  scan(notify);
   print_status(NULL);
 }
 
   print_status(NULL);
 }
 
@@ -813,6 +1225,67 @@ next_active(int since, int step)
     move_cursor(besti);
 }
 
     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(0);
+}
+
+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(NULL);
+      is_active = 0;
+      is_pos = 0;
+      redraw_line(cursor_at);
+      return 0;
+    }
+
+  is_buf[is_pos] = 0;
+  for (int i=0; i<cursor_max; i++)
+    {
+      struct mbox *b = mbox_array[i];
+      if (!strncmp(b->name, 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)
 
 #define STR2(c) #c
 #define STR(c) STR2(c)
 
@@ -829,15 +1302,21 @@ Options:\n\
 -o <pattern>=<opts>\tSet mailbox options\n\
 -o <opts>\t\tSet default options for all mailboxes\n\
 -p <pri>\t\tSet minimum priority to show\n\
 -o <pattern>=<opts>\tSet mailbox options\n\
 -o <opts>\t\tSet default options for all mailboxes\n\
 -p <pri>\t\tSet minimum priority to show\n\
+-s <key>=<val>\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\
 \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\
 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<led>\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\
 s\t\t\tShow message snippets\n\
 t\t\t\tHighlight the entry\n\
+!<key>\t\t\tSet hot key\n\
 \n\
 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
 It can be freely distributed and used according to the GNU GPL v2.\n\
 \n\
 CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
 It can be freely distributed and used according to the GNU GPL v2.\n\
@@ -867,6 +1346,10 @@ parse_options(char *c)
   while (x = *c++)
     if (x >= '0' && x <= '9')
       o->priority = x - '0';
   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);
     else
       {
        int value = !!islower(x);
@@ -875,6 +1358,9 @@ parse_options(char *c)
          case 'b':
            o->beep = value;
            break;
          case 'b':
            o->beep = value;
            break;
+         case 'd':
+           o->osd = value;
+           break;
          case 'e':
            o->hide_if_empty = value;
            break;
          case 'e':
            o->hide_if_empty = value;
            break;
@@ -884,6 +1370,12 @@ parse_options(char *c)
          case 'h':
            o->hide = value;
            break;
          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;
          case 's':
            o->snippets = value;
            break;
@@ -903,9 +1395,10 @@ main(int argc, char **argv)
   clist_init(&mboxes);
   clist_init(&options);
   clist_init(&patterns);
   clist_init(&mboxes);
   clist_init(&options);
   clist_init(&patterns);
+  clist_init(&osd_opts);
 
   int c;
 
   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':
     switch (c)
       {
       case 'c':
@@ -928,29 +1421,51 @@ main(int argc, char **argv)
       case 'p':
        minimum_priority = atol(optarg);
        break;
       case 'p':
        minimum_priority = atol(optarg);
        break;
+      case 's':
+       add_osd_opt(optarg);
+       break;
       default:
        usage();
       }
   while (optind < argc)
     add_pattern(argv[optind++]);
 
       default:
        usage();
       }
   while (optind < argc)
     add_pattern(argv[optind++]);
 
-  rfc2047_init();
+  charset_init();
   term_init();
   term_init();
-  scan_and_redraw();
+  x11_init();
+  scan_and_redraw(0);
   next_active(0, 1);
 
   int should_exit = 0;
   next_active(0, 1);
 
   int should_exit = 0;
+restart:
   while (!should_exit)
     {
       time_t now = time(NULL);
       int remains = last_scan_time + check_interval - now;
   while (!should_exit)
     {
       time_t now = time(NULL);
       int remains = last_scan_time + check_interval - now;
-      if (remains <= 0 || force_refresh)
-       scan_and_redraw();
+      if (force_refresh)
+       scan_and_redraw(0);
+      if (remains <= 0)
+       scan_and_redraw(1);
       else
        {
          remains *= 10;
          halfdelay((remains > 255) ? 255 : remains);
          int ch = getch();
       else
        {
          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; i<num_mboxes; i++)
+           if (ch == mbox_array[i]->o.hotkey)
+             {
+               if (i < cursor_max)
+                 cursor_at = i;
+               mbox_run(mbox_array[i]);
+               goto restart;
+             }
          switch (ch)
            {
            case 'q':
          switch (ch)
            {
            case 'q':
@@ -983,18 +1498,7 @@ main(int argc, char **argv)
            case '\r':
            case '\n':
              if (cursor_at < cursor_max)
            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);
              break;
            case 'l' & 0x1f:
              clearok(stdscr, TRUE);
@@ -1012,11 +1516,22 @@ main(int argc, char **argv)
              allow_bells = 0;
              print_status("Bells and whistles are now disabled. Pssst!");
              break;
              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')
                {
                  minimum_priority = ch - '0';
            default:
              if (ch >= '0' && ch <= '9')
                {
                  minimum_priority = ch - '0';
-                 scan_and_redraw();
+                 scan_and_redraw(0);
                }
              else
                debug("Pressed unknown key %d\n", ch);
                }
              else
                debug("Pressed unknown key %d\n", ch);
@@ -1025,6 +1540,7 @@ main(int argc, char **argv)
        }
     }
 
        }
     }
 
+  x11_cleanup();
   term_cleanup();
   return 0;
 }
   term_cleanup();
   return 0;
 }