]> 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 2d9762f48be04d818bded150f4305265a0f06d6b..9bc036672c4139ee175a48b34239bf0b0698bc52 100644 (file)
--- a/cm.c
+++ b/cm.c
@@ -1,25 +1,26 @@
 /*
  *     Incoming Mail Checker
  *
 /*
  *     Incoming Mail Checker
  *
- *     (c) 2005--2010 Martin Mares <mj@ucw.cz>
+ *     (c) 2005--2015 Martin Mares <mj@ucw.cz>
  */
 
 #define _GNU_SOURCE
 
  */
 
 #define _GNU_SOURCE
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
 #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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
 
 #ifdef CONFIG_WIDE_CURSES
 #include <ncursesw/ncurses.h>
 
 #ifdef CONFIG_WIDE_CURSES
 #include <ncursesw/ncurses.h>
@@ -38,6 +39,7 @@ 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;
@@ -52,6 +54,7 @@ struct options {
   int hotkey;
   int led;
   int osd;
   int hotkey;
   int led;
   int osd;
+  int unread_is_new;
 };
 
 struct option_node {
 };
 
 struct option_node {
@@ -71,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;
@@ -80,14 +85,17 @@ 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;
+  int best_is_new;
   char sender_snippet[128];
   char subject_snippet[128];
   char sender_snippet[128];
   char subject_snippet[128];
+  char mdir_best[MDIR_MAX_NAME_LEN];
 };
 
 static clist mboxes;
 };
 
 static clist mboxes;
@@ -146,6 +154,7 @@ init_options(struct options *o)
   o->hotkey = -1;
   o->led = -1;
   o->osd = -1;
   o->hotkey = -1;
   o->led = -1;
   o->osd = -1;
+  o->unread_is_new = -1;
 }
 
 static void
 }
 
 static void
@@ -169,6 +178,7 @@ setup_options(struct mbox *b)
        MERGE(hotkey);
        MERGE(led);
        MERGE(osd);
        MERGE(hotkey);
        MERGE(led);
        MERGE(osd);
+       MERGE(unread_is_new);
       }
 }
 
       }
 }
 
@@ -288,7 +298,7 @@ build_snippet(char *buf, char *term, struct mbox *b)
   add_snippet(&buf, term, b->subject_snippet);
 }
 
   add_snippet(&buf, term, b->subject_snippet);
 }
 
-static int mb_fd, mb_pos;
+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
@@ -353,14 +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 compressed = 0;
   const char from[] = "\nFrom ";
 
   int c;
   int compressed = 0;
   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;
@@ -368,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)
     {
@@ -377,6 +455,7 @@ 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);
 
   char signature[2];
   c = read(mb_fd, signature, 2);
@@ -405,11 +484,12 @@ scan_mbox(struct mbox *b, struct stat *st)
       close(mb_fd);
       mb_fd = fds[0];
       compressed = 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 && !compressed)
+  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))
@@ -433,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
     {
@@ -453,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++;
@@ -506,13 +566,16 @@ scan_mbox(struct mbox *b, struct stat *st)
       if (flagged)
        b->flagged++;
       if (debug_mode > 1)
       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))
+       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;
+         b->best_is_new = new;
          prepare_snippets(b, sender, subject);
        }
 
          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])
        {
@@ -534,6 +597,138 @@ scan_mbox(struct mbox *b, struct stat *st)
     }
 }
 
     }
 }
 
+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
+scan_mdir(struct mbox *b)
+{
+  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)
 {
 static void
 scan(int notify)
 {
@@ -586,31 +781,65 @@ scan(int notify)
          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;
 
     }
   force_refresh = 0;
 
@@ -631,6 +860,7 @@ static Atom osd_pty;
 static unsigned osd_care;
 #define OSD_MSG_SIZE 1024
 static char osd_last_msg[OSD_MSG_SIZE];
 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)
 
 static void
 x11_init(void)
@@ -838,7 +1068,7 @@ rethink_osd(int notify)
     if (b->o.osd > 0)
       {
        p.total_new += b->new;
     if (b->o.osd > 0)
       {
        p.total_new += b->new;
-       if (b->new && (!p.mbox || p.mbox->o.priority < b->o.priority))
+       if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
          p.mbox = b;
       }
 
          p.mbox = b;
       }
 
@@ -856,6 +1086,7 @@ rethink_osd(int notify)
        }
       else
        debug("OSD: No changes\n");
        }
       else
        debug("OSD: No changes\n");
+      osd_last_time = time(NULL);
     }
 }
 
     }
 }
 
@@ -905,6 +1136,7 @@ redraw_line(int i)
       int hi = b->o.highlight;
       unsigned namepos = 0;
       unsigned namelen = strlen(b->name);
       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)
@@ -951,11 +1183,14 @@ 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)
                printw("%2d hr%c  ", age/3600, (age >= 7200 ? 's' : ' '));
              else
              else if (age < 86400)
                printw("%2d hr%c  ", age/3600, (age >= 7200 ? 's' : ' '));
              else
@@ -976,6 +1211,7 @@ redraw_line(int i)
              int xx, yy;
              getyx(stdscr, yy, xx);
              int remains = COLS-1-xx;
              int xx, yy;
              getyx(stdscr, yy, xx);
              int remains = COLS-1-xx;
+             (void) yy;
 
              char snip[256];
              build_snippet(snip, snip + sizeof(snip) - 1, b);
 
              char snip[256];
              build_snippet(snip, snip + sizeof(snip) - 1, b);
@@ -993,6 +1229,7 @@ redraw_line(int i)
                }
            }
        }
                }
            }
        }
+      b->display_valid_until = last_scan_time + valid;
     }
   attrset(attrs[0][0][M_IDLE]);
   clrtoeol();
     }
   attrset(attrs[0][0][M_IDLE]);
   clrtoeol();
@@ -1199,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;
     }
@@ -1289,6 +1537,7 @@ 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\
 -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\
 \n\
 Mailbox options (set with `-o', use upper case to negate):\n\
 0-9\t\t\tSet mailbox priority (0=default)\n\
@@ -1299,6 +1548,7 @@ 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\
 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\
+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\
@@ -1359,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;
@@ -1384,7 +1637,7 @@ main(int argc, char **argv)
   clist_init(&osd_opts);
 
   int c;
   clist_init(&osd_opts);
 
   int c;
-  while ((c = getopt(argc, argv, "c:dim:o:p:s:")) >= 0)
+  while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0)
     switch (c)
       {
       case 'c':
     switch (c)
       {
       case 'c':
@@ -1410,6 +1663,9 @@ main(int argc, char **argv)
       case 's':
        add_osd_opt(optarg);
        break;
       case 's':
        add_osd_opt(optarg);
        break;
+      case 't':
+       simple_tab = 1;
+       break;
       default:
        usage();
       }
       default:
        usage();
       }