]> mj.ucw.cz Git - checkmail.git/blobdiff - cm.c
Maildir: Skip Dovecot extended flags properly
[checkmail.git] / cm.c
diff --git a/cm.c b/cm.c
index 92141ef269851101c971aff5876840a3847220ac..9bc036672c4139ee175a48b34239bf0b0698bc52 100644 (file)
--- a/cm.c
+++ b/cm.c
@@ -1,22 +1,32 @@
 /*
  *     Incoming Mail Checker
  *
 /*
  *     Incoming Mail Checker
  *
- *     (c) 2005--2007 Martin Mares <mj@ucw.cz>
+ *     (c) 2005--2015 Martin Mares <mj@ucw.cz>
  */
 
  */
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
+#define _GNU_SOURCE
+
 #include <ctype.h>
 #include <ctype.h>
-#include <getopt.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <fcntl.h>
-#include <glob.h>
 #include <fnmatch.h>
 #include <fnmatch.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include <getopt.h>
+#include <glob.h>
 #include <pwd.h>
 #include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <time.h>
 #include <time.h>
+#include <unistd.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"
 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";
+static int simple_tab;
 
 struct options {
   int priority;
 
 struct options {
   int priority;
@@ -40,6 +52,9 @@ struct options {
   int sender_personal;
   int sender_mbox;
   int hotkey;
   int sender_personal;
   int sender_mbox;
   int hotkey;
+  int led;
+  int osd;
+  int unread_is_new;
 };
 
 struct option_node {
 };
 
 struct option_node {
@@ -59,6 +74,8 @@ static struct options global_options = {
   .sender_personal = 1
 };
 
   .sender_personal = 1
 };
 
+#define MDIR_MAX_NAME_LEN 128
+
 struct mbox {
   cnode n;
   struct options o;
 struct mbox {
   cnode n;
   struct options o;
@@ -68,21 +85,33 @@ struct mbox {
   int scanning;
   int seen;
   time_t last_time;
   int scanning;
   int seen;
   time_t last_time;
+  time_t best_time;
+  time_t display_valid_until;
   int last_size, last_pos;
   int total, new, flagged;
   int last_total, last_new, last_flagged;
   int last_beep_new;
   int force_refresh;
   int last_size, last_pos;
   int total, new, flagged;
   int last_total, last_new, last_flagged;
   int last_beep_new;
   int force_refresh;
-  int snippet_is_new;
-  char snippet[256];
+  int best_is_new;
+  char sender_snippet[128];
+  char subject_snippet[128];
+  char mdir_best[MDIR_MAX_NAME_LEN];
 };
 
 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)
@@ -123,6 +152,9 @@ init_options(struct options *o)
   o->sender_personal = -1;
   o->sender_mbox = -1;
   o->hotkey = -1;
   o->sender_personal = -1;
   o->sender_mbox = -1;
   o->hotkey = -1;
+  o->led = -1;
+  o->osd = -1;
+  o->unread_is_new = -1;
 }
 
 static void
 }
 
 static void
@@ -144,9 +176,24 @@ setup_options(struct mbox *b)
        MERGE(sender_personal);
        MERGE(sender_mbox);
        MERGE(hotkey);
        MERGE(sender_personal);
        MERGE(sender_mbox);
        MERGE(hotkey);
+       MERGE(led);
+       MERGE(osd);
+       MERGE(unread_is_new);
       }
 }
 
       }
 }
 
+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)
 {
@@ -216,27 +263,42 @@ mbox_visible_p(struct mbox *b)
 }
 
 static void
 }
 
 static void
-prepare_snippet(struct mbox *b, char *sender, char *subject)
+prepare_snippets(struct mbox *b, char *sender, char *subject)
 {
 {
+  char *pos, *term;
+
   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;
+  pos = b->sender_snippet;
+  term = pos + sizeof(b->sender_snippet) - 1;
   if (sender[0] && (b->o.sender_mbox || b->o.sender_personal))
   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);
-      add_snippet(&pos, term, ": ");
-    }
+    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])
     add_subject_snippet(&pos, term, subject);
   else
   if (subject[0])
     add_subject_snippet(&pos, term, subject);
   else
