From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / starwars.c
index 5827da7538cbe526dcf36fad608af1a5a2ccdecc..9ee8cc3f05ed11e00cdb993ae1beeac83c0842d7 100644 (file)
@@ -1,5 +1,4 @@
-/*
- * starwars, Copyright (c) 1998-2007 Jamie Zawinski <jwz@jwz.org> and
+/* starwars, Copyright (c) 1998-2015 Jamie Zawinski <jwz@jwz.org> and
  * Claudio Matsuoka <claudio@helllabs.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
@@ -23,7 +22,7 @@
  *
  * For the fanboys:
  *
- *     starwars -program 'cat starwars.txt' -columns 25 -no-wrap -texture
+ *     starwars -program 'cat starwars.txt' -columns 25 -no-wrap
  */
 
 #ifdef HAVE_CONFIG_H
 # include <unistd.h>
 #endif
 
-#ifdef HAVE_UNAME
-# include <sys/utsname.h>
-#endif /* HAVE_UNAME */
-
-#ifndef HAVE_COCOA
-# include <X11/Intrinsic.h>
-#endif
-
-
+#include "starwars.h"
 #define DEFAULTS "*delay:    40000     \n" \
                 "*showFPS:  False     \n" \
                 "*fpsTop:   True      \n" \
-                "*font:   " DEF_FONT "\n"
+                "*usePty:   False     \n" \
+                "*texFontCacheSize: 300\n" \
+                "*font:   " DEF_FONT "\n" \
+                "*textLiteral: " DEF_TEXT "\n" \
+                "*program: xscreensaver-text --cols 0"  /* don't wrap */
 
 # define refresh_sws 0
+# define release_sws 0
 # define sws_handle_event 0
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
 #include "xlockmore.h"
+#include "textclient.h"
+#include "utf8wc.h"
 
 #ifdef USE_GL /* whole file */
 
 # endif
 
 
-#define DEF_PROGRAM    "xscreensaver-text --cols 0"  /* don't wrap */
 #define DEF_LINES      "125"
 #define DEF_STEPS      "35"
 #define DEF_SPIN       "0.03"
-#define DEF_FONT_SIZE  "-1"
+#define DEF_SIZE       "-1"
 #define DEF_COLUMNS    "-1"
-#define DEF_WRAP       "True"
-#define DEF_ALIGN      "Center"
+#define DEF_LINE_WRAP  "True"
+#define DEF_ALIGNMENT  "Center"
 #define DEF_SMOOTH     "True"
 #define DEF_THICK      "True"
 #define DEF_FADE       "True"
 #define DEF_TEXTURES   "True"
 #define DEF_DEBUG      "False"
 
-/* Utopia 800 needs 64 512x512 textures (4096x4096 bitmap).
-   Utopia 720 needs 16 512x512 textures (2048x2048 bitmap).
-   Utopia 480 needs 16 512x512 textures (2048x2048 bitmap).
-   Utopia 400 needs  4 512x512 textures (1024x1024 bitmap).
-   Utopia 180 needs  1 512x512 texture.
-   Times  240 needs  1 512x512 texture.
- */
-#define DEF_FONT       "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-iso8859-1"
+#define DEF_FONT       "-*-utopia-bold-r-normal-*-*-360-*-*-*-*-*-*"
 
 #define TAB_WIDTH        8
 
 #define MAX_THICK_LINES   25
 #define FONT_WEIGHT       14
-#define KEEP_ASPECT
+
+#ifndef HAVE_MOBILE
+# define KEEP_ASPECT   /* Letterboxing looks dumb on iPhone. */
+#endif
 
 #include "texfont.h"
 #include "glutstroke.h"
