]> mj.ucw.cz Git - checkmail.git/blobdiff - cm.c
OSD message options (and even messages themselves) made configurable
[checkmail.git] / cm.c
diff --git a/cm.c b/cm.c
index 36a29e9145e86904a10acb9bac8d0788cad1f56c..b9b2050266fced91e61fc8854426ee3cedd87fbc 100644 (file)
--- a/cm.c
+++ b/cm.c
@@ -1,9 +1,11 @@
 /*
  *     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 <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>
+#endif
 
 #include "util.h"
 #include "clists.h"
@@ -25,6 +34,7 @@
 static int check_interval = 30;
 static int force_refresh;
 static int allow_bells = 1;
+static int allow_osd = 1;
 static int minimum_priority;
 static time_t last_scan_time;
 static char *run_cmd = "mutt -f %s";
@@ -39,6 +49,9 @@ struct options {
   int show_flagged;
   int sender_personal;
   int sender_mbox;
+  int hotkey;
+  int led;
+  int osd;
 };
 
 struct option_node {
@@ -80,6 +93,14 @@ static clist mboxes;
 static struct mbox **mbox_array;
 static int num_mboxes, mbox_array_size;
 
+struct osd_opt_node {
+  cnode n;
+  char *val;
+  char key[1];
+};
+
+static clist osd_opts;
+
 static void redraw_line(int i);
 static void rethink_display(void);
 
@@ -121,6 +142,9 @@ init_options(struct options *o)
   o->show_flagged = -1;
   o->sender_personal = -1;
   o->sender_mbox = -1;
+  o->hotkey = -1;
+  o->led = -1;
+  o->osd = -1;
 }
 
 static void
@@ -141,9 +165,24 @@ setup_options(struct mbox *b)
        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)
 {
@@ -303,6 +342,7 @@ scan_mbox(struct mbox *b, struct stat *st)
 {
   char buf[1024], sender[1024], subject[1024];
   int c;
+  int compressed = 0;
   const char from[] = "\nFrom ";
 
   if (!st->st_size)
@@ -312,7 +352,7 @@ scan_mbox(struct mbox *b, struct stat *st)
       return;
     }
 
-  /* FIXME: Locking! */
+  /* FIXME: Should we do some locking? */
 
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
@@ -321,10 +361,39 @@ scan_mbox(struct mbox *b, struct stat *st)
       b->total = b->new = b->flagged = -1;
       return;
     }
+
+  char signature[2];
+  c = read(mb_fd, signature, 2);
+  lseek(mb_fd, 0, SEEK_SET);
+
+  if (c == 2 && !memcmp(signature, "\037\213", 2)) //gzip
+    {
+      debug("[decompressing] ");
+      int fds[2];
+      if (pipe(fds))
+       die("pipe failed: %m");
+      int pid = fork();
+      if (pid < 0)
+       die("fork failed: %m");
+      if (!pid)
+       {
+         if (dup2(mb_fd, 0) < 0 || dup2(fds[1], 1) < 0)
+           die("dup2 failed: %m");
+         close(fds[0]);
+         close(fds[1]);
+         close(mb_fd);
+         execlp("gzip", "gzip", "-cd", NULL);
+         die("Cannot execute gzip: %m");
+       }
+      close(fds[1]);
+      close(mb_fd);
+      mb_fd = fds[0];
+      compressed = 1;
+    }
   mb_reset(0);
 
   int incremental = 0;
-  if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh)
+  if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && !compressed)
     {
       mb_seek(b->last_pos);
       if (mb_check(from, 6))
@@ -439,6 +508,12 @@ scan_mbox(struct mbox *b, struct stat *st)
 
  done:
   close(mb_fd);
+  if (compressed)
+    {
+      int status;
+      if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status))
+       b->total = b->new = b->flagged = -1;
+    }
 }
 
 static void
@@ -526,6 +601,146 @@ scan(void)
   rethink_display();
 }
 