-    add_snippet(&pos, term, "No subject");
+    add_snippet_raw(&pos, term, "No subject");
 }
 
 }
 
-static int mb_fd, mb_pos;
+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, mb_seekable;
 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
 
 static void
 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
 
 static void
@@ -301,13 +363,84 @@ mb_check(const char *p, int len)
   return 1;
 }
 
   return 1;
 }
 
+static void
+mb_skip(int len)
+{
+  while (len)
+    {
+      int avail = mb_end - mb_cc;
+      if (!avail)
+       {
+         if (mb_seekable && len >= (int) sizeof(mb_buf))
+           {
+             int next = len - len % sizeof(mb_buf);
+             mb_seek(mb_tell() + next);
+             len -= next;
+             continue;
+           }
+         if (mb_ll_get() < 0)
+           return;
+         len--;
+       }
+      else
+       {
+         int next = (avail < len) ? avail : len;
+         len -= next;
+         mb_cc += next;
+       }
+    }
+}
+
+#define HDR_BUF_SIZE 1024
+
+static int
+read_hdr_field(char *buf)
+{
+  uns i = 0;
+  for (;;)
+    {
+      int c = mb_get();
+      if (c < 0)
+       {
+         debug("[truncated] ");
+         return -1;
+       }
+      if (c == '\n')
+       {
+         if (!i)
+           break;
+         int fold = -1;
+         do
+           {
+             fold++;
+             c = mb_get();
+           }
+         while (c == ' ' || c == '\t');
+         mb_unget(c);
+         if (!fold)
+           break;
+         c = ' ';
+       }
+      if (c == '\r')
+       continue;
+      if (i < HDR_BUF_SIZE - 1)
+       buf[i++] = c;
+    }
+  buf[i] = 0;
+  if (debug_mode > 2)
+    debug("Header: <%s>\n", buf);
+  return buf[0] ? 1 : 0;
+}
+
 static void
 scan_mbox(struct mbox *b, struct stat *st)
 {
 static void
 scan_mbox(struct mbox *b, struct stat *st)
 {
-  char buf[1024], sender[1024], subject[1024];
+  char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
   int c;
   int c;
+  int compressed = 0;
   const char from[] = "\nFrom ";
 
   const char from[] = "\nFrom ";
 
+  b->best_time = st->st_mtime;
   if (!st->st_size)
     {
       b->total = b->new = b->flagged = 0;
   if (!st->st_size)
     {
       b->total = b->new = b->flagged = 0;
@@ -315,8 +448,6 @@ scan_mbox(struct mbox *b, struct stat *st)
       return;
     }
 
       return;
     }
 
-  /* FIXME: Should we do some locking? */
-
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
     {
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
     {
@@ -324,10 +455,41 @@ scan_mbox(struct mbox *b, struct stat *st)
       b->total = b->new = b->flagged = -1;
       return;
     }
       b->total = b->new = b->flagged = -1;
       return;
     }
+  mb_seekable = 1;
+
+  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_seekable = 0;
+    }
   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 && mb_seekable)
     {
       mb_seek(b->last_pos);
       if (mb_check(from, 6))
     {
       mb_seek(b->last_pos);
       if (mb_check(from, 6))
@@ -351,7 +513,7 @@ scan_mbox(struct mbox *b, struct stat *st)
        }
       b->total = b->new = b->flagged = 0;
       b->last_total = b->last_new = b->last_flagged = 0;
        }
       b->total = b->new = b->flagged = 0;
       b->last_total = b->last_new = b->last_flagged = 0;
