From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / phosphor.c
index 6f9fe9170a261d88bf174ce61a31bed7e897ee8e..1238048976d0e3dd6c8eebb1fa36e52f464acdf9 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1999-2006 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1999-2014 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
 # include "config.h"
 #endif /* HAVE_CONFIG_H */
 
-#include <stdio.h>
-#include <signal.h>
-#include <sys/wait.h>
-
-#ifndef HAVE_COCOA
-# define XK_MISCELLANY
-# include <X11/keysymdef.h>
-# include <X11/Xatom.h>
+#ifndef HAVE_JWXYZ
 # include <X11/Intrinsic.h>
 #endif
 
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-# include <fcntl.h>  /* for O_RDWR */
-#endif
-
-#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"
+#include "textclient.h"
+#include "utf8wc.h"
 
 #define FUZZY_BORDER
 
@@ -87,16 +67,19 @@ 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;
-  pid_t pid;
+
   int escstate;
   int csiparam[NPAR];
   int curparam;
+  int unicruds; char unicrud[7];
+
   p_char **chars;
   p_cell *cells;
   XGCValues gcv;
@@ -110,32 +93,21 @@ 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;
+  Bool pty_p;
+
+  text_data *tc;
 
   char last_c;
   int bk;
 
-  Bool meta_done_once;
-  unsigned int meta_mask;
-
 } 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:
@@ -171,6 +143,11 @@ static void launch_text_generator (p_state *state);
 static void clear (p_state *);
 static void set_cursor (p_state *, Bool on);
 
+static unsigned short scale_color_channel (unsigned short ch1, unsigned short ch2)
+{
+  return (ch1 * 100 + ch2 * 156) >> 8;
+}
+
 static void *
 phosphor_init (Display *dpy, Window window)
 {
@@ -187,8 +164,7 @@ phosphor_init (Display *dpy, Window window)
 /*  XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
 
   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");
+  state->pty_p = get_boolean_resource (dpy, "usePty", "UsePty");
 
   if (!strcasecmp (fontname, "builtin") ||
       !strcasecmp (fontname, "(builtin)"))
@@ -219,33 +195,8 @@ phosphor_init (Display *dpy, Window window)
   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++)
-    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 (dpy, "cursor", "Time");
-  state->subproc_relaunch_delay =
-    (1000 * get_integer_resource (dpy, "relaunch", "Time"));
 
 # ifdef BUILTIN_FONT
   if (! font)
@@ -269,7 +220,7 @@ phosphor_init (Display *dpy, Window window)
   state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
 
   {
-    int ncolors = MAX (0, state->ticks - 3);
+    int ncolors = MAX (1, state->ticks - 3);
     XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
     int h1, h2;
     double s1, s2, v1, v2;
@@ -278,23 +229,31 @@ phosphor_init (Display *dpy, Window window)
                                            "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");
+    unsigned long flare = fg;
 
-    XColor start, end;
+    XColor fg_color, bg_color;
 
-    start.pixel = fade;
-    XQueryColor (state->dpy, state->xgwa.colormap, &start);
+    fg_color.pixel = fg;
+    XQueryColor (state->dpy, state->xgwa.colormap, &fg_color);
 
-    end.pixel = bg;
-    XQueryColor (state->dpy, state->xgwa.colormap, &end);
+    bg_color.pixel = bg;
+    XQueryColor (state->dpy, state->xgwa.colormap, &bg_color);
 
     /* 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,
+    rgb_to_hsv (scale_color_channel(fg_color.red, bg_color.red),
+                scale_color_channel(fg_color.green, bg_color.green),
+                scale_color_channel(fg_color.blue, bg_color.blue),
+                &h1, &s1, &v1);
+    rgb_to_hsv (bg_color.red, bg_color.green, bg_color.blue, &h2, &s2, &v2);
+
+    /* Avoid rainbow effects when fading to black/grey/white. */
+    if (s2 < 0.003)
+      h2 = h1;
+    if (s1 < 0.003)
+      h1 = h2;
+
+    make_color_ramp (state->xgwa.screen, state->xgwa.visual,
+                     state->xgwa.colormap,
                      h1, s1, v1,
                      h2, s2, v2,
                      colors, &ncolors,
@@ -303,6 +262,21 @@ phosphor_init (Display *dpy, Window window)
     /* Adjust to the number of colors we actually got. */
     state->ticks = ncolors + STATE_MAX;
 
+    /* If the foreground is brighter than the background, the flare is white.
+     * Otherwise, the flare is left at the foreground color (i.e. no flare). */
+    rgb_to_hsv (fg_color.red, fg_color.green, fg_color.blue, &h1, &s1, &v1);
+    if (v2 <= v1)
+      {
+        XColor white;
+        /* WhitePixel is only for the default visual, which can be overridden
+         * on the command line. */
+        white.red = 0xffff;
+        white.green = 0xffff;
+        white.blue = 0xffff;
+        if (XAllocColor(state->dpy, state->xgwa.colormap, &white))
+          flare = white.pixel;
+      }
+
     /* Now, GCs all around.
      */
     state->gcv.font = (font ? font->fid : 0);
@@ -346,16 +320,23 @@ phosphor_init (Display *dpy, Window window)
 
   set_cursor (state, True);
 
-  launch_text_generator (state);
 /*  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,
+                      0);
+
   return state;
 }
 
 
 /* Re-query the window size and update the internal character grid if changed.
  */
-static void
+static Bool
 resize_grid (p_state *state)
 {
   int ow = state->grid_width;
@@ -370,7 +351,7 @@ resize_grid (p_state *state)
 
   if (ow == state->grid_width &&
       oh == state->grid_height)
-    return;
+    return False;
 
   state->cells = (p_cell *) calloc (sizeof(p_cell),
                                     state->grid_width * state->grid_height);
@@ -392,6 +373,7 @@ resize_grid (p_state *state)
     state->cursor_y = state->grid_height-1;
 
   free (ocells);
+  return True;
 }
 
 
@@ -444,7 +426,7 @@ capture_font_bits (p_state *state)
                            GCCapStyle | GCLineWidth),
                           &state->gcv);
 
