http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / hacks / phosphor.c
index 1188f2e60696504e35889b621005504dcebb0558..724bbb5312807fa3c04e196fc367394a95969380 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1999, 2000 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1999-2008 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -9,15 +9,41 @@
  * implied warranty.
  *
  * Phosphor -- simulate a glass tty with long-sustain phosphor.
+ * Written by Jamie Zawinski <jwz@jwz.org>
+ * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
  */
 
-#include "screenhack.h"
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
 #include <stdio.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include <X11/Intrinsic.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#ifndef HAVE_COCOA
+# define XK_MISCELLANY
+# include <X11/keysymdef.h>
+# include <X11/Xatom.h>
+# include <X11/Intrinsic.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+# include <fcntl.h>  /* for O_RDWR */
+#endif
 
-extern XtAppContext app;
+#ifdef HAVE_FORKPTY
+# include <sys/ioctl.h>
+# ifdef HAVE_PTY_H
+#  include <pty.h>
+# endif
+# ifdef HAVE_UTIL_H
+#  include <util.h>
+# endif
+#endif /* HAVE_FORKPTY */
+
+#include "screenhack.h"
 
 #define FUZZY_BORDER
 
@@ -32,6 +58,14 @@ extern XtAppContext app;
 
 #define CURSOR_INDEX 128
 
+#define NPAR 16
+
+#define BUILTIN_FONT
+
+#ifdef BUILTIN_FONT
+# include "images/6x10font.xbm"
+#endif /* BUILTIN_FONT */
+
 typedef struct {
   unsigned char name;
   int width, height;
@@ -55,8 +89,14 @@ typedef struct {
   XFontStruct *font;
   int grid_width, grid_height;
   int char_width, char_height;
+  int saved_x, saved_y;
   int scale;
   int ticks;
+  int mode;
+  pid_t pid;
+  int escstate;
+  int csiparam[NPAR];
+  int curparam;
   p_char **chars;
   p_cell *cells;
   XGCValues gcv;
@@ -70,12 +110,23 @@ typedef struct {
 
   int cursor_x, cursor_y;
   XtIntervalId cursor_timer;
+  XtIntervalId pipe_timer;
   Time cursor_blink;
 
   FILE *pipe;
   XtInputId pipe_id;
   Bool input_available_p;
   Time subproc_relaunch_delay;
+  XComposeStatus compose;
+  Bool meta_sends_esc_p;
+  Bool swap_bs_del_p;
+  int delay;
+
+  char last_c;
+  int bk;
+
+  Bool meta_done_once;
+  unsigned int meta_mask;
 
 } p_state;
 
@@ -116,36 +167,75 @@ static void launch_text_generator (p_state *state);
    that box (the "charcell" box) then we're going to lose it.  Alas.
  */
 
-static p_state *
-init_phosphor (Display *dpy, Window window)
+
+static void clear (p_state *);
+static void set_cursor (p_state *, Bool on);
+
+static void *
+phosphor_init (Display *dpy, Window window)
 {
   int i;
   unsigned long flags;
   p_state *state = (p_state *) calloc (sizeof(*state), 1);
-  char *fontname = get_string_resource ("font", "Font");
+  char *fontname = get_string_resource (dpy, "font", "Font");
   XFontStruct *font;
 
   state->dpy = dpy;
   state->window = window;
 
   XGetWindowAttributes (dpy, window, &state->xgwa);
+/*  XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
 
-  state->font = XLoadQueryFont (dpy, fontname);
+  state->delay = get_integer_resource (dpy, "delay", "Integer");
+  state->meta_sends_esc_p = get_boolean_resource (dpy, "metaSendsESC", "Boolean");
+  state->swap_bs_del_p    = get_boolean_resource (dpy, "swapBSDEL",    "Boolean");
 
-  if (!state->font)
+  if (!strcasecmp (fontname, "builtin") ||
+      !strcasecmp (fontname, "(builtin)"))
     {
-      fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
+#ifndef BUILTIN_FONT
+      fprintf (stderr, "%s: no builtin font\n", progname);
       state->font = XLoadQueryFont (dpy, "fixed");
+#endif /* !BUILTIN_FONT */
     }
-  if (!state->font)
+  else
     {
-      fprintf(stderr, "couldn't load font \"fixed\"");
-      exit(1);
+      state->font = XLoadQueryFont (dpy, fontname);
+
+      if (!state->font)
+        {
+          fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
+          state->font = XLoadQueryFont (dpy, "fixed");
+        }
+      if (!state->font)
+        {
+          fprintf(stderr, "couldn't load font \"fixed\"");
+          exit(1);
+        }
     }
 
   font = state->font;
-  state->scale = get_integer_resource ("scale", "Integer");
-  state->ticks = STATE_MAX + get_integer_resource ("ticks", "Integer");
+  state->scale = get_integer_resource (dpy, "scale", "Integer");
+  state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
+  state->escstate = 0;
+
+  {
+    char *s = get_string_resource (dpy, "mode", "Integer");
+    state->mode = 0;
+    if (!s || !*s || !strcasecmp (s, "pipe"))
+      state->mode = 0;
+    else if (!strcasecmp (s, "pty"))
+      state->mode = 1;
+    else
+      fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
+               progname, s);
+
+#ifndef HAVE_FORKPTY
+    fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
+             progname);
+    state->mode = 0;
+#endif /* HAVE_FORKPTY */
+  }
 
 #if 0
   for (i = 0; i < font->n_properties; i++)