@@ -109,23 +102,20 @@ typedef struct {
 
   GLuint text_list, star_list;
   texture_font_data *texfont;
-  int polygon_count;
-
-  FILE *pipe;
-  XtInputId pipe_id;
-  XtIntervalId pipe_timer;
-  Time subproc_relaunch_delay;
+  text_data *tc;
 
   char *buf;
   int buf_size;
   int buf_tail;
 
   char **lines;
+  int *line_widths;
   int total_lines;
 
   double star_theta;
   double char_width;
   double line_height;
+  double descent;
   double font_scale;
   double intra_line_scroll;
 
@@ -138,7 +128,6 @@ typedef struct {
 
 static sws_configuration *scs = NULL;
 
-static char *program;
 static int max_lines;
 static int scroll_steps;
 static float star_spin;
@@ -154,11 +143,10 @@ static char *alignment_str;
 static int alignment;
 
 static XrmOptionDescRec opts[] = {
-  {"-program",     ".program",   XrmoptionSepArg, 0 },
   {"-lines",       ".lines",     XrmoptionSepArg, 0 },
   {"-steps",       ".steps",     XrmoptionSepArg, 0 },
   {"-spin",        ".spin",      XrmoptionSepArg, 0 },
-  {"-size",       ".fontSize",  XrmoptionSepArg, 0 },
+  {"-size",       ".size",      XrmoptionSepArg, 0 },
   {"-columns",    ".columns",   XrmoptionSepArg, 0 },
 /*{"-font",        ".font",      XrmoptionSepArg, 0 },*/
   {"-fade",        ".fade",      XrmoptionNoArg,  "True"   },
@@ -180,14 +168,13 @@ static XrmOptionDescRec opts[] = {
 };
 
 static argtype vars[] = {
-  {&program,        "program",   "Program",    DEF_PROGRAM,   t_String},
   {&max_lines,      "lines",     "Integer",    DEF_LINES,     t_Int},
   {&scroll_steps,   "steps",     "Integer",    DEF_STEPS,     t_Int},
   {&star_spin,      "spin",      "Float",      DEF_SPIN,      t_Float},
-  {&font_size,      "fontSize",  "Float",      DEF_FONT_SIZE, t_Float},
+  {&font_size,      "size",      "Float",      DEF_SIZE,      t_Float},
   {&target_columns, "columns",   "Integer",    DEF_COLUMNS,   t_Int},
-  {&wrap_p,         "lineWrap",  "Boolean",    DEF_WRAP,      t_Bool},
-  {&alignment_str,  "alignment", "Alignment",  DEF_ALIGN,     t_String},
+  {&wrap_p,         "lineWrap",  "Boolean",    DEF_LINE_WRAP, t_Bool},
+  {&alignment_str,  "alignment", "Alignment",  DEF_ALIGNMENT, t_String},
   {&smooth_p,       "smooth",    "Boolean",    DEF_SMOOTH,    t_Bool},
   {&thick_p,        "thick",     "Boolean",    DEF_THICK,     t_Bool},
   {&fade_p,         "fade",      "Boolean",    DEF_FADE,      t_Bool},
@@ -260,246 +247,176 @@ strip (char *s, Bool leading, Bool trailing)
 }
 
 
-/* The GLUT font only has ASCII characters in them, so do what we can to
-   convert Latin1 characters to the nearest ASCII equivalent... 
- */
-static void
-latin1_to_ascii (char *s)
-{
-  unsigned char *us = (unsigned char *) s;
-  const unsigned char ascii[95] = {
-    '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
-    '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
-    '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
-    'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
-    'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
-    'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
-    'u', 'u', 'y', 'p', 'y' };
-  while (*us)
-    {
-      if (*us >= 161)
-        *us = ascii[*us - 161];
-      else if (*us > 127)
-        *us = '?';
-      us++;
-    }
-}
-
-\f
-/* Subprocess.
-   (This bit mostly cribbed from phosphor.c)
- */
-
-static void drain_input (sws_configuration *sc);
-
-static void
-subproc_cb (XtPointer closure, int *source, XtInputId *id)
-{
-  sws_configuration *sc = (sws_configuration *) closure;
-  drain_input (sc);
-}
-
-
-static void
-launch_text_generator (sws_configuration *sc)
+static int
+sw_string_width (sws_configuration *sc, const char *s)
 {
-  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);
-  char *oprogram = get_string_resource (sc->dpy, "program", "Program");
-  char *program = (char *) malloc (strlen (oprogram) + 10);
-  strcpy (program, "( ");
-  strcat (program, oprogram);
-  strcat (program, " ) 2>&1");
-
-  if ((sc->pipe = popen (program, "r")))
+  if (textures_p)
     {
-      sc->pipe_id =
-        XtAppAddInput (app, fileno (sc->pipe),
-                       (XtPointer) (XtInputReadMask | XtInputExceptMask),
-                       subproc_cb, (XtPointer) sc);
+      XCharStruct e;
+      texture_string_metrics (sc->texfont, s, &e, 0, 0);
+      return e.width;
     }
   else
-    {
-      perror (program);
-    }
-}
-
-
-static void
-relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
-{
-  sws_configuration *sc = (sws_configuration *) closure;
-  if (!sc->pipe_timer) abort();
-  sc->pipe_timer = 0;
-  launch_text_generator (sc);
+    return glutStrokeLength (GLUT_FONT, (unsigned char *) s);
 }
 
 
-/* When the subprocess has generated some output, this reads as much as it
-   can into sc->buf at sc->buf_tail.
- */
-static void
-drain_input (sws_configuration *sc)
+static int
+sw_string_width2 (sws_configuration *sc, const char *s, size_t size)
 {
-  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);
-  if (sc->buf_tail < sc->buf_size - 2)
-    {
-      int target = sc->buf_size - sc->buf_tail - 2;
-      int n = (sc->pipe
-               ? read (fileno (sc->pipe),
-                       (void *) (sc->buf + sc->buf_tail),
-                       target)
-               : 0);
-      if (n > 0)
-        {
-          sc->buf_tail += n;
-          sc->buf[sc->buf_tail] = 0;
-        }
-      else
-        {
-          if (sc->pipe)
-            {
-              XtRemoveInput (sc->pipe_id);
-              sc->pipe_id = 0;
-              pclose (sc->pipe);
-              sc->pipe = 0;
-            }
-
-          /* If the process didn't print a terminating newline, add one. */
-          if (sc->buf_tail > 1 &&
-              sc->buf[sc->buf_tail-1] != '\n')
-            {
-              sc->buf[sc->buf_tail++] = '\n';
-              sc->buf[sc->buf_tail] = 0;
-            }
-
-          /* Then add one more, just for giggles. */
-          sc->buf[sc->buf_tail++] = '\n';
-          sc->buf[sc->buf_tail] = 0;
-
-          /* Set up a timer to re-launch the subproc in a bit. */
-          sc->pipe_timer = XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
-                                            relaunch_generator_timer,
-                                            (XtPointer) sc);
-        }
-    }
-}
+  char *s2 = (char *) malloc (size + 1);
+  int result;
 
