From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / glx / starwars.c
index 3277df5e08108e318c2ebe43c3acf3eeb1a47d73..1398c159c9bd28da58ef4664ae64c8e04059d69f 100644 (file)
@@ -1,5 +1,4 @@
-/*
- * starwars, Copyright (c) 1998-2005 Jamie Zawinski <jwz@jwz.org> and
+/* starwars, Copyright (c) 1998-2014 Jamie Zawinski <jwz@jwz.org> and
  * Claudio Matsuoka <claudio@helllabs.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  *
  * For the fanboys:
  *
- *     starwars -program 'cat starwars.txt' -columns 25 -no-wrap -texture
+ *     starwars -program 'cat starwars.txt' -columns 25 -no-wrap
  */
 
-#include <X11/Intrinsic.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
 
-extern XtAppContext app;
+#include <ctype.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "starwars.h"
+#define DEFAULTS "*delay:    40000     \n" \
+                "*showFPS:  False     \n" \
+                "*fpsTop:   True      \n" \
+                "*usePty:   False     \n" \
+                "*font:   " DEF_FONT "\n" \
+                "*textLiteral: " DEF_TEXT "\n"
 
-#define PROGCLASS      "StarWars"
-#define HACK_INIT      init_sws
-#define HACK_DRAW      draw_sws
-#define HACK_RESHAPE   reshape_sws
-#define sws_opts       xlockmore_opts
 
-#define DEF_PROGRAM    "(default)"
+# define refresh_sws 0
+# define sws_handle_event 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#include "xlockmore.h"
+#include "textclient.h"
+
+#ifdef USE_GL /* whole file */
+
+/* Should be in <GL/glext.h> */
+# ifndef  GL_TEXTURE_MAX_ANISOTROPY_EXT
+#  define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
+# endif
+# ifndef  GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
+#  define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
+# 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 480 needs a 2048x2048 texture.
-   Utopia 400 needs a 1024x1024 texture.
-   Utopia 180 needs a 512x512 texture.
-   Times 240 needs a 512x512 texture.
+/* 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-*-*-400-*-*-*-*-iso8859-1"
+#define DEF_FONT       "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-iso8859-1"
 
 #define TAB_WIDTH        8
 
 #define MAX_THICK_LINES   25
 #define FONT_WEIGHT       14
-#define KEEP_ASPECT
-
-#define DEFAULTS "*delay:        40000 \n"                  \
-                "*showFPS:       False \n"                  \
-                "*fpsTop:        True \n"                   \
-                "*program:     " DEF_PROGRAM           "\n" \
-                "*lines:       " DEF_LINES             "\n" \
-                "*spin:        " DEF_SPIN              "\n" \
-                "*steps:       " DEF_STEPS             "\n" \
-                "*smooth:      " DEF_SMOOTH            "\n" \
-                "*thick:       " DEF_THICK             "\n" \
-                "*fade:        " DEF_FADE              "\n" \
-                "*textures:    " DEF_TEXTURES          "\n" \
-                "*fontSize:    " DEF_FONT_SIZE         "\n" \
-                "*columns:     " DEF_COLUMNS           "\n" \
-                "*lineWrap:    " DEF_WRAP              "\n" \
-                "*alignment:   " DEF_ALIGN             "\n" \
-                "*font:        " DEF_FONT              "\n" \
-                "*debug:       " DEF_DEBUG             "\n" \
-
-#undef countof
-#define countof(x) (sizeof((x))/sizeof((*x)))
 
-#include "xlockmore.h"
-
-#ifdef USE_GL /* whole file */
+#ifndef USE_IPHONE
+# define KEEP_ASPECT   /* Letterboxing looks dumb on iPhone. */
+#endif
 
-#include <ctype.h>
-#include <GL/glu.h>
-#include <sys/stat.h>
 #include "texfont.h"
 #include "glutstroke.h"
 #include "glut_roman.h"
 #define GLUT_FONT (&glutStrokeRoman)
 