+#ifdef CONFIG_X11
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+static Display *x11_dpy;
+static unsigned leds_care, leds_have, leds_want;
+static unsigned osd_care, osd_have, osd_want;
+static Atom osd_pty;
+
+static void
+x11_init(void)
+{
+  leds_care = (global_options.led >= 0 ? (1 << global_options.led) : 0);
+  osd_care = (global_options.osd >= 0);
+  CLIST_FOR_EACH(struct option_node *, o, options)
+    {
+      if (o->o.led > 0)
+       leds_care |= (1 << o->o.led);
+      if (o->o.osd > 0)
+       osd_care = 1;
+    }
+
+  if (!leds_care && !osd_care)
+    {
+      debug("X11: No mailbox wants LEDs or OSD\n");
+      return;
+    }
+  if (!getenv("DISPLAY"))
+    {
+      debug("X11: Do not have X display\n");
+      return;
+    }
+  if (!(x11_dpy = XOpenDisplay(NULL)))
+    die("Cannot open X display, although the DISPLAY variable is set");
+
+  if (osd_care)
+    {
+      osd_pty = XInternAtom(x11_dpy, "OSD_QUEUE", False);
+      if (!osd_pty)
+       die("Cannot intern OSD_QUEUE atom");
+
+      // If OSD options contain no message, add one
+      int seen_msg = 0;
+      CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
+       if (!n->key[0])
+         seen_msg = 1;
+      if (!seen_msg)
+       add_osd_opt("=You have new mail");
+    }
+
+  leds_have = ~0U;
+  debug("X11: Initialized\n");
+}
+
+static void
+sync_leds(void)
+{
+  if (leds_want == leds_have)
+    return;
+
+  debug("LEDS: have %02x, want %02x, care %02x\n", leds_have, leds_want, leds_care);
+  for (int i=1; i<10; i++)
+    if (leds_care & (leds_have ^ leds_want) & (1 << i))
+      {
+       XKeyboardControl cc;
+       cc.led = i;
+       cc.led_mode = (leds_want & (1 << i)) ? LedModeOn : LedModeOff;
+       XChangeKeyboardControl(x11_dpy, KBLed | KBLedMode, &cc);
+      }
+  XFlush(x11_dpy);
+  leds_have = leds_want;
+}
+
+static void
+sync_osd(void)
+{
+  if (!osd_want || !allow_osd)
+    {
+      osd_have = 0;
+      return;
+    }
+  if (osd_have)
+    return;
+  debug("OSD: Displaying\n");
+
+  char msg[1024];
+  unsigned pos = 0;
+  CLIST_FOR_EACH(struct osd_opt_node *, n, osd_opts)
+    {
+      pos += snprintf(msg+pos, sizeof(msg)-pos-1, "%s:%s\n", n->key, n->val);
+      if (pos > sizeof(msg)-1)
+       {
+         pos = sprintf(msg, "OSD message too long!\n");
+         break;
+       }
+    }
+  msg[pos++] = '\n';
+
+  XChangeProperty(x11_dpy, DefaultRootWindow(x11_dpy), osd_pty, XA_STRING, 8, PropModeAppend, (unsigned char *) msg, pos);
+  XFlush(x11_dpy);
+}
+
+static void
+rethink_leds(void)
+{
+  if (!x11_dpy)
+    return;
+
+  leds_want = 0;
+  osd_want = 0;
+  CLIST_FOR_EACH(struct mbox *, b, mboxes)
+    {
+      if (b->o.led > 0 && b->new)
+       leds_want |= (1 << b->o.led);
+      if (b->o.osd > 0 && b->new)
+       osd_want = 1;
+    }
+  sync_leds();
+  sync_osd();
+}
+
+static void
+x11_cleanup(void)
+{
+  if (!x11_dpy)
+    return;
+
+  leds_want = 0;
+  sync_leds();
+}
+
+#else
+
+static void x11_init(void) { }
+static void rethink_leds(void) { }
+static void x11_cleanup(void) { }
+
+#endif
+
 static int cursor_at, cursor_max;
 
 enum {
@@ -549,13 +764,15 @@ redraw_line(int i)
       int hi = b->o.highlight;
 
       attrset(attrs[cc][hi][M_IDLE]);
-      if (cc)
+      if (b->o.hotkey)
+       printw("%c ", b->o.hotkey);
+      else if (cc)
        printw("> ");
       else
        printw("  ");
       if (b->new)
        attrset(attrs[cc][hi][M_NEW]);
-      else if (b->flagged)
+      else if (b->flagged && b->o.show_flagged)
        attrset(attrs[cc][hi][M_FLAG]);
       printw("%-20s ", b->name);
       if (b->scanning < 0)
@@ -591,13 +808,14 @@ redraw_line(int i)
                printw("        ");
              snip = 1;
            }