+  strncpy (s2, s, size);
+  s2[size] = 0;
 
-static int
-string_width (sws_configuration *sc, const char *s)
-{
-  if (textures_p)
-    return texture_string_width (sc->texfont, s, 0);
-  else
-    return glutStrokeLength (GLUT_FONT, (unsigned char *) s);
-}
+  result = sw_string_width (sc, s2);
 
-static int
-char_width (sws_configuration *sc, char c)
-{
-  char s[2];
-  s[0] = c;
-  s[1] = 0;
-  return string_width (sc, s);
+  free (s2);
+  return result;
 }
 
 
-/* Populates the sc->lines list with as many lines as are currently in
-   sc->buf (which was filled by drain_input().
+/* Populates the sc->lines list with as many lines as possible.
  */
 static void
 get_more_lines (sws_configuration *sc)
 {
   /* wrap anyway, if it's absurdly long. */
   int wrap_pix = (wrap_p ? sc->line_pixel_width : 10000);
-  
-  int col = 0;
-  int col_pix = 0;
 
   char *s = sc->buf;
-  while (sc->total_lines < max_lines)
+
+  int target = sc->buf_size - sc->buf_tail - 2;
+
+  /* Fill as much as we can into sc->buf.
+   */
+  while (target > 0)
     {
-      int cw;
+      int c = textclient_getc (sc->tc);
+      if (c <= 0)
+        break;
+      sc->buf[sc->buf_tail++] = (char) c;
+      sc->buf[sc->buf_tail] = 0;
+      target--;
+    }
 
-      if (s >= sc->buf + sc->buf_tail)
-        /* Reached end of buffer before end of line.  Bail. */
-        return;
+  while (sc->total_lines < max_lines)
+    {
+      char *next_s = s;
+      unsigned counter = 0;
 
-      cw = char_width (sc, *s);
+      /* OS X is really slow to calcuate the bounds for a line of text,
+         so skip ahead a bit.
 
-      if (*s == '\r' || *s == '\n' ||
-          col_pix + cw >= wrap_pix)
+         Really though, the right thing to do is probably to wrap
+         CTLineCreateTruncatedLine, one way or another. */
+      for (;;)
         {
-          int L = s - sc->buf;
+          if (next_s >= sc->buf + sc->buf_tail)
+            break;
 
-          if (*s == '\r' || *s == '\n')
+          if (!counter)
             {
-              if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
-                *s++ = 0;
+              if (s > sc->buf &&
+                  sw_string_width2 (sc, sc->buf, next_s - sc->buf) >= wrap_pix)
+                break;
 
-              *s++ = 0;
+              counter = 12; /* <-- Adjust to taste. */
+              s = next_s;
             }
-          else
+
+          if (*next_s == '\r' || *next_s == '\n')
+            break;
+
+          --counter;
+          ++next_s;
+        }
+
+      for (;;)
+        {
+          if (s >= sc->buf + sc->buf_tail)
+            /* Reached end of buffer before end of line.  Bail. */
+            return;
+
+          /* When checking pixel width, always measure the line from the
+             beginning, or else multi-byte UTF-8 characters, particularly
+             combining diacriticals, aren't measured right. */
+
+          if (*s == '\r' || *s == '\n' ||
+              (s > sc->buf && sw_string_width2 (sc, sc->buf, s - sc->buf) >= wrap_pix))
             {
-              /* We wrapped -- try to back up to the previous word boundary. */
-              char *s2 = s;
-              int n = 0;
-              while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
-                s2--, n++;
-              if (s2 > sc->buf)
+              int L = s - sc->buf;
+
+              if (*s == '\r' || *s == '\n')
                 {
-                  s = s2;
+                  if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
+                    *s++ = 0;
+
                   *s++ = 0;
-                  L = s - sc->buf;
                 }
-            }
+              else
+                {
+                  /* We wrapped -- try to back up to the previous word boundary. */
+                  char *s2 = s;
+                  int n = 0;
+                  while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
+                    s2--, n++;
+                  if (s2 > sc->buf)
+                    {
+                      s = s2;
+                      *s++ = 0;
+                      L = s - sc->buf;
+                    }
+                }
 
-          sc->lines[sc->total_lines] = (char *) malloc (L+1);
-          memcpy (sc->lines[sc->total_lines], sc->buf, L);
-          sc->lines[sc->total_lines][L] = 0;
+              sc->lines[sc->total_lines] = (char *) malloc (L+1);
+              memcpy (sc->lines[sc->total_lines], sc->buf, L);
+              sc->lines[sc->total_lines][L] = 0;
 
-          if (!textures_p)
-            latin1_to_ascii (sc->lines[sc->total_lines]);
+              if (!textures_p)
+                {
+                  /* The GLUT font only has ASCII characters. */
+                  char *s1 = utf8_to_latin1 (sc->lines[sc->total_lines], True);
+                  free (sc->lines[sc->total_lines]);
+                  sc->lines[sc->total_lines] = s1;
+                }
 
-          {
-            char *t = sc->lines[sc->total_lines];
-            char *ut = untabify (t);
-            strip (ut, (alignment == 0), 1); /* if centering, strip
-                                                leading whitespace too */
-            sc->lines[sc->total_lines] = ut;
-            free (t);
-          }
+              {
+                char *t = sc->lines[sc->total_lines];
+                char *ut = untabify (t);
+                strip (ut, (alignment == 0), 1); /* if centering, strip
+                                                    leading whitespace too */
+                sc->lines[sc->total_lines] = ut;
+                free (t);
+              }
 
-          sc->total_lines++;
+              sc->line_widths[sc->total_lines] =
+                sw_string_width(sc, sc->lines[sc->total_lines]);
+
+              sc->total_lines++;
+
+              if (sc->buf_tail > (s - sc->buf))
+                {
+                  int i = sc->buf_tail - (s - sc->buf);
+                  memmove (sc->buf, s, i);
+                  sc->buf_tail = i;
+                  sc->buf[sc->buf_tail] = 0;
+                }
+              else
+                {
+                  sc->buf_tail = 0;
+                }
 
-          if (sc->buf_tail > (s - sc->buf))
-            {
-              int i = sc->buf_tail - (s - sc->buf);
-              memmove (sc->buf, s, i);
-              sc->buf_tail = i;
               sc->buf[sc->buf_tail] = 0;
+              s = sc->buf;
+
+              break;
             }
           else
             {
-              sc->buf_tail = 0;
+              s++;
             }
-
-          sc->buf[sc->buf_tail] = 0;
-          s = sc->buf;
-          col = 0;
-          col_pix = 0;
-        }
-      else
-        {
-          col++;
-          col_pix += cw;
-          if (*s == '\t')
-            {
-              int tab_pix = TAB_WIDTH * sc->char_width;
-              col     = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
-              col_pix = tab_pix   * ((col / tab_pix)   + 1);
-            }
-          s++;
         }
     }
 }