-#ifdef HAVE_UNAME
-# include <sys/utsname.h>
-#endif /* HAVE_UNAME */
-
-
 typedef struct {
+  Display *dpy;
   GLXContext *glx_context;
 
   GLuint text_list, star_list;
   texture_font_data *texfont;
   int polygon_count;
+  text_data *tc;
 
-  FILE *pipe;
-  XtInputId pipe_id;
-  Time subproc_relaunch_delay;
-
-  char buf [1024];
+  char *buf;
+  int buf_size;
   int buf_tail;
+
   char **lines;
   int total_lines;
 
@@ -152,9 +152,9 @@ static XrmOptionDescRec opts[] = {
   {"-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 },
+/*{"-font",        ".font",      XrmoptionSepArg, 0 },*/
   {"-fade",        ".fade",      XrmoptionNoArg,  "True"   },
   {"-no-fade",     ".fade",      XrmoptionNoArg,  "False"  },
   {"-textures",    ".textures",  XrmoptionNoArg,  "True"   },
@@ -166,6 +166,7 @@ static XrmOptionDescRec opts[] = {
   {"-wrap",       ".lineWrap",  XrmoptionNoArg,  "True"   },
   {"-no-wrap",    ".lineWrap",  XrmoptionNoArg,  "False"  },
   {"-nowrap",     ".lineWrap",  XrmoptionNoArg,  "False"  },
+  {"-alignment",   ".alignment", XrmoptionSepArg, 0        },
   {"-left",        ".alignment", XrmoptionNoArg,  "Left"   },
   {"-right",       ".alignment", XrmoptionNoArg,  "Right"  },
   {"-center",      ".alignment", XrmoptionNoArg,  "Center" },
@@ -177,10 +178,10 @@ static argtype vars[] = {
   {&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},
@@ -188,7 +189,7 @@ static argtype vars[] = {
   {&debug_p,        "debug",     "Boolean",    DEF_DEBUG,     t_Bool},
 };
 
-ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
+ENTRYPOINT ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
 
 
 
@@ -278,151 +279,9 @@ latin1_to_ascii (char *s)
     }
 }
 
-\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)
-{
-  char *oprogram = get_string_resource ("program", "Program");
-  char *program;
-
-  if (!strcasecmp(oprogram, "(default)"))
-    {
-      oprogram = FORTUNE_PROGRAM;
-
-#if defined(__linux__) && defined(HAVE_UNAME)
-      {
-        static int done_once = 0;
-        if (!done_once)
-          {
-            struct utsname uts;
-            struct stat st;
-            done_once = 1;
-            if (uname (&uts) == 0)
-              {
-                static char cmd[200];
-                char *s;
-                /* strip version at the first non-digit-dash-dot, to
-                   lose any "SMP" crap at the end. */
-                for (s = uts.release; *s; s++)
-                  if (!isdigit(*s) && *s != '.' && *s != '-')
-                    *s = 0;
-                sprintf (cmd, "cat /usr/src/linux-%s/README", uts.release);
-                if (!stat (cmd+4, &st))
-                  oprogram = cmd;
-                else
-                  {
-                    /* kernel source not installed?  try X... */
-                    strcpy (cmd, "cat /usr/X11R6/lib/X11/doc/README");
-                    if (!stat (cmd+4, &st))
-                      oprogram = cmd;
-                  }
-              }
-          }
-      }
-#endif /* __linux__ && HAVE_UNAME */
-
-#ifdef __APPLE__   /* MacOS X + XDarwin */
-      {
-        static int done_once = 0;
-        if (!done_once)
-          {
-            struct stat st;
-            static char *cmd = "cat /usr/X11R6/README";
-            if (!stat (cmd+4, &st))
-              oprogram = cmd;
-          }
-      }
-#endif /* __APPLE__ */
-    }
-
-  program = (char *) malloc (strlen (oprogram) + 10);
-  strcpy (program, "( ");
-  strcat (program, oprogram);
-  strcat (program, " ) 2>&1");
-
-  if ((sc->pipe = popen (program, "r")))
-    {
-      sc->pipe_id =
-        XtAppAddInput (app, fileno (sc->pipe),
-                       (XtPointer) (XtInputReadMask | XtInputExceptMask),
-                       subproc_cb, (XtPointer) sc);
-    }
-  else
-    {
-      perror (program);
-    }
-}
-
-
-static void
-relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
-{
-  sws_configuration *sc = (sws_configuration *) closure;
-  launch_text_generator (sc);
-}
-
-
-/* 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)
-{
-  if (sc->buf_tail < sizeof(sc->buf) - 2)
-    {
-      int target = sizeof(sc->buf) - sc->buf_tail - 2;
-      int n = read (fileno (sc->pipe),
-                    (void *) (sc->buf + sc->buf_tail),
-                    target);
-      if (n > 0)
-        {
-          sc->buf_tail += n;
-          sc->buf[sc->buf_tail] = 0;
-        }
-      else
-        {
-          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. */
-          XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
-                           relaunch_generator_timer,
-                           (XtPointer) sc);
-        }
-    }
-}
-
 
 static int