-#ifdef HAVE_COCOA
+#ifdef HAVE_JWXYZ
   jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
   jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
 #endif
@@ -643,8 +625,6 @@ set_cursor (p_state *state, Bool on)
 }
 
 
-
-
 static void
 cursor_off_timer (XtPointer closure, XtIntervalId *id)
 {
@@ -752,9 +732,10 @@ scroll (p_state *state)
 static void
 print_char (p_state *state, int c)
 {
+  int cols = state->grid_width;
+  int rows = state->grid_height;
   p_cell *cell = &state->cells[state->grid_width * state->cursor_y
                               + 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)
@@ -763,12 +744,18 @@ print_char (p_state *state, int c)
       cell->changed = True;
     }
   
-  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.
-                    */
+#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.
+                     */
     {
+      int i;
+      int start, end;
+
+      /* Mostly duplicated in apple2-main.c */
+
       switch (state->escstate)
        {
        case 0:
@@ -783,20 +770,23 @@ print_char (p_state *state, int c)
                state->cursor_x--;
              break;
            case 9: /* HT */
-             if (state->cursor_x < state->grid_width - 8)
+             if (state->cursor_x < cols - 8)
                {
                  state->cursor_x = (state->cursor_x & ~7) + 8;
                }
              else
                {
                  state->cursor_x = 0;
-                 if (state->cursor_y < state->grid_height - 1)
+                 if (state->cursor_y < rows - 1)
                    state->cursor_y++;
                  else
                    scroll (state);
                }
              break;
            case 10: /* LF */
+# ifndef HAVE_FORKPTY
+              state->cursor_x = 0;     /* No ptys on iPhone; assume CRLF. */
+# endif
            case 11: /* VT */
            case 12: /* FF */
              if(state->last_c == 13)
@@ -805,14 +795,14 @@ print_char (p_state *state, int c)
                  cell->p_char = state->chars[state->bk];
                  cell->changed = True;
                }
-             if (state->cursor_y < state->grid_height - 1)
+             if (state->cursor_y < rows - 1)
                state->cursor_y++;
              else
                scroll (state);
              break;
            case 13: /* CR */
              state->cursor_x = 0;
-             cell = &state->cells[state->grid_width * state->cursor_y];
+             cell = &state->cells[cols * state->cursor_y];
              if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
                state->bk = ' ';
              else
@@ -820,8 +810,7 @@ print_char (p_state *state, int c)
              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 */
+              /* Dummy case - there is one and only one font. */
              break;
            case 24: /* CAN */
            case 26: /* SUB */
@@ -841,6 +830,49 @@ print_char (p_state *state, int c)
              state->curparam = 0;
              break;
            default:
+
+              /* states 102-106 are for UTF-8 decoding */
+
+              if ((c & 0xE0) == 0xC0) {        /* 110xxxxx: 11 bits, 2 bytes */
+                state->unicruds = 1;
+                state->unicrud[0] = c;
+                state->escstate = 102;
+                break;
+              } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx: 16 bits, 3 bytes */
+                state->unicruds = 1;
+                state->unicrud[0] = c;
+                state->escstate = 103;
+                break;
+              } else if ((c & 0xF8) == 0xF0) { /* 11110xxx: 21 bits, 4 bytes */
+                state->unicruds = 1;
+                state->unicrud[0] = c;
+                state->escstate = 104;
+                break;
+              } else if ((c & 0xFC) == 0xF8) { /* 111110xx: 26 bits, 5 bytes */
+                state->unicruds = 1;
+                state->unicrud[0] = c;
+                state->escstate = 105;
+                break;
+              } else if ((c & 0xFE) == 0xFC) { /* 1111110x: 31 bits, 6 bytes */
+                state->unicruds = 1;
+                state->unicrud[0] = c;
+                state->escstate = 106;
+                break;
+              }
+
+            PRINT:
+
+              /* If the cursor is in column 39 and we print a character, then
+                 that character shows up in column 39, and the cursor is no
+                 longer visible on the screen (it's in "column 40".)  If
+                 another character is printed, then that character shows up in
+                 column 0, and the cursor moves to column 1.
+
+                 This is empirically what xterm and gnome-terminal do, so that
+                 must be the right thing.  (In xterm, the cursor vanishes,
+                 whereas; in gnome-terminal, the cursor overprints the
+                 character in col 39.)
+               */
              cell->state = FLARE;
              cell->p_char = state->chars[c];
              cell->changed = True;
@@ -849,10 +881,10 @@ print_char (p_state *state, int c)
              if (c != ' ' && cell->p_char->blank_p)
                cell->p_char = state->chars[CURSOR_INDEX];
 
-             if (state->cursor_x >= state->grid_width - 1)
+             if (state->cursor_x >= cols - 1 /*####*/)
                {
                  state->cursor_x = 0;
-                 if (state->cursor_y >= state->grid_height - 1)
+                 if (state->cursor_y >= rows - 1)
                    scroll (state);
                  else
                    state->cursor_y++;
@@ -872,7 +904,7 @@ print_char (p_state *state, int c)
              state->escstate = 0;
              break;
            case 'D': /* Linefeed */
-             if (state->cursor_y < state->grid_height - 1)
+             if (state->cursor_y < rows - 1)
                state->cursor_y++;
              else
                scroll (state);
@@ -903,10 +935,14 @@ print_char (p_state *state, int c)
                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 '%': /* Select charset */
+              /* @: Select default (ISO 646 / ISO 8859-1)
+                 G: Select UTF-8
+                 8: Select UTF-8 (obsolete)
+
+                 We can just ignore this and always process UTF-8, I think?
+                 We must still catch the last byte, though.
+               */
            case '(':
            case ')':
              /* I don't support different fonts either - see above
@@ -947,15 +983,15 @@ print_char (p_state *state, int c)
            case '@':
              for (i = 0; i < state->csiparam[0]; i++)
                {
-                 if(++state->cursor_x > state->grid_width)
+                 if(++state->cursor_x > cols)
                    {
                      state->cursor_x = 0;
-                     if (state->cursor_y < state->grid_height - 1)
+                     if (state->cursor_y < rows - 1)
                        state->cursor_y++;
                      else
                        scroll (state);
                    }
-                 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
+                 cell = &state->cells[cols * state->cursor_y + state->cursor_x];
                  if (cell->state == FLARE || cell->state == NORMAL)
                    {
                      cell->state = FADE;
@@ -979,16 +1015,16 @@ print_char (p_state *state, int c)
            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;
+             if ((state->cursor_y += state->csiparam[0]) >= rows - 1 /*####*/)
+               state->cursor_y = rows - 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;
+             if ((state->cursor_x += state->csiparam[0]) >= cols - 1 /*####*/)
+               state->cursor_x = cols - 1;
              state->escstate = 0;
              break;
            case 'D':
@@ -999,22 +1035,22 @@ print_char (p_state *state, int c)
              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;
+             if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
+               state->cursor_y = rows - 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;
+             if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols - 1 /*####*/)
+               state->cursor_x = cols - 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 = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
+               state->cursor_y = rows - 1;
+             if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols - 1 /*####*/)
+               state->cursor_x = cols - 1;
              if(state->cursor_y < 0)
                state->cursor_y = 0;
              if(state->cursor_x < 0)