@@ -522,29 +439,45 @@ draw_string (sws_configuration *sc, GLfloat x, GLfloat y, const char *s)
 
   if (debug_p)
     {
-      GLfloat w;
       GLfloat h = sc->line_height / sc->font_scale;
-      char c[2];
-      c[1]=0;
-      s = os;
+      char **chars = utf8_split (os, 0);
+      int i, w = 0;
+      char *s2 = (char *) malloc (strlen(s) + 1);
+      *s2 = 0;
+
       if (textures_p) glDisable (GL_TEXTURE_2D);
       glLineWidth (1);
-      glColor3f (0.4, 0.4, 0.4);
-      glPushMatrix ();
-      glTranslatef (x, y, 0);
-      while (*s)
+
+      glColor3f (0.2, 0.2, 0.5);
+
+      glBegin (GL_LINES);
+
+      for (i = 0; chars[i]; i++)
         {
-          *c = *s++;
-          w = string_width (sc, c);
-          glBegin (GL_LINE_LOOP);
-          glVertex3f (0, 0, 0);
-          glVertex3f (w, 0, 0);
-          glVertex3f (w, h, 0);
-          glVertex3f (0, h, 0);
-          glEnd();
-          glTranslatef (w, 0, 0);
+          glVertex3f (x + w, y - sc->descent, 0);      /* char left */
+          glVertex3f (x + w, y - sc->descent + h, 0);
+          strcat (s2, chars[i]);
+          w = sw_string_width (sc, s2);
+          free (chars[i]);
         }
-      glPopMatrix ();
+
+      glVertex3f (x + w, y - sc->descent, 0);          /* char right */
+      glVertex3f (x + w, y - sc->descent + h, 0);
+
+      glVertex3f (x,     y - sc->descent + h, 0);      /* ascent */
+      glVertex3f (x + w, y - sc->descent + h, 0);
+
+      glVertex3f (x - sc->char_width,     y, 0);       /* baseline */
+      glVertex3f (x + sc->char_width + w, y, 0);
+
+      glVertex3f (x,     y - sc->descent, 0);          /* descent */
+      glVertex3f (x + w, y - sc->descent, 0);
+
+      glEnd();
+
+      free (s2);
+      free (chars);
+
       if (textures_p) glEnable (GL_TEXTURE_2D);
     }
 }