-      b->snippet_is_new = 0;
+      b->best_is_new = 0;
     }
   else
     {
     }
   else
     {
@@ -371,51 +533,31 @@ scan_mbox(struct mbox *b, struct stat *st)
       while ((c = mb_get()) >= 0 && c != '\n')
        ;
 
       while ((c = mb_get()) >= 0 && c != '\n')
        ;
 
+      int content_length = -1;
       int new = 1;
       int flagged = 0;
       sender[0] = 0;
       subject[0] = 0;
       for (;;)
        {
       int new = 1;
       int flagged = 0;
       sender[0] = 0;
       subject[0] = 0;
       for (;;)
        {
-         uns i = 0;
-         for (;;)
-           {
-             c = mb_get();
-             if (c < 0)
-               {
-                 debug("[truncated] ");
-                 goto done;
-               }
-             if (c == '\n')
-               {
-                 int fold = -1;
-                 do
-                   {
-                     fold++;
-                     c = mb_get();
-                   }
-                 while (c == ' ' || c == '\t');
-                 mb_unget(c);
-                 if (!fold)
-                   break;
-                 c = ' ';
-               }
-             if (c == '\r')
-               continue;
-             if (i < sizeof(buf) - 1)
-               buf[i++] = c;
-           }
-         buf[i] = 0;
-         if (!buf[0])
+         int c = read_hdr_field(buf);
+         if (c < 0)
+           goto done;
+         if (!c)
            break;
          if (!strncasecmp(buf, "Status:", 7))
            break;
          if (!strncasecmp(buf, "Status:", 7))
-           new = 0;
+           {
+             if (!b->o.unread_is_new || strchr(buf + 7, 'R'))
+               new = 0;
+           }
          else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
            flagged = 1;
          else if (!strncasecmp(buf, "From:", 5))
            strcpy(sender, buf+5);
          else if (!strncasecmp(buf, "Subject:", 8))
            strcpy(subject, buf+8);
          else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F'))
            flagged = 1;
          else if (!strncasecmp(buf, "From:", 5))
            strcpy(sender, buf+5);
          else if (!strncasecmp(buf, "Subject:", 8))
            strcpy(subject, buf+8);
+         else if (!strncasecmp(buf, "Content-Length:", 15))
+           content_length = atoi(buf + 15);
        }
 
       b->total++;
        }
 
       b->total++;
@@ -423,12 +565,17 @@ scan_mbox(struct mbox *b, struct stat *st)
        b->new++;
       if (flagged)
        b->flagged++;
        b->new++;
       if (flagged)
        b->flagged++;
-      if (new || (flagged && !b->snippet_is_new))
+      if (debug_mode > 1)
+       debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
+      if (new || (flagged && !b->best_is_new))
        {
        {
-         b->snippet_is_new = new;
-         prepare_snippet(b, sender, subject);
+         b->best_is_new = new;
+         prepare_snippets(b, sender, subject);
        }
 
        }
 
+      if (content_length >= 0)
+       mb_skip(content_length);
+
       int ct = 1;
       while (from[ct])
        {
       int ct = 1;
       while (from[ct])
        {
@@ -442,12 +589,150 @@ 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 time_t
+mdir_mtime(struct mbox *b)
+{
+  time_t mtime = 0;
+  char path[strlen(b->path) + 5];
+
+  for (int new=0; new<2; new++)
+    {
+      sprintf(path, "%s/%s", b->path, (new ? "new" : "cur"));
+      struct stat st;
+      if (stat(path, &st) < 0)
+       debug("[cannot stat %s] ", path);
+      else if (st.st_mtime > mtime)
+       mtime = st.st_mtime;
+    }
+  return mtime;
+}
+
+static void
+mdir_get_snippet(struct mbox *b)
+{
+  char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
+  sender[0] = 0;
+  subject[0] = 0;
+
+  char path[strlen(b->path) + MDIR_MAX_NAME_LEN];
+  sprintf(path, "%s/%s", b->path, b->mdir_best);
+  mb_fd = open(path, O_RDONLY);
+  if (mb_fd < 0)
+    {
+      debug("[open failed: %m] ");
+      prepare_snippets(b, sender, subject);
+      return;
+    }
+  mb_seekable = 1;
+  mb_reset(0);
+
+  while (read_hdr_field(buf) > 0)
+    {
+      if (!strncasecmp(buf, "From:", 5))
+       strcpy(sender, buf+5);
+      else if (!strncasecmp(buf, "Subject:", 8))
+       strcpy(subject, buf+8);
+    }
+
+  close(mb_fd);
+  prepare_snippets(b, sender, subject);
 }
 
 static void
 }
 
 static void
-scan(void)
+scan_mdir(struct mbox *b)
 {
 {
-  debug("Searching for mailboxes...\n");
+  int dir_len = strlen(b->path);
+  char path[dir_len + MDIR_MAX_NAME_LEN];
+  strcpy(path, b->path);
+
+  b->total = b->new = b->flagged = 0;
+  b->best_time = 0;
+  b->best_is_new = 0;
+
+  for (int new=0; new<2; new++)
+    {
+      strcpy(path + dir_len, (new ? "/new" : "/cur"));
+      DIR *d = opendir(path);
+      if (!d)
+       {
+         debug("[cannot open %s: %m] ");
+         continue;
+       }
+      struct dirent *de;
+      while (de = readdir(d))
+       {
+         char *name = de->d_name;
+         if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN)
+           continue;
+         path[dir_len + 4] = '/';
+         strcpy(path + dir_len + 5, name);
+
+         char *colon = strchr(name, ':');
+         int seen = 0;
+         int flagged = 0;
+         if (colon && colon[1] == '2' && colon[2] == ',')
+           {
+             // Another comma can separate extended flags (Dovecot extension)
+             for (int i=3; colon[i] && colon[i] != ','; i++)
+               switch (colon[i])
+                 {
+                 case 'S':
+                   seen = 1;
+                   break;
+                 case 'F':
+                   flagged = 1;
+                   break;
+                 }
+           }
+
+         int is_new = new;
+         if (b->o.unread_is_new && !seen)
+           is_new = 1;
+
+         b->total++;
+         if (is_new)
+           b->new++;
+         if (flagged)
+           b->flagged++;
+         if (debug_mode > 1)
+           debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
+
+         if (is_new || flagged)
+           {
+             // Need to pick the best message to display
+             struct stat st;
+             if (stat(path, &st) < 0)
+               debug("[cannot stat %s: %m] ", path);
+             else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
+               {
+                 b->best_is_new = is_new;
+                 b->best_time = st.st_mtime;
+                 strcpy(b->mdir_best, path + dir_len + 1);
+               }
+           }
+       }
+      closedir(d);
+    }
+
+  if (b->best_time && b->o.snippets)
+    {
+      debug("best <%s> ", b->mdir_best);
+      mdir_get_snippet(b);
+    }
+}
+
+static void
+scan(int notify)
+{
+  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)
     {
@@ -484,7 +769,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)
@@ -496,50 +781,349 @@ scan(void)
          debug("inactive\n");
          continue;
        }
          debug("inactive\n");
          continue;
        }