@@ -1023,11 +1059,11 @@ print_char (p_state *state, int c)
              break;
            case 'J':
              start = 0;
-             end = state->grid_height * state->grid_width;
+             end = rows * cols;
              if (state->csiparam[0] == 0)
-               start = state->grid_width * state->cursor_y + state->cursor_x;
+               start = cols * state->cursor_y + state->cursor_x;
              if (state->csiparam[0] == 1)
-               end = state->grid_width * state->cursor_y + state->cursor_x;
+               end = cols * state->cursor_y + state->cursor_x;
              for (i = start; i < end; i++)
                {
                  cell = &state->cells[i];
@@ -1042,7 +1078,7 @@ print_char (p_state *state, int c)
              break;
            case 'K':
              start = 0;
-             end = state->grid_width;
+             end = cols;
              if (state->csiparam[0] == 0)
                start = state->cursor_x;
              if (state->csiparam[1] == 1)
@@ -1058,6 +1094,9 @@ print_char (p_state *state, int c)
                }
              state->escstate = 0;
              break;
+            case 'm': /* Set attributes unimplemented (bold, blink, rev) */
+              state->escstate = 0;
+              break;
            case 's': /* Save position */
              state->saved_x = state->cursor_x;
              state->saved_y = state->cursor_y;
@@ -1097,10 +1136,44 @@ print_char (p_state *state, int c)
        case 3:
          state->escstate = 0;
          break;