@@ -675,6 +608,7 @@ reshape_sws (ModeInfo *mi, int width, int height)
     int w = mi->xgwa.width;
     int h = mi->xgwa.height;
     int yoff = 0;
+    GLfloat rot = current_device_rotation();
 
 #ifdef KEEP_ASPECT
     {
@@ -685,15 +619,28 @@ reshape_sws (ModeInfo *mi, int width, int height)
     }
 #endif
 
-    glMatrixMode (GL_PROJECTION);
     glViewport (0, yoff, w, h);
 
+    glMatrixMode (GL_PROJECTION);
+    glLoadIdentity();
+
     glMatrixMode (GL_MODELVIEW);
     glLoadIdentity ();
     gluPerspective (80.0, 1/desired_aspect, 1000, 55000);
     gluLookAt (0.0, 0.0, 4600.0,
                0.0, 0.0, 0.0,
                0.0, 1.0, 0.0);
+
+    /* Horrible kludge to prevent the text from materializing already
+       on screen on iPhone in landscape mode.
+     */
+    if ((rot >  45 && rot <  135) ||
+        (rot < -45 && rot > -135))
+      {
+        GLfloat s = 1.1;
+        glScalef (s, s, s);
+      }
+
     glRotatef (-60.0, 1.0, 0.0, 0.0);
 
 #if 0