@@ -153,12 +243,22 @@ init_phosphor (Display *dpy, Window window)
       printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
 #endif /* 0 */
 
-  state->cursor_blink = get_integer_resource ("cursor", "Time");
+  state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
   state->subproc_relaunch_delay =
-    (1000 * get_integer_resource ("relaunch", "Time"));
+    (1000 * get_integer_resource (dpy, "relaunch", "Time"));
 
-  state->char_width  = font->max_bounds.width;
-  state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
+# ifdef BUILTIN_FONT
+  if (! font)
+    {
+      state->char_width  = (font6x10_width / 256) - 1;
+      state->char_height = font6x10_height;
+    }
+  else
+# endif /* BUILTIN_FONT */
+    {
+      state->char_width  = font->max_bounds.width;
+      state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
+    }
 
   state->grid_width = state->xgwa.width / (state->char_width * state->scale);
   state->grid_height = state->xgwa.height /(state->char_height * state->scale);
@@ -174,14 +274,14 @@ init_phosphor (Display *dpy, Window window)
     int h1, h2;
     double s1, s2, v1, v2;
 
-    unsigned long fg = get_pixel_resource ("foreground", "Foreground",
-                                           state->dpy, state->xgwa.colormap);
-    unsigned long bg = get_pixel_resource ("background", "Background",
-                                           state->dpy, state->xgwa.colormap);
-    unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
-                                              state->dpy,state->xgwa.colormap);
-    unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
-                                             state->dpy,state->xgwa.colormap);
+    unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
+                                           "foreground", "Foreground");
+    unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
+                                           "background", "Background");
+    unsigned long flare = get_pixel_resource (state->dpy,state->xgwa.colormap,
+                                              "flareForeground", "Foreground");
+    unsigned long fade = get_pixel_resource (state->dpy,state->xgwa.colormap,
+                                             "fadeForeground", "Foreground");
 
     XColor start, end;
 
@@ -205,7 +305,7 @@ init_phosphor (Display *dpy, Window window)
 
     /* Now, GCs all around.
      */
-    state->gcv.font = font->fid;
+    state->gcv.font = (font ? font->fid : 0);
     state->gcv.cap_style = CapRound;
 #ifdef FUZZY_BORDER
     state->gcv.line_width = (int) (((long) state->scale) * 1.3);
@@ -244,22 +344,88 @@ init_phosphor (Display *dpy, Window window)
 
   capture_font_bits (state);
 
+  set_cursor (state, True);
+
   launch_text_generator (state);
+/*  clear (state);*/
 
   return state;
 }
 
 
+/* Re-query the window size and update the internal character grid if changed.
+ */
+static void
+resize_grid (p_state *state)
+{
+  int ow = state->grid_width;
+  int oh = state->grid_height;
+  p_cell *ocells = state->cells;
+  int x, y;
+
+  XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
+
+  state->grid_width = state->xgwa.width   /(state->char_width  * state->scale);
+  state->grid_height = state->xgwa.height /(state->char_height * state->scale);
+
+  if (ow == state->grid_width &&
+      oh == state->grid_height)
+    return;
+
+  state->cells = (p_cell *) calloc (sizeof(p_cell),
+                                    state->grid_width * state->grid_height);
+
+  for (y = 0; y < state->grid_height; y++)
+    {
+      for (x = 0; x < state->grid_width; x++)
+        {
+          p_cell *ncell = &state->cells [state->grid_width * y + x];
+          if (x < ow && y < oh)
+            *ncell = ocells [ow * y + x];
+          ncell->changed = True;
+        }
+    }
+
+  if (state->cursor_x >= state->grid_width)
+    state->cursor_x = state->grid_width-1;
+  if (state->cursor_y >= state->grid_height)
+    state->cursor_y = state->grid_height-1;
+
+  free (ocells);
+}
+
+
 static void
 capture_font_bits (p_state *state)
 {
   XFontStruct *font = state->font;
-  int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
-  int height = state->char_height;
+  int safe_width, height;
   unsigned char string[257];
   int i;
-  Pixmap p = XCreatePixmap (state->dpy, state->window,
-                            (safe_width * 256), height, 1);
+  Pixmap p;
+
+# ifdef BUILTIN_FONT
+  Pixmap p2 = 0;
+
+  if (!font)
+    {
+      safe_width = state->char_width + 1;
+      height = state->char_height;
+      p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
+                                        (char *) font6x10_bits,
+                                        font6x10_width,
+                                        font6x10_height,
+                                        1, 0, 1);
+    }
+  else
+# endif /* BUILTIN_FONT */
+    {
+      safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
+      height = state->char_height;
+    }
+
+  p = XCreatePixmap (state->dpy, state->window,
+                     (safe_width * 256), height, 1);
 
   for (i = 0; i < 256; i++)
     string[i] = (unsigned char) i;