+
+        case 102:
+        case 103:
+        case 104:
+        case 105:
+        case 106:
+          {
+            int total = state->escstate - 100;  /* see what I did there */
+            if (state->unicruds < total) {
+              /* Buffer more bytes of the UTF-8 sequence */
+              state->unicrud[state->unicruds++] = c;
+            }
+
+            if (state->unicruds >= total) {
+              /* Done! Convert it to Latin1 and print that. */
+              char *s;
+              state->unicrud[state->unicruds] = 0;
+              s = utf8_to_latin1 ((const char *) state->unicrud, False);
+              state->unicruds = 0;
+              state->escstate = 0;
+              if (s) {
+                c = (unsigned char) s[0];
+                free (s);
+                goto PRINT;
+              } else {
+                c = 0;
+              }
+            }
+          }
+          break;
+
+        default:
+          abort();
        }
       set_cursor (state, True);
     }
   else
+#endif /* HAVE_FORKPTY */
     {
       if (c == '\t') c = ' ';   /* blah. */
 
@@ -1111,7 +1184,7 @@ print_char (p_state *state, int c)
          else
            {
              state->cursor_x = 0;
-             if (state->cursor_y == state->grid_height - 1)
+             if (state->cursor_y == rows - 1)
                scroll (state);
              else
                state->cursor_y++;
@@ -1123,6 +1196,8 @@ print_char (p_state *state, int c)
        }
       else
        {
+          /* #### This should do UTF-8 decoding */
+
          cell->state = FLARE;
          cell->p_char = state->chars[c];
          cell->changed = True;
@@ -1131,10 +1206,10 @@ print_char (p_state *state, int c)
          if (c != ' ' && cell->p_char->blank_p)
            cell->p_char = state->chars[CURSOR_INDEX];
 
-         if (state->cursor_x >= state->grid_width - 1)
+         if (state->cursor_x >= cols - 1)
            {
              state->cursor_x = 0;
-             if (state->cursor_y >= state->grid_height - 1)
+             if (state->cursor_y >= rows - 1)
                scroll (state);
              else
                state->cursor_y++;
@@ -1209,222 +1284,15 @@ 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);
-  return state->delay;
-}
 
-\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;
-}
-
-
-static void
-launch_text_generator (p_state *state)
-{
-  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");
-
-#ifdef HAVE_FORKPTY
-  if(state->mode == 1)
-    {
-      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 */
-    {
-      /* 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);
-}
-
-
-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);
-}
-
-
-static void
-drain_input (p_state *state)
-{
-  XtAppContext app = XtDisplayToApplicationContext (state->dpy);
-  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;
-         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, '\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. */
-          state->pipe_timer =
-            XtAppAddTimeOut (app, state->subproc_relaunch_delay,
-                             relaunch_generator_timer,
-                             (XtPointer) state);
-        }
-        
-      state->input_available_p = False;
-    }
-}
-
-
-/* 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;
+  return state->delay;
 }
 
 
@@ -1433,23 +1301,17 @@ phosphor_reshape (Display *dpy, Window window, void *closure,
                  unsigned int w, unsigned int h)
 {
   p_state *state = (p_state *) closure;
-  resize_grid (state);
+  Bool changed_p = resize_grid (state);
 
-# 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 */
+  if (! changed_p) return;
+
+  textclient_reshape (state->tc, w, h,
+                      state->grid_width  - 1,
+                      state->grid_height - 1,
+                      0);
 }
 