@@ -717,8 +664,8 @@ reshape_sws (ModeInfo *mi, int width, int height)
   {
     GLdouble mm[17], pm[17];
     GLint vp[5];
-    GLfloat x = 0.5, y1 = 0, z = 0;
-    GLfloat y2 = sc->line_height;
+    GLdouble x = 0.5, y1 = 0, z = 0;
+    GLdouble y2 = sc->line_height;
     GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
 
     glGetDoublev (GL_MODELVIEW_MATRIX, mm);
@@ -747,8 +694,6 @@ gl_init (ModeInfo *mi)
 {
   sws_configuration *sc = &scs[MI_SCREEN(mi)];
 
-  program = get_string_resource (mi->dpy, "program", "Program");
-
   glDisable (GL_LIGHTING);
   glDisable (GL_DEPTH_TEST);
 
@@ -772,6 +717,9 @@ gl_init (ModeInfo *mi)
 }
 
 
+static void free_sws (ModeInfo *mi);
+
+
 ENTRYPOINT void 
 init_sws (ModeInfo *mi)
 {
@@ -779,34 +727,34 @@ init_sws (ModeInfo *mi)
 
   sws_configuration *sc = 0;
 
-  if (!scs) {
-    scs = (sws_configuration *)
-      calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
-    if (!scs) {
-      fprintf(stderr, "%s: out of memory\n", progname);
-      exit(1);
-    }
-  }
+  MI_INIT (mi, scs, free_sws);
 
   sc = &scs[MI_SCREEN(mi)];
 
   sc->dpy = MI_DISPLAY(mi);
   sc = &scs[MI_SCREEN(mi)];
+  /* Unchecked malloc. :( */
   sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
+  sc->line_widths = (int *) calloc (max_lines+1, sizeof(int));
 
   if ((sc->glx_context = init_GL(mi)) != NULL) {
     gl_init(mi);
     reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+    clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
+
     init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
   }
 
   if (textures_p)
     {
-      int cw, lh;
+      XCharStruct e;
+      int cw, ascent, descent;
       sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
-      cw = texture_string_width (sc->texfont, "n", &lh);
+      texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
+      cw = e.width;
       sc->char_width = cw;
-      font_height = lh;
+      font_height = ascent + descent;
+      sc->descent = descent;
       glEnable(GL_ALPHA_TEST);
       glEnable (GL_TEXTURE_2D);
 
@@ -832,6 +780,7 @@ init_sws (ModeInfo *mi)
     {
       font_height = GLUT_FONT->top - GLUT_FONT->bottom;
       sc->char_width = glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems wide */
+      sc->descent = 0;
     }
   
   sc->font_scale = 1.0 / sc->char_width;
@@ -862,17 +811,19 @@ init_sws (ModeInfo *mi)
   sc->font_scale /= target_columns;
   sc->line_height = font_height * sc->font_scale;
 
-
-  /* Buffer only two lines of text.
+  /* Buffer only a few lines of text.
      If the buffer is too big, there's a significant delay between
      when the program launches and when the text appears, which can be
      irritating for time-sensitive output (clock, current music, etc.)
+
+     I'd like to buffer only 2 lines, but we need to assume that we
+     could get a whole line of N-byte Unicrud, and if we fill the buffer
+     before hitting the end of the line, we stall.
    */
-  sc->buf_size = target_columns * 2;
+  sc->buf_size = target_columns * 2 * 4;
   if (sc->buf_size < 80) sc->buf_size = 80;
   sc->buf = (char *) calloc (1, sc->buf_size);
 
-  sc->subproc_relaunch_delay = 2 * 1000;   /* 2 seconds */
   sc->total_lines = max_lines-1;
 
   if (random() & 1)
@@ -894,7 +845,7 @@ init_sws (ModeInfo *mi)
       exit (1);
     }
 
-  launch_text_generator (sc);
+  sc->tc = textclient_open (sc->dpy);
 
   /* one more reshape, after line_height has been computed */
   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
@@ -920,6 +871,7 @@ draw_stars (ModeInfo *mi)
                -100.0, 100.0);
       glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
       if (textures_p) glDisable (GL_TEXTURE_2D);
+
       glCallList (sc->star_list);
       if (textures_p) glEnable (GL_TEXTURE_2D);
     }
