From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / phosphor.c
index c01f5a5a17b7e37aaac8b749483f1c49720ef078..47f58dcc7db36ea1de3bc9798921eabbffff49b7 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1999-2005 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
  * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
  */
 
-#include "screenhack.h"
-
-#include <stdio.h>
-#include <signal.h>
-#include <sys/wait.h>
-
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include <X11/Intrinsic.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
 
-#define XK_MISCELLANY
-#include <X11/keysymdef.h>
-
-#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 */
+#ifndef HAVE_COCOA
+# include <X11/Intrinsic.h>
+#endif
 
-extern XtAppContext app;
+#include "screenhack.h"
+#include "textclient.h"
 
 #define FUZZY_BORDER
 
@@ -53,6 +39,12 @@ extern XtAppContext app;
 
 #define NPAR 16
 
+#define BUILTIN_FONT
+
+#ifdef BUILTIN_FONT
+# include "images/6x10font.xbm"
+#endif /* BUILTIN_FONT */
+
 typedef struct {
   unsigned char name;
   int width, height;
@@ -74,13 +66,13 @@ 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;
@@ -98,23 +90,20 @@ typedef struct {
   int cursor_x, cursor_y;
   XtIntervalId cursor_timer;
   Time cursor_blink;
+  int delay;
+  Bool pty_p;
 
-  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;
+  text_data *tc;
+
+  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:
@@ -146,72 +135,77 @@ 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 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)
 {
   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->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
-  state->swap_bs_del_p    = get_boolean_resource ("swapBSDEL",    "Boolean");
+/*  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;
 
-  {
-    char *s = get_string_resource ("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 ("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);
@@ -227,27 +221,35 @@ 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 = 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,
@@ -256,9 +258,24 @@ init_phosphor (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->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);
@@ -297,7 +314,17 @@ 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,
+                      0);
 
   return state;
 }
@@ -305,7 +332,7 @@ init_phosphor (Display *dpy, Window window)
 
 /* 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;
@@ -320,7 +347,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);
@@ -342,6 +369,7 @@ resize_grid (p_state *state)
     state->cursor_y = state->grid_height-1;
 
   free (ocells);
+  return True;
 }
 
 
@@ -349,12 +377,33 @@ 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;
@@ -368,10 +417,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);
@@ -380,7 +435,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);
   }
@@ -388,31 +444,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);
+                  (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));
 
-#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
-
-  XSync (state->dpy, False);
   state->font_bits = XGetImage (state->dpy, p, 0, 0,
                                 (safe_width * 256), height, ~0L, XYPixmap);
   XFreePixmap (state->dpy, p);
@@ -448,13 +513,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;
@@ -554,12 +621,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);
@@ -569,6 +635,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);
@@ -661,12 +728,8 @@ scroll (p_state *state)
 static void
 print_char (p_state *state, int c)
 {
-  static char last_c = 0;
-  static int bk;
-
   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)
@@ -675,12 +738,14 @@ 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, start, end;
       switch (state->escstate)
        {
        case 0:
@@ -711,10 +776,10 @@ print_char (p_state *state, int c)
            case 10: /* LF */
            case 11: /* VT */
            case 12: /* FF */
-             if(last_c == 13)
+             if(state->last_c == 13)
                {
                  cell->state = NORMAL;
-                 cell->p_char = state->chars[bk];
+                 cell->p_char = state->chars[state->bk];
                  cell->changed = True;
                }
              if (state->cursor_y < state->grid_height - 1)
@@ -726,9 +791,9 @@ print_char (p_state *state, int c)
              state->cursor_x = 0;
              cell = &state->cells[state->grid_width * state->cursor_y];
              if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
-               bk = ' ';
+               state->bk = ' ';
              else
-               bk = cell->p_char->name;
+               state->bk = cell->p_char->name;
              break;
            case 14: /* SO */
            case 15: /* SI */
@@ -1013,12 +1078,13 @@ print_char (p_state *state, int c)
       set_cursor (state, True);
     }
   else
+#endif /* HAVE_FORKPTY */
     {
       if (c == '\t') c = ' ';   /* blah. */
 
       if (c == '\r' || c == '\n')  /* handle CR, LF, or CRLF as "new line". */
        {
-         if (c == '\n' && last_c == '\r')
+         if (c == '\n' && state->last_c == '\r')
            ;   /* CRLF -- do nothing */
          else
            {
@@ -1055,7 +1121,7 @@ print_char (p_state *state, int c)
       set_cursor (state, True);
     }
 
-  last_c = c;
+  state->last_c = c;
 }
 
 
@@ -1117,304 +1183,101 @@ 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.
- */
-
-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)
-{
-  char buf[255];
-  char *oprogram = get_string_resource ("program", "Program");
-  char *program = (char *) malloc (strlen (oprogram) + 50);
-
-  /* oprogram contains a "%d" where the current number of columns goes
-   */
-  strcpy (program, "( ");
-  sprintf (program + strlen(program), oprogram, 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". */
-      fclose (stdin);
 
-      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);
-       }
-    }
+  c = textclient_getc (state->tc);
+  if (c > 0) 
+    print_char (state, c);
 