-      if (force_refresh)
-       b->force_refresh = 1;
+      b->force_refresh = force_refresh;
       if (stat(b->path, &st) < 0)
        {
          b->total = b->new = b->flagged = -1;
          debug("%m\n");
       if (stat(b->path, &st) < 0)
        {
          b->total = b->new = b->flagged = -1;
          debug("%m\n");
+         continue;
        }
        }
-      else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
+
+      time_t current_mtime;
+      int current_size;
+      int is_mdir;
+      if (S_ISREG(st.st_mode))
+       {
+         // Regular mailbox
+         is_mdir = 0;
+         current_mtime = st.st_mtime;
+         current_size = st.st_size;
+         debug("[mbox] ");
+       }
+      else if (S_ISDIR(st.st_mode))
+       {
+         // Maildir
+         is_mdir = 1;
+         current_mtime = mdir_mtime(b);
+         current_size = 0;
+         debug("[mdir] ");
+       }
+      else
+       {
+         debug("neither file nor directory\n");
+         continue;
+       }
+
+      if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
        {
          b->scanning = 1;
          redraw_line(b->index);
          refresh();
 
        {
          b->scanning = 1;
          redraw_line(b->index);
          refresh();
 
-         scan_mbox(b, &st);
-         b->last_time = st.st_mtime;
-         b->last_size = st.st_size;
+         if (is_mdir)
+           scan_mdir(b);
+         else
+           scan_mbox(b, &st);
+         b->last_time = current_mtime;
+         b->last_size = current_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();
          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->force_refresh = 0;
+       }
+      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");
        }
       else
        debug("not changed\n");
-      b->force_refresh = 0;
     }
   force_refresh = 0;
 
   debug("Scan finished\n");
   last_scan_time = time(NULL);
     }
   force_refresh = 0;
 
   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->best_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)