@@ -941,11 +893,6 @@ draw_sws (ModeInfo *mi)
   if (!sc->glx_context)
     return;
 
-#if 0
-  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
-    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
-#endif
-
   glDrawBuffer (GL_BACK);
   glXMakeCurrent (dpy, window, *(sc->glx_context));
 
@@ -956,14 +903,33 @@ draw_sws (ModeInfo *mi)
   glMatrixMode (GL_MODELVIEW);
   glPushMatrix ();
 
+# ifdef HAVE_MOBILE
+  /* Need to do this every time to get device rotation right */
+  reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+# endif
+
   if (debug_p)
     {
       int i;
       glPushMatrix ();
       if (textures_p) glDisable (GL_TEXTURE_2D);
       glLineWidth (1);
-      glColor3f (0.4, 0.4, 0.4);
       glTranslatef (0,-1, 0);
+
+      glColor3f(1, 0, 0);      /* Red line is where text appears */
+      glPushMatrix();
+      glTranslatef(0, -0.028, 0);
+      glLineWidth (4);
+      glBegin(GL_LINES);
+      glVertex3f(-0.5,  1, 0);
+      glVertex3f( 0.5,  1, 0);
+      glVertex3f(-0.5, -1, 0);
+      glVertex3f( 0.5, -1, 0);
+      glEnd();
+      glLineWidth (1);
+      glPopMatrix();
+
+      glColor3f (0.2, 0.2, 0.2);
       for (i = 0; i < 16; i++)
         {
           box (1, 1, 1);
@@ -972,14 +938,79 @@ draw_sws (ModeInfo *mi)
         }
       if (textures_p) glEnable (GL_TEXTURE_2D);
       glPopMatrix ();
+      check_gl_error ("debug render");
     }
 
   /* Scroll to current position */
   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
 
   glColor3f (1.0, 1.0, 0.4);
-  glCallList (sc->text_list);
-  mi->polygon_count = sc->polygon_count;
+
+  mi->polygon_count = 0;
+
+  glPushMatrix ();
+  glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
+  for (i = 0; i < sc->total_lines; i++)
+    {
+      double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
+      int offscreen_lines = 2;
+
+      double x = -0.5;
+      double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
+                   * sc->line_height);
+      double xoff = 0;
+      char *line = sc->lines[i];
+
+      if (debug_p)
+        {
+          double xx = x * 1.4;  /* a little more to the left */
+          char n[20];
+          sprintf(n, "%d:", i);
+          glColor3f (1.0, 1.0, 1.0);
+          draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
+        }
+
+      if (!line || !*line)
+        continue;
+
+      if (sc->line_thickness != 1 && !textures_p)
+        {
+          int max_thick_lines = MAX_THICK_LINES;
+          GLfloat thinnest_line = 1.0;
+          GLfloat thickest_line = sc->line_thickness;
+          GLfloat range = thickest_line - thinnest_line;
+          GLfloat thickness;
+
+          int j = sc->total_lines - i - 1;
+
+          if (j > max_thick_lines)
+            thickness = thinnest_line;
+          else
+            thickness = (thinnest_line +
+                         (range * ((max_thick_lines - j) /
+                                   (GLfloat) max_thick_lines)));
+
+          glLineWidth (thickness);
+        }
+
+      if (alignment >= 0)
+        {
+          int n = sc->line_widths[i];
+          xoff = 1.0 - (n * sc->font_scale);
+        }
+
+      if (alignment == 0)
+        xoff /= 2;
+
+      glColor3f (fade, fade, 0.5 * fade);
+      draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
+                   line);
+      if (textures_p)
+        mi->polygon_count += strlen (line);
+    }
+  glPopMatrix ();
+
+
 
   sc->intra_line_scroll += sc->line_height / scroll_steps;
 