-string_width (sws_configuration *sc, const char *s)
+sw_string_width (sws_configuration *sc, const char *s)
 {
   if (textures_p)
     return texture_string_width (sc->texfont, s, 0);
@@ -436,12 +295,11 @@ char_width (sws_configuration *sc, char c)
   char s[2];
   s[0] = c;
   s[1] = 0;
-  return string_width (sc, s);
+  return sw_string_width (sc, s);
 }
 
 
-/* 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)
@@ -453,15 +311,28 @@ get_more_lines (sws_configuration *sc)
   int col_pix = 0;
 
   char *s = sc->buf;
+
+  int target = sc->buf_size - sc->buf_tail - 2;
+
+  /* Fill as much as we can into sc->buf.
+   */
+  while (target > 0)
+    {
+      int c = textclient_getc (sc->tc);
+      if (c <= 0)
+        break;
+      sc->buf[sc->buf_tail++] = (char) c;
+      sc->buf[sc->buf_tail] = 0;
+      target--;
+    }
+
   while (sc->total_lines < max_lines)
     {
       int cw;
 
       if (s >= sc->buf + sc->buf_tail)
-        {
-          /* Reached end of buffer before end of line.  Bail. */
-          return;
-        }
+        /* Reached end of buffer before end of line.  Bail. */
+        return;
 
       cw = char_width (sc, *s);
 
@@ -573,7 +444,7 @@ draw_string (sws_configuration *sc, GLfloat x, GLfloat y, const char *s)
       while (*s)
         {
           *c = *s++;
-          w = string_width (sc, c);
+          w = sw_string_width (sc, c);
           glBegin (GL_LINE_LOOP);
           glVertex3f (0, 0, 0);
           glVertex3f (w, 0, 0);
@@ -647,7 +518,6 @@ box (double width, double height, double depth)
   glVertex3f( width/2,   height/2, -depth/2);
   glEnd();
 
-  glEnd();
   glBegin(GL_LINES);
   glVertex3f(-width/2,   height/2,  depth/2);
   glVertex3f(-width/2,  -height/2, -depth/2);
@@ -702,7 +572,7 @@ init_stars (ModeInfo *mi, int width, int height)
 
 /* Window management, etc
  */
-void
+ENTRYPOINT void
 reshape_sws (ModeInfo *mi, int width, int height)
 {
   sws_configuration *sc = &scs[MI_SCREEN(mi)];
@@ -714,6 +584,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
     {
@@ -733,6 +604,19 @@ reshape_sws (ModeInfo *mi, int width, int height)
     gluLookAt (0.0, 0.0, 4600.0,
                0.0, 0.0, 0.0,
                0.0, 1.0, 0.0);
+
+    glRotatef(rot, 0, 0, 1);
+
+    /* 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
@@ -756,8 +640,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);
@@ -786,7 +670,7 @@ gl_init (ModeInfo *mi)
 {
   sws_configuration *sc = &scs[MI_SCREEN(mi)];
 
-  program = get_string_resource ("program", "Program");
+  program = get_string_resource (mi->dpy, "program", "Program");
 
   glDisable (GL_LIGHTING);
   glDisable (GL_DEPTH_TEST);
@@ -811,12 +695,12 @@ gl_init (ModeInfo *mi)
 }
 
 
-void 
+ENTRYPOINT void 
 init_sws (ModeInfo *mi)
 {
   double font_height;
 
-  sws_configuration *sc;
+  sws_configuration *sc = 0;
 
   if (!scs) {
     scs = (sws_configuration *)
@@ -825,16 +709,19 @@ init_sws (ModeInfo *mi)
       fprintf(stderr, "%s: out of memory\n", progname);
       exit(1);
     }
-
-    sc = &scs[MI_SCREEN(mi)];
-    sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
   }
 
   sc = &scs[MI_SCREEN(mi)];
 
+  sc->dpy = MI_DISPLAY(mi);
+  sc = &scs[MI_SCREEN(mi)];
+  sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
+
   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));
   }
 
@@ -847,6 +734,24 @@ init_sws (ModeInfo *mi)
       font_height = lh;
       glEnable(GL_ALPHA_TEST);
       glEnable (GL_TEXTURE_2D);
+
+      check_gl_error ("loading font");
+
+      /* "Anistropic filtering helps for quadrilateral-angled textures.
+         A sharper image is accomplished by interpolating and filtering
+         multiple samples from one or more mipmaps to better approximate
+         very distorted textures.  This is the next level of filtering
+         after trilinear filtering." */
+      if (smooth_p && 
+          strstr ((char *) glGetString(GL_EXTENSIONS),
+                  "GL_EXT_texture_filter_anisotropic"))
+      {
+        GLfloat anisotropic = 0.0;
+        glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
+        if (anisotropic >= 1.0)
+          glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
+                           anisotropic);
+      }
     }
   else
     {
@@ -883,7 +788,15 @@ init_sws (ModeInfo *mi)
   sc->line_height = font_height * sc->font_scale;
 
 
-  sc->subproc_relaunch_delay = 2 * 1000;
+  /* Buffer only two 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.)
+   */
+  sc->buf_size = target_columns * 2;
+  if (sc->buf_size < 80) sc->buf_size = 80;
+  sc->buf = (char *) calloc (1, sc->buf_size);
+
   sc->total_lines = max_lines-1;
 
   if (random() & 1)
@@ -905,7 +818,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));
@@ -931,6 +844,10 @@ 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);
+
+      /* Keep the stars pointing in the same direction after rotation */
+      glRotatef(current_device_rotation(), 0, 0, 1);
+
       glCallList (sc->star_list);
       if (textures_p) glEnable (GL_TEXTURE_2D);
     }