+
 static Bool
 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
 {
@@ -1458,33 +1320,7 @@ phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
   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 textclient_putc (state->tc, &event->xkey);
   return False;
 }
 
@@ -1493,14 +1329,9 @@ 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);
+  textclient_close (state->tc);
   if (state->cursor_timer)
     XtRemoveTimeOut (state->cursor_timer);
-  if (state->pipe_timer)
-    XtRemoveTimeOut (state->pipe_timer);
 
   /* #### there's more to free here */
 
@@ -1512,8 +1343,7 @@ phosphor_free (Display *dpy, Window window, void *closure)
 static const char *phosphor_defaults [] = {
   ".background:                   Black",
   ".foreground:                   #00FF00",
-  "*fadeForeground:       #006400",
-  "*flareForeground:      #FFFFFF",
+  "*fpsSolid:             true",
 #if defined(BUILTIN_FONT)
   "*font:                 (builtin)",
 #elif defined(HAVE_COCOA)
@@ -1530,9 +1360,9 @@ static const char *phosphor_defaults [] = {
   "*metaSendsESC:         True",
   "*swapBSDEL:            True",
 #ifdef HAVE_FORKPTY
-  "*mode:                  pty",
+  "*usePty:                True",
 #else  /* !HAVE_FORKPTY */
-  "*mode:                  pipe",
+  "*usePty:                False",
 #endif /* !HAVE_FORKPTY */
   0
 };
@@ -1543,8 +1373,8 @@ static XrmOptionDescRec phosphor_options [] = {
   { "-ticks",          ".ticks",               XrmoptionSepArg, 0 },
   { "-delay",          ".delay",               XrmoptionSepArg, 0 },
   { "-program",                ".program",             XrmoptionSepArg, 0 },
-  { "-pty",            ".mode",                XrmoptionNoArg, "pty"   },
-  { "-pipe",           ".mode",                XrmoptionNoArg, "pipe"  },
+  { "-pipe",           ".usePty",              XrmoptionNoArg, "False" },
+  { "-pty",            ".usePty",              XrmoptionNoArg, "True"  },
   { "-meta",           ".metaSendsESC",        XrmoptionNoArg, "False" },
   { "-esc",            ".metaSendsESC",        XrmoptionNoArg, "True"  },
   { "-bs",             ".swapBSDEL",           XrmoptionNoArg, "False" },