@@ -994,8 +1025,10 @@ draw_sws (ModeInfo *mi)
       /* Scroll the contents of the lines array toward 0. */
       if (sc->total_lines > 0)
         {
-          for (i = 1; i < sc->total_lines; i++)
+          for (i = 1; i < sc->total_lines; i++) {
             sc->lines[i-1] = sc->lines[i];
+            sc->line_widths[i-1] = sc->line_widths[i];
+          }
           sc->lines[--sc->total_lines] = 0;
         }
 
@@ -1007,72 +1040,6 @@ draw_sws (ModeInfo *mi)
            here so that new text still pulls in from the bottom of
            the screen, isntead of just appearing. */
         sc->total_lines = max_lines;
-
-      glDeleteLists (sc->text_list, 1);
-      sc->text_list = glGenLists (1);
-      glNewList (sc->text_list, GL_COMPILE);
-      sc->polygon_count = 0;
-      glPushMatrix ();
-      glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
-      for (i = 0; i < sc->total_lines; i++)
-        {
-          double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
-          int offscreen_lines = 2;
-
-          double x = -0.5;
-          double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
-                       * sc->line_height);
-          double xoff = 0;
-          char *line = sc->lines[i];
-
-          if (debug_p)
-            {
-              double xx = x * 1.4;  /* a little more to the left */
-              char n[20];
-              sprintf(n, "%d:", i);
-              draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
-            }
-
-          if (!line || !*line)
-            continue;
-
-          if (sc->line_thickness != 1 && !textures_p)
-            {
-              int max_thick_lines = MAX_THICK_LINES;
-              GLfloat thinnest_line = 1.0;
-              GLfloat thickest_line = sc->line_thickness;
-              GLfloat range = thickest_line - thinnest_line;
-              GLfloat thickness;
-
-              int j = sc->total_lines - i - 1;
-
-              if (j > max_thick_lines)
-                thickness = thinnest_line;
-              else
-                thickness = (thinnest_line +
-                             (range * ((max_thick_lines - j) /
-                                       (GLfloat) max_thick_lines)));
-
-              glLineWidth (thickness);
-            }
-
-          if (alignment >= 0)
-            {
-              int n = string_width (sc, line);
-              xoff = 1.0 - (n * sc->font_scale);
-            }
-
-          if (alignment == 0)
-            xoff /= 2;
-
-          glColor3f (fade, fade, 0.5 * fade);
-          draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
-                       line);
-          if (textures_p)
-            sc->polygon_count += strlen (line);
-        }
-      glPopMatrix ();
-      glEndList ();
     }
 
   glPopMatrix ();
@@ -1084,29 +1051,23 @@ draw_sws (ModeInfo *mi)
   sc->star_theta += star_spin;
 }
 
-ENTRYPOINT void
-release_sws (ModeInfo *mi)
+static void
+free_sws (ModeInfo *mi)
 {
-  if (scs) {
-    int screen;
-    for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
-      sws_configuration *sc = &scs[screen];
-      if (sc->pipe_id)
-        XtRemoveInput (sc->pipe_id);
-      if (sc->pipe)
-        pclose (sc->pipe);
-      if (sc->pipe_timer)
-        XtRemoveTimeOut (sc->pipe_timer);
-
-      /* #### there's more to free here */
-    }
-    free (scs);
-    scs = 0;
-  }
-  FreeAllGL(mi);
+  sws_configuration *sc = &scs[MI_SCREEN(mi)];
+  if (sc->tc)
+    textclient_close (sc->tc);
+
+  /* #### there's more to free here */
 }
 
 
+#ifdef __GNUC__
+ __extension__ /* don't warn about "string length is greater than the length
+                  ISO C89 compilers are required to support" when including
+                  "starwars.txt" in the defaults... */
+#endif
+
 XSCREENSAVER_MODULE_2 ("StarWars", starwars, sws)
 
 #endif /* USE_GL */