@@ -940,10 +857,11 @@ draw_stars (ModeInfo *mi)
   glPopMatrix ();
 }
 
-void
+ENTRYPOINT void
 draw_sws (ModeInfo *mi)
 {
   sws_configuration *sc = &scs[MI_SCREEN(mi)];
+/*  XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
   Display *dpy = MI_DISPLAY(mi);
   Window window = MI_WINDOW(mi);
   int i;
@@ -951,9 +869,6 @@ draw_sws (ModeInfo *mi)
   if (!sc->glx_context)
     return;
 
-  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
-    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
-
   glDrawBuffer (GL_BACK);
   glXMakeCurrent (dpy, window, *(sc->glx_context));
 
@@ -964,14 +879,33 @@ draw_sws (ModeInfo *mi)
   glMatrixMode (GL_MODELVIEW);
   glPushMatrix ();
 
+# ifdef USE_IPHONE
+  /* 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.4, 0.4, 0.4);
       for (i = 0; i < 16; i++)
         {
           box (1, 1, 1);
@@ -1025,7 +959,7 @@ draw_sws (ModeInfo *mi)
       for (i = 0; i < sc->total_lines; i++)
         {
           double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
-          int offscreen_lines = 3;
+          int offscreen_lines = 2;
 
           double x = -0.5;
           double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
@@ -1066,7 +1000,7 @@ draw_sws (ModeInfo *mi)
 
           if (alignment >= 0)
             {
-              int n = string_width (sc, line);
+              int n = sw_string_width (sc, line);
               xoff = 1.0 - (n * sc->font_scale);
             }
 
@@ -1092,4 +1026,31 @@ draw_sws (ModeInfo *mi)
   sc->star_theta += star_spin;
 }
 
+ENTRYPOINT void
+release_sws (ModeInfo *mi)
+{
+  if (scs) {
+    int screen;
+    for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
+      sws_configuration *sc = &scs[screen];
+      if (sc->tc)
+        textclient_close (sc->tc);
+
+      /* #### there's more to free here */
+    }
+    free (scs);
+    scs = 0;
+  }
+  FreeAllGL(mi);
+}
+
+
+#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 */