@@ -548,8 +1132,11 @@ 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]);
       if (b->o.hotkey)
 
       attrset(attrs[cc][hi][M_IDLE]);
       if (b->o.hotkey)
@@ -558,11 +1145,22 @@ redraw_line(int i)
        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]);
       else if (b->flagged && b->o.show_flagged)
        attrset(attrs[cc][hi][M_FLAG]);
       if (b->new)
        attrset(attrs[cc][hi][M_NEW]);
       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)
       if (b->scanning < 0)
        ;
       else if (b->scanning)
@@ -585,13 +1183,16 @@ redraw_line(int i)
              attrset(attrs[cc][hi][M_NEW]);
              printw("%6d  ", b->new);
              attrset(attrs[cc][hi][M_IDLE]);
              attrset(attrs[cc][hi][M_NEW]);
              printw("%6d  ", b->new);
              attrset(attrs[cc][hi][M_IDLE]);
-             int age = (last_scan_time - b->last_time);
+             int age = (last_scan_time - b->best_time);
              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;
@@ -605,15 +1206,30 @@ redraw_line(int i)
              attrset(attrs[cc][0][M_FLAG]);    /* We avoid the highlight intentionally */
              snip = 1;
            }
              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();
@@ -643,7 +1259,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;
@@ -676,7 +1292,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();
 }
 
@@ -692,20 +1310,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);
@@ -714,11 +1343,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));
        }
@@ -742,10 +1404,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);
 }
 
@@ -774,10 +1436,21 @@ next_active(int since, int step)
   do
     {
       struct mbox *b = mbox_array[i];
   do
     {
       struct mbox *b = mbox_array[i];
-      if (b->new && b->o.priority > bestp)
+      if (simple_tab)
        {
        {
-         besti = i;
-         bestp = b->o.priority;
+         if (b->new)
+           {
+             besti = i;
+             break;
+           }
+       }
+      else
+       {
+         if (b->new && b->o.priority > bestp)
+           {
+             besti = i;
+             bestp = b->o.priority;
+           }
        }
       i = (i+step) % cursor_max;
     }
        }
       i = (i+step) % cursor_max;
     }
@@ -797,7 +1470,54 @@ mbox_run(struct mbox *b)
   redraw_all();
   refresh();
   b->force_refresh = 1;
   redraw_all();
   refresh();
   b->force_refresh = 1;
-  scan_and_redraw();
+  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 STR2(c) #c
@@ -816,14 +1536,19 @@ 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\
+-t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\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\
 m\t\t\tShow mailbox name of the sender\n\
+o\t\t\tCount old, but unread messages as new\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\
 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\
@@ -859,6 +1584,8 @@ parse_options(char *c)
       o->priority = x - '0';
     else if (x == '!' && *c)
       o->hotkey = *c++;
       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);
@@ -867,6 +1594,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;
@@ -879,6 +1609,9 @@ parse_options(char *c)
          case 'm':
            o->sender_mbox = value;
            break;
          case 'm':
            o->sender_mbox = value;
            break;
+         case 'o':
+           o->unread_is_new = value;
+           break;
          case 'p':
            o->sender_personal = value;
            break;
          case 'p':
            o->sender_personal = value;
            break;
@@ -901,9 +1634,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:t")) >= 0)
     switch (c)
       {
       case 'c':
     switch (c)
       {
       case 'c':
@@ -926,6 +1660,12 @@ 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;
+      case 't':
+       simple_tab = 1;
+       break;
       default:
        usage();
       }
       default:
        usage();
       }
@@ -934,7 +1674,8 @@ main(int argc, char **argv)
 
   charset_init();
   term_init();
 
   charset_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;
@@ -943,13 +1684,22 @@ restart:
     {
       time_t now = time(NULL);
       int remains = last_scan_time + check_interval - now;
     {
       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)
              {
          for (int i=0; i<num_mboxes; i++)
            if (ch == mbox_array[i]->o.hotkey)
              {
@@ -1008,11 +1758,22 @@ restart:
              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);
@@ -1021,6 +1782,7 @@ restart:
        }
     }
 
        }
     }
 
+  x11_cleanup();
   term_cleanup();
   return 0;
 }
   term_cleanup();
   return 0;
 }