-         else if (b->flagged)
+         else if (b->flagged && b->o.show_flagged)
            {
              attrset(attrs[cc][hi][M_FLAG]);
              printw("%6d  ", b->flagged);
              attrset(attrs[cc][hi][M_IDLE]);
              printw("        ");
-             snip = b->o.show_flagged;
+             attrset(attrs[cc][0][M_FLAG]);    /* We avoid the highlight intentionally */
+             snip = 1;
            }
          if (snip && b->o.snippets && b->snippet[0])
            {
@@ -605,7 +823,16 @@ redraw_line(int i)
              getyx(stdscr, yy, xx);
              int remains = COLS-1-xx;
              if (remains > 2)
-               printw("%-.*s", remains, b->snippet);
+               {
+#ifdef CONFIG_WIDE_CURSES
+                 size_t len = strlen(b->snippet)+1;
+                 wchar_t snip[len];
+                 mbstowcs(snip, b->snippet, len);
+                 addnwstr(snip, remains);
+#else
+                 printw("%-.*s", remains, b->snippet);
+#endif
+               }
            }
        }
     }
@@ -670,6 +897,7 @@ rethink_display(void)
       redraw_all();
       refresh();
     }
+  rethink_leds();
   if (beeeep && allow_bells)
     beep();
 }
@@ -780,6 +1008,20 @@ next_active(int since, int step)
     move_cursor(besti);
 }
 
+static void
+mbox_run(struct mbox *b)
+{
+  char cmd[strlen(run_cmd) + strlen(b->path) + 16];
+  sprintf(cmd, run_cmd, b->path);
+  term_cleanup();
+  system(cmd);
+  term_init();
+  redraw_all();
+  refresh();
+  b->force_refresh = 1;
+  scan_and_redraw();
+}
+
 #define STR2(c) #c
 #define STR(c) STR2(c)
 
@@ -796,17 +1038,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\
+-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\
+d\t\t\tSend an on-screen-display message (requires OSDD)\n\
 e\t\t\tHide from display if empty\n\
 f\t\t\tShow flagged messages if there are no new ones\n\
 h\t\t\tHide from display\n\
+l<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\
+!<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\
@@ -836,6 +1082,10 @@ parse_options(char *c)
   while (x = *c++)
     if (x >= '0' && x <= '9')
       o->priority = x - '0';
+    else if (x == '!' && *c)
+      o->hotkey = *c++;
+    else if (x == 'l' && *c >= '1' && *c <= '9')
+      o->led = *c++ - '0';
     else
       {
        int value = !!islower(x);
@@ -844,6 +1094,9 @@ parse_options(char *c)
          case 'b':
            o->beep = value;
            break;
+         case 'd':
+           o->osd = value;
+           break;
          case 'e':
            o->hide_if_empty = value;
            break;
@@ -878,9 +1131,10 @@ main(int argc, char **argv)
   clist_init(&mboxes);
   clist_init(&options);
   clist_init(&patterns);
+  clist_init(&osd_opts);
 
   int c;
-  while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0)
+  while ((c = getopt(argc, argv, "c:dim:o:p:s:")) >= 0)
     switch (c)
       {
       case 'c':
@@ -903,6 +1157,9 @@ main(int argc, char **argv)
       case 'p':
        minimum_priority = atol(optarg);
        break;
+      case 's':
+       add_osd_opt(optarg);
+       break;
       default:
        usage();
       }
@@ -911,10 +1168,12 @@ main(int argc, char **argv)
 
   charset_init();
   term_init();
+  x11_init();
   scan_and_redraw();
   next_active(0, 1);
 
   int should_exit = 0;
+restart:
   while (!should_exit)
     {
       time_t now = time(NULL);
@@ -926,6 +1185,14 @@ main(int argc, char **argv)
          remains *= 10;
          halfdelay((remains > 255) ? 255 : remains);
          int ch = getch();
+         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':
@@ -958,18 +1225,7 @@ main(int argc, char **argv)
            case '\r':
            case '\n':
              if (cursor_at < cursor_max)
-               {
-                 struct mbox *b = mbox_array[cursor_at];
-                 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
-                 sprintf(cmd, run_cmd, b->path);
-                 term_cleanup();
-                 system(cmd);
-                 term_init();
-                 redraw_all();
-                 refresh();
-                 b->force_refresh = 1;
-                 scan_and_redraw();
-               }
+               mbox_run(mbox_array[cursor_at]);
              break;
            case 'l' & 0x1f:
              clearok(stdscr, TRUE);
@@ -987,6 +1243,14 @@ main(int argc, char **argv)
              allow_bells = 0;
              print_status("Bells and whistles are now disabled. Pssst!");
              break;
+           case 'd':
+             allow_osd = 1;
+             print_status("On-screen display is now enabled.");
+             break;
+           case 'D':
+             allow_osd = 0;
+             print_status("On-screen display is now disabled. Watch your step.");
+             break;
            default:
              if (ch >= '0' && ch <= '9')
                {
@@ -1000,6 +1264,7 @@ main(int argc, char **argv)
        }
     }
 
+  x11_cleanup();
   term_cleanup();
   return 0;
 }