@@ -273,10 +439,16 @@ capture_font_bits (p_state *state)
 
   state->gcv.foreground = 1;
   state->gc1 = XCreateGC (state->dpy, p,
-                          (GCFont | GCForeground | GCBackground |
+                          ((font ? GCFont : 0) |
+                           GCForeground | GCBackground |
                            GCCapStyle | GCLineWidth),
                           &state->gcv);
 
+#ifdef HAVE_COCOA
+  jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
+  jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
+#endif
+
 #ifdef FUZZY_BORDER
   {
     state->gcv.line_width = (int) (((long) state->scale) * 0.8);
@@ -285,7 +457,8 @@ capture_font_bits (p_state *state)
     if (state->gcv.line_width < 1)
       state->gcv.line_width = 1;
     state->gc2 = XCreateGC (state->dpy, p,
-                            (GCFont | GCForeground | GCBackground |
+                            ((font ? GCFont : 0) |
+                             GCForeground | GCBackground |
                              GCCapStyle | GCLineWidth),
                             &state->gcv);
   }
@@ -293,31 +466,40 @@ capture_font_bits (p_state *state)
 
   XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
 
-  for (i = 0; i < 256; i++)
+# ifdef BUILTIN_FONT
+  if (p2)
     {
-      if (string[i] < font->min_char_or_byte2 ||
-          string[i] > font->max_char_or_byte2)
-        continue;
-      XDrawString (state->dpy, p, state->gc1,
-                   i * safe_width, font->ascent,
-                   (char *) (string + i), 1);
+      XCopyPlane (state->dpy, p2, p, state->gc1,
+                  0, 0, font6x10_width, font6x10_height, 
+                  0, 0, 1);
+      XFreePixmap (state->dpy, p2);
+    }
+  else
+# endif /* BUILTIN_FONT */
+    {
+      for (i = 0; i < 256; i++)
+        {
+          if (string[i] < font->min_char_or_byte2 ||
+              string[i] > font->max_char_or_byte2)
+            continue;
+          XDrawString (state->dpy, p, state->gc1,
+                       i * safe_width, font->ascent,
+                       (char *) (string + i), 1);
+        }
     }
 
   /* Draw the cursor. */
   XFillRectangle (state->dpy, p, state->gc1,
                   (CURSOR_INDEX * safe_width), 1,
-                  (font->per_char
-                   ? font->per_char['n'-font->min_char_or_byte2].width
-                   : font->max_bounds.width),
-                  font->ascent - 1);
-
-#if 0
-  XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
-              0, 0, (safe_width * 256), height, 0, 0, 1L);
-  XSync(state->dpy, False);
-#endif
+                  (font
+                   ? (font->per_char
+                      ? font->per_char['n'-font->min_char_or_byte2].width
+                      : font->max_bounds.width)
+                   : state->char_width),
+                  (font
+                   ? font->ascent - 1
+                   : state->char_height));
 
-  XSync (state->dpy, False);
   state->font_bits = XGetImage (state->dpy, p, 0, 0,
                                 (safe_width * 256), height, ~0L, XYPixmap);
   XFreePixmap (state->dpy, p);
@@ -353,13 +535,15 @@ char_to_pixmap (p_state *state, p_char *pc, int c)
   int x1, y;
 
   XFontStruct *font = state->font;
-  int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
+  int safe_width = (font 
+                    ? font->max_bounds.rbearing - font->min_bounds.lbearing
+                    : state->char_width + 1);
 
   int width  = state->scale * state->char_width;
   int height = state->scale * state->char_height;
 
-  if (c < font->min_char_or_byte2 ||
-      c > font->max_char_or_byte2)
+  if (font && (c < font->min_char_or_byte2 ||
+               c > font->max_char_or_byte2))
     goto DONE;
 
   gc = state->gc1;
@@ -450,7 +634,6 @@ static void
 set_cursor (p_state *state, Bool on)
 {
   if (set_cursor_1 (state, on))
-;
     {
       if (state->cursor_timer)
         XtRemoveTimeOut (state->cursor_timer);
@@ -466,6 +649,7 @@ static void
 cursor_off_timer (XtPointer closure, XtIntervalId *id)
 {
   p_state *state = (p_state *) closure;
+  XtAppContext app = XtDisplayToApplicationContext (state->dpy);
   set_cursor_1 (state, False);
   state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
                                          cursor_on_timer, closure);
@@ -475,6 +659,7 @@ static void
 cursor_on_timer (XtPointer closure, XtIntervalId *id)
 {
   p_state *state = (p_state *) closure;
+  XtAppContext app = XtDisplayToApplicationContext (state->dpy);
   set_cursor_1 (state, True);
   state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
                                          cursor_off_timer, closure);
@@ -568,7 +753,8 @@ static void
 print_char (p_state *state, int c)
 {
   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
-                              + state->cursor_x];
+                              + state->cursor_x];
+  int i, start, end;
 
   /* Start the cursor fading (in case we don't end up overwriting it.) */
   if (cell->state == FLARE || cell->state == NORMAL)
@@ -576,41 +762,388 @@ print_char (p_state *state, int c)
       cell->state = FADE;
       cell->changed = True;
     }