-  free (program);
+  return state->delay;
 }
 
 
 static void
-relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
+phosphor_reshape (Display *dpy, Window window, void *closure, 
+                 unsigned int w, unsigned int h)
 {
   p_state *state = (p_state *) closure;
-  launch_text_generator (state);
-}
-
-
-static void
-drain_input (p_state *state)
-{
-  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;
+  Bool changed_p = resize_grid (state);
 
-          if (state->cursor_x != 0)    /* break line if unbroken */
-            print_char (state, '\n');  /* blank line */
-          print_char (state, '\n');
+  if (! changed_p) return;
 
-          /* 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;
-    }
+  textclient_reshape (state->tc, w, h,
+                      state->grid_width  - 1,
+                      state->grid_height - 1,
+                      0);
 }
 
 
-/* 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)
+static Bool
+phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
 {
-  unsigned int modbits = 0;
-  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);
-  return modbits;
-}
+  p_state *state = (p_state *) closure;
 
-/* Returns a mask of the bit or bits of a KeyPress event that mean "meta". 
- */
-static unsigned int
-meta_modifier (Display *dpy)
-{
-  static Bool done_once = False;
-  static unsigned int mask = 0;
-  if (!done_once)
-    {
-      /* Really, we are supposed to recompute this if a KeymapNotify
-         event comes in, but fuck it. */
-      done_once = True;
-      mask = do_icccm_meta_key_stupidity (dpy);
-    }
-  return mask;
+  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
-handle_events (p_state *state)
+phosphor_free (Display *dpy, Window window, void *closure)
 {
-  XSync (state->dpy, False);
-  while (XPending (state->dpy))
-    {
-      XEvent event;
-      XNextEvent (state->dpy, &event);
-
-      if (event.xany.type == ConfigureNotify)
-        {
-          resize_grid (state);
+  p_state *state = (p_state *) closure;
 
-# 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 */
-        }
-      else 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->dpy))
-                {
-                  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. */
-            }
-        }
+  textclient_close (state->tc);
+  if (state->cursor_timer)
+    XtRemoveTimeOut (state->cursor_timer);
 
-      screenhack_handle_event (state->dpy, &event);
-    }
+  /* #### there's more to free here */
 
-  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
-    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
+  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",
+#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:              xscreensaver-text --cols %d",
+  "*program:              xscreensaver-text",
   "*relaunch:             5",
   "*metaSendsESC:         True",
   "*swapBSDEL:            True",
 #ifdef HAVE_FORKPTY
-  "*mode:                  pty",
+  "*usePty:                True",
 #else  /* !HAVE_FORKPTY */
-  "*mode:                  pipe",
+  "*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 },
-  { "-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" },
@@ -1423,19 +1286,4 @@ XrmOptionDescRec options [] = {
 };
 
 
-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);
-      handle_events (state);
-      if (delay) usleep (delay);
-    }
-}
+XSCREENSAVER_MODULE ("Phosphor", phosphor)