From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / phosphor.c
index 8ba63dd28390b4f467474981837dcf5d06c8f5f7..63df6dfeb1388ee71c5dd96402e46b896a535780 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1999, 2000 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1999-2013 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,20 @@
  * 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"
-#include <stdio.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include <X11/Intrinsic.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#ifndef HAVE_COCOA
+# include <X11/Intrinsic.h>
+#endif
 
-extern XtAppContext app;
+#include "screenhack.h"
+#include "textclient.h"
 
 #define FUZZY_BORDER
 
@@ -32,6 +37,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;
@@ -53,10 +66,16 @@ typedef struct {
   Window window;
   XWindowAttributes xgwa;
   XFontStruct *font;
+  const char *program;
   int grid_width, grid_height;
   int char_width, char_height;
+  int saved_x, saved_y;
   int scale;
   int ticks;
+  int mode;
+  int escstate;
+  int csiparam[NPAR];
+  int curparam;
   p_char **chars;
   p_cell *cells;
   XGCValues gcv;
@@ -71,20 +90,20 @@ typedef struct {
   int cursor_x, cursor_y;
   XtIntervalId cursor_timer;
   Time cursor_blink;
+  int delay;
+  Bool pty_p;
+
+  text_data *tc;
 
-  FILE *pipe;
-  XtInputId pipe_id;
-  Bool input_available_p;
-  Time subproc_relaunch_delay;
+  char last_c;
+  int bk;
 
 } p_state;
 
 
 static void capture_font_bits (p_state *state);
 static p_char *make_character (p_state *state, int c);
-static void drain_input (p_state *state);
 static void char_to_pixmap (p_state *state, p_char *pc, int c);
-static void launch_text_generator (p_state *state);
 
 
 /* About font metrics:
@@ -116,49 +135,72 @@ 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->pty_p = get_boolean_resource (dpy, "usePty", "UsePty");
 
-  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;
 
-#if 0
-  for (i = 0; i < font->n_properties; i++)
-    if (font->properties[i].name == XA_FONT)
-      printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
-#endif /* 0 */
 
-  state->cursor_blink = get_integer_resource ("cursor", "Time");
-  state->subproc_relaunch_delay =
-    (1000 * get_integer_resource ("relaunch", "Time"));
+  state->cursor_blink = get_integer_resource (dpy, "cursor", "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 +216,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;
 
@@ -194,7 +236,8 @@ init_phosphor (Display *dpy, Window window)
     /* Now allocate a ramp of colors from the main color to the background. */
     rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
     rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
-    make_color_ramp (state->dpy, state->xgwa.colormap,
+    make_color_ramp (state->xgwa.screen, state->xgwa.visual,
+                     state->xgwa.colormap,
                      h1, s1, v1,
                      h2, s2, v2,
                      colors, &ncolors,
@@ -205,7 +248,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 +287,95 @@ init_phosphor (Display *dpy, Window window)
 
   capture_font_bits (state);
 
-  launch_text_generator (state);
+  set_cursor (state, True);
+
+/*  clear (state);*/
+
+  state->tc = textclient_open (dpy);
+  textclient_reshape (state->tc,
+                      state->xgwa.width,
+                      state->xgwa.height,
+                      state->grid_width  - 1,
+                      state->grid_height - 1);
 
   return state;
 }
 
 
+/* Re-query the window size and update the internal character grid if changed.
+ */
+static Bool
+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 False;
+
+  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);
+  return True;
+}
+
+
 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 +389,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 +407,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 +416,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 +485,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;
@@ -459,12 +593,11 @@ set_cursor (p_state *state, Bool on)
 }
 
 
-
-
 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);
@@ -474,6 +607,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);
@@ -566,10 +700,8 @@ scroll (p_state *state)
 static void
 print_char (p_state *state, int c)
 {
-  static char last_c = 0;
-
   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
-                              + state->cursor_x];
+                              + state->cursor_x];
 
   /* Start the cursor fading (in case we don't end up overwriting it.) */
   if (cell->state == FLARE || cell->state == NORMAL)
@@ -577,48 +709,391 @@ print_char (p_state *state, int c)
       cell->state = FADE;
       cell->changed = True;
     }