-
-  if (c == '\t') c = ' ';   /* blah. */
-
-  if (c == '\r' || c == '\n')
-    {
-      state->cursor_x = 0;
-      if (state->cursor_y == state->grid_height - 1)
-        scroll (state);
-      else
-        state->cursor_y++;
-    }
-  else if (c == '\014')
+  
+  if (state->pid)  /* Only interpret VT100 sequences if running in pty-mode.
+                      It would be nice if we could just interpret them all
+                      the time, but that would require subprocesses to send
+                      CRLF line endings instead of bare LF, so that's no good.
+                    */
     {
-      clear (state);
+      switch (state->escstate)
+       {
+       case 0:
+         switch (c)
+           {
+           case 7: /* BEL */
+             /* Dummy case - we don't want the screensaver to beep */
+              /* #### But maybe this should flash the screen? */
+             break;
+           case 8: /* BS */
+             if (state->cursor_x > 0)
+               state->cursor_x--;
+             break;
+           case 9: /* HT */
+             if (state->cursor_x < state->grid_width - 8)
+               {
+                 state->cursor_x = (state->cursor_x & ~7) + 8;
+               }
+             else
+               {
+                 state->cursor_x = 0;
+                 if (state->cursor_y < state->grid_height - 1)
+                   state->cursor_y++;
+                 else
+                   scroll (state);
+               }
+             break;
+           case 10: /* LF */
+           case 11: /* VT */
+           case 12: /* FF */
+             if(state->last_c == 13)
+               {
+                 cell->state = NORMAL;
+                 cell->p_char = state->chars[state->bk];
+                 cell->changed = True;
+               }
+             if (state->cursor_y < state->grid_height - 1)
+               state->cursor_y++;
+             else
+               scroll (state);
+             break;
+           case 13: /* CR */
+             state->cursor_x = 0;
+             cell = &state->cells[state->grid_width * state->cursor_y];
+             if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
+               state->bk = ' ';
+             else
+               state->bk = cell->p_char->name;
+             break;
+           case 14: /* SO */
+           case 15: /* SI */
+             /* Dummy case - I don't want to load several fonts for
+                the maybe two programs world-wide that use that */
+             break;
+           case 24: /* CAN */
+           case 26: /* SUB */
+             /* Dummy case - these interrupt escape sequences, so
+                they don't do anything in this state */
+             break;
+           case 27: /* ESC */
+             state->escstate = 1;
+             break;
+           case 127: /* DEL */
+             /* Dummy case - this is supposed to be ignored */
+             break;
+           case 155: /* CSI */
+             state->escstate = 2;
+             for(i = 0; i < NPAR; i++)
+               state->csiparam[i] = 0;
+             state->curparam = 0;
+             break;
+           default:
+             cell->state = FLARE;
+             cell->p_char = state->chars[c];
+             cell->changed = True;
+             state->cursor_x++;
+
+             if (c != ' ' && cell->p_char->blank_p)
+               cell->p_char = state->chars[CURSOR_INDEX];
+
+             if (state->cursor_x >= state->grid_width - 1)
+               {
+                 state->cursor_x = 0;
+                 if (state->cursor_y >= state->grid_height - 1)
+                   scroll (state);
+                 else
+                   state->cursor_y++;
+               }
+             break;
+           }
+         break;
+       case 1:
+         switch (c)
+           {
+           case 24: /* CAN */
+           case 26: /* SUB */
+             state->escstate = 0;
+             break;
+           case 'c': /* Reset */
+             clear (state);
+             state->escstate = 0;
+             break;
+           case 'D': /* Linefeed */
+             if (state->cursor_y < state->grid_height - 1)
+               state->cursor_y++;
+             else
+               scroll (state);
+             state->escstate = 0;
+             break;
+           case 'E': /* Newline */
+             state->cursor_x = 0;
+             state->escstate = 0;
+             break;
+           case 'M': /* Reverse newline */
+             if (state->cursor_y > 0)
+               state->cursor_y--;
+             state->escstate = 0;
+             break;
+           case '7': /* Save state */
+             state->saved_x = state->cursor_x;
+             state->saved_y = state->cursor_y;
+             state->escstate = 0;
+             break;
+           case '8': /* Restore state */
+             state->cursor_x = state->saved_x;
+             state->cursor_y = state->saved_y;
+             state->escstate = 0;
+             break;
+           case '[': /* CSI */
+             state->escstate = 2;
+             for(i = 0; i < NPAR; i++)
+               state->csiparam[i] = 0;
+             state->curparam = 0;
+             break;
+           case '%': /* Select charset */
+             /* No, I don't support UTF-8, since the phosphor font
+                isn't even Unicode anyway. We must still catch the
+                last byte, though. */
+           case '(':
+           case ')':
+             /* I don't support different fonts either - see above
+                for SO and SI */
+             state->escstate = 3;
+             break;
+           default:
+             /* Escape sequences not supported:
+              * 
+              * H - Set tab stop
+              * Z - Terminal identification
+              * > - Keypad change
+              * = - Other keypad change
+              * ] - OS command
+              */
+             state->escstate = 0;
+             break;
+           }
+         break;
+       case 2:
+         switch (c)
+           {
+           case 24: /* CAN */
+           case 26: /* SUB */
+             state->escstate = 0;
+             break;
+            case '0': case '1': case '2': case '3': case '4':
+            case '5': case '6': case '7': case '8': case '9':
+             if (state->curparam < NPAR)
+               state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
+             break;
+           case ';':
+             state->csiparam[++state->curparam] = 0;
+             break;
+           case '[':
+             state->escstate = 3;
+             break;
+           case '@':
+             for (i = 0; i < state->csiparam[0]; i++)
+               {
+                 if(++state->cursor_x > state->grid_width)
+                   {
+                     state->cursor_x = 0;
+                     if (state->cursor_y < state->grid_height - 1)
+                       state->cursor_y++;
+                     else
+                       scroll (state);
+                   }
+                 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
+                 if (cell->state == FLARE || cell->state == NORMAL)
+                   {
+                     cell->state = FADE;
+                     cell->changed = True;
+                   }
+               }
+             state->escstate = 0;
+             break;
+           case 'F':
+             state->cursor_x = 0;
+           case 'A':
+             if (state->csiparam[0] == 0)
+               state->csiparam[0] = 1;
+             if ((state->cursor_y -= state->csiparam[0]) < 0)
+               state->cursor_y = 0;
+             state->escstate = 0;
+             break;
+           case 'E':
+             state->cursor_x = 0;
+           case 'e':
+           case 'B':
+             if (state->csiparam[0] == 0)
+               state->csiparam[0] = 1;
+             if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
+               state->cursor_y = state->grid_height - 1;
+             state->escstate = 0;
+             break;
+           case 'a':
+           case 'C':
+             if (state->csiparam[0] == 0)
+               state->csiparam[0] = 1;
+             if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
+               state->cursor_x = state->grid_width - 1;
+             state->escstate = 0;
+             break;
+           case 'D':
+             if (state->csiparam[0] == 0)
+               state->csiparam[0] = 1;
+             if ((state->cursor_x -= state->csiparam[0]) < 0)
+               state->cursor_x = 0;
+             state->escstate = 0;
+             break;
+           case 'd':
+             if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
+               state->cursor_y = state->grid_height - 1;
+             state->escstate = 0;
+             break;
+           case '`':
+           case 'G':
+             if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
+               state->cursor_x = state->grid_width - 1;
+             state->escstate = 0;
+             break;
+           case 'f':
+           case 'H':
+             if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
+               state->cursor_y = state->grid_height - 1;
+             if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
+               state->cursor_x = state->grid_width - 1;
+             if(state->cursor_y < 0)
+               state->cursor_y = 0;
+             if(state->cursor_x < 0)
+               state->cursor_x = 0;
+             state->escstate = 0;
+             break;
+           case 'J':
+             start = 0;
+             end = state->grid_height * state->grid_width;
+             if (state->csiparam[0] == 0)
+               start = state->grid_width * state->cursor_y + state->cursor_x;
+             if (state->csiparam[0] == 1)
+               end = state->grid_width * state->cursor_y + state->cursor_x;
+             for (i = start; i < end; i++)
+               {
+                 cell = &state->cells[i];
+                 if (cell->state == FLARE || cell->state == NORMAL)
+                   {
+                     cell->state = FADE;
+                     cell->changed = True;
+                   }
+               }
+             set_cursor (state, True);
+             state->escstate = 0;
+             break;
+           case 'K':
+             start = 0;
+             end = state->grid_width;
+             if (state->csiparam[0] == 0)
+               start = state->cursor_x;
+             if (state->csiparam[1] == 1)
+               end = state->cursor_x;
+             for (i = start; i < end; i++)
+               {
+                 if (cell->state == FLARE || cell->state == NORMAL)
+                   {
+                     cell->state = FADE;
+                     cell->changed = True;
+                   }
+                 cell++;
+               }
+             state->escstate = 0;
+             break;
+           case 's': /* Save position */
+             state->saved_x = state->cursor_x;
+             state->saved_y = state->cursor_y;
+             state->escstate = 0;
+             break;
+           case 'u': /* Restore position */
+             state->cursor_x = state->saved_x;
+             state->cursor_y = state->saved_y;
+             state->escstate = 0;
+             break;
+           case '?': /* DEC Private modes */
+             if ((state->curparam != 0) || (state->csiparam[0] != 0))
+               state->escstate = 0;
+             break;
+           default:
+             /* Known unsupported CSIs:
+              *
+              * L - Insert blank lines
+              * M - Delete lines (I don't know what this means...)
+              * P - Delete characters
+              * X - Erase characters (difference with P being...?)
+              * c - Terminal identification
+              * g - Clear tab stop(s)
+              * h - Set mode (Mainly due to its complexity and lack of good
+                     docs)
+              * l - Clear mode
+              * m - Set mode (Phosphor is, per defenition, green on black)
+              * n - Status report
+              * q - Set keyboard LEDs
+              * r - Set scrolling region (too exhausting - noone uses this,
+                     right?)
+              */
+             state->escstate = 0;
+             break;
+           }
+         break;
+       case 3:
+         state->escstate = 0;
+         break;
+       }
+      set_cursor (state, True);
     }
   else
     {
-      cell->state = FLARE;
-      cell->p_char = state->chars[c];
-      cell->changed = True;
-      state->cursor_x++;
-
-      if (c != ' ' && cell->p_char->blank_p)
-        cell->p_char = state->chars[CURSOR_INDEX];
-
-      if (state->cursor_x >= state->grid_width - 1)
-        {
-          state->cursor_x = 0;
-          if (state->cursor_y >= state->grid_height - 1)
-            scroll (state);
-          else
-            state->cursor_y++;
-        }
+      if (c == '\t') c = ' ';   /* blah. */
+
+      if (c == '\r' || c == '\n')  /* handle CR, LF, or CRLF as "new line". */
+       {
+         if (c == '\n' && state->last_c == '\r')
+           ;   /* CRLF -- do nothing */
+         else
+           {
+             state->cursor_x = 0;
+             if (state->cursor_y == state->grid_height - 1)
+               scroll (state);
+             else
+               state->cursor_y++;
+           }
+       }
+      else if (c == '\014')
+       {
+         clear (state);
+       }
+      else
+       {
+         cell->state = FLARE;
+         cell->p_char = state->chars[c];
+         cell->changed = True;
+         state->cursor_x++;
+
+         if (c != ' ' && cell->p_char->blank_p)
+           cell->p_char = state->chars[CURSOR_INDEX];
+
+         if (state->cursor_x >= state->grid_width - 1)
+           {
+             state->cursor_x = 0;
+             if (state->cursor_y >= state->grid_height - 1)
+               scroll (state);
+             else
+               state->cursor_y++;
+           }
+       }
+      set_cursor (state, True);
     }
-  set_cursor (state, True);
+
+  state->last_c = c;
 }
 
 
@@ -672,12 +1205,14 @@ update_display (p_state *state, Bool changed_only)
 }
 
 
-static void
-run_phosphor (p_state *state)
+static unsigned long
+phosphor_draw (Display *dpy, Window window, void *closure)
 {
+  p_state *state = (p_state *) closure;
   update_display (state, True);
   decay (state);
   drain_input (state);
+  return state->delay;
 }
 
 \f
@@ -695,24 +1230,97 @@ subproc_cb (XtPointer closure, int *source, XtInputId *id)
 static void
 launch_text_generator (p_state *state)
 {
-  char *oprogram = get_string_resource ("program", "Program");
-  char *program = (char *) malloc (strlen (oprogram) + 10);
+  XtAppContext app = XtDisplayToApplicationContext (state->dpy);
+  Display *dpy = state->dpy;
+  char buf[255];
+  char *oprogram = get_string_resource (dpy, "program", "Program");
+  char *program = (char *) malloc (strlen (oprogram) + 50);
 
   strcpy (program, "( ");
   strcat (program, oprogram);
+
+  /* Kludge!  Special-case "xscreensaver-text" to tell it how wide
+     the screen is.  We used to do this by just always feeding
+     `program' through sprintf() and setting the default value to
+     "xscreensaver-text --cols %d", but that makes things blow up
+     if someone ever uses a --program that includes a % anywhere.
+   */
+  if (!strcmp (oprogram, "xscreensaver-text"))
+    sprintf (program + strlen(program), " --cols %d", state->grid_width-1);
+
   strcat (program, " ) 2>&1");
 
-  if ((state->pipe = popen (program, "r")))
+#ifdef HAVE_FORKPTY
+  if(state->mode == 1)
     {
-      state->pipe_id =
-        XtAppAddInput (app, fileno (state->pipe),
-                       (XtPointer) (XtInputReadMask | XtInputExceptMask),
-                       subproc_cb, (XtPointer) state);
+      int fd;
+      struct winsize ws;
+      
+      ws.ws_row = state->grid_height - 1;
+      ws.ws_col = state->grid_width  - 2;
+      ws.ws_xpixel = state->xgwa.width;
+      ws.ws_ypixel = state->xgwa.height;
+      
+      state->pipe = NULL;
+      if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
+       {
+          /* Unable to fork */
+          sprintf (buf, "%.100s: forkpty", progname);
+         perror(buf);
+       }
+      else if(!state->pid)
+       {
+          /* This is the child fork. */
+          char *av[10];
+          int i = 0;
+         if (putenv("TERM=vt100"))
+            abort();
+          av[i++] = "/bin/sh";
+          av[i++] = "-c";
+          av[i++] = program;
+          av[i] = 0;
+          execvp (av[0], av);
+          sprintf (buf, "%.100s: %.100s", progname, oprogram);
+         perror(buf);
+         exit(1);
+       }
+      else
+       {
+          /* This is the parent fork. */
+         state->pipe = fdopen(fd, "r+");
+         state->pipe_id =
+           XtAppAddInput (app, fileno (state->pipe),
+                          (XtPointer) (XtInputReadMask | XtInputExceptMask),
+                          subproc_cb, (XtPointer) state);
+       }
     }
   else
+#endif /* HAVE_FORKPTY */
     {
-      perror (program);
+      /* don't mess up controlling terminal if someone dumbly does
+         "-pipe -program tcsh". */
+      static int protected_stdin_p = 0;
+      if (! protected_stdin_p) {
+        fclose (stdin);
+        open ("/dev/null", O_RDWR); /* re-allocate fd 0 */
+        protected_stdin_p = 1;
+      }
+
+      if ((state->pipe = popen (program, "r")))
+       {
+         state->pipe_id =
+           XtAppAddInput (app, fileno (state->pipe),
+                          (XtPointer) (XtInputReadMask | XtInputExceptMask),
+                          subproc_cb, (XtPointer) state);
+       }
+      else
+       {
+          sprintf (buf, "%.100s: %.100s", progname, program);
+         perror (buf);
+       }
     }
+
+  free (program);
 }
 
 
@@ -720,6 +1328,8 @@ static void
 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
 {
   p_state *state = (p_state *) closure;
+  if (!state->pipe_timer) abort();
+  state->pipe_timer = 0;
   launch_text_generator (state);
 }
 
@@ -727,7 +1337,8 @@ relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
 static void
 drain_input (p_state *state)
 {
-  if (state->input_available_p)
+  XtAppContext app = XtDisplayToApplicationContext (state->dpy);
+  if (state->input_available_p && state->pipe)
     {
       unsigned char s[2];
       int n = read (fileno (state->pipe), (void *) s, 1);
@@ -739,17 +1350,29 @@ drain_input (p_state *state)
         {
           XtRemoveInput (state->pipe_id);
           state->pipe_id = 0;
-          pclose (state->pipe);
+         if (state->pid)
+           {
+             waitpid(state->pid, NULL, 0);
+             fclose (state->pipe);
+           }
+         else
+           {
+             pclose (state->pipe);
+           }
           state->pipe = 0;
 
-          if (state->cursor_x != 0)    /* break line if unbroken */
-            print_char (state, '\n');  /* blank line */
+          if (state->cursor_x != 0) {  /* break line if unbroken */
+            print_char (state, '\r');
+            print_char (state, '\n');
+          }
+          print_char (state, '\r');    /* blank line */
           print_char (state, '\n');
 
           /* Set up a timer to re-launch the subproc in a bit. */
-          XtAppAddTimeOut (app, state->subproc_relaunch_delay,
-                           relaunch_generator_timer,
-                           (XtPointer) state);
+          state->pipe_timer =
+            XtAppAddTimeOut (app, state->subproc_relaunch_delay,
+                             relaunch_generator_timer,
+                             (XtPointer) state);
         }
         
       state->input_available_p = False;
@@ -757,51 +1380,177 @@ drain_input (p_state *state)
 }
 
 
-\f
-char *progclass = "Phosphor";
+/* The interpretation of the ModN modifiers is dependent on what keys
+   are bound to them: Mod1 does not necessarily mean "meta".  It only
+   means "meta" if Meta_L or Meta_R are bound to it.  If Meta_L is on
+   Mod5, then Mod5 is the one that means Meta.  Oh, and Meta and Alt
+   aren't necessarily the same thing.  Icepicks in my forehead!
+ */
+static unsigned int
+do_icccm_meta_key_stupidity (Display *dpy)
+{
+  unsigned int modbits = 0;
+# ifndef HAVE_COCOA
+  int i, j, k;
+  XModifierKeymap *modmap = XGetModifierMapping (dpy);
+  for (i = 3; i < 8; i++)
+    for (j = 0; j < modmap->max_keypermod; j++)
+      {
+        int code = modmap->modifiermap[i * modmap->max_keypermod + j];
+        KeySym *syms;
+        int nsyms = 0;
+        if (code == 0) continue;
+        syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
+        for (k = 0; k < nsyms; k++)
+          if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
+              syms[k] == XK_Alt_L  || syms[k] == XK_Alt_R)
+            modbits |= (1 << i);
+        XFree (syms);
+      }
+  XFreeModifiermap (modmap);
+# endif /* HAVE_COCOA */
+  return modbits;
+}
+
+/* Returns a mask of the bit or bits of a KeyPress event that mean "meta". 
+ */
+static unsigned int
+meta_modifier (p_state *state)
+{
+  if (!state->meta_done_once)
+    {
+      /* Really, we are supposed to recompute this if a KeymapNotify
+         event comes in, but fuck it. */
+      state->meta_done_once = True;
+      state->meta_mask = do_icccm_meta_key_stupidity (state->dpy);
+    }
+  return state->meta_mask;
+}
+
+
+static void
+phosphor_reshape (Display *dpy, Window window, void *closure, 
+                 unsigned int w, unsigned int h)
+{
+  p_state *state = (p_state *) closure;
+  resize_grid (state);
 
-char *defaults [] = {
+# if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
+  if (state->pid)
+    {
+      /* Tell the sub-process that the screen size has changed. */
+      struct winsize ws;
+      ws.ws_row = state->grid_height - 1;
+      ws.ws_col = state->grid_width  - 2;
+      ws.ws_xpixel = state->xgwa.width;
+      ws.ws_ypixel = state->xgwa.height;
+      ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
+      kill (state->pid, SIGWINCH);
+    }
+# endif /* HAVE_FORKPTY && TIOCSWINSZ */
+}
+
+static Bool
+phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+  p_state *state = (p_state *) closure;
+
+  if (event->xany.type == Expose)
+    update_display (state, False);
+  else if (event->xany.type == KeyPress)
+    {
+      KeySym keysym;
+      unsigned char c = 0;
+      XLookupString (&event->xkey, (char *) &c, 1, &keysym,
+                     &state->compose);
+      if (c != 0 && state->pipe)
+        {
+          if (!state->swap_bs_del_p) ;
+          else if (c == 127) c = 8;
+          else if (c == 8)   c = 127;
+
+          /* If meta was held down, send ESC, or turn on the high bit. */
+          if (event->xkey.state & meta_modifier (state))
+            {
+              if (state->meta_sends_esc_p)
+                fputc ('\033', state->pipe);
+              else
+                c |= 0x80;
+            }
+
+          fputc (c, state->pipe);
+          fflush (state->pipe);
+          event->xany.type = 0;  /* don't interpret this event defaultly. */
+        }
+      return True;
+    }
+
+  return False;
+}
+
+static void
+phosphor_free (Display *dpy, Window window, void *closure)
+{
+  p_state *state = (p_state *) closure;
+
+  if (state->pipe_id)
+    XtRemoveInput (state->pipe_id);
+  if (state->pipe)
+    pclose (state->pipe);
+  if (state->cursor_timer)
+    XtRemoveTimeOut (state->cursor_timer);
+  if (state->pipe_timer)
+    XtRemoveTimeOut (state->pipe_timer);
+
+  /* #### there's more to free here */
+
+  free (state);
+}
+
+
+
+static const char *phosphor_defaults [] = {
   ".background:                   Black",
-  ".foreground:                   Green",
-  "*fadeForeground:       DarkGreen",
-  "*flareForeground:      White",
+  ".foreground:                   #00FF00",
+  "*fadeForeground:       #006400",
+  "*flareForeground:      #FFFFFF",
+#if defined(BUILTIN_FONT)
+  "*font:                 (builtin)",
+#elif defined(HAVE_COCOA)
+  "*font:                 Monaco 15",
+#else
   "*font:                 fixed",
+#endif
   "*scale:                6",
   "*ticks:                20",
   "*delay:                50000",
   "*cursor:               333",
-  "*program:            " ZIPPY_PROGRAM,
+  "*program:              xscreensaver-text",
   "*relaunch:             5",
+  "*metaSendsESC:         True",
+  "*swapBSDEL:            True",
+#ifdef HAVE_FORKPTY
+  "*mode:                  pty",
+#else  /* !HAVE_FORKPTY */
+  "*mode:                  pipe",
+#endif /* !HAVE_FORKPTY */
   0
 };
 
-XrmOptionDescRec options [] = {
+static XrmOptionDescRec phosphor_options [] = {
   { "-font",           ".font",                XrmoptionSepArg, 0 },
   { "-scale",          ".scale",               XrmoptionSepArg, 0 },
   { "-ticks",          ".ticks",               XrmoptionSepArg, 0 },
   { "-delay",          ".delay",               XrmoptionSepArg, 0 },
   { "-program",                ".program",             XrmoptionSepArg, 0 },
+  { "-pty",            ".mode",                XrmoptionNoArg, "pty"   },
+  { "-pipe",           ".mode",                XrmoptionNoArg, "pipe"  },
+  { "-meta",           ".metaSendsESC",        XrmoptionNoArg, "False" },
+  { "-esc",            ".metaSendsESC",        XrmoptionNoArg, "True"  },
+  { "-bs",             ".swapBSDEL",           XrmoptionNoArg, "False" },
+  { "-del",            ".swapBSDEL",           XrmoptionNoArg, "True"  },
   { 0, 0, 0, 0 }
 };
 
 
-void
-screenhack (Display *dpy, Window window)
-{
-  int delay = get_integer_resource ("delay", "Integer");
-  p_state *state = init_phosphor (dpy, window);
-
-  clear (state);
-
-  while (1)
-    {
-      run_phosphor (state);
-      XSync (dpy, False);
-      screenhack_handle_events (dpy);
-
-      if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
-        XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
-
-      if (delay) usleep (delay);
-    }
-}
+XSCREENSAVER_MODULE ("Phosphor", phosphor)