-
-  if (c == '\t') c = ' ';   /* blah. */
-
-  if (c == '\r' || c == '\n')
-    {
-      if (c == '\n' && 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')
+  
+#ifdef HAVE_FORKPTY
+  if (state->pty_p) /* 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);
+      int i, start, end;
+      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
+#endif /* HAVE_FORKPTY */
     {
-      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);
 
-  last_c = c;
+  state->last_c = c;
 }
 
 
@@ -680,136 +1155,108 @@ 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;
+  int c;
   update_display (state, True);
   decay (state);
-  drain_input (state);
-}
 
-\f
-/* Subprocess.
- */
+  c = textclient_getc (state->tc);
+  if (c > 0) 
+    print_char (state, c);
 
-static void
-subproc_cb (XtPointer closure, int *source, XtInputId *id)
-{
-  p_state *state = (p_state *) closure;
-  state->input_available_p = True;
+  return state->delay;
 }
 
 
 static void
-launch_text_generator (p_state *state)
+phosphor_reshape (Display *dpy, Window window, void *closure, 
+                 unsigned int w, unsigned int h)
 {
-  char *oprogram = get_string_resource ("program", "Program");
-  char *program = (char *) malloc (strlen (oprogram) + 10);
+  p_state *state = (p_state *) closure;
+  Bool changed_p = resize_grid (state);
 
-  strcpy (program, "( ");
-  strcat (program, oprogram);
-  strcat (program, " ) 2>&1");
+  if (! changed_p) return;
 
-  if ((state->pipe = popen (program, "r")))
-    {
-      state->pipe_id =
-        XtAppAddInput (app, fileno (state->pipe),
-                       (XtPointer) (XtInputReadMask | XtInputExceptMask),
-                       subproc_cb, (XtPointer) state);
-    }
-  else
-    {
-      perror (program);
-    }
+  textclient_reshape (state->tc, w, h,
+                      state->grid_width  - 1,
+                      state->grid_height - 1);
 }
 
 
-static void
-relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
+static Bool
+phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
 {
   p_state *state = (p_state *) closure;
-  launch_text_generator (state);
-}
 
+  if (event->xany.type == Expose)
+    update_display (state, False);
+  else if (event->xany.type == KeyPress)
+    return textclient_putc (state->tc, &event->xkey);
+  return False;
+}
 
 static void
-drain_input (p_state *state)
+phosphor_free (Display *dpy, Window window, void *closure)
 {
-  if (state->input_available_p)
-    {
-      unsigned char s[2];
-      int n = read (fileno (state->pipe), (void *) s, 1);
-      if (n == 1)
-        {
-          print_char (state, s[0]);
-        }
-      else
-        {
-          XtRemoveInput (state->pipe_id);
-          state->pipe_id = 0;
-          pclose (state->pipe);
-          state->pipe = 0;
-
-          if (state->cursor_x != 0)    /* break line if unbroken */
-            print_char (state, '\n');  /* 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->input_available_p = False;
-    }
+  p_state *state = (p_state *) closure;
+
+  textclient_close (state->tc);
+  if (state->cursor_timer)
+    XtRemoveTimeOut (state->cursor_timer);
+
+  /* #### there's more to free here */
+
+  free (state);
 }
 
 
-\f
-char *progclass = "Phosphor";
 
-char *defaults [] = {
+static const char *phosphor_defaults [] = {
   ".background:                   Black",
-  ".foreground:                   Green",
-  "*fadeForeground:       DarkGreen",
-  "*flareForeground:      White",
+  ".foreground:                   #00FF00",
+  "*fpsSolid:             true",
+  "*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:            " FORTUNE_PROGRAM,
+  "*program:              xscreensaver-text",
   "*relaunch:             5",
+  "*metaSendsESC:         True",
+  "*swapBSDEL:            True",
+#ifdef HAVE_FORKPTY
+  "*usePty:                True",
+#else  /* !HAVE_FORKPTY */
+  "*usePty:                False",
+#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 },
+  { "-pipe",           ".usePty",              XrmoptionNoArg, "False" },
+  { "-pty",            ".usePty",              XrmoptionNoArg, "True"  },
+